Skip to content

Commit 6c1b24a

Browse files
committed
fix(runtime): normalize legacy command names in status
1 parent 7eb3127 commit 6c1b24a

2 files changed

Lines changed: 67 additions & 13 deletions

File tree

src/runtime/engine/manager.ts

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -333,22 +333,55 @@ function buildUsageField(record: SessionRecord): { usage?: AcpRuntimeSessionUsag
333333
function buildAvailableCommandsField(record: SessionRecord): {
334334
availableCommands?: AcpRuntimeAvailableCommand[];
335335
} {
336-
const commands = record.acpx?.available_commands;
336+
const commands = record.acpx?.available_commands as readonly unknown[] | undefined;
337337
if (!commands || commands.length === 0) {
338338
return {};
339339
}
340-
return {
341-
availableCommands: commands.map((command) => {
342-
const runtimeCommand: AcpRuntimeAvailableCommand = { name: command.name };
343-
if (command.description) {
344-
runtimeCommand.description = command.description;
345-
}
346-
if (command.has_input !== undefined) {
347-
runtimeCommand.hasInput = command.has_input;
348-
}
349-
return runtimeCommand;
350-
}),
351-
};
340+
const availableCommands = commands
341+
.map((command) => runtimeAvailableCommand(command))
342+
.filter((command): command is AcpRuntimeAvailableCommand => command !== undefined);
343+
return availableCommands.length > 0 ? { availableCommands } : {};
344+
}
345+
346+
function runtimeAvailableCommand(command: unknown): AcpRuntimeAvailableCommand | undefined {
347+
if (typeof command === "string") {
348+
const name = command.trim();
349+
return name ? { name } : undefined;
350+
}
351+
const record = commandRecord(command);
352+
if (!record) {
353+
return undefined;
354+
}
355+
const name = trimmedField(record.name);
356+
if (!name) {
357+
return undefined;
358+
}
359+
const runtimeCommand: AcpRuntimeAvailableCommand = { name };
360+
const description = trimmedField(record.description);
361+
if (description) {
362+
runtimeCommand.description = description;
363+
}
364+
if (typeof record.has_input === "boolean") {
365+
runtimeCommand.hasInput = record.has_input;
366+
}
367+
return runtimeCommand;
368+
}
369+
370+
function commandRecord(
371+
value: unknown,
372+
): { name?: unknown; description?: unknown; has_input?: unknown } | undefined {
373+
if (!value || typeof value !== "object" || Array.isArray(value)) {
374+
return undefined;
375+
}
376+
return value as { name?: unknown; description?: unknown; has_input?: unknown };
377+
}
378+
379+
function trimmedField(value: unknown): string | undefined {
380+
if (typeof value !== "string") {
381+
return undefined;
382+
}
383+
const trimmed = value.trim();
384+
return trimmed ? trimmed : undefined;
352385
}
353386

354387
function advertisedConfigOptionIds(record: SessionRecord): Set<string> | undefined {

test/runtime-manager.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2940,3 +2940,24 @@ test("AcpRuntimeManager getStatus omits usage and availableCommands when the rec
29402940
assert.equal(status.usage, undefined);
29412941
assert.equal(status.availableCommands, undefined);
29422942
});
2943+
2944+
test("AcpRuntimeManager getStatus accepts legacy available command names", async () => {
2945+
const record = makeSessionRecord({
2946+
acpxRecordId: "legacy-commands:1",
2947+
acpSessionId: "legacy-commands-sid",
2948+
agentCommand: "codex --acp",
2949+
cwd: "/workspace",
2950+
});
2951+
record.acpx = {
2952+
available_commands: ["/compact", "/clear"] as never,
2953+
};
2954+
2955+
const store = new InMemorySessionStore([record]);
2956+
const manager = new AcpRuntimeManager(
2957+
createRuntimeOptions({ cwd: "/workspace", sessionStore: store }),
2958+
);
2959+
2960+
const status = await manager.getStatus(createHandle("legacy-commands:1"));
2961+
2962+
assert.deepEqual(status.availableCommands, [{ name: "/compact" }, { name: "/clear" }]);
2963+
});

0 commit comments

Comments
 (0)