Skip to content

Commit a332caa

Browse files
committed
feat: Enhance shared chat and CLI session handling
- Update shared-chat.js to improve waiting and delivered message display with accurate counts and titles. - Introduce new delivery busy states in shared-cli-sessions.js to better manage session states during interactions. - Add new utility functions in cliInteractiveHeuristics.ts for normalizing and extracting information from interactive screens. - Implement integration tests for msg_wait lifecycle to ensure proper handling of wait states and cancellations. - Extend unit tests for CLI session management to cover various scenarios including wake handling and message delivery acknowledgments. - Update shared-modals.js to include new adapters (Gemini and Copilot) in the UI. - Create new JSON configuration for agent chat bus connection.
1 parent 3e85c15 commit a332caa

17 files changed

Lines changed: 2950 additions & 219 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Unknown resource URI: mcp://agentchatbus/tools/bus_connect

agentchatbus-ts/src/adapters/mcp/tools.ts

Lines changed: 231 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,42 @@ const toolDefinitions: ToolDefinition[] = [
232232
}
233233
}
234234
},
235-
{ name: "template_list", description: "List templates.", inputSchema: { type: "object" } },
236-
{ name: "template_get", description: "Get template by ID.", inputSchema: { type: "object", required: ["template_id"] } },
237-
{ name: "template_create", description: "Create a template.", inputSchema: { type: "object", required: ["id", "name"] } },
235+
{
236+
name: "template_list",
237+
description: "List templates.",
238+
inputSchema: {
239+
type: "object",
240+
properties: {},
241+
additionalProperties: false
242+
}
243+
},
244+
{
245+
name: "template_get",
246+
description: "Get template by ID.",
247+
inputSchema: {
248+
type: "object",
249+
required: ["template_id"],
250+
properties: {
251+
template_id: { type: "string", description: "Template ID to fetch." }
252+
},
253+
additionalProperties: false
254+
}
255+
},
256+
{
257+
name: "template_create",
258+
description: "Create a template.",
259+
inputSchema: {
260+
type: "object",
261+
required: ["id", "name"],
262+
properties: {
263+
id: { type: "string", description: "Unique template ID." },
264+
name: { type: "string", description: "Human-readable template name." },
265+
description: { type: "string", description: "Optional template description." },
266+
system_prompt: { type: "string", description: "Optional system prompt body." },
267+
metadata: { type: "object", description: "Optional template metadata." }
268+
}
269+
}
270+
},
238271
{
239272
name: "agent_register",
240273
description: "Register an agent onto the bus. The display name is auto-generated as 'IDE (Model)' — e.g. 'Cursor (GPT-4)'. If the same IDE+Model pair is already registered, a numeric suffix is appended: 'Cursor (GPT-4) 2'. Optional `display_name` can be provided as a human-friendly alias. Use `capabilities` for simple string tags and `skills` for structured A2A-compatible skill declarations. Returns agent_id and a secret token for subsequent calls.",
@@ -266,14 +299,141 @@ const toolDefinitions: ToolDefinition[] = [
266299
}
267300
}
268301
},
269-
{ name: "agent_heartbeat", description: "Heartbeat an agent.", inputSchema: { type: "object", required: ["agent_id", "token"] } },
270-
{ name: "agent_resume", description: "Resume an agent.", inputSchema: { type: "object", required: ["agent_id", "token"] } },
271-
{ name: "agent_unregister", description: "Unregister an agent.", inputSchema: { type: "object", required: ["agent_id", "token"] } },
272-
{ name: "agent_list", description: "List agents.", inputSchema: { type: "object" } },
273-
{ name: "agent_update", description: "Update an agent's profile. Accepts optional display_name, description, capabilities, skills, and emoji.", inputSchema: { type: "object", required: ["agent_id", "token"], properties: { agent_id: { type: "string" }, token: { type: "string" }, display_name: { type: "string" }, description: { type: "string" }, capabilities: { type: "array", items: { type: "string" } }, skills: { type: "array" }, emoji: { type: "string", description: "Single emoji for agent avatar. Must be a valid emoji character." } } } },
274-
{ name: "agent_set_typing", description: "Set typing indicator.", inputSchema: { type: "object", required: ["thread_id", "agent_id", "is_typing"] } },
275-
{ name: "msg_react", description: "React to a message.", inputSchema: { type: "object", required: ["message_id", "agent_id", "reaction"] } },
276-
{ name: "msg_unreact", description: "Remove a reaction from a message.", inputSchema: { type: "object", required: ["message_id", "agent_id", "reaction"] } },
302+
{
303+
name: "agent_heartbeat",
304+
description: "Heartbeat an agent.",
305+
inputSchema: {
306+
type: "object",
307+
required: ["agent_id", "token"],
308+
properties: {
309+
agent_id: { type: "string", description: "Agent ID to heartbeat." },
310+
token: { type: "string", description: "Agent token." }
311+
},
312+
additionalProperties: false
313+
}
314+
},
315+
{
316+
name: "agent_resume",
317+
description: "Resume an agent.",
318+
inputSchema: {
319+
type: "object",
320+
required: ["agent_id", "token"],
321+
properties: {
322+
agent_id: { type: "string", description: "Agent ID to resume." },
323+
token: { type: "string", description: "Agent token." }
324+
},
325+
additionalProperties: false
326+
}
327+
},
328+
{
329+
name: "agent_unregister",
330+
description: "Unregister an agent.",
331+
inputSchema: {
332+
type: "object",
333+
required: ["agent_id", "token"],
334+
properties: {
335+
agent_id: { type: "string", description: "Agent ID to unregister." },
336+
token: { type: "string", description: "Agent token." }
337+
},
338+
additionalProperties: false
339+
}
340+
},
341+
{
342+
name: "agent_list",
343+
description: "List agents.",
344+
inputSchema: {
345+
type: "object",
346+
properties: {},
347+
additionalProperties: false
348+
}
349+
},
350+
{
351+
name: "agent_update",
352+
description: "Update an agent's profile. Accepts optional display_name, description, capabilities, skills, and emoji.",
353+
inputSchema: {
354+
type: "object",
355+
required: ["agent_id", "token"],
356+
properties: {
357+
agent_id: { type: "string", description: "Agent ID to update." },
358+
token: { type: "string", description: "Agent token." },
359+
display_name: { type: "string", description: "Optional human-friendly alias shown in UI and message labels." },
360+
description: { type: "string", description: "Optional short description of this agent's role." },
361+
capabilities: {
362+
type: "array",
363+
items: { type: "string" },
364+
description: "Simple capability tags for fast matching."
365+
},
366+
skills: {
367+
type: "array",
368+
description: "Structured skill declarations (A2A AgentCard compatible).",
369+
items: {
370+
type: "object",
371+
properties: {
372+
id: { type: "string", description: "Machine-readable skill identifier." },
373+
name: { type: "string", description: "Human-readable skill name." },
374+
description: { type: "string", description: "What this skill does." },
375+
tags: {
376+
type: "array",
377+
items: { type: "string" },
378+
description: "Additional tags for routing."
379+
},
380+
examples: {
381+
type: "array",
382+
items: { type: "string" },
383+
description: "Example prompts."
384+
}
385+
},
386+
required: ["id", "name"],
387+
additionalProperties: false
388+
}
389+
},
390+
emoji: { type: "string", description: "Single emoji for agent avatar. Must be a valid emoji character." }
391+
},
392+
additionalProperties: false
393+
}
394+
},
395+
{
396+
name: "agent_set_typing",
397+
description: "Set typing indicator.",
398+
inputSchema: {
399+
type: "object",
400+
required: ["thread_id", "agent_id", "is_typing"],
401+
properties: {
402+
thread_id: { type: "string", description: "Thread ID." },
403+
agent_id: { type: "string", description: "Agent ID." },
404+
is_typing: { type: "boolean", description: "Typing indicator state." }
405+
},
406+
additionalProperties: false
407+
}
408+
},
409+
{
410+
name: "msg_react",
411+
description: "React to a message.",
412+
inputSchema: {
413+
type: "object",
414+
required: ["message_id", "agent_id", "reaction"],
415+
properties: {
416+
message_id: { type: "string", description: "Message ID." },
417+
agent_id: { type: "string", description: "Agent ID." },
418+
reaction: { type: "string", description: "Reaction content." }
419+
},
420+
additionalProperties: false
421+
}
422+
},
423+
{
424+
name: "msg_unreact",
425+
description: "Remove a reaction from a message.",
426+
inputSchema: {
427+
type: "object",
428+
required: ["message_id", "agent_id", "reaction"],
429+
properties: {
430+
message_id: { type: "string", description: "Message ID." },
431+
agent_id: { type: "string", description: "Agent ID." },
432+
reaction: { type: "string", description: "Reaction content." }
433+
},
434+
additionalProperties: false
435+
}
436+
},
277437
{
278438
name: "bus_connect",
279439
description: "One-step connect: register an agent and join (or create) a thread. Returns agent identity, thread details, full message history, and sync context (current_seq, reply_token, reply_window). Clients can use that sync context directly for the first msg_post without an extra msg_wait call. If the thread does not exist, it is created automatically and the agent becomes the thread administrator.",
@@ -311,10 +471,54 @@ const toolDefinitions: ToolDefinition[] = [
311471
}
312472
}
313473
},
314-
{ name: "bus_get_config", description: "Get bus config.", inputSchema: { type: "object" } },
315-
{ name: "msg_search", description: "Search messages.", inputSchema: { type: "object", required: ["query"] } },
316-
{ name: "msg_edit", description: "Edit a message.", inputSchema: { type: "object", required: ["message_id", "new_content"] } },
317-
{ name: "msg_edit_history", description: "Get message edit history.", inputSchema: { type: "object", required: ["message_id"] } }
474+
{
475+
name: "bus_get_config",
476+
description: "Get bus config.",
477+
inputSchema: {
478+
type: "object",
479+
properties: {},
480+
additionalProperties: false
481+
}
482+
},
483+
{
484+
name: "msg_search",
485+
description: "Search messages.",
486+
inputSchema: {
487+
type: "object",
488+
required: ["query"],
489+
properties: {
490+
query: { type: "string", description: "Search query." }
491+
},
492+
additionalProperties: false
493+
}
494+
},
495+
{
496+
name: "msg_edit",
497+
description: "Edit a message.",
498+
inputSchema: {
499+
type: "object",
500+
required: ["message_id", "new_content"],
501+
properties: {
502+
message_id: { type: "string", description: "Message ID to edit." },
503+
new_content: { type: "string", description: "Replacement message content." },
504+
agent_id: { type: "string", description: "Agent ID performing the edit." },
505+
token: { type: "string", description: "Agent token used for authentication." }
506+
},
507+
additionalProperties: false
508+
}
509+
},
510+
{
511+
name: "msg_edit_history",
512+
description: "Get message edit history.",
513+
inputSchema: {
514+
type: "object",
515+
required: ["message_id"],
516+
properties: {
517+
message_id: { type: "string", description: "Message ID." }
518+
},
519+
additionalProperties: false
520+
}
521+
}
318522
];
319523

320524
export function listTools(): ToolDefinition[] {
@@ -323,6 +527,7 @@ export function listTools(): ToolDefinition[] {
323527

324528
type ToolCallContext = {
325529
sessionId?: string;
530+
abortSignal?: AbortSignal;
326531
};
327532

328533
const toolCallContext = new AsyncLocalStorage<ToolCallContext>();
@@ -401,6 +606,10 @@ function setConnectionAgent(agentId: string, token: string): void {
401606
connectionAgents.set(sessionId, { agentId, token });
402607
}
403608

609+
function getToolAbortSignal(): AbortSignal | undefined {
610+
return toolCallContext.getStore()?.abortSignal;
611+
}
612+
404613
function toPythonUtcIsoString(value: string): string {
405614
const zuluMatch = value.match(/^(.+?)(?:\.(\d+))?Z$/);
406615
if (zuluMatch) {
@@ -479,7 +688,8 @@ export async function withToolCallContext<T>(
479688
context: ToolCallContext,
480689
fn: () => Promise<T>
481690
): Promise<T> {
482-
return await toolCallContext.run(context, fn);
691+
const parent = toolCallContext.getStore() || {};
692+
return await toolCallContext.run({ ...parent, ...context }, fn);
483693
}
484694

485695
export async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
@@ -686,7 +896,8 @@ export async function callTool(name: string, args: Record<string, unknown>): Pro
686896
agents_waiting: Object.entries(threadWaitStates).map(([agentId, ws]) => ({
687897
agent_id: agentId,
688898
entered_at: ws.entered_at,
689-
timeout_ms: ws.timeout_ms
899+
timeout_ms: ws.timeout_ms,
900+
wait_call_id: ws.wait_call_id
690901
})),
691902
count: Object.keys(threadWaitStates).length
692903
};
@@ -724,7 +935,7 @@ export async function callTool(name: string, args: Record<string, unknown>): Pro
724935

725936
// Fix #6: Exit wait state for the posting agent
726937
if (authorAgentId) {
727-
getStore().exitWaitState(threadId, authorAgentId);
938+
getStore().exitWaitState(threadId, authorAgentId, undefined, "msg_post");
728939
}
729940

730941
const postPayload: Record<string, unknown> = {
@@ -1066,7 +1277,8 @@ export async function callTool(name: string, args: Record<string, unknown>): Pro
10661277
agentId,
10671278
agentToken: verifiedAgent ? token : undefined,
10681279
timeoutMs: effectiveTimeoutMs,
1069-
forAgent
1280+
forAgent,
1281+
abortSignal: getToolAbortSignal()
10701282
});
10711283

10721284
// Match Python dispatch.py L1279-1296: support blocks return format

agentchatbus-ts/src/core/services/adapters/copilotInteractiveAdapter.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,21 @@ export class CopilotInteractiveAdapter implements CliSessionAdapter {
6666
hooks: CliAdapterRunHooks,
6767
useConpty: boolean,
6868
): Promise<CliAdapterRunResult> {
69+
const initialPrompt = String(input.prompt || "").trim();
6970
const commandParts = [
7071
`& ${toPowerShellSingleQuoted(this.copilotCommand)}`,
72+
"--model",
73+
"gpt-5-mini",
7174
"--no-alt-screen",
7275
"--disable-builtin-mcps",
76+
"--allow-all-tools",
77+
"--no-ask-user",
78+
"--no-custom-instructions",
7379
];
80+
if (initialPrompt) {
81+
commandParts.push("-i");
82+
commandParts.push(toPowerShellSingleQuoted(initialPrompt));
83+
}
7484

7585
return await runInteractivePtyInChild({
7686
workerName: "interactivePtyWorker",

agentchatbus-ts/src/core/services/adapters/geminiInteractiveAdapter.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ export class GeminiInteractiveAdapter implements CliSessionAdapter {
6969
const commandParts = [
7070
`& ${toPowerShellSingleQuoted(this.geminiCommand)}`,
7171
"--no-alt-screen",
72-
"-C",
73-
toPowerShellSingleQuoted(input.workspace),
7472
];
7573

7674
return await runInteractivePtyInChild({

0 commit comments

Comments
 (0)