@gajae-code/coding-agent 0.4.2 → 0.4.4
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.
- package/CHANGELOG.md +13 -0
- package/dist/types/async/job-manager.d.ts +44 -1
- package/dist/types/cli/setup-cli.d.ts +14 -1
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +41 -0
- package/dist/types/commit/model-selection.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/model-resolver.d.ts +1 -19
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +15 -1
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/policy.d.ts +24 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +52 -0
- package/dist/types/extensibility/extensions/types.d.ts +13 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/harness-control-plane/types.d.ts +7 -2
- package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
- package/dist/types/session/agent-session.d.ts +12 -1
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/setup/hermes-setup.d.ts +71 -0
- package/dist/types/task/render.d.ts +7 -1
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser.d.ts +80 -0
- package/dist/types/tools/image-gen.d.ts +1 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/subagent-render.d.ts +25 -0
- package/dist/types/tools/subagent.d.ts +5 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +163 -2
- package/src/cli/setup-cli.ts +86 -2
- package/src/cli.ts +2 -0
- package/src/commands/coordinator.ts +70 -0
- package/src/commands/mcp-serve.ts +62 -0
- package/src/commands/setup.ts +30 -1
- package/src/commands/ultragoal.ts +7 -1
- package/src/commit/agentic/index.ts +2 -2
- package/src/commit/model-selection.ts +7 -22
- package/src/commit/pipeline.ts +2 -2
- package/src/config/model-registry.ts +17 -9
- package/src/config/model-resolver.ts +14 -84
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +14 -1
- package/src/coordinator/contract.ts +20 -0
- package/src/coordinator-mcp/policy.ts +160 -0
- package/src/coordinator-mcp/safety.ts +80 -0
- package/src/coordinator-mcp/server.ts +1316 -0
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/gjc-runtime/goal-mode-request.ts +21 -1
- package/src/gjc-runtime/session-state-sidecar.ts +79 -0
- package/src/harness-control-plane/owner.ts +3 -3
- package/src/harness-control-plane/rpc-adapter.ts +7 -1
- package/src/harness-control-plane/types.ts +8 -11
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +17 -9
- package/src/modes/acp/acp-event-mapper.ts +33 -1
- package/src/modes/components/custom-editor.ts +19 -3
- package/src/modes/components/hook-selector.ts +109 -5
- package/src/modes/controllers/extension-ui-controller.ts +16 -1
- package/src/modes/controllers/input-controller.ts +27 -7
- package/src/modes/controllers/selector-controller.ts +7 -1
- package/src/modes/interactive-mode.ts +3 -1
- package/src/modes/rpc/rpc-client.ts +16 -3
- package/src/modes/rpc/rpc-mode.ts +5 -2
- package/src/modes/shared/agent-wire/command-contract.ts +18 -0
- package/src/modes/shared/agent-wire/event-contract.ts +147 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
- package/src/modes/shared/agent-wire/event-observation.ts +397 -0
- package/src/modes/shared/agent-wire/protocol.ts +24 -81
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/agents/architect.md +6 -0
- package/src/prompts/agents/critic.md +6 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/tools/browser.md +3 -2
- package/src/runtime-mcp/manager.ts +15 -2
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +66 -4
- package/src/session/session-manager.ts +1 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
- package/src/setup/hermes-setup.ts +429 -0
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +14 -0
- package/src/tools/ask.ts +30 -10
- package/src/tools/bash.ts +6 -1
- package/src/tools/browser/actions.ts +189 -0
- package/src/tools/browser.ts +91 -1
- package/src/tools/image-gen.ts +42 -15
- package/src/tools/index.ts +7 -1
- package/src/tools/inspect-image.ts +10 -8
- package/src/tools/job.ts +12 -2
- package/src/tools/monitor.ts +98 -17
- package/src/tools/renderers.ts +2 -0
- package/src/tools/subagent-render.ts +160 -0
- package/src/tools/subagent.ts +49 -7
- package/src/utils/commit-message-generator.ts +6 -13
- package/src/utils/title-generator.ts +1 -1
- package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
- package/src/harness-control-plane/frame-mapper.ts +0 -286
- package/src/priority.json +0 -37
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
51
51
|
"@babel/parser": "^7.29.3",
|
|
52
52
|
"@mozilla/readability": "^0.6.0",
|
|
53
|
-
"@gajae-code/stats": "0.4.
|
|
54
|
-
"@gajae-code/agent-core": "0.4.
|
|
55
|
-
"@gajae-code/ai": "0.4.
|
|
56
|
-
"@gajae-code/natives": "0.4.
|
|
57
|
-
"@gajae-code/tui": "0.4.
|
|
58
|
-
"@gajae-code/utils": "0.4.
|
|
53
|
+
"@gajae-code/stats": "0.4.4",
|
|
54
|
+
"@gajae-code/agent-core": "0.4.4",
|
|
55
|
+
"@gajae-code/ai": "0.4.4",
|
|
56
|
+
"@gajae-code/natives": "0.4.4",
|
|
57
|
+
"@gajae-code/tui": "0.4.4",
|
|
58
|
+
"@gajae-code/utils": "0.4.4",
|
|
59
59
|
"@puppeteer/browsers": "^2.13.0",
|
|
60
60
|
"@types/turndown": "5.0.6",
|
|
61
61
|
"@xterm/headless": "^6.0.0",
|
package/src/async/job-manager.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { logger } from "@gajae-code/utils";
|
|
2
|
-
import type { AgentSource } from "../task/types";
|
|
2
|
+
import type { AgentProgress, AgentSource } from "../task/types";
|
|
3
3
|
|
|
4
4
|
const DELIVERY_RETRY_BASE_MS = 500;
|
|
5
5
|
const DELIVERY_RETRY_MAX_MS = 30_000;
|
|
6
6
|
const DELIVERY_RETRY_JITTER_MS = 200;
|
|
7
7
|
const DEFAULT_RETENTION_MS = 5 * 60 * 1000;
|
|
8
8
|
const DEFAULT_MAX_RUNNING_JOBS = 15;
|
|
9
|
+
const MONITOR_TOMBSTONE_TTL_MS = 5 * 60_000;
|
|
9
10
|
|
|
10
11
|
export interface AsyncJob {
|
|
11
12
|
id: string;
|
|
@@ -120,6 +121,27 @@ export interface AsyncJobDeliveryState {
|
|
|
120
121
|
pendingJobIds: string[];
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
export interface AsyncJobLifecycleCleanup {
|
|
125
|
+
onCancel?: (job: AsyncJob) => void;
|
|
126
|
+
onTerminal?: (job: AsyncJob) => void;
|
|
127
|
+
onEvict?: (job: AsyncJob) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Idempotent residual cleanup invoked by a post-eviction tombstone purge
|
|
130
|
+
* (e.g. a late `job cancel` after the job left the registry). Kept distinct
|
|
131
|
+
* from the at-most-once lifecycle phases so a tombstone purge never has to
|
|
132
|
+
* re-invoke a phase hook. Must be safe to call repeatedly.
|
|
133
|
+
*/
|
|
134
|
+
onTombstonePurge?: (job: AsyncJob) => void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface MonitorTombstone {
|
|
138
|
+
jobId: string;
|
|
139
|
+
ownerId?: string;
|
|
140
|
+
status: AsyncJob["status"];
|
|
141
|
+
expiresAt: number;
|
|
142
|
+
purge: () => unknown;
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
export interface AsyncJobRegisterOptions {
|
|
124
146
|
id?: string;
|
|
125
147
|
/** Registry id of the agent that owns this job; used to scope cancelAll. */
|
|
@@ -127,6 +149,7 @@ export interface AsyncJobRegisterOptions {
|
|
|
127
149
|
/** Structured metadata for tool-specific control surfaces. */
|
|
128
150
|
metadata?: AsyncJobMetadata;
|
|
129
151
|
onProgress?: (text: string, details?: Record<string, unknown>) => void | Promise<void>;
|
|
152
|
+
lifecycle?: AsyncJobLifecycleCleanup;
|
|
130
153
|
}
|
|
131
154
|
|
|
132
155
|
/**
|
|
@@ -214,6 +237,9 @@ export class AsyncJobManager {
|
|
|
214
237
|
readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
|
|
215
238
|
readonly #outputState = new Map<string, AsyncJobOutputState>();
|
|
216
239
|
readonly #ownerCleanups = new Map<string, Set<() => void>>();
|
|
240
|
+
readonly #lifecycles = new Map<string, AsyncJobLifecycleCleanup>();
|
|
241
|
+
readonly #lifecyclePhases = new Map<string, Set<"cancel" | "terminal" | "evict">>();
|
|
242
|
+
readonly #monitorTombstones = new Map<string, MonitorTombstone>();
|
|
217
243
|
readonly #outputRetentionBytes = DEFAULT_JOB_OUTPUT_RETENTION_BYTES;
|
|
218
244
|
readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
|
|
219
245
|
readonly #maxRunningJobs: number;
|
|
@@ -222,6 +248,7 @@ export class AsyncJobManager {
|
|
|
222
248
|
#disposed = false;
|
|
223
249
|
readonly #subagentRecords = new Map<string, SubagentRecord>();
|
|
224
250
|
readonly #liveHandles = new Map<string, SubagentLiveHandle>();
|
|
251
|
+
readonly #subagentProgress = new Map<string, AgentProgress>();
|
|
225
252
|
readonly #resumeQueue: ResumeQueueEntry[] = [];
|
|
226
253
|
#resumeSeq = 0;
|
|
227
254
|
#resumeRunner?: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined;
|
|
@@ -292,6 +319,7 @@ export class AsyncJobManager {
|
|
|
292
319
|
);
|
|
293
320
|
}
|
|
294
321
|
|
|
322
|
+
this.#expireMonitorTombstones();
|
|
295
323
|
const id = this.#resolveJobId(options?.id);
|
|
296
324
|
this.#suppressedDeliveries.delete(id);
|
|
297
325
|
const abortController = new AbortController();
|
|
@@ -308,6 +336,7 @@ export class AsyncJobManager {
|
|
|
308
336
|
ownerId: options?.ownerId,
|
|
309
337
|
metadata: options?.metadata,
|
|
310
338
|
};
|
|
339
|
+
if (options?.lifecycle) this.#lifecycles.set(id, options.lifecycle);
|
|
311
340
|
|
|
312
341
|
const reportProgress = async (text: string, details?: Record<string, unknown>): Promise<void> => {
|
|
313
342
|
if (!options?.onProgress) return;
|
|
@@ -325,8 +354,10 @@ export class AsyncJobManager {
|
|
|
325
354
|
const result = await run({ jobId: id, signal: abortController.signal, reportProgress });
|
|
326
355
|
const outcome: SubagentRunOutcome =
|
|
327
356
|
typeof result === "string" ? { kind: "completed", text: result } : result;
|
|
357
|
+
|
|
328
358
|
if (job.status === "cancelled") {
|
|
329
359
|
job.resultText = outcome.kind === "completed" ? outcome.text : outcome.note;
|
|
360
|
+
this.#runLifecycle(id, "terminal");
|
|
330
361
|
this.#scheduleEviction(id);
|
|
331
362
|
this.#markRecordTerminal(id, "cancelled");
|
|
332
363
|
this.#drainResumeQueue();
|
|
@@ -342,20 +373,24 @@ export class AsyncJobManager {
|
|
|
342
373
|
this.#drainResumeQueue();
|
|
343
374
|
return;
|
|
344
375
|
}
|
|
376
|
+
|
|
345
377
|
job.status = "completed";
|
|
346
378
|
job.resultText = outcome.text;
|
|
347
379
|
this.#enqueueDelivery(id, outcome.text);
|
|
380
|
+
this.#runLifecycle(id, "terminal");
|
|
348
381
|
this.#scheduleEviction(id);
|
|
349
382
|
this.#markRecordTerminal(id, "completed");
|
|
350
383
|
this.#drainResumeQueue();
|
|
351
384
|
} catch (error) {
|
|
352
385
|
if (job.status === "cancelled") {
|
|
353
386
|
job.errorText = error instanceof Error ? error.message : String(error);
|
|
387
|
+
this.#runLifecycle(id, "terminal");
|
|
354
388
|
this.#scheduleEviction(id);
|
|
355
389
|
this.#markRecordTerminal(id, "cancelled");
|
|
356
390
|
this.#drainResumeQueue();
|
|
357
391
|
return;
|
|
358
392
|
}
|
|
393
|
+
this.#runLifecycle(id, "terminal");
|
|
359
394
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
360
395
|
job.status = "failed";
|
|
361
396
|
job.errorText = errorText;
|
|
@@ -381,6 +416,7 @@ export class AsyncJobManager {
|
|
|
381
416
|
if (!job) return false;
|
|
382
417
|
if (filter?.ownerId && job.ownerId !== filter.ownerId) return false;
|
|
383
418
|
if (job.status === "paused") {
|
|
419
|
+
this.#runLifecycle(id, "cancel");
|
|
384
420
|
// Paused jobs have no running promise to abort; transition directly.
|
|
385
421
|
// The session file is kept, so the record stays resumable by id.
|
|
386
422
|
job.status = "cancelled";
|
|
@@ -390,12 +426,76 @@ export class AsyncJobManager {
|
|
|
390
426
|
return true;
|
|
391
427
|
}
|
|
392
428
|
if (job.status !== "running") return false;
|
|
429
|
+
this.#runLifecycle(id, "cancel");
|
|
393
430
|
job.status = "cancelled";
|
|
394
431
|
job.abortController.abort();
|
|
395
|
-
this.#scheduleEviction(id);
|
|
396
432
|
return true;
|
|
397
433
|
}
|
|
398
434
|
|
|
435
|
+
#runLifecycle(jobId: string, phase: "cancel" | "terminal" | "evict"): void {
|
|
436
|
+
const fired = this.#lifecyclePhases.get(jobId) ?? new Set<"cancel" | "terminal" | "evict">();
|
|
437
|
+
if (fired.has(phase)) return;
|
|
438
|
+
fired.add(phase);
|
|
439
|
+
this.#lifecyclePhases.set(jobId, fired);
|
|
440
|
+
const lifecycle = this.#lifecycles.get(jobId);
|
|
441
|
+
const job = this.#jobs.get(jobId);
|
|
442
|
+
if (!lifecycle || !job) return;
|
|
443
|
+
try {
|
|
444
|
+
if (phase === "cancel") lifecycle.onCancel?.(job);
|
|
445
|
+
else if (phase === "terminal") lifecycle.onTerminal?.(job);
|
|
446
|
+
else lifecycle.onEvict?.(job);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
logger.warn("Async job lifecycle cleanup failed", {
|
|
449
|
+
jobId,
|
|
450
|
+
phase,
|
|
451
|
+
error: error instanceof Error ? error.message : String(error),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
#expireMonitorTombstones(): void {
|
|
457
|
+
const now = Date.now();
|
|
458
|
+
for (const [jobId, tombstone] of this.#monitorTombstones) {
|
|
459
|
+
if (tombstone.expiresAt <= now) this.#monitorTombstones.delete(jobId);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#recordMonitorTombstone(jobId: string): void {
|
|
464
|
+
const job = this.#jobs.get(jobId);
|
|
465
|
+
if (!job?.metadata?.monitor) return;
|
|
466
|
+
const lifecycle = this.#lifecycles.get(jobId);
|
|
467
|
+
this.#monitorTombstones.set(jobId, {
|
|
468
|
+
jobId,
|
|
469
|
+
ownerId: job.ownerId,
|
|
470
|
+
status: job.status,
|
|
471
|
+
expiresAt: Date.now() + MONITOR_TOMBSTONE_TTL_MS,
|
|
472
|
+
purge: () => (lifecycle?.onTombstonePurge ?? lifecycle?.onEvict)?.(job),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
getMonitorTombstone(jobId: string, filter?: AsyncJobFilter): MonitorTombstone | undefined {
|
|
477
|
+
this.#expireMonitorTombstones();
|
|
478
|
+
const tombstone = this.#monitorTombstones.get(jobId);
|
|
479
|
+
if (!tombstone) return undefined;
|
|
480
|
+
if (filter?.ownerId && tombstone.ownerId !== filter.ownerId) return undefined;
|
|
481
|
+
return tombstone;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
purgeMonitorTombstone(jobId: string, filter?: AsyncJobFilter): { found: boolean; status?: AsyncJob["status"] } {
|
|
485
|
+
const tombstone = this.getMonitorTombstone(jobId, filter);
|
|
486
|
+
if (!tombstone) return { found: false };
|
|
487
|
+
this.#monitorTombstones.delete(jobId);
|
|
488
|
+
try {
|
|
489
|
+
tombstone.purge();
|
|
490
|
+
} catch (error) {
|
|
491
|
+
logger.warn("Monitor tombstone purge failed", {
|
|
492
|
+
jobId,
|
|
493
|
+
error: error instanceof Error ? error.message : String(error),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return { found: true, status: tombstone.status };
|
|
497
|
+
}
|
|
498
|
+
|
|
399
499
|
// ── Subagent control plane (pause / resume / steer support) ──────────
|
|
400
500
|
|
|
401
501
|
/** Register or replace the canonical record for a subagent. */
|
|
@@ -432,6 +532,38 @@ export class AsyncJobManager {
|
|
|
432
532
|
this.#liveHandles.delete(subagentId);
|
|
433
533
|
}
|
|
434
534
|
|
|
535
|
+
/**
|
|
536
|
+
* Retain the latest live `AgentProgress` for a subagent (deep-cloned so later
|
|
537
|
+
* mutation of the live object cannot corrupt retained state). Read by the
|
|
538
|
+
* `subagent` await panel; cleared on terminal/cancel/purge/dispose.
|
|
539
|
+
*
|
|
540
|
+
* Ignored for ids without a canonical `SubagentRecord` (e.g. foreground/inline
|
|
541
|
+
* task runs that share the executor path) so the map only holds detached
|
|
542
|
+
* subagent progress and never accumulates untracked foreground task state.
|
|
543
|
+
*/
|
|
544
|
+
recordSubagentProgress(subagentId: string, progress: AgentProgress): void {
|
|
545
|
+
if (!this.#subagentRecords.has(subagentId)) return;
|
|
546
|
+
this.#subagentProgress.set(subagentId, structuredClone(progress));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
getSubagentProgress(subagentId: string): AgentProgress | undefined {
|
|
550
|
+
return this.#subagentProgress.get(subagentId);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* True only when a live, in-session progress producer exists for this id: a
|
|
555
|
+
* canonical registered record with a live handle or an in-memory running job.
|
|
556
|
+
* False for `SubagentTool` backward-compat job synthesis and resumed-from-disk
|
|
557
|
+
* records, which have no live producer to stream from.
|
|
558
|
+
*/
|
|
559
|
+
hasLiveSubagent(subagentId: string, filter?: AsyncJobFilter): boolean {
|
|
560
|
+
const rec = this.getSubagentRecord(subagentId, filter);
|
|
561
|
+
if (!rec) return false;
|
|
562
|
+
if (this.#liveHandles.has(rec.subagentId)) return true;
|
|
563
|
+
const job = rec.currentJobId ? this.#jobs.get(rec.currentJobId) : undefined;
|
|
564
|
+
return job?.status === "running";
|
|
565
|
+
}
|
|
566
|
+
|
|
435
567
|
/** Install the TaskTool-owned resume runner. Returns the new job id, or undefined on failure. */
|
|
436
568
|
setResumeRunner(
|
|
437
569
|
runner: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined,
|
|
@@ -462,6 +594,7 @@ export class AsyncJobManager {
|
|
|
462
594
|
if (rec) {
|
|
463
595
|
rec.status = "paused";
|
|
464
596
|
this.#liveHandles.delete(rec.subagentId);
|
|
597
|
+
this.#subagentProgress.delete(rec.subagentId);
|
|
465
598
|
}
|
|
466
599
|
}
|
|
467
600
|
|
|
@@ -470,6 +603,7 @@ export class AsyncJobManager {
|
|
|
470
603
|
if (!rec) return;
|
|
471
604
|
rec.status = status;
|
|
472
605
|
this.#liveHandles.delete(rec.subagentId);
|
|
606
|
+
this.#subagentProgress.delete(rec.subagentId);
|
|
473
607
|
}
|
|
474
608
|
|
|
475
609
|
/** Request a graceful safe-boundary pause of a running subagent. */
|
|
@@ -527,6 +661,9 @@ export class AsyncJobManager {
|
|
|
527
661
|
message?: string,
|
|
528
662
|
): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
|
|
529
663
|
const prevJobId = rec.currentJobId;
|
|
664
|
+
// Clear any retained progress from the previous run so a resumed subagent
|
|
665
|
+
// never renders the prior run's tool/output as live before it emits again.
|
|
666
|
+
this.#subagentProgress.delete(rec.subagentId);
|
|
530
667
|
const newJobId = this.#resumeRunner?.(rec.subagentId, message, this.#resumeDescriptors.get(rec.subagentId));
|
|
531
668
|
if (!newJobId) return { ok: false, reason: "resume_failed" };
|
|
532
669
|
if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
|
|
@@ -564,6 +701,7 @@ export class AsyncJobManager {
|
|
|
564
701
|
}
|
|
565
702
|
rec.status = "cancelled";
|
|
566
703
|
this.#liveHandles.delete(rec.subagentId);
|
|
704
|
+
this.#subagentProgress.delete(rec.subagentId);
|
|
567
705
|
this.#drainResumeQueue();
|
|
568
706
|
return true;
|
|
569
707
|
}
|
|
@@ -572,6 +710,7 @@ export class AsyncJobManager {
|
|
|
572
710
|
if (idx !== -1) this.#resumeQueue.splice(idx, 1);
|
|
573
711
|
rec.status = "cancelled";
|
|
574
712
|
rec.queued = undefined;
|
|
713
|
+
this.#subagentProgress.delete(rec.subagentId);
|
|
575
714
|
return true;
|
|
576
715
|
}
|
|
577
716
|
return false;
|
|
@@ -586,6 +725,7 @@ export class AsyncJobManager {
|
|
|
586
725
|
this.#liveHandles.delete(sid);
|
|
587
726
|
this.#resumeDescriptors.delete(sid);
|
|
588
727
|
this.#subagentRecords.delete(sid);
|
|
728
|
+
this.#subagentProgress.delete(sid);
|
|
589
729
|
}
|
|
590
730
|
}
|
|
591
731
|
}
|
|
@@ -836,6 +976,7 @@ export class AsyncJobManager {
|
|
|
836
976
|
*/
|
|
837
977
|
cancelAll(filter?: AsyncJobFilter): void {
|
|
838
978
|
for (const job of this.getRunningJobs(filter)) {
|
|
979
|
+
this.#runLifecycle(job.id, "cancel");
|
|
839
980
|
job.status = "cancelled";
|
|
840
981
|
job.abortController.abort();
|
|
841
982
|
this.#scheduleEviction(job.id);
|
|
@@ -898,6 +1039,17 @@ export class AsyncJobManager {
|
|
|
898
1039
|
// manager. Errors in cleanup callbacks are logged but never escalated.
|
|
899
1040
|
this.runOwnerCleanups();
|
|
900
1041
|
this.cancelAll();
|
|
1042
|
+
for (const tombstone of this.#monitorTombstones.values()) {
|
|
1043
|
+
try {
|
|
1044
|
+
tombstone.purge();
|
|
1045
|
+
} catch (error) {
|
|
1046
|
+
logger.warn("Monitor tombstone purge failed during dispose", {
|
|
1047
|
+
jobId: tombstone.jobId,
|
|
1048
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
this.#monitorTombstones.clear();
|
|
901
1053
|
await this.waitForAll();
|
|
902
1054
|
const drained = await this.drainDeliveries({ timeoutMs: options?.timeoutMs ?? 3_000 });
|
|
903
1055
|
this.#clearEvictionTimers();
|
|
@@ -910,6 +1062,7 @@ export class AsyncJobManager {
|
|
|
910
1062
|
this.#ownerCleanups.clear();
|
|
911
1063
|
this.#subagentRecords.clear();
|
|
912
1064
|
this.#liveHandles.clear();
|
|
1065
|
+
this.#subagentProgress.clear();
|
|
913
1066
|
this.#resumeDescriptors.clear();
|
|
914
1067
|
this.#resumeQueue.length = 0;
|
|
915
1068
|
this.#notifyChange();
|
|
@@ -945,7 +1098,11 @@ export class AsyncJobManager {
|
|
|
945
1098
|
#scheduleEviction(jobId: string): void {
|
|
946
1099
|
this.#notifyChange();
|
|
947
1100
|
if (this.#retentionMs <= 0) {
|
|
1101
|
+
this.#recordMonitorTombstone(jobId);
|
|
1102
|
+
this.#runLifecycle(jobId, "evict");
|
|
948
1103
|
this.#jobs.delete(jobId);
|
|
1104
|
+
this.#lifecycles.delete(jobId);
|
|
1105
|
+
this.#lifecyclePhases.delete(jobId);
|
|
949
1106
|
this.#suppressedDeliveries.delete(jobId);
|
|
950
1107
|
this.#watchedJobs.delete(jobId);
|
|
951
1108
|
this.#outputState.delete(jobId);
|
|
@@ -957,7 +1114,11 @@ export class AsyncJobManager {
|
|
|
957
1114
|
}
|
|
958
1115
|
const timer = setTimeout(() => {
|
|
959
1116
|
this.#evictionTimers.delete(jobId);
|
|
1117
|
+
this.#recordMonitorTombstone(jobId);
|
|
1118
|
+
this.#runLifecycle(jobId, "evict");
|
|
960
1119
|
this.#jobs.delete(jobId);
|
|
1120
|
+
this.#lifecycles.delete(jobId);
|
|
1121
|
+
this.#lifecyclePhases.delete(jobId);
|
|
961
1122
|
this.#suppressedDeliveries.delete(jobId);
|
|
962
1123
|
this.#watchedJobs.delete(jobId);
|
|
963
1124
|
this.#outputState.delete(jobId);
|
package/src/cli/setup-cli.ts
CHANGED
|
@@ -14,6 +14,12 @@ import {
|
|
|
14
14
|
readGjcManagedCodexHooksStatus,
|
|
15
15
|
} from "../hooks/codex-native-hooks-config";
|
|
16
16
|
import { theme } from "../modes/theme/theme";
|
|
17
|
+
import {
|
|
18
|
+
formatHermesSetupResult,
|
|
19
|
+
type HermesSetupFlags,
|
|
20
|
+
hermesSetupExitCode,
|
|
21
|
+
runHermesSetup,
|
|
22
|
+
} from "../setup/hermes-setup";
|
|
17
23
|
import {
|
|
18
24
|
addApiCompatibleProvider,
|
|
19
25
|
formatProviderPresetList,
|
|
@@ -21,7 +27,7 @@ import {
|
|
|
21
27
|
parseProviderCompatibility,
|
|
22
28
|
} from "../setup/provider-onboarding";
|
|
23
29
|
|
|
24
|
-
export type SetupComponent = "defaults" | "hooks" | "provider" | "python" | "stt";
|
|
30
|
+
export type SetupComponent = "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
|
|
25
31
|
|
|
26
32
|
export interface SetupCommandArgs {
|
|
27
33
|
component: SetupComponent;
|
|
@@ -36,10 +42,23 @@ export interface SetupCommandArgs {
|
|
|
36
42
|
apiKeyEnv?: string;
|
|
37
43
|
model?: string[];
|
|
38
44
|
modelsPath?: string;
|
|
45
|
+
smoke?: boolean;
|
|
46
|
+
install?: boolean;
|
|
47
|
+
root?: string[];
|
|
48
|
+
repo?: string;
|
|
49
|
+
profile?: string;
|
|
50
|
+
sessionCommand?: string;
|
|
51
|
+
stateRoot?: string;
|
|
52
|
+
mutation?: string[];
|
|
53
|
+
artifactByteCap?: string;
|
|
54
|
+
serverKey?: string;
|
|
55
|
+
gjcCommand?: string;
|
|
56
|
+
target?: string;
|
|
57
|
+
profileDir?: string;
|
|
39
58
|
};
|
|
40
59
|
}
|
|
41
60
|
|
|
42
|
-
const VALID_COMPONENTS: SetupComponent[] = ["defaults", "hooks", "provider", "python", "stt"];
|
|
61
|
+
const VALID_COMPONENTS: SetupComponent[] = ["defaults", "hermes", "hooks", "provider", "python", "stt"];
|
|
43
62
|
|
|
44
63
|
function hasProviderSetupFlags(flags: SetupCommandArgs["flags"]): boolean {
|
|
45
64
|
return (
|
|
@@ -88,6 +107,32 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
|
88
107
|
flags.check = true;
|
|
89
108
|
} else if (arg === "--force" || arg === "-f") {
|
|
90
109
|
flags.force = true;
|
|
110
|
+
} else if (arg === "--smoke") {
|
|
111
|
+
flags.smoke = true;
|
|
112
|
+
} else if (arg === "--install") {
|
|
113
|
+
flags.install = true;
|
|
114
|
+
} else if (arg === "--root") {
|
|
115
|
+
flags.root = [...(flags.root ?? []), args[++i] ?? ""];
|
|
116
|
+
} else if (arg === "--repo") {
|
|
117
|
+
flags.repo = args[++i];
|
|
118
|
+
} else if (arg === "--profile") {
|
|
119
|
+
flags.profile = args[++i];
|
|
120
|
+
} else if (arg === "--session-command") {
|
|
121
|
+
flags.sessionCommand = args[++i];
|
|
122
|
+
} else if (arg === "--state-root") {
|
|
123
|
+
flags.stateRoot = args[++i];
|
|
124
|
+
} else if (arg === "--mutation") {
|
|
125
|
+
flags.mutation = [...(flags.mutation ?? []), args[++i] ?? ""];
|
|
126
|
+
} else if (arg === "--artifact-byte-cap") {
|
|
127
|
+
flags.artifactByteCap = args[++i];
|
|
128
|
+
} else if (arg === "--server-key") {
|
|
129
|
+
flags.serverKey = args[++i];
|
|
130
|
+
} else if (arg === "--gjc-command") {
|
|
131
|
+
flags.gjcCommand = args[++i];
|
|
132
|
+
} else if (arg === "--target") {
|
|
133
|
+
flags.target = args[++i];
|
|
134
|
+
} else if (arg === "--profile-dir") {
|
|
135
|
+
flags.profileDir = args[++i];
|
|
91
136
|
} else if (arg === "--compat") {
|
|
92
137
|
flags.compat = args[++i];
|
|
93
138
|
} else if (arg === "--preset") {
|
|
@@ -177,6 +222,9 @@ export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
|
|
|
177
222
|
case "defaults":
|
|
178
223
|
await handleDefaultsSetup(cmd.flags);
|
|
179
224
|
break;
|
|
225
|
+
case "hermes":
|
|
226
|
+
await handleHermesSetup(cmd.flags);
|
|
227
|
+
break;
|
|
180
228
|
case "hooks":
|
|
181
229
|
await handleHooksSetup(cmd.flags);
|
|
182
230
|
break;
|
|
@@ -192,6 +240,26 @@ export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
|
|
|
192
240
|
}
|
|
193
241
|
}
|
|
194
242
|
|
|
243
|
+
async function handleHermesSetup(flags: HermesSetupFlags): Promise<void> {
|
|
244
|
+
try {
|
|
245
|
+
const result = await runHermesSetup(flags);
|
|
246
|
+
if (flags.json) {
|
|
247
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
process.stdout.write(`${chalk.green(`${theme.status.success} Hermes MCP setup ready`)}\n`);
|
|
251
|
+
process.stdout.write(`${chalk.dim(formatHermesSetupResult(result))}\n`);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
254
|
+
if (flags.json) {
|
|
255
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: message }, null, 2)}\n`);
|
|
256
|
+
} else {
|
|
257
|
+
process.stderr.write(`${chalk.red(`${theme.status.error} Hermes MCP setup failed`)}\n`);
|
|
258
|
+
process.stderr.write(`${chalk.dim(message)}\n`);
|
|
259
|
+
}
|
|
260
|
+
process.exit(hermesSetupExitCode(error));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
195
263
|
async function handleProviderSetup(flags: {
|
|
196
264
|
json?: boolean;
|
|
197
265
|
force?: boolean;
|
|
@@ -410,6 +478,7 @@ ${chalk.bold("Usage:")}
|
|
|
410
478
|
|
|
411
479
|
${chalk.bold("Components:")}
|
|
412
480
|
defaults Install bundled GJC default workflow skills (default)
|
|
481
|
+
hermes Optional: render/install a Hermes MCP bridge setup package
|
|
413
482
|
hooks Optional: install GJC native Codex UserPromptSubmit/Stop skill-state hooks
|
|
414
483
|
provider Optional: add a preset, OpenAI-compatible, or Anthropic-compatible API provider
|
|
415
484
|
python Optional: verify a Python 3 interpreter is reachable for code execution
|
|
@@ -421,6 +490,11 @@ ${chalk.bold("Provider example:")}
|
|
|
421
490
|
${APP_NAME} setup provider --preset glm
|
|
422
491
|
MY_PROVIDER_KEY=sk-... ${APP_NAME} setup provider --compat openai --provider my-oai --base-url https://api.example.com/v1 --api-key-env MY_PROVIDER_KEY --model gpt-example
|
|
423
492
|
|
|
493
|
+
${chalk.bold("Hermes example:")}
|
|
494
|
+
${APP_NAME} setup hermes --root /path/to/repo
|
|
495
|
+
${APP_NAME} setup hermes --root /path/to/repo --profile my-bot --repo gajae-code --profile-dir /path/to/hermes/profile --install
|
|
496
|
+
${APP_NAME} setup hermes --root /path/to/repo --session-command "gjc --model <provider/model>"
|
|
497
|
+
|
|
424
498
|
${chalk.bold("Options:")}
|
|
425
499
|
-c, --check Check if dependencies are installed without installing
|
|
426
500
|
-f, --force Overwrite existing default workflow skill files
|
|
@@ -432,6 +506,15 @@ ${chalk.bold("Options:")}
|
|
|
432
506
|
--api-key-env Read provider API key from this environment variable
|
|
433
507
|
--model, --models Model id to add (repeat or comma-separate)
|
|
434
508
|
--models-path Override models config path
|
|
509
|
+
--smoke Run Hermes MCP setup smoke checks
|
|
510
|
+
--install Install generated Hermes setup files
|
|
511
|
+
--root Allowed Hermes MCP workdir/artifact root (repeatable)
|
|
512
|
+
--profile Hermes MCP profile namespace
|
|
513
|
+
--repo Hermes MCP repo namespace
|
|
514
|
+
--session-command Explicit GJC session command; omitted by default
|
|
515
|
+
--mutation Hermes MCP mutation classes: sessions,questions,reports,all
|
|
516
|
+
--target Hermes config file target for config-only install
|
|
517
|
+
--profile-dir Hermes profile directory for full setup install
|
|
435
518
|
|
|
436
519
|
${chalk.bold("Examples:")}
|
|
437
520
|
${APP_NAME} setup Install bundled GJC default workflow skills
|
|
@@ -439,6 +522,7 @@ ${chalk.bold("Examples:")}
|
|
|
439
522
|
${APP_NAME} setup defaults --check Check bundled GJC default workflow skills are installed
|
|
440
523
|
${APP_NAME} setup hooks Install native Codex skill-state hooks
|
|
441
524
|
${APP_NAME} setup hooks --check Check native Codex skill-state hooks
|
|
525
|
+
${APP_NAME} setup hermes Render a model-agnostic Hermes MCP setup preview
|
|
442
526
|
${APP_NAME} setup python Install Python execution dependencies
|
|
443
527
|
${APP_NAME} setup stt Install speech-to-text dependencies
|
|
444
528
|
${APP_NAME} setup stt --check Check if STT dependencies are available
|
package/src/cli.ts
CHANGED
|
@@ -36,10 +36,12 @@ const commands: CommandEntry[] = [
|
|
|
36
36
|
{ name: "skills", load: () => import("./commands/skills").then(m => m.default) },
|
|
37
37
|
{ name: "session", load: () => import("./commands/session").then(m => m.default) },
|
|
38
38
|
{ name: "harness", load: () => import("./commands/harness").then(m => m.default) },
|
|
39
|
+
{ name: "coordinator", load: () => import("./commands/coordinator").then(m => m.default) },
|
|
39
40
|
{ name: "team", load: () => import("./commands/team").then(m => m.default) },
|
|
40
41
|
{ name: "ultragoal", load: () => import("./commands/ultragoal").then(m => m.default) },
|
|
41
42
|
{ name: "ralplan", load: () => import("./commands/ralplan").then(m => m.default) },
|
|
42
43
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
44
|
+
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
43
45
|
{
|
|
44
46
|
name: "contribute-pr",
|
|
45
47
|
aliases: ["contribution-prep"],
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
2
|
+
import {
|
|
3
|
+
COORDINATOR_MCP_PROTOCOL_VERSION,
|
|
4
|
+
COORDINATOR_MCP_SERVER_NAME,
|
|
5
|
+
COORDINATOR_MCP_TOOL_NAMES,
|
|
6
|
+
} from "../coordinator/contract";
|
|
7
|
+
|
|
8
|
+
function writeJson(value: unknown): void {
|
|
9
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
10
|
+
`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function coordinatorContractPayload(): {
|
|
14
|
+
ok: true;
|
|
15
|
+
server: { name: string; protocolVersion: string };
|
|
16
|
+
readOnly: true;
|
|
17
|
+
tools: string[];
|
|
18
|
+
} {
|
|
19
|
+
return {
|
|
20
|
+
ok: true,
|
|
21
|
+
server: { name: COORDINATOR_MCP_SERVER_NAME, protocolVersion: COORDINATOR_MCP_PROTOCOL_VERSION },
|
|
22
|
+
readOnly: true,
|
|
23
|
+
tools: [...COORDINATOR_MCP_TOOL_NAMES],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default class Coordinator extends Command {
|
|
28
|
+
static description = "Inspect GJC coordinator MCP bridge contracts";
|
|
29
|
+
static strict = false;
|
|
30
|
+
|
|
31
|
+
static args = {
|
|
32
|
+
action: Args.string({ description: "Action to run (check or tools)", required: false }),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
static flags = {
|
|
36
|
+
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
async run(): Promise<void> {
|
|
40
|
+
const { args, flags } = await this.parse(Coordinator);
|
|
41
|
+
const action = args.action ?? "check";
|
|
42
|
+
if (action !== "check" && action !== "tools") {
|
|
43
|
+
const payload = { ok: false, reason: "unknown_coordinator_subcommand", subcommand: action };
|
|
44
|
+
if (flags.json) writeJson(payload);
|
|
45
|
+
else
|
|
46
|
+
process.stderr.write(`unknown_coordinator_subcommand:${action}
|
|
47
|
+
`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const payload = coordinatorContractPayload();
|
|
52
|
+
if (flags.json) {
|
|
53
|
+
writeJson(action === "tools" ? { ok: true, tools: payload.tools } : payload);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (action === "tools") {
|
|
57
|
+
for (const tool of payload.tools)
|
|
58
|
+
process.stdout.write(`${tool}
|
|
59
|
+
`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
process.stdout.write(
|
|
63
|
+
`server: ${payload.server.name}
|
|
64
|
+
protocol: ${payload.server.protocolVersion}
|
|
65
|
+
readOnly: true
|
|
66
|
+
tools: ${payload.tools.length}
|
|
67
|
+
`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
2
|
+
import {
|
|
3
|
+
COORDINATOR_MCP_PROTOCOL_VERSION,
|
|
4
|
+
COORDINATOR_MCP_SERVER_NAME,
|
|
5
|
+
COORDINATOR_MCP_TOOL_NAMES,
|
|
6
|
+
} from "../coordinator/contract";
|
|
7
|
+
import { runCoordinatorMcpStdio } from "../coordinator-mcp/server";
|
|
8
|
+
|
|
9
|
+
function writeJson(value: unknown): void {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateMcpServeSubcommandForTest(server: string | undefined): void {
|
|
14
|
+
if (server !== "coordinator") throw new Error(`unknown_mcp_serve_subcommand:${server ?? ""}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default class McpServe extends Command {
|
|
18
|
+
static description = "Serve GJC MCP compatibility bridges";
|
|
19
|
+
static strict = false;
|
|
20
|
+
|
|
21
|
+
static args = {
|
|
22
|
+
server: Args.string({ description: "MCP server to run (coordinator)", required: false }),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
static flags = {
|
|
26
|
+
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
|
|
27
|
+
check: Flags.boolean({ description: "Validate server configuration and print a smoke summary", default: false }),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
async run(): Promise<void> {
|
|
31
|
+
const { args, flags } = await this.parse(McpServe);
|
|
32
|
+
const server = args.server ?? "";
|
|
33
|
+
try {
|
|
34
|
+
validateMcpServeSubcommandForTest(server);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
const subcommand = server;
|
|
37
|
+
if (flags.json) {
|
|
38
|
+
writeJson({ ok: false, reason: "unknown_mcp_serve_subcommand", subcommand });
|
|
39
|
+
} else {
|
|
40
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (flags.check) {
|
|
46
|
+
const payload = {
|
|
47
|
+
ok: true,
|
|
48
|
+
server: { name: COORDINATOR_MCP_SERVER_NAME, protocolVersion: COORDINATOR_MCP_PROTOCOL_VERSION },
|
|
49
|
+
readOnly: true,
|
|
50
|
+
tools: [...COORDINATOR_MCP_TOOL_NAMES],
|
|
51
|
+
};
|
|
52
|
+
if (flags.json) writeJson(payload);
|
|
53
|
+
else
|
|
54
|
+
process.stdout.write(
|
|
55
|
+
`server: ${payload.server.name}\nprotocol: ${payload.server.protocolVersion}\ntools: ${payload.tools.length}\n`,
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await runCoordinatorMcpStdio();
|
|
61
|
+
}
|
|
62
|
+
}
|