Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions ts/packages/cli/src/cliSearchMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import {
SearchMenuBase,
SearchMenuItem,
SearchMenuPosition,
} from "agent-dispatcher/helpers/completion";

// CLI adapter for ISearchMenu. Extends SearchMenuBase (which provides
// TST-based prefix matching) and captures the filtered items so that
// questionWithCompletion can read them for ghost-text display.
//
// Architecture: docs/architecture/completion.md — §CLI integration
export class CliSearchMenu extends SearchMenuBase {
private currentItems: SearchMenuItem[] = [];
private readonly onUpdate: () => void;

constructor(onUpdate: () => void) {
super();
this.onUpdate = onUpdate;
}

protected override onShow(
_position: SearchMenuPosition,
_prefix: string,
items: SearchMenuItem[],
): void {
this.currentItems = items;
this.onUpdate();
}

protected override onHide(): void {
this.currentItems = [];
this.onUpdate();
}

/** Returns the items currently visible in the menu (trie-filtered). */
public getItems(): SearchMenuItem[] {
return this.currentItems;
}
}
56 changes: 1 addition & 55 deletions ts/packages/cli/src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
replayDisplayHistory,
withEnhancedConsoleClientIO,
} from "../enhancedConsole.js";
import { isSlashCommand, getSlashCompletions } from "../slashCommands.js";
import {
connectAgentServer,
ensureAgentServer,
Expand Down Expand Up @@ -66,59 +65,6 @@ function promptYesNo(question: string): Promise<boolean> {
});
}

type CompletionData = {
allCompletions: string[];
filterStartIndex: number;
prefix: string;
};

async function getCompletionsData(
line: string,
dispatcher: Dispatcher,
): Promise<CompletionData | null> {
try {
if (isSlashCommand(line)) {
const completions = getSlashCompletions(line);
if (completions.length === 0) return null;
return {
allCompletions: completions,
filterStartIndex: 0,
prefix: "",
};
}
const direction = "forward" as const;
const result = await dispatcher.getCommandCompletion(line, direction);
if (result.completions.length === 0) {
return null;
}

const allCompletions: string[] = [];
for (const group of result.completions) {
for (const completion of group.completions) {
allCompletions.push(completion);
}
}

const filterStartIndex = result.startIndex;
const prefix = line.substring(0, filterStartIndex);

const needsSep = result.completions.some(
(g) =>
g.separatorMode === "space" ||
g.separatorMode === "spacePunctuation",
);
const separator = needsSep ? " " : "";

return {
allCompletions,
filterStartIndex,
prefix: prefix + separator,
};
} catch (e) {
return null;
}
}

export default class Connect extends Command {
static description =
"Connect to the agent server in interactive mode. Defaults to the 'CLI' session, or specify --session <id> to join a specific one.";
Expand Down Expand Up @@ -291,7 +237,7 @@ export default class Connect extends Command {
dispatcher.processCommand(command),
dispatcher,
undefined,
(line: string) => getCompletionsData(line, dispatcher),
dispatcher, // session-based completions
dispatcher,
);
} finally {
Expand Down
70 changes: 1 addition & 69 deletions ts/packages/cli/src/commands/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
processCommandsEnhanced,
withEnhancedConsoleClientIO,
} from "../enhancedConsole.js";
import { isSlashCommand, getSlashCompletions } from "../slashCommands.js";
import { getStatusSummary } from "agent-dispatcher/helpers/status";
import { getFsStorageProvider } from "dispatcher-node-providers";

Expand All @@ -32,73 +31,6 @@ const { schemaNames } = await getAllActionConfigProvider(
defaultAppAgentProviders,
);

/**
* Get completions for the current input line using dispatcher's command completion API
*/
// Return completion data including where filtering starts
type CompletionData = {
allCompletions: string[]; // All available completions (just the completion text)
filterStartIndex: number; // Where user typing should filter (after the space/trigger)
prefix: string; // Fixed prefix before completions
};

// Architecture: docs/architecture/completion.md — §CLI integration
async function getCompletionsData(
line: string,
dispatcher: Dispatcher,
): Promise<CompletionData | null> {
try {
// Handle slash command completions
if (isSlashCommand(line)) {
const completions = getSlashCompletions(line);
if (completions.length === 0) return null;
return {
allCompletions: completions,
filterStartIndex: 0,
prefix: "",
};
}
// Send the full input to the backend; the grammar matcher reports
// how much it consumed (matchedPrefixLength → startIndex) so the
// CLI need not split on spaces to find token boundaries.
// CLI tab-completion is always a forward action.
const direction = "forward" as const;
const result = await dispatcher.getCommandCompletion(line, direction);
if (result.completions.length === 0) {
return null;
}

// Extract just the completion strings
const allCompletions: string[] = [];
for (const group of result.completions) {
for (const completion of group.completions) {
allCompletions.push(completion);
}
}

const filterStartIndex = result.startIndex;
const prefix = line.substring(0, filterStartIndex);

// When any group reports a separator-requiring mode between the
// typed prefix and the completion text, prepend a space so the
// readline display doesn't produce "playmusic" for "play" + "music".
const needsSep = result.completions.some(
(g) =>
g.separatorMode === "space" ||
g.separatorMode === "spacePunctuation",
);
const separator = needsSep ? " " : "";

return {
allCompletions,
filterStartIndex,
prefix: prefix + separator,
};
} catch (e) {
return null;
}
}

export default class Interactive extends Command {
static description = "Interactive mode";
static flags = {
Expand Down Expand Up @@ -217,7 +149,7 @@ export default class Interactive extends Command {
dispatcher.processCommand(command),
dispatcher,
undefined, // inputs
(line: string) => getCompletionsData(line, dispatcher),
dispatcher, // session-based completions
dispatcher,
);
} finally {
Expand Down
Loading
Loading