@gajae-code/coding-agent 0.4.4 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/types/cli/fast-help.d.ts +1 -0
  3. package/dist/types/cli/setup-cli.d.ts +2 -0
  4. package/dist/types/commands/harness.d.ts +3 -0
  5. package/dist/types/commands/setup.d.ts +6 -0
  6. package/dist/types/config/model-registry.d.ts +3 -0
  7. package/dist/types/config/models-config-schema.d.ts +5 -0
  8. package/dist/types/coordinator/contract.d.ts +1 -1
  9. package/dist/types/coordinator-mcp/server.d.ts +8 -2
  10. package/dist/types/harness-control-plane/finalize.d.ts +5 -0
  11. package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
  12. package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
  13. package/dist/types/harness-control-plane/receipts.d.ts +46 -0
  14. package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
  15. package/dist/types/harness-control-plane/types.d.ts +9 -1
  16. package/dist/types/main.d.ts +2 -2
  17. package/dist/types/modes/utils/abort-message.d.ts +4 -0
  18. package/dist/types/session/session-manager.d.ts +8 -0
  19. package/dist/types/setup/hermes-setup.d.ts +7 -0
  20. package/dist/types/task/fork-context-advisory.d.ts +13 -0
  21. package/dist/types/task/receipt.d.ts +1 -0
  22. package/dist/types/task/roi-reconciliation.d.ts +27 -0
  23. package/dist/types/task/types.d.ts +10 -0
  24. package/package.json +8 -7
  25. package/scripts/build-binary.ts +4 -0
  26. package/src/cli/fast-help.ts +80 -0
  27. package/src/cli/setup-cli.ts +12 -3
  28. package/src/cli.ts +107 -16
  29. package/src/commands/coordinator.ts +44 -1
  30. package/src/commands/harness.ts +92 -9
  31. package/src/commands/mcp-serve.ts +3 -2
  32. package/src/commands/setup.ts +4 -0
  33. package/src/config/models-config-schema.ts +1 -0
  34. package/src/coordinator/contract.ts +1 -0
  35. package/src/coordinator-mcp/server.ts +385 -182
  36. package/src/cursor.ts +30 -2
  37. package/src/gjc-runtime/launch-worktree.ts +12 -1
  38. package/src/gjc-runtime/session-state-sidecar.ts +38 -0
  39. package/src/harness-control-plane/finalize.ts +39 -5
  40. package/src/harness-control-plane/owner.ts +9 -1
  41. package/src/harness-control-plane/phase-rollup.ts +96 -0
  42. package/src/harness-control-plane/receipt-ingest.ts +127 -0
  43. package/src/harness-control-plane/receipts.ts +229 -1
  44. package/src/harness-control-plane/rpc-adapter.ts +8 -0
  45. package/src/harness-control-plane/types.ts +29 -1
  46. package/src/internal-urls/docs-index.generated.ts +6 -5
  47. package/src/main.ts +7 -3
  48. package/src/modes/components/status-line.ts +6 -6
  49. package/src/modes/controllers/event-controller.ts +5 -4
  50. package/src/modes/interactive-mode.ts +4 -5
  51. package/src/modes/print-mode.ts +1 -1
  52. package/src/modes/theme/theme.ts +2 -2
  53. package/src/modes/utils/abort-message.ts +41 -0
  54. package/src/modes/utils/context-usage.ts +15 -8
  55. package/src/modes/utils/ui-helpers.ts +5 -6
  56. package/src/sdk.ts +9 -4
  57. package/src/session/agent-session.ts +16 -5
  58. package/src/session/session-manager.ts +20 -0
  59. package/src/setup/hermes/templates/operator-instructions.v1.md +3 -2
  60. package/src/setup/hermes-setup.ts +63 -8
  61. package/src/task/fork-context-advisory.ts +99 -0
  62. package/src/task/index.ts +31 -2
  63. package/src/task/receipt.ts +2 -0
  64. package/src/task/roi-reconciliation.ts +90 -0
  65. package/src/task/types.ts +7 -0
  66. package/src/tools/index.ts +2 -2
  67. package/src/tools/subagent-render.ts +10 -1
  68. package/src/utils/title-generator.ts +16 -2
@@ -32,10 +32,12 @@ interface JsonRpcRequest {
32
32
  params?: unknown;
33
33
  }
34
34
 
35
+ type JsonRpcResult = any;
36
+
35
37
  interface JsonRpcResponse {
36
38
  jsonrpc: "2.0";
37
39
  id: string | number | null;
38
- result?: any;
40
+ result?: JsonRpcResult;
39
41
  error?: { code: number; message: string; data?: unknown };
40
42
  }
41
43
 
@@ -46,9 +48,41 @@ interface SessionStartInput {
46
48
  worktree: true;
47
49
  }
48
50
 
51
+ interface SessionRegisterInput {
52
+ sessionId: string;
53
+ cwd: string;
54
+ tmuxSession: string;
55
+ tmuxTarget: string;
56
+ visible: boolean;
57
+ warpAttached: boolean | null;
58
+ source: string;
59
+ model: string | null;
60
+ }
61
+
62
+ interface CoordinatorFinalResponse {
63
+ text: string | null;
64
+ format: "markdown";
65
+ source: string | null;
66
+ artifact_path: string | null;
67
+ truncated: boolean;
68
+ }
69
+
70
+ function reportableFinalResponse(response: CoordinatorFinalResponse): boolean {
71
+ return (
72
+ (typeof response.text === "string" && response.text.trim().length > 0) ||
73
+ (typeof response.artifact_path === "string" && response.artifact_path.trim().length > 0)
74
+ );
75
+ }
76
+
77
+ interface RuntimeSessionStatePayload extends CoordinatorSessionState {
78
+ final_response?: CoordinatorFinalResponse;
79
+ error?: { code: string; message: string; recoverable: boolean } | null;
80
+ }
81
+
49
82
  interface CoordinatorServices {
50
83
  listSessions?: () => unknown[] | Promise<unknown[]>;
51
84
  startSession?: (input: SessionStartInput) => unknown | Promise<unknown>;
85
+ commandRunner?: (command: string[]) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
52
86
  }
53
87
 
54
88
  interface CoordinatorMcpServerOptions {
@@ -134,10 +168,16 @@ interface CoordinatorSessionState {
134
168
  reason: string | null;
135
169
  }
136
170
 
171
+ const MISSING_FINAL_RESPONSE_ADVISORY = "completion_missing_final_response";
137
172
  const ACTIVE_TURN_STATUSES = new Set<TurnStatus>(["delivering", "active", "waiting_for_answer", "completing"]);
138
173
  const TERMINAL_TURN_STATUSES = new Set<TurnStatus>(["completed", "failed", "cancelled", "superseded"]);
139
174
  const TURN_ID_PATTERN = /^turn-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
140
175
  const SAFE_EXTERNAL_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/;
176
+ function asRecord(value: unknown): Record<string, unknown> | null {
177
+ return typeof value === "object" && value !== null && !Array.isArray(value)
178
+ ? (value as Record<string, unknown>)
179
+ : null;
180
+ }
141
181
 
142
182
  function textResult(
143
183
  payload: unknown,
@@ -162,6 +202,27 @@ function toolSchema(name: CoordinatorToolName): {
162
202
  const sessionId = { type: "string", description: "GJC coordinator bridge session id." };
163
203
  const pathField = { type: "string", description: "Artifact path inside configured safe roots." };
164
204
  const common = { type: "object", properties: {} as Record<string, unknown> };
205
+ if (name === "gjc_coordinator_register_session") {
206
+ return {
207
+ name,
208
+ description: "Register an existing visible tmux GJC session as a coordinator-authoritative session.",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ session_id: sessionId,
213
+ cwd,
214
+ tmux_session: { type: "string" },
215
+ tmux_target: { type: "string" },
216
+ visible: { type: "boolean" },
217
+ warp_attached: { type: "boolean" },
218
+ source: { type: "string" },
219
+ model: { type: "string" },
220
+ allow_mutation: allowMutation,
221
+ },
222
+ required: ["session_id", "cwd", "tmux_session", "tmux_target", "allow_mutation"],
223
+ },
224
+ };
225
+ }
165
226
  if (name === "gjc_coordinator_start_session") {
166
227
  return {
167
228
  name,
@@ -293,7 +354,7 @@ function toolSchema(name: CoordinatorToolName): {
293
354
  return { name, description: "List known scoped GJC coordinator bridge sessions.", inputSchema: common };
294
355
  }
295
356
 
296
- function normalizeSession(session: any): Record<string, unknown> {
357
+ function normalizeSession(session: Record<string, unknown>): Record<string, unknown> {
297
358
  return {
298
359
  session_id: session.sessionId ?? session.session_id ?? session.name ?? "unknown",
299
360
  ...(session.tmuxSession ? { tmux_session: session.tmuxSession } : {}),
@@ -307,7 +368,7 @@ async function ensureDir(dir: string): Promise<void> {
307
368
  await fs.mkdir(dir, { recursive: true });
308
369
  }
309
370
 
310
- async function readJsonFile(file: string): Promise<any | null> {
371
+ async function readJsonFile(file: string): Promise<unknown | null> {
311
372
  try {
312
373
  return JSON.parse(await fs.readFile(file, "utf8"));
313
374
  } catch {
@@ -320,7 +381,7 @@ async function writeJsonFile(file: string, value: unknown): Promise<void> {
320
381
  await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
321
382
  }
322
383
 
323
- async function listJsonFiles(dir: string): Promise<any[]> {
384
+ async function listJsonFiles(dir: string): Promise<unknown[]> {
324
385
  try {
325
386
  const entries = await fs.readdir(dir);
326
387
  const values = await Promise.all(
@@ -342,6 +403,28 @@ function safeTurnId(value: unknown): string {
342
403
  return value;
343
404
  }
344
405
 
406
+ function safeTmuxSessionName(value: unknown): string {
407
+ if (typeof value !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$/.test(value)) {
408
+ throw new Error("invalid_tmux_session");
409
+ }
410
+ return value;
411
+ }
412
+
413
+ function safeTmuxTarget(value: unknown): string {
414
+ if (typeof value !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,160}$/.test(value)) {
415
+ throw new Error("invalid_tmux_target");
416
+ }
417
+ return value;
418
+ }
419
+
420
+ function optionalString(value: unknown): string | null {
421
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
422
+ }
423
+
424
+ function optionalBoolean(value: unknown): boolean | null {
425
+ return typeof value === "boolean" ? value : null;
426
+ }
427
+
345
428
  function turnsDir(namespaceDir: string): string {
346
429
  return path.join(namespaceDir, "turns");
347
430
  }
@@ -371,7 +454,7 @@ async function writeTurnRecord(namespaceDir: string, turn: TurnRecord): Promise<
371
454
  }
372
455
 
373
456
  async function readActiveTurn(namespaceDir: string, sessionId: string): Promise<TurnRecord | null> {
374
- const active = await readJsonFile(activeTurnFile(namespaceDir, sessionId));
457
+ const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, sessionId)));
375
458
  if (!active || typeof active.turn_id !== "string") return null;
376
459
  const turn = await readTurnRecord(namespaceDir, active.turn_id);
377
460
  if (!turn || turn.session_id !== sessionId || !ACTIVE_TURN_STATUSES.has(turn.status)) return null;
@@ -388,7 +471,7 @@ async function writeActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<
388
471
  }
389
472
 
390
473
  async function clearActiveTurn(namespaceDir: string, turn: TurnRecord): Promise<void> {
391
- const active = await readJsonFile(activeTurnFile(namespaceDir, turn.session_id));
474
+ const active = asRecord(await readJsonFile(activeTurnFile(namespaceDir, turn.session_id)));
392
475
  if (active?.turn_id === turn.turn_id) await fs.rm(activeTurnFile(namespaceDir, turn.session_id), { force: true });
393
476
  }
394
477
 
@@ -469,6 +552,14 @@ async function markTurnTerminalFromSessionState(
469
552
  sessionState: CoordinatorSessionState,
470
553
  ): Promise<TurnRecord> {
471
554
  const terminalStatus: TurnStatus = sessionState.state === "errored" ? "failed" : "completed";
555
+ const runtimeState = sessionState as RuntimeSessionStatePayload;
556
+ const finalResponse = runtimeState.final_response ?? {
557
+ text: null,
558
+ format: "markdown" as const,
559
+ source: "runtime_state",
560
+ artifact_path: null,
561
+ truncated: false,
562
+ };
472
563
  const timestamp = new Date().toISOString();
473
564
  const resolved: TurnRecord = {
474
565
  ...turn,
@@ -478,16 +569,24 @@ async function markTurnTerminalFromSessionState(
478
569
  prompt_acknowledged: true,
479
570
  state: "acknowledged",
480
571
  },
481
- final_response: {
482
- text: null,
483
- format: "markdown",
484
- source: "runtime_state",
485
- artifact_path: null,
486
- truncated: false,
487
- },
572
+ final_response: finalResponse,
573
+ evidence: reportableFinalResponse(finalResponse)
574
+ ? turn.evidence
575
+ : [
576
+ ...turn.evidence,
577
+ {
578
+ type: MISSING_FINAL_RESPONSE_ADVISORY,
579
+ message: "Runtime completed without reportable final_response text or artifact_path.",
580
+ created_at: timestamp,
581
+ },
582
+ ],
488
583
  error:
489
584
  terminalStatus === "failed"
490
- ? { code: "runtime_errored", message: sessionState.reason ?? "runtime_errored", recoverable: true }
585
+ ? (runtimeState.error ?? {
586
+ code: "runtime_errored",
587
+ message: sessionState.reason ?? "runtime_errored",
588
+ recoverable: true,
589
+ })
491
590
  : null,
492
591
  updated_at: timestamp,
493
592
  completed_at: timestamp,
@@ -505,7 +604,6 @@ async function markTurnTerminalFromSessionState(
505
604
  function shellQuote(value: string): string {
506
605
  return `'${value.replaceAll("'", "'\\''")}'`;
507
606
  }
508
-
509
607
  function makeTurnRecord(
510
608
  config: CoordinatorMcpConfig,
511
609
  sessionId: string,
@@ -571,8 +669,14 @@ async function runCommand(command: string[]): Promise<{ exitCode: number; stdout
571
669
  return { exitCode, stdout, stderr };
572
670
  }
573
671
 
574
- async function sendTmuxPromptKeys(target: string, prompt: string): Promise<boolean> {
575
- const sent = await runCommand(["tmux", "send-keys", "-t", target, prompt, "C-m", "C-m"]);
672
+ type CommandRunner = (command: string[]) => Promise<{ exitCode: number; stdout: string; stderr: string }>;
673
+
674
+ async function sendTmuxPromptKeys(
675
+ target: string,
676
+ prompt: string,
677
+ runner: CommandRunner = runCommand,
678
+ ): Promise<boolean> {
679
+ const sent = await runner(["tmux", "send-keys", "-t", target, prompt, "C-m", "C-m"]);
576
680
  return sent.exitCode === 0;
577
681
  }
578
682
 
@@ -582,12 +686,64 @@ function boundedLineCount(value: unknown): number {
582
686
  return Math.min(parsed, 400);
583
687
  }
584
688
 
689
+ async function assertTmuxTargetAvailable(
690
+ tmuxSession: string,
691
+ tmuxTarget: string,
692
+ runner: CommandRunner = runCommand,
693
+ ): Promise<void> {
694
+ const session = await runner(["tmux", "has-session", "-t", tmuxSession]);
695
+ if (session.exitCode !== 0) throw new Error("tmux_session_unavailable");
696
+ const pane = await runner(["tmux", "display-message", "-p", "-t", tmuxTarget, "#{pane_id}"]);
697
+ if (pane.exitCode !== 0 || pane.stdout.trim().length === 0) throw new Error("tmux_target_unavailable");
698
+ }
699
+
700
+ async function registerExistingTmuxSession(
701
+ input: SessionRegisterInput,
702
+ namespaceDir: string,
703
+ sessionFilePath: string,
704
+ runner: CommandRunner = runCommand,
705
+ ): Promise<{ session: Record<string, unknown>; sessionState: CoordinatorSessionState }> {
706
+ await assertTmuxTargetAvailable(input.tmuxSession, input.tmuxTarget, runner);
707
+ const existing = asRecord(await readJsonFile(sessionFilePath));
708
+ if (existing) {
709
+ const existingSession = typeof existing.tmux_session === "string" ? existing.tmux_session : existing.tmuxSession;
710
+ const existingTarget = typeof existing.tmux_target === "string" ? existing.tmux_target : existing.tmuxTarget;
711
+ if (existingSession && existingSession !== input.tmuxSession) throw new Error("session_id_conflict");
712
+ if (existingTarget && existingTarget !== input.tmuxTarget) throw new Error("session_id_conflict");
713
+ }
714
+ const timestamp = new Date().toISOString();
715
+ const session = {
716
+ ...(existing ?? {}),
717
+ session_id: input.sessionId,
718
+ sessionId: input.sessionId,
719
+ tmux_session: input.tmuxSession,
720
+ tmuxSession: input.tmuxSession,
721
+ tmux_target: input.tmuxTarget,
722
+ tmuxTarget: input.tmuxTarget,
723
+ cwd: input.cwd,
724
+ created_at: typeof existing?.created_at === "string" ? existing.created_at : timestamp,
725
+ createdAt: typeof existing?.createdAt === "string" ? existing.createdAt : timestamp,
726
+ registered_at: timestamp,
727
+ visible: input.visible,
728
+ authoritative: true,
729
+ warp_attached: input.warpAttached,
730
+ source: input.source,
731
+ model: input.model,
732
+ };
733
+ await writeJsonFile(sessionFilePath, session);
734
+ const state = await writeSessionState(namespaceDir, input.sessionId, "ready_for_input", {
735
+ live: true,
736
+ reason: null,
737
+ });
738
+ return { session, sessionState: state };
739
+ }
740
+
585
741
  async function startTmuxSession(
586
742
  config: CoordinatorMcpConfig,
587
743
  input: SessionStartInput,
588
744
  namespaceDir: string,
589
- ): Promise<Record<string, unknown> | null> {
590
- if (!config.sessionCommand) return null;
745
+ ): Promise<Record<string, unknown>> {
746
+ if (!config.sessionCommand) throw new Error("coordinator_session_command_required");
591
747
  const sessionName = `gjc-coordinator-${randomUUID().slice(0, 8)}`;
592
748
  const runtimeStateFile = sessionStateFile(namespaceDir, sessionName);
593
749
  const sessionCommand = [
@@ -635,16 +791,23 @@ async function captureTmuxTail(session: Record<string, unknown>, lines: number):
635
791
  return captured.stdout.split("\n").slice(-lines);
636
792
  }
637
793
 
638
- async function sendTmuxPrompt(session: Record<string, unknown>, prompt: string): Promise<boolean> {
794
+ async function sendTmuxPrompt(
795
+ session: Record<string, unknown>,
796
+ prompt: string,
797
+ runner: CommandRunner = runCommand,
798
+ ): Promise<boolean> {
639
799
  const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
640
800
  if (typeof target !== "string" || target.length === 0) return false;
641
- return await sendTmuxPromptKeys(target, prompt);
801
+ return await sendTmuxPromptKeys(target, prompt, runner);
642
802
  }
643
803
 
644
- async function hasTmuxSession(session: Record<string, unknown>): Promise<boolean | null> {
804
+ async function hasTmuxSession(
805
+ session: Record<string, unknown>,
806
+ runner: CommandRunner = runCommand,
807
+ ): Promise<boolean | null> {
645
808
  const tmuxSession = typeof session.tmux_session === "string" ? session.tmux_session : session.tmuxSession;
646
809
  if (typeof tmuxSession !== "string" || tmuxSession.length === 0) return null;
647
- const checked = await runCommand(["tmux", "has-session", "-t", tmuxSession]);
810
+ const checked = await runner(["tmux", "has-session", "-t", tmuxSession]);
648
811
  return checked.exitCode === 0;
649
812
  }
650
813
 
@@ -673,8 +836,12 @@ function summarizePaneTail(lines: string[]): Record<string, unknown> {
673
836
  };
674
837
  }
675
838
 
676
- async function inspectTmuxSession(session: Record<string, unknown>, lines = 80): Promise<Record<string, unknown>> {
677
- const live = await hasTmuxSession(session);
839
+ async function inspectTmuxSession(
840
+ session: Record<string, unknown>,
841
+ lines = 80,
842
+ runner: CommandRunner = runCommand,
843
+ ): Promise<Record<string, unknown>> {
844
+ const live = await hasTmuxSession(session, runner);
678
845
  const tail = live ? await captureTmuxTail(session, lines) : [];
679
846
  return {
680
847
  live,
@@ -763,6 +930,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
763
930
  const config = buildCoordinatorMcpConfig(options.env ?? process.env);
764
931
  const services = options.services ?? {};
765
932
  const namespaceDir = coordinatorNamespacePath(config);
933
+ const commandRunner = services.commandRunner ?? runCommand;
766
934
 
767
935
  async function listSessions(): Promise<unknown[]> {
768
936
  if (!config.namespace.profile || !config.namespace.repo) return [];
@@ -772,6 +940,78 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
772
940
  function sessionFile(sessionId: unknown): string {
773
941
  return path.join(namespaceDir, "sessions", `${safeExternalId("session", sessionId)}.json`);
774
942
  }
943
+ async function listQuestions(args: Record<string, unknown>): Promise<unknown[]> {
944
+ const sessionId = args.session_id == null ? null : safeExternalId("session", args.session_id);
945
+ const status = typeof args.status === "string" && args.status.length > 0 ? args.status : null;
946
+ return (await listJsonFiles(path.join(namespaceDir, "questions"))).filter(question => {
947
+ const record = asRecord(question);
948
+ if (!record) return false;
949
+ if (sessionId && record.session_id !== sessionId) return false;
950
+ if (status && record.status !== status) return false;
951
+ return true;
952
+ });
953
+ }
954
+
955
+ async function validateEvidencePaths(value: unknown): Promise<Array<{ path: string }>> {
956
+ if (value == null) return [];
957
+ if (!Array.isArray(value)) throw new Error("coordinator_evidence_paths_must_be_array");
958
+ const evidence: Array<{ path: string }> = [];
959
+ for (const item of value) {
960
+ const resolved = await assertCoordinatorArtifactPath(config, item);
961
+ evidence.push({ path: resolved.path });
962
+ }
963
+ return evidence;
964
+ }
965
+
966
+ async function activateTurn(session: Record<string, unknown>, turn: TurnRecord): Promise<TurnRecord> {
967
+ const tmuxKeysSent = await sendTmuxPrompt(session, turn.prompt.text, commandRunner);
968
+ const timestamp = new Date().toISOString();
969
+ const target = typeof session.tmux_target === "string" ? session.tmux_target : session.tmuxTarget;
970
+ const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
971
+ const activeTurn: TurnRecord = {
972
+ ...turn,
973
+ status: "active",
974
+ delivery: {
975
+ delivered: false,
976
+ queued: !tmuxKeysSent,
977
+ target: typeof target === "string" ? target : null,
978
+ tmux_keys_sent: tmuxKeysSent,
979
+ prompt_acknowledged: false,
980
+ state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
981
+ attempts: [
982
+ {
983
+ delivered: false,
984
+ tmux_keys_sent: tmuxKeysSent,
985
+ channel: "tmux_keys",
986
+ created_at: timestamp,
987
+ reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
988
+ },
989
+ ],
990
+ },
991
+ liveness: { checked_at: timestamp, live, reason: live === false ? "tmux_session_missing" : null },
992
+ started_at: turn.started_at ?? timestamp,
993
+ updated_at: timestamp,
994
+ };
995
+ await writeActiveTurn(namespaceDir, activeTurn);
996
+ await writeSessionState(namespaceDir, activeTurn.session_id, tmuxKeysSent ? "running" : "stale", {
997
+ currentTurnId: activeTurn.turn_id,
998
+ live,
999
+ reason: tmuxKeysSent ? null : "tmux_delivery_unavailable",
1000
+ });
1001
+ await writeTurnRecord(namespaceDir, activeTurn);
1002
+ return activeTurn;
1003
+ }
1004
+
1005
+ async function promoteNextQueuedTurn(sessionId: string): Promise<TurnRecord | null> {
1006
+ const session = asRecord(await readJsonFile(sessionFile(sessionId)));
1007
+ if (!session) return null;
1008
+ const queuedTurns = (await listJsonFiles(turnsDir(namespaceDir)))
1009
+ .map(turn => asRecord(turn) as TurnRecord | null)
1010
+ .filter((turn): turn is TurnRecord => turn?.session_id === sessionId && turn.status === "queued")
1011
+ .sort((left, right) => left.created_at.localeCompare(right.created_at));
1012
+ const nextTurn = queuedTurns[0];
1013
+ return nextTurn ? await activateTurn(session, nextTurn) : null;
1014
+ }
775
1015
 
776
1016
  async function readTurnPayload(
777
1017
  turnId: unknown,
@@ -783,7 +1023,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
783
1023
  if (sessionId != null && turn.session_id !== safeExternalId("session", sessionId)) {
784
1024
  return { ok: false, reason: "turn_session_mismatch" };
785
1025
  }
786
- const session = await readJsonFile(sessionFile(turn.session_id));
1026
+ const session = asRecord(await readJsonFile(sessionFile(turn.session_id)));
787
1027
  let resolvedTurn = turn;
788
1028
  let advisoryStatus: Record<string, unknown> = { live: false };
789
1029
  let sessionState = await readSessionState(namespaceDir, turn.session_id);
@@ -811,77 +1051,88 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
811
1051
  resolvedTurn = await markTurnFailedForUnavailableSession(namespaceDir, turn, "session_record_missing");
812
1052
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
813
1053
  } else if (session) {
814
- advisoryStatus = await inspectTmuxSession(session, boundedLineCount(lines));
1054
+ advisoryStatus = await inspectTmuxSession(session, boundedLineCount(lines), commandRunner);
815
1055
  if (ACTIVE_TURN_STATUSES.has(turn.status) && hasTmuxIdentity(session) && advisoryStatus.live === false) {
816
1056
  resolvedTurn = await markTurnFailedForUnavailableSession(namespaceDir, turn, "tmux_session_missing");
817
1057
  sessionState = await readSessionState(namespaceDir, resolvedTurn.session_id);
818
1058
  }
819
1059
  }
1060
+ const missingFinalResponse =
1061
+ resolvedTurn.status === "completed" && !reportableFinalResponse(resolvedTurn.final_response);
820
1062
  return {
821
1063
  ok: true,
822
1064
  turn: resolvedTurn,
823
1065
  advisory_status: advisoryStatus,
824
1066
  session_state: sessionState,
1067
+ ...(missingFinalResponse
1068
+ ? {
1069
+ completion_missing_final_response: true,
1070
+ advisory: MISSING_FINAL_RESPONSE_ADVISORY,
1071
+ }
1072
+ : {}),
825
1073
  };
826
1074
  }
827
1075
 
828
1076
  async function callTool(name: string, args: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
829
1077
  try {
830
1078
  if (name === "gjc_coordinator_list_sessions") return { ok: true, sessions: await listSessions() };
1079
+ if (name === "gjc_coordinator_register_session") {
1080
+ requireCoordinatorMutation(config, "sessions", args);
1081
+ const sessionId = safeExternalId("session", args.session_id);
1082
+ const cwd = await assertCoordinatorWorkdir(config, args.cwd);
1083
+ const tmuxSession = safeTmuxSessionName(args.tmux_session);
1084
+ const tmuxTarget = safeTmuxTarget(args.tmux_target);
1085
+ const registered = await registerExistingTmuxSession(
1086
+ {
1087
+ sessionId,
1088
+ cwd,
1089
+ tmuxSession,
1090
+ tmuxTarget,
1091
+ visible: args.visible !== false,
1092
+ warpAttached: optionalBoolean(args.warp_attached),
1093
+ source: optionalString(args.source) ?? "register_session",
1094
+ model: optionalString(args.model),
1095
+ },
1096
+ namespaceDir,
1097
+ sessionFile(sessionId),
1098
+ commandRunner,
1099
+ );
1100
+ return {
1101
+ ok: true,
1102
+ session: registered.session,
1103
+ session_state: registered.sessionState,
1104
+ registered: true,
1105
+ };
1106
+ }
831
1107
  if (name === "gjc_coordinator_read_status") {
832
1108
  const sessionId = args.session_id;
833
1109
  if (sessionId) {
834
- const session = await readJsonFile(sessionFile(sessionId));
1110
+ const session = asRecord(await readJsonFile(sessionFile(sessionId)));
835
1111
  return {
836
1112
  ok: true,
837
1113
  session,
838
- status: session ? await inspectTmuxSession(session) : { live: false },
1114
+ status: session ? await inspectTmuxSession(session, 80, commandRunner) : { live: false },
839
1115
  session_state: await readSessionState(namespaceDir, safeExternalId("session", sessionId)),
840
1116
  };
841
1117
  }
842
1118
  const sessions = await listSessions();
843
1119
  const statuses = await Promise.all(
844
- sessions.map(async session => {
845
- if (typeof session !== "object" || session === null) return { session, status: { live: null } };
846
- const normalized = session as Record<string, unknown>;
847
- const listedSessionId =
848
- typeof normalized.session_id === "string"
849
- ? normalized.session_id
850
- : normalizeSession(normalized).session_id;
851
- return {
852
- session,
853
- status: await inspectTmuxSession(normalized, 40),
854
- session_state: await readSessionState(namespaceDir, listedSessionId as string),
855
- };
856
- }),
1120
+ sessions.map(async session =>
1121
+ typeof session === "object" && session !== null
1122
+ ? {
1123
+ session,
1124
+ status: await inspectTmuxSession(session as Record<string, unknown>, 40, commandRunner),
1125
+ }
1126
+ : { session, status: { live: null } },
1127
+ ),
857
1128
  );
858
1129
  return { ok: true, sessions, statuses };
859
1130
  }
860
1131
  if (name === "gjc_coordinator_read_tail") {
861
- const session = await readJsonFile(sessionFile(args.session_id));
1132
+ const session = asRecord(await readJsonFile(sessionFile(args.session_id)));
862
1133
  return { ok: true, lines: session ? await captureTmuxTail(session, boundedLineCount(args.lines)) : [] };
863
1134
  }
864
- if (name === "gjc_coordinator_list_questions") {
865
- const questions = await listJsonFiles(path.join(namespaceDir, "questions"));
866
- const sessionId = typeof args.session_id === "string" ? safeExternalId("session", args.session_id) : null;
867
- if (sessionId) {
868
- const openQuestion = questions.find(
869
- question =>
870
- question &&
871
- typeof question === "object" &&
872
- (question as { session_id?: unknown }).session_id === sessionId &&
873
- (question as { status?: unknown }).status === "open",
874
- ) as { turn_id?: unknown } | undefined;
875
- if (openQuestion) {
876
- await writeSessionState(namespaceDir, sessionId, "needs_user_input", {
877
- currentTurnId: typeof openQuestion.turn_id === "string" ? openQuestion.turn_id : null,
878
- live: null,
879
- reason: "open_question",
880
- });
881
- }
882
- }
883
- return { ok: true, questions };
884
- }
1135
+ if (name === "gjc_coordinator_list_questions") return { ok: true, questions: await listQuestions(args) };
885
1136
  if (name === "gjc_coordinator_list_artifacts") return { ok: true, roots: config.allowedRoots };
886
1137
  if (name === "gjc_coordinator_read_artifact")
887
1138
  return await readCoordinatorArtifact(config, { path: args.path });
@@ -899,83 +1150,57 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
899
1150
  const started = services.startSession
900
1151
  ? await services.startSession(input)
901
1152
  : await startTmuxSession(config, input, namespaceDir);
902
- const session = normalizeSession(
903
- started ?? { sessionId: `gjc-coordinator-${Date.now()}`, cwd, createdAt: new Date().toISOString() },
904
- );
1153
+ const startedRecord = asRecord(started);
1154
+ if (!startedRecord) throw new Error("coordinator_session_command_required");
1155
+ const session = normalizeSession(startedRecord);
905
1156
  await writeJsonFile(sessionFile(session.session_id), session);
906
- const live = hasTmuxIdentity(session) ? await hasTmuxSession(session) : null;
907
- let turn: TurnRecord | null = null;
1157
+ const live = hasTmuxIdentity(session) ? await hasTmuxSession(session, commandRunner) : null;
908
1158
  let sessionState = await writeSessionState(
909
1159
  namespaceDir,
910
- session.session_id as string,
1160
+ String(session.session_id),
911
1161
  input.prompt ? "running" : "ready_for_input",
912
1162
  { live, reason: null },
913
1163
  );
914
- if (input.prompt) {
915
- const tmuxKeysSent = session.initialPromptTmuxKeysSent === true;
916
- turn = makeTurnRecord(config, session.session_id as string, input.prompt, "active");
917
- const timestamp = new Date().toISOString();
918
- turn.delivery = {
919
- delivered: false,
920
- queued: !tmuxKeysSent,
921
- target:
922
- typeof session.tmux_target === "string"
923
- ? session.tmux_target
924
- : typeof session.tmuxTarget === "string"
925
- ? session.tmuxTarget
926
- : null,
927
- tmux_keys_sent: tmuxKeysSent,
928
- prompt_acknowledged: false,
929
- state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
930
- attempts: [
931
- {
932
- delivered: false,
933
- tmux_keys_sent: tmuxKeysSent,
934
- channel: "tmux_keys",
935
- created_at: timestamp,
936
- reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
937
- },
938
- ],
939
- };
940
- turn.liveness = { checked_at: timestamp, live, reason: live === false ? "tmux_session_missing" : null };
941
- turn.updated_at = timestamp;
942
- await writeTurnRecord(namespaceDir, turn);
943
- await writeActiveTurn(namespaceDir, turn);
944
- await writeJsonFile(path.join(namespaceDir, "prompts", `${Date.now()}.json`), {
945
- session_id: turn.session_id,
1164
+ if (typeof args.prompt === "string" && args.prompt.length > 0) {
1165
+ const turn = await activateTurn(
1166
+ session,
1167
+ makeTurnRecord(config, String(session.session_id), args.prompt, "active"),
1168
+ );
1169
+ sessionState = (await readSessionState(namespaceDir, turn.session_id)) ?? sessionState;
1170
+ const prompt = {
1171
+ session_id: session.session_id,
946
1172
  turn_id: turn.turn_id,
947
- prompt: input.prompt,
1173
+ prompt: args.prompt,
948
1174
  queued: turn.delivery.queued,
949
1175
  delivered: turn.delivery.delivered,
950
1176
  tmux_keys_sent: turn.delivery.tmux_keys_sent ?? false,
951
1177
  prompt_acknowledged: turn.delivery.prompt_acknowledged ?? false,
952
1178
  created_at: turn.created_at,
953
- });
954
- sessionState = await writeSessionState(
955
- namespaceDir,
956
- turn.session_id,
957
- tmuxKeysSent ? "running" : "stale",
958
- {
959
- currentTurnId: turn.turn_id,
960
- live,
961
- reason: tmuxKeysSent ? null : "tmux_delivery_unavailable",
962
- },
963
- );
1179
+ };
1180
+ await writeJsonFile(path.join(namespaceDir, "prompts", `${Date.now()}.json`), prompt);
1181
+ return {
1182
+ ok: true,
1183
+ session,
1184
+ session_state: sessionState,
1185
+ turn,
1186
+ turn_id: turn.turn_id,
1187
+ active_turn_id: turn.turn_id,
1188
+ status: turn.status,
1189
+ queued: turn.delivery.queued,
1190
+ delivered: turn.delivery.delivered,
1191
+ delivery: turn.delivery,
1192
+ };
964
1193
  }
965
- return { ok: true, session, session_state: sessionState, ...(turn ? { turn, turn_id: turn.turn_id } : {}) };
1194
+ return { ok: true, session, session_state: sessionState };
966
1195
  }
967
1196
  if (name === "gjc_coordinator_send_prompt") {
968
1197
  requireCoordinatorMutation(config, "sessions", args);
969
1198
  const sessionId = safeExternalId("session", args.session_id);
970
- const session = await readJsonFile(sessionFile(sessionId));
1199
+ const session = asRecord(await readJsonFile(sessionFile(sessionId)));
971
1200
  if (!session) return { ok: false, reason: "unknown_session", session_id: sessionId };
972
1201
  if (typeof args.prompt !== "string" || args.prompt.length === 0)
973
1202
  return { ok: false, reason: "prompt_required" };
974
- let activeTurn = await readActiveTurn(namespaceDir, sessionId);
975
- if (activeTurn && hasTmuxIdentity(session) && (await hasTmuxSession(session)) === false) {
976
- activeTurn = await markTurnFailedForUnavailableSession(namespaceDir, activeTurn, "tmux_session_missing");
977
- activeTurn = null;
978
- }
1203
+ const activeTurn = await readActiveTurn(namespaceDir, sessionId);
979
1204
  if (activeTurn && args.force !== true && args.queue !== true) {
980
1205
  return {
981
1206
  ok: false,
@@ -996,61 +1221,34 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
996
1221
  await clearActiveTurn(namespaceDir, superseded);
997
1222
  }
998
1223
  const shouldQueue = args.queue === true && args.force !== true;
999
- const turn = makeTurnRecord(config, sessionId, args.prompt, shouldQueue ? "queued" : "active");
1000
- if (!shouldQueue) {
1001
- const tmuxKeysSent = await sendTmuxPrompt(session, args.prompt);
1002
- const timestamp = new Date().toISOString();
1003
- const live = hasTmuxIdentity(session) ? await hasTmuxSession(session) : null;
1004
- turn.delivery = {
1005
- delivered: false,
1006
- queued: !tmuxKeysSent,
1007
- target: typeof session.tmux_target === "string" ? session.tmux_target : null,
1008
- tmux_keys_sent: tmuxKeysSent,
1009
- prompt_acknowledged: false,
1010
- state: tmuxKeysSent ? "tmux_keys_sent" : "unavailable",
1011
- attempts: [
1012
- {
1013
- delivered: false,
1014
- tmux_keys_sent: tmuxKeysSent,
1015
- channel: "tmux_keys",
1016
- created_at: timestamp,
1017
- reason: tmuxKeysSent ? "awaiting_runtime_ack" : "tmux_delivery_unavailable",
1018
- },
1019
- ],
1020
- };
1021
- turn.liveness = { checked_at: timestamp, live, reason: live === false ? "tmux_session_missing" : null };
1022
- turn.updated_at = timestamp;
1023
- await writeActiveTurn(namespaceDir, turn);
1024
- await writeSessionState(namespaceDir, sessionId, tmuxKeysSent ? "running" : "stale", {
1025
- currentTurnId: turn.turn_id,
1026
- live,
1027
- reason: tmuxKeysSent ? null : "tmux_delivery_unavailable",
1028
- });
1029
- }
1030
- await writeTurnRecord(namespaceDir, turn);
1031
- const queued = {
1224
+ const turn = shouldQueue
1225
+ ? makeTurnRecord(config, sessionId, args.prompt, "queued")
1226
+ : await activateTurn(session, makeTurnRecord(config, sessionId, args.prompt, "active"));
1227
+ if (shouldQueue) await writeTurnRecord(namespaceDir, turn);
1228
+ const recordedTurn = turn;
1229
+ const prompt = {
1032
1230
  session_id: sessionId,
1033
- turn_id: turn.turn_id,
1231
+ turn_id: recordedTurn.turn_id,
1034
1232
  prompt: args.prompt,
1035
- queued: turn.delivery.queued,
1036
- delivered: turn.delivery.delivered,
1037
- tmux_keys_sent: turn.delivery.tmux_keys_sent ?? false,
1038
- prompt_acknowledged: turn.delivery.prompt_acknowledged ?? false,
1039
- created_at: turn.created_at,
1233
+ queued: recordedTurn.delivery.queued,
1234
+ delivered: recordedTurn.delivery.delivered,
1235
+ tmux_keys_sent: recordedTurn.delivery.tmux_keys_sent ?? false,
1236
+ prompt_acknowledged: recordedTurn.delivery.prompt_acknowledged ?? false,
1237
+ created_at: recordedTurn.created_at,
1040
1238
  };
1041
- await writeJsonFile(path.join(namespaceDir, "prompts", `${Date.now()}.json`), queued);
1239
+ await writeJsonFile(path.join(namespaceDir, "prompts", `${Date.now()}.json`), prompt);
1042
1240
  return {
1043
1241
  ok: true,
1044
1242
  session_id: sessionId,
1045
- turn_id: turn.turn_id,
1046
- active_turn_id: shouldQueue ? activeTurn?.turn_id : turn.turn_id,
1047
- status: turn.status,
1048
- queued: turn.delivery.queued,
1049
- delivered: turn.delivery.delivered,
1050
- delivery: turn.delivery,
1051
- prompt: queued,
1052
- tmux_keys_sent: turn.delivery.tmux_keys_sent ?? false,
1053
- prompt_acknowledged: turn.delivery.prompt_acknowledged ?? false,
1243
+ turn_id: recordedTurn.turn_id,
1244
+ active_turn_id: shouldQueue ? activeTurn?.turn_id : recordedTurn.turn_id,
1245
+ status: recordedTurn.status,
1246
+ queued: recordedTurn.delivery.queued,
1247
+ delivered: recordedTurn.delivery.delivered,
1248
+ delivery: recordedTurn.delivery,
1249
+ prompt,
1250
+ tmux_keys_sent: recordedTurn.delivery.tmux_keys_sent ?? false,
1251
+ prompt_acknowledged: recordedTurn.delivery.prompt_acknowledged ?? false,
1054
1252
  session_state: await readSessionState(namespaceDir, sessionId),
1055
1253
  };
1056
1254
  }
@@ -1090,7 +1288,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1090
1288
  requireCoordinatorMutation(config, "questions", args);
1091
1289
  const questionId = safeExternalId("question", args.question_id);
1092
1290
  const questionPath = questionFile(namespaceDir, questionId);
1093
- const question = await readJsonFile(questionPath);
1291
+ const question = asRecord(await readJsonFile(questionPath));
1094
1292
  if (!question) return { ok: false, reason: "unknown_question" };
1095
1293
  if (args.session_id != null && question.session_id !== safeExternalId("session", args.session_id)) {
1096
1294
  return { ok: false, reason: "question_session_mismatch" };
@@ -1098,6 +1296,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1098
1296
  if (args.turn_id != null && question.turn_id !== safeTurnId(args.turn_id)) {
1099
1297
  return { ok: false, reason: "question_turn_mismatch" };
1100
1298
  }
1299
+ const answeredTurnId = typeof question.turn_id === "string" ? question.turn_id : null;
1101
1300
  const answered = {
1102
1301
  ...question,
1103
1302
  status: "answered",
@@ -1106,8 +1305,8 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1106
1305
  };
1107
1306
  await writeJsonFile(questionPath, answered);
1108
1307
  let turn: TurnRecord | null = null;
1109
- if (typeof answered.turn_id === "string") {
1110
- turn = await readTurnRecord(namespaceDir, answered.turn_id);
1308
+ if (answeredTurnId) {
1309
+ turn = await readTurnRecord(namespaceDir, answeredTurnId);
1111
1310
  if (turn) {
1112
1311
  const timestamp = new Date().toISOString();
1113
1312
  turn = {
@@ -1123,29 +1322,33 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1123
1322
  live: null,
1124
1323
  reason: null,
1125
1324
  });
1126
- const session = await readJsonFile(sessionFile(turn.session_id));
1127
- if (session && typeof args.answer === "string") await sendTmuxPrompt(session, args.answer);
1325
+ const session = asRecord(await readJsonFile(sessionFile(turn.session_id)));
1326
+ if (session && typeof args.answer === "string")
1327
+ await sendTmuxPrompt(session, args.answer, commandRunner);
1128
1328
  }
1129
1329
  }
1130
1330
  return { ok: true, question: answered, ...(turn ? { turn } : {}) };
1131
1331
  }
1132
1332
  if (name === "gjc_coordinator_report_status") {
1133
1333
  requireCoordinatorMutation(config, "reports", args);
1334
+ const evidence = await validateEvidencePaths(args.evidence_paths);
1335
+ const sessionId = args.session_id == null ? null : safeExternalId("session", args.session_id);
1134
1336
  const report = {
1135
- session_id: args.session_id,
1337
+ session_id: sessionId,
1136
1338
  turn_id: args.turn_id,
1137
1339
  status: args.status,
1138
1340
  summary: args.summary,
1139
1341
  blocker: args.blocker,
1140
1342
  pr_url: args.pr_url,
1141
- evidence_paths: args.evidence_paths ?? [],
1343
+ evidence_paths: evidence.map(item => item.path),
1142
1344
  created_at: new Date().toISOString(),
1143
1345
  };
1144
1346
  let turn: TurnRecord | null = null;
1347
+ let promotedTurn: TurnRecord | null = null;
1145
1348
  if (args.turn_id != null) {
1146
1349
  turn = await readTurnRecord(namespaceDir, args.turn_id);
1147
1350
  if (!turn) return { ok: false, reason: "unknown_turn" };
1148
- if (args.session_id != null && turn.session_id !== safeExternalId("session", args.session_id)) {
1351
+ if (sessionId != null && turn.session_id !== sessionId) {
1149
1352
  return { ok: false, reason: "turn_session_mismatch" };
1150
1353
  }
1151
1354
  const terminalStatus = asTerminalTurnStatus(args.status);
@@ -1171,9 +1374,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1171
1374
  artifact_path: null,
1172
1375
  truncated: false,
1173
1376
  },
1174
- evidence: Array.isArray(args.evidence_paths)
1175
- ? args.evidence_paths.map(evidencePath => ({ path: evidencePath }))
1176
- : [],
1377
+ evidence,
1177
1378
  error:
1178
1379
  terminalStatus === "failed"
1179
1380
  ? {
@@ -1198,6 +1399,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1198
1399
  reason: terminalStatus === "failed" ? "reported_failure" : null,
1199
1400
  },
1200
1401
  );
1402
+ promotedTurn = await promoteNextQueuedTurn(turn.session_id);
1201
1403
  }
1202
1404
  }
1203
1405
  await writeJsonFile(path.join(namespaceDir, "reports", `${Date.now()}.json`), report);
@@ -1205,6 +1407,7 @@ export function createCoordinatorMcpServer(options: CoordinatorMcpServerOptions
1205
1407
  ok: true,
1206
1408
  report,
1207
1409
  ...(turn ? { turn, session_state: await readSessionState(namespaceDir, turn.session_id) } : {}),
1410
+ ...(promotedTurn ? { promoted_turn: promotedTurn } : {}),
1208
1411
  };
1209
1412
  }
1210
1413
  return { ok: false, reason: "unknown_tool", tool: name };
@@ -1254,7 +1457,7 @@ function legacyToolResult(payload: unknown): { content: Array<{ type: "text"; te
1254
1457
  export async function handleCoordinatorMcpRequest(
1255
1458
  request: JsonRpcRequest,
1256
1459
  options: LegacyHandlerOptions = {},
1257
- ): Promise<any> {
1460
+ ): Promise<JsonRpcResponse> {
1258
1461
  if (request.method === "initialize") {
1259
1462
  return {
1260
1463
  jsonrpc: "2.0",