Skip to content

Commit 1e9b31a

Browse files
committed
fix(ci): route coding tools ls through capability router
1 parent 4372ce5 commit 1e9b31a

7 files changed

Lines changed: 109 additions & 23 deletions

File tree

plugins/plugin-coding-tools/src/actions/enter-worktree.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type IAgentRuntime,
1212
type Memory,
1313
type State,
14+
UnavailableCapabilityRouter,
1415
} from "@elizaos/core";
1516
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1617

@@ -147,6 +148,7 @@ function makeGitRouter(
147148
throw new Error("model unavailable");
148149
},
149150
},
151+
plugin: new UnavailableCapabilityRouter("desktop").plugin,
150152
};
151153
}
152154

@@ -239,14 +241,7 @@ describe("ENTER_WORKTREE", () => {
239241
expect(calls).toEqual([
240242
{
241243
root: env.repoDir,
242-
args: [
243-
"worktree",
244-
"add",
245-
"-b",
246-
"routed-feature",
247-
worktreePath,
248-
"main",
249-
],
244+
args: ["worktree", "add", "-b", "routed-feature", worktreePath, "main"],
250245
},
251246
]);
252247
expect(env.session.getCwd(env.conversationId)).toBe(

plugins/plugin-coding-tools/src/actions/exit-worktree.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type IAgentRuntime,
1212
type Memory,
1313
type State,
14+
UnavailableCapabilityRouter,
1415
} from "@elizaos/core";
1516
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1617

@@ -148,6 +149,7 @@ function makeGitRouter(
148149
throw new Error("model unavailable");
149150
},
150151
},
152+
plugin: new UnavailableCapabilityRouter("desktop").plugin,
151153
};
152154
}
153155

plugins/plugin-coding-tools/src/actions/ls.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
type IAgentRuntime,
1010
type Memory,
1111
type State,
12+
UnavailableCapabilityRouter,
1213
} from "@elizaos/core";
1314
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1415

@@ -68,6 +69,7 @@ function makeListRouter(
6869
model: {
6970
status: async () => unavailableCapability("model", "model.status"),
7071
},
72+
plugin: new UnavailableCapabilityRouter("desktop").plugin,
7173
};
7274
}
7375

plugins/plugin-coding-tools/src/actions/ls.ts

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import * as path from "node:path";
33

44
import {
55
type ActionResult,
6+
CapabilityError,
67
logger as coreLogger,
8+
getCapabilityRouter,
79
type HandlerCallback,
810
type IAgentRuntime,
911
type Memory,
@@ -34,6 +36,57 @@ interface LsEntry {
3436
size?: number;
3537
}
3638

39+
function normalizeEntryKind(kind: string): EntryType {
40+
if (kind === "directory") return "dir";
41+
if (kind === "symlink") return "symlink";
42+
return "file";
43+
}
44+
45+
async function listWithCapabilityRouter(params: {
46+
runtime: IAgentRuntime;
47+
dir: string;
48+
ignore?: string[];
49+
}): Promise<
50+
| {
51+
ok: true;
52+
entries: LsEntry[];
53+
truncated: boolean;
54+
totalAfterIgnore: number;
55+
}
56+
| { ok: false; reason: "unavailable" | "failed"; message: string }
57+
> {
58+
const router = getCapabilityRouter(params.runtime);
59+
if (!router) return { ok: false, reason: "unavailable", message: "" };
60+
try {
61+
const result = await router.fs.list({
62+
path: params.dir,
63+
limit: ENTRY_LIMIT,
64+
includeHidden: true,
65+
...(params.ignore ? { ignore: params.ignore } : {}),
66+
});
67+
return {
68+
ok: true,
69+
entries: result.entries.map((entry) => {
70+
const type = normalizeEntryKind(entry.kind);
71+
return type === "file"
72+
? { name: entry.name, type, size: entry.size }
73+
: { name: entry.name, type };
74+
}),
75+
truncated: result.truncated,
76+
totalAfterIgnore: result.totalAfterIgnore,
77+
};
78+
} catch (error) {
79+
if (
80+
error instanceof CapabilityError &&
81+
error.code === "CAPABILITY_UNAVAILABLE"
82+
) {
83+
return { ok: false, reason: "unavailable", message: error.message };
84+
}
85+
const message = error instanceof Error ? error.message : String(error);
86+
return { ok: false, reason: "failed", message };
87+
}
88+
}
89+
3790
function globToRegExp(pattern: string): RegExp {
3891
let regex = "";
3992
let i = 0;
@@ -114,11 +167,43 @@ export async function lsHandler(
114167
const dir = validation.resolved;
115168

116169
const ignoreRaw = readArrayParam(options, "ignore");
117-
const ignoreMatchers: RegExp[] = (ignoreRaw ?? [])
118-
.filter(
119-
(entry): entry is string => typeof entry === "string" && entry.length > 0,
120-
)
121-
.map((entry) => globToRegExp(entry));
170+
const ignore = (ignoreRaw ?? []).filter(
171+
(entry): entry is string => typeof entry === "string" && entry.length > 0,
172+
);
173+
174+
const routed = await listWithCapabilityRouter({
175+
runtime,
176+
dir,
177+
ignore: ignore.length > 0 ? ignore : undefined,
178+
});
179+
if (routed.ok) {
180+
const lines = [
181+
`Directory: ${dir}`,
182+
...routed.entries.map((e) => (e.type === "dir" ? `${e.name}/` : e.name)),
183+
];
184+
if (routed.truncated) {
185+
lines.push(
186+
`…[truncated, listed ${ENTRY_LIMIT} of ${routed.totalAfterIgnore} entries]`,
187+
);
188+
}
189+
const text = lines.join("\n");
190+
coreLogger.debug(
191+
`${CODING_TOOLS_LOG_PREFIX} LS dir=${dir} count=${routed.entries.length} truncated=${routed.truncated}`,
192+
);
193+
if (callback) await callback({ text, source: "coding-tools" });
194+
return successActionResult(text, {
195+
entries: routed.entries,
196+
truncated: routed.truncated,
197+
});
198+
}
199+
if (routed.reason === "failed") {
200+
return failureToActionResult({
201+
reason: "io_error",
202+
message: `readdir failed: ${routed.message}`,
203+
});
204+
}
205+
206+
const ignoreMatchers: RegExp[] = ignore.map((entry) => globToRegExp(entry));
122207

123208
let names: string[];
124209
try {

plugins/plugin-coding-tools/src/actions/read.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CapabilityError,
66
type ElizaCapabilityRouter,
77
type IAgentRuntime,
8+
UnavailableCapabilityRouter,
89
} from "@elizaos/core";
910
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1011
import { setupEnv, type TestEnv } from "./_test-helpers.js";
@@ -175,6 +176,7 @@ describe("READ", () => {
175176
});
176177
},
177178
},
179+
plugin: new UnavailableCapabilityRouter("desktop").plugin,
178180
};
179181
const runtime = {
180182
...env.runtime,

plugins/plugin-coding-tools/src/actions/write.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type ElizaCapabilityRouter,
77
type FileWriteTextParams,
88
type IAgentRuntime,
9+
UnavailableCapabilityRouter,
910
} from "@elizaos/core";
1011
import { afterEach, beforeEach, describe, expect, it } from "vitest";
1112
import { setupEnv, type TestEnv } from "./_test-helpers.js";
@@ -49,12 +50,12 @@ function makeWriteRouter(
4950
git: {
5051
status: async () => unavailableCapability("git", "git.status"),
5152
diff: async () => unavailableCapability("git", "git.diff"),
52-
commandRun: async () =>
53-
unavailableCapability("git", "git.command.run"),
53+
commandRun: async () => unavailableCapability("git", "git.command.run"),
5454
},
5555
model: {
5656
status: async () => unavailableCapability("model", "model.status"),
5757
},
58+
plugin: new UnavailableCapabilityRouter("desktop").plugin,
5859
};
5960
}
6061

plugins/plugin-coding-tools/src/lib/run-shell.test.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
CAPABILITY_ROUTER_SERVICE_TYPE,
33
type ElizaCapabilityRouter,
44
type IAgentRuntime,
5+
UnavailableCapabilityRouter,
56
} from "@elizaos/core";
67
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
78
import { runShell } from "./run-shell.js";
@@ -65,6 +66,7 @@ function remoteRouter(): {
6566
model: {
6667
status: vi.fn(),
6768
},
69+
plugin: new UnavailableCapabilityRouter("server").plugin,
6870
} satisfies ElizaCapabilityRouter;
6971
return { router, runCommand };
7072
}
@@ -103,14 +105,11 @@ describe("plugin-coding-tools runShell mobile routing", () => {
103105
process.env.ELIZA_RUNTIME_MODE = "local-yolo";
104106

105107
await expect(
106-
runShell(
107-
{ getService: () => null } as IAgentRuntime,
108-
{
109-
command: "codex exec 'touch changed.txt'",
110-
cwd: "/workspace",
111-
timeoutMs: 10_000,
112-
},
113-
),
108+
runShell({ getService: () => null } as IAgentRuntime, {
109+
command: "codex exec 'touch changed.txt'",
110+
cwd: "/workspace",
111+
timeoutMs: 10_000,
112+
}),
114113
).rejects.toThrow(
115114
"Local coding tools are unavailable on iOS because the runtime does not expose shell, coding, or orchestrator subprocess capabilities.",
116115
);

0 commit comments

Comments
 (0)