@gajae-code/coding-agent 0.4.3 → 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 (92) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/dist/types/async/job-manager.d.ts +19 -1
  3. package/dist/types/cli/fast-help.d.ts +1 -0
  4. package/dist/types/cli/setup-cli.d.ts +16 -1
  5. package/dist/types/commands/coordinator.d.ts +19 -0
  6. package/dist/types/commands/harness.d.ts +3 -0
  7. package/dist/types/commands/mcp-serve.d.ts +24 -0
  8. package/dist/types/commands/setup.d.ts +47 -0
  9. package/dist/types/config/model-registry.d.ts +3 -0
  10. package/dist/types/config/models-config-schema.d.ts +5 -0
  11. package/dist/types/coordinator/contract.d.ts +4 -0
  12. package/dist/types/coordinator-mcp/policy.d.ts +24 -0
  13. package/dist/types/coordinator-mcp/safety.d.ts +26 -0
  14. package/dist/types/coordinator-mcp/server.d.ts +58 -0
  15. package/dist/types/extensibility/extensions/types.d.ts +13 -0
  16. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  17. package/dist/types/harness-control-plane/finalize.d.ts +5 -0
  18. package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
  19. package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
  20. package/dist/types/harness-control-plane/receipts.d.ts +46 -0
  21. package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
  22. package/dist/types/harness-control-plane/types.d.ts +9 -1
  23. package/dist/types/main.d.ts +2 -2
  24. package/dist/types/modes/components/hook-selector.d.ts +11 -0
  25. package/dist/types/modes/utils/abort-message.d.ts +4 -0
  26. package/dist/types/session/session-manager.d.ts +8 -0
  27. package/dist/types/setup/hermes-setup.d.ts +78 -0
  28. package/dist/types/task/fork-context-advisory.d.ts +13 -0
  29. package/dist/types/task/receipt.d.ts +1 -0
  30. package/dist/types/task/render.d.ts +7 -1
  31. package/dist/types/task/roi-reconciliation.d.ts +27 -0
  32. package/dist/types/task/types.d.ts +10 -0
  33. package/dist/types/tools/subagent-render.d.ts +25 -0
  34. package/dist/types/tools/subagent.d.ts +5 -1
  35. package/package.json +8 -7
  36. package/scripts/build-binary.ts +4 -0
  37. package/src/async/job-manager.ts +43 -1
  38. package/src/cli/fast-help.ts +80 -0
  39. package/src/cli/setup-cli.ts +95 -2
  40. package/src/cli.ts +109 -16
  41. package/src/commands/coordinator.ts +113 -0
  42. package/src/commands/harness.ts +92 -9
  43. package/src/commands/mcp-serve.ts +63 -0
  44. package/src/commands/setup.ts +34 -1
  45. package/src/config/models-config-schema.ts +1 -0
  46. package/src/coordinator/contract.ts +21 -0
  47. package/src/coordinator-mcp/policy.ts +160 -0
  48. package/src/coordinator-mcp/safety.ts +80 -0
  49. package/src/coordinator-mcp/server.ts +1519 -0
  50. package/src/cursor.ts +30 -2
  51. package/src/extensibility/extensions/types.ts +13 -0
  52. package/src/gjc-runtime/launch-worktree.ts +12 -1
  53. package/src/gjc-runtime/session-state-sidecar.ts +117 -0
  54. package/src/harness-control-plane/finalize.ts +39 -5
  55. package/src/harness-control-plane/owner.ts +9 -1
  56. package/src/harness-control-plane/phase-rollup.ts +96 -0
  57. package/src/harness-control-plane/receipt-ingest.ts +127 -0
  58. package/src/harness-control-plane/receipts.ts +229 -1
  59. package/src/harness-control-plane/rpc-adapter.ts +8 -0
  60. package/src/harness-control-plane/types.ts +29 -1
  61. package/src/internal-urls/docs-index.generated.ts +6 -4
  62. package/src/main.ts +7 -3
  63. package/src/modes/components/hook-selector.ts +109 -5
  64. package/src/modes/components/status-line.ts +6 -6
  65. package/src/modes/controllers/event-controller.ts +5 -4
  66. package/src/modes/controllers/extension-ui-controller.ts +16 -1
  67. package/src/modes/interactive-mode.ts +4 -5
  68. package/src/modes/print-mode.ts +1 -1
  69. package/src/modes/theme/theme.ts +2 -2
  70. package/src/modes/utils/abort-message.ts +41 -0
  71. package/src/modes/utils/context-usage.ts +15 -8
  72. package/src/modes/utils/ui-helpers.ts +5 -6
  73. package/src/prompts/agents/architect.md +6 -0
  74. package/src/prompts/agents/critic.md +6 -0
  75. package/src/prompts/agents/planner.md +8 -1
  76. package/src/sdk.ts +9 -4
  77. package/src/session/agent-session.ts +22 -5
  78. package/src/session/session-manager.ts +20 -0
  79. package/src/setup/hermes/templates/operator-instructions.v1.md +30 -0
  80. package/src/setup/hermes-setup.ts +484 -0
  81. package/src/task/fork-context-advisory.ts +99 -0
  82. package/src/task/index.ts +33 -2
  83. package/src/task/receipt.ts +2 -0
  84. package/src/task/render.ts +14 -0
  85. package/src/task/roi-reconciliation.ts +90 -0
  86. package/src/task/types.ts +7 -0
  87. package/src/tools/ask.ts +30 -10
  88. package/src/tools/index.ts +2 -2
  89. package/src/tools/renderers.ts +2 -0
  90. package/src/tools/subagent-render.ts +169 -0
  91. package/src/tools/subagent.ts +49 -7
  92. package/src/utils/title-generator.ts +16 -2
@@ -22,6 +22,17 @@ export interface HookSelectorOptions {
22
22
  */
23
23
  wrapFocused?: boolean;
24
24
  scrollTitleRows?: number;
25
+ /**
26
+ * Inline free-text entry for the option with this label (e.g. the ask
27
+ * tool's "Other (type your own)"). Selecting it keeps the title and option
28
+ * list on screen and opens a prompt-style editor below the list instead of
29
+ * replacing the whole selector. Enter submits via `onSubmit`; Escape
30
+ * returns to option selection.
31
+ */
32
+ customInput?: {
33
+ optionLabel: string;
34
+ onSubmit: (text: string) => void;
35
+ };
25
36
  }
26
37
  export declare class HookSelectorComponent extends Container {
27
38
  #private;
@@ -0,0 +1,4 @@
1
+ export declare function buildAbortDisplayMessage({ errorMessage, retryAttempt }: {
2
+ errorMessage?: string;
3
+ retryAttempt: number;
4
+ }): string;
@@ -401,6 +401,14 @@ export declare class SessionManager {
401
401
  appendCompaction<T = unknown>(summary: string, shortSummary: string | undefined, firstKeptEntryId: string, tokensBefore: number, details?: T, fromExtension?: boolean, preserveData?: Record<string, unknown>): string;
402
402
  /** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */
403
403
  appendCustomEntry(customType: string, data?: unknown): string;
404
+ /**
405
+ * Write mutated message entries back into the canonical entry store by id.
406
+ *
407
+ * `getBranch()` materializes resident-blob entries into copies, so in-place
408
+ * mutation of returned entries (e.g. pruning tool outputs) does not affect
409
+ * the canonical store. This applies such mutations for real.
410
+ */
411
+ applyEntryMessageUpdates(entries: readonly SessionMessageEntry[]): void;
404
412
  /**
405
413
  * Rewrite the session file after in-place entry updates.
406
414
  * Use sparingly (e.g., pruning old tool outputs).
@@ -0,0 +1,78 @@
1
+ import { COORDINATOR_MCP_PROTOCOL_VERSION, COORDINATOR_MCP_SERVER_NAME } from "../coordinator/contract";
2
+ export type HermesMutationClass = "sessions" | "questions" | "reports";
3
+ export type HermesSetupMode = "render" | "install" | "check" | "smoke";
4
+ export interface HermesSetupFlags {
5
+ json?: boolean;
6
+ check?: boolean;
7
+ smoke?: boolean;
8
+ install?: boolean;
9
+ force?: boolean;
10
+ root?: string[];
11
+ repo?: string;
12
+ profile?: string;
13
+ sessionCommand?: string;
14
+ noWorktree?: boolean;
15
+ worktreeName?: string;
16
+ stateRoot?: string;
17
+ mutation?: string[];
18
+ artifactByteCap?: string;
19
+ serverKey?: string;
20
+ gjcCommand?: string;
21
+ target?: string;
22
+ profileDir?: string;
23
+ }
24
+ export interface CoordinatorSetupSpec {
25
+ schemaVersion: 1;
26
+ coordinator: "hermes";
27
+ serverKey: string;
28
+ serverName: typeof COORDINATOR_MCP_SERVER_NAME;
29
+ protocolVersion: typeof COORDINATOR_MCP_PROTOCOL_VERSION;
30
+ gjcCommand: string;
31
+ args: ["mcp-serve", "coordinator"];
32
+ roots: string[];
33
+ namespace: {
34
+ profile?: string;
35
+ repo?: string;
36
+ };
37
+ sessionCommand?: string;
38
+ sessionCommandSource: "default" | "explicit";
39
+ worktree: {
40
+ enabled: boolean;
41
+ name?: string;
42
+ };
43
+ stateRoot?: string;
44
+ mutationPolicy: {
45
+ classes: HermesMutationClass[];
46
+ perCallConsentRequired: true;
47
+ };
48
+ artifactByteCap?: number;
49
+ installTarget?: {
50
+ kind: "profile-dir" | "config-file";
51
+ path: string;
52
+ };
53
+ operatorTemplateVersion: 1;
54
+ contractDocVersion: 1;
55
+ }
56
+ export interface HermesSetupResult {
57
+ ok: boolean;
58
+ mode: HermesSetupMode;
59
+ files_written: string[];
60
+ previews: Array<{
61
+ path: string;
62
+ content: string;
63
+ }>;
64
+ warnings: string[];
65
+ smoke: null | {
66
+ ok: boolean;
67
+ protocolVersion: string;
68
+ serverName: string;
69
+ requiredTools: string[];
70
+ missingTools: string[];
71
+ };
72
+ }
73
+ export declare function buildHermesSetupSpec(flags: HermesSetupFlags): CoordinatorSetupSpec;
74
+ export declare function computeHermesSetupSignature(spec: CoordinatorSetupSpec): string;
75
+ export declare function renderHermesServerBlock(spec: CoordinatorSetupSpec): Record<string, unknown>;
76
+ export declare function runHermesSetup(flags: HermesSetupFlags): Promise<HermesSetupResult>;
77
+ export declare function formatHermesSetupResult(result: HermesSetupResult): string;
78
+ export declare function hermesSetupExitCode(error: unknown): number;
@@ -0,0 +1,13 @@
1
+ import type { ForkContextMode } from "./types";
2
+ export interface ForkContextAdvisory {
3
+ recommendedMode: ForkContextMode;
4
+ reasons: string[];
5
+ estimatedClonedTokens: Record<ForkContextMode, number>;
6
+ callerModeRespected: true;
7
+ }
8
+ export declare function adviseForkContextMode(input: {
9
+ assignment: string;
10
+ context?: string;
11
+ explicitMode?: ForkContextMode;
12
+ parentContextTokens?: number;
13
+ }): ForkContextAdvisory;
@@ -56,6 +56,7 @@ export interface TaskResultReceipt {
56
56
  };
57
57
  extractedToolCounts?: Record<string, number>;
58
58
  forkContext?: SingleResult["forkContext"];
59
+ forkContextAdvisory?: SingleResult["forkContextAdvisory"];
59
60
  roi?: TaskRoi;
60
61
  }
61
62
  /**
@@ -1,11 +1,17 @@
1
1
  import type { Component } from "@gajae-code/tui";
2
2
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
3
3
  import type { Theme } from "../modes/theme/theme";
4
- import type { TaskParams, TaskToolDetails } from "./types";
4
+ import type { AgentProgress, TaskParams, TaskToolDetails } from "./types";
5
5
  /**
6
6
  * Render the tool call arguments.
7
7
  */
8
8
  export declare function renderCall(args: TaskParams, _options: RenderResultOptions, theme: Theme): Component;
9
+ /**
10
+ * Public wrapper to render a single subagent's live `AgentProgress` for the
11
+ * `subagent` await panel. Reuses the internal task-progress renderer so the
12
+ * await panel stays at parity with the inline task panel.
13
+ */
14
+ export declare function renderSubagentLiveProgress(progress: AgentProgress, expanded: boolean, theme: Theme, spinnerFrame?: number): string[];
9
15
  /**
10
16
  * Render the tool result.
11
17
  */
@@ -0,0 +1,27 @@
1
+ import type { TaskResultReceipt } from "./receipt";
2
+ import type { SpawnPlanReceipt } from "./spawn-gate";
3
+ /**
4
+ * Pure, advisory-only reconciliation between a spawn plan's inline-token promise
5
+ * and receipt-safe child outputs. These signals never change task success/failure
6
+ * semantics or runtime behavior; they only describe budget/ROI observations for
7
+ * model-facing summaries.
8
+ */
9
+ export interface SpawnRoiChildReconciliation {
10
+ id: string;
11
+ inlineTokens: number;
12
+ maxInlineTokens: number;
13
+ overBudget: boolean;
14
+ overageTokens: number;
15
+ lowRoi: boolean;
16
+ }
17
+ export interface SpawnRoiReconciliation {
18
+ childCount: number;
19
+ promisedMaxInlineTokens: number;
20
+ children: SpawnRoiChildReconciliation[];
21
+ overBudgetChildIds: string[];
22
+ lowRoiChildIds: string[];
23
+ totalInlineTokens: number;
24
+ totalOverageTokens: number;
25
+ advisoryFlags: string[];
26
+ }
27
+ export declare function reconcileSpawnRoi(plan: SpawnPlanReceipt | undefined, receipts: readonly TaskResultReceipt[]): SpawnRoiReconciliation | undefined;
@@ -2,6 +2,7 @@ import type { ThinkingLevel } from "@gajae-code/agent-core";
2
2
  import type { Usage } from "@gajae-code/ai";
3
3
  import * as z from "zod/v4";
4
4
  import type { TaskResultReceipt } from "./receipt";
5
+ import type { SpawnRoiReconciliation } from "./roi-reconciliation";
5
6
  import { type TaskSimpleMode } from "./simple-mode";
6
7
  import type { SpawnPlanReceipt } from "./spawn-gate";
7
8
  import type { NestedRepoPatch } from "./worktree";
@@ -413,6 +414,14 @@ export interface SingleResult {
413
414
  mode: ForkContextMode;
414
415
  clonedTokens: number;
415
416
  };
417
+ /**
418
+ * Advisory fork-context mode recommendation for this task (logged only;
419
+ * never changes the actual mode selection).
420
+ */
421
+ forkContextAdvisory?: {
422
+ recommendedMode: ForkContextMode;
423
+ reasons: string[];
424
+ };
416
425
  }
417
426
  /** Tool details for TUI rendering */
418
427
  export interface TaskToolDetails {
@@ -431,6 +440,7 @@ export interface TaskToolDetails {
431
440
  /** Advisory ids for terminal children that spent tokens without detectable output/review/changes. */
432
441
  lowRoiChildIds: string[];
433
442
  };
443
+ roiReconciliation?: SpawnRoiReconciliation;
434
444
  progress?: AgentProgress[];
435
445
  async?: {
436
446
  state: "running" | "paused" | "queued" | "completed" | "failed";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * TUI renderer for the `subagent` tool.
3
+ *
4
+ * The await panel surfaces each awaited subagent's live streaming status at
5
+ * parity with the inline `task` panel by reusing `renderSubagentLiveProgress`.
6
+ * Falls back to a `running, no activity yet` placeholder when a live producer
7
+ * exists but has not emitted yet, and to a static status line when no live
8
+ * producer is available (resumed-from-disk or backward-compat records).
9
+ */
10
+ import type { Component } from "@gajae-code/tui";
11
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
12
+ import type { Theme } from "../modes/theme/theme";
13
+ import type { SubagentToolDetails } from "./subagent";
14
+ export declare const subagentToolRenderer: {
15
+ inline: boolean;
16
+ renderCall(_args: unknown, _options: RenderResultOptions, theme: Theme): Component;
17
+ renderResult(result: {
18
+ content: Array<{
19
+ type: string;
20
+ text?: string;
21
+ }>;
22
+ details?: SubagentToolDetails;
23
+ }, options: RenderResultOptions, theme: Theme): Component;
24
+ mergeCallAndResult: boolean;
25
+ };
@@ -1,6 +1,6 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
2
  import * as z from "zod/v4";
3
- import type { AgentSource } from "../task/types";
3
+ import type { AgentProgress, AgentSource } from "../task/types";
4
4
  import type { ToolSession } from "./index";
5
5
  declare const subagentSchema: z.ZodObject<{
6
6
  action: z.ZodEnum<{
@@ -42,6 +42,10 @@ export interface SubagentSnapshot {
42
42
  outputRef?: string;
43
43
  truncated?: boolean;
44
44
  guidance?: string;
45
+ /** Live streaming progress for the awaited subagent (await panel only; UI detail). */
46
+ progress?: AgentProgress;
47
+ /** True when a live in-session progress producer exists for this subagent. */
48
+ liveProgressAvailable?: boolean;
45
49
  }
46
50
  export interface SubagentToolDetails {
47
51
  subagents: SubagentSnapshot[];
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.3",
4
+ "version": "0.4.5",
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",
@@ -36,6 +36,7 @@
36
36
  "check:types": "tsgo -p tsconfig.json --noEmit",
37
37
  "lint": "biome lint .",
38
38
  "test": "bun test",
39
+ "bench:context": "bun run bench/context-optimization.bench.ts",
39
40
  "generate-schemas": "bun ../../scripts/generate-json-schemas.ts",
40
41
  "check:schemas": "bun ../../scripts/generate-json-schemas.ts --check",
41
42
  "fix": "biome check --write --unsafe . && bun run format-prompts && bun run generate-docs-index",
@@ -50,12 +51,12 @@
50
51
  "@agentclientprotocol/sdk": "0.21.0",
51
52
  "@babel/parser": "^7.29.3",
52
53
  "@mozilla/readability": "^0.6.0",
53
- "@gajae-code/stats": "0.4.3",
54
- "@gajae-code/agent-core": "0.4.3",
55
- "@gajae-code/ai": "0.4.3",
56
- "@gajae-code/natives": "0.4.3",
57
- "@gajae-code/tui": "0.4.3",
58
- "@gajae-code/utils": "0.4.3",
54
+ "@gajae-code/stats": "0.4.5",
55
+ "@gajae-code/agent-core": "0.4.5",
56
+ "@gajae-code/ai": "0.4.5",
57
+ "@gajae-code/natives": "0.4.5",
58
+ "@gajae-code/tui": "0.4.5",
59
+ "@gajae-code/utils": "0.4.5",
59
60
  "@puppeteer/browsers": "^2.13.0",
60
61
  "@types/turndown": "5.0.6",
61
62
  "@xterm/headless": "^6.0.0",
@@ -39,6 +39,10 @@ async function main(): Promise<void> {
39
39
  "bun",
40
40
  "build",
41
41
  "--compile",
42
+ // Minify shrinks the bundled JS the compiled binary must parse at
43
+ // startup (302MB → ~114MB --help RSS measured on darwin-arm64).
44
+ // --keep-names below preserves identifiers for error reports.
45
+ "--minify",
42
46
  "--no-compile-autoload-bunfig",
43
47
  "--no-compile-autoload-dotenv",
44
48
  "--no-compile-autoload-tsconfig",
@@ -1,5 +1,5 @@
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;
@@ -248,6 +248,7 @@ export class AsyncJobManager {
248
248
  #disposed = false;
249
249
  readonly #subagentRecords = new Map<string, SubagentRecord>();
250
250
  readonly #liveHandles = new Map<string, SubagentLiveHandle>();
251
+ readonly #subagentProgress = new Map<string, AgentProgress>();
251
252
  readonly #resumeQueue: ResumeQueueEntry[] = [];
252
253
  #resumeSeq = 0;
253
254
  #resumeRunner?: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined;
@@ -531,6 +532,38 @@ export class AsyncJobManager {
531
532
  this.#liveHandles.delete(subagentId);
532
533
  }
533
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
+
534
567
  /** Install the TaskTool-owned resume runner. Returns the new job id, or undefined on failure. */
535
568
  setResumeRunner(
536
569
  runner: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined,
@@ -561,6 +594,7 @@ export class AsyncJobManager {
561
594
  if (rec) {
562
595
  rec.status = "paused";
563
596
  this.#liveHandles.delete(rec.subagentId);
597
+ this.#subagentProgress.delete(rec.subagentId);
564
598
  }
565
599
  }
566
600
 
@@ -569,6 +603,7 @@ export class AsyncJobManager {
569
603
  if (!rec) return;
570
604
  rec.status = status;
571
605
  this.#liveHandles.delete(rec.subagentId);
606
+ this.#subagentProgress.delete(rec.subagentId);
572
607
  }
573
608
 
574
609
  /** Request a graceful safe-boundary pause of a running subagent. */
@@ -626,6 +661,9 @@ export class AsyncJobManager {
626
661
  message?: string,
627
662
  ): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
628
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);
629
667
  const newJobId = this.#resumeRunner?.(rec.subagentId, message, this.#resumeDescriptors.get(rec.subagentId));
630
668
  if (!newJobId) return { ok: false, reason: "resume_failed" };
631
669
  if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
@@ -663,6 +701,7 @@ export class AsyncJobManager {
663
701
  }
664
702
  rec.status = "cancelled";
665
703
  this.#liveHandles.delete(rec.subagentId);
704
+ this.#subagentProgress.delete(rec.subagentId);
666
705
  this.#drainResumeQueue();
667
706
  return true;
668
707
  }
@@ -671,6 +710,7 @@ export class AsyncJobManager {
671
710
  if (idx !== -1) this.#resumeQueue.splice(idx, 1);
672
711
  rec.status = "cancelled";
673
712
  rec.queued = undefined;
713
+ this.#subagentProgress.delete(rec.subagentId);
674
714
  return true;
675
715
  }
676
716
  return false;
@@ -685,6 +725,7 @@ export class AsyncJobManager {
685
725
  this.#liveHandles.delete(sid);
686
726
  this.#resumeDescriptors.delete(sid);
687
727
  this.#subagentRecords.delete(sid);
728
+ this.#subagentProgress.delete(sid);
688
729
  }
689
730
  }
690
731
  }
@@ -1021,6 +1062,7 @@ export class AsyncJobManager {
1021
1062
  this.#ownerCleanups.clear();
1022
1063
  this.#subagentRecords.clear();
1023
1064
  this.#liveHandles.clear();
1065
+ this.#subagentProgress.clear();
1024
1066
  this.#resumeDescriptors.clear();
1025
1067
  this.#resumeQueue.length = 0;
1026
1068
  this.#notifyChange();
@@ -0,0 +1,80 @@
1
+ import { APP_NAME, CONFIG_DIR_NAME } from "@gajae-code/utils/dirs";
2
+
3
+ export function getExtraHelpText(): string {
4
+ return `Environment Variables:
5
+ # Core Providers
6
+ ANTHROPIC_API_KEY - Anthropic Claude models
7
+ ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth (takes precedence over API key)
8
+ CLAUDE_CODE_USE_FOUNDRY - Enable Anthropic Foundry mode (uses Foundry endpoint + mTLS)
9
+ FOUNDRY_BASE_URL - Anthropic Foundry base URL (e.g., https://<foundry-host>)
10
+ ANTHROPIC_FOUNDRY_API_KEY - Anthropic token used as Authorization: Bearer <token> in Foundry mode
11
+ ANTHROPIC_CUSTOM_HEADERS - Extra Foundry headers (e.g., "user-id: USERNAME")
12
+ CLAUDE_CODE_CLIENT_CERT - Client certificate (PEM path or inline PEM) for mTLS
13
+ CLAUDE_CODE_CLIENT_KEY - Client private key (PEM path or inline PEM) for mTLS
14
+ NODE_EXTRA_CA_CERTS - CA bundle path (or inline PEM) for server certificate validation
15
+ OPENAI_API_KEY - OpenAI GPT models
16
+ GEMINI_API_KEY - Google Gemini models
17
+ GITHUB_TOKEN - GitHub Copilot (or GH_TOKEN, COPILOT_GITHUB_TOKEN)
18
+
19
+ # Additional LLM Providers
20
+ AZURE_OPENAI_API_KEY - Azure OpenAI models
21
+ GROQ_API_KEY - Groq models
22
+ CEREBRAS_API_KEY - Cerebras models
23
+ XAI_API_KEY - xAI Grok models
24
+ OPENROUTER_API_KEY - OpenRouter aggregated models
25
+ KILO_API_KEY - Kilo Gateway models
26
+ MISTRAL_API_KEY - Mistral models
27
+ ZAI_API_KEY - z.ai models (ZhipuAI/GLM)
28
+ MINIMAX_API_KEY - MiniMax models
29
+ OPENCODE_API_KEY - OpenCode Zen/OpenCode Go models
30
+ CURSOR_ACCESS_TOKEN - Cursor AI models
31
+ AI_GATEWAY_API_KEY - Vercel AI Gateway
32
+
33
+ # Cloud Providers
34
+ AWS_PROFILE - AWS Bedrock (or AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)
35
+ GOOGLE_CLOUD_PROJECT - Google Vertex AI (requires GOOGLE_CLOUD_LOCATION)
36
+ GOOGLE_APPLICATION_CREDENTIALS - Service account for Vertex AI
37
+
38
+ # Search & Tools
39
+ EXA_API_KEY - Exa web search
40
+ BRAVE_API_KEY - Brave web search
41
+ PERPLEXITY_API_KEY - Perplexity web search (API)
42
+ PERPLEXITY_COOKIES - Perplexity web search (session cookie)
43
+ TAVILY_API_KEY - Tavily web search
44
+ ANTHROPIC_SEARCH_API_KEY - Anthropic search provider
45
+
46
+ # Configuration
47
+ GJC_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
48
+ GJC_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
49
+ GJC_SMOL_MODEL - Override smol/fast model (see --smol)
50
+ GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
51
+ GJC_PLAN_MODEL - Override planning model (see --plan)
52
+ GJC_NO_PTY - Disable PTY-based interactive bash execution
53
+ --tmux - Launch interactive startup inside a new tmux session
54
+ gjc session - List, inspect, create, remove, or attach tagged GJC-managed tmux sessions
55
+ GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
56
+ GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
57
+
58
+ For complete environment variable reference, see:
59
+ docs/environment-variables.md
60
+ Available Tools (default-enabled unless noted):
61
+ read - Read file contents
62
+ bash - Execute bash commands
63
+ edit - Edit files with find/replace
64
+ write - Write files (creates/overwrites)
65
+ grep - Search file contents
66
+ find - Find files by glob pattern
67
+ lsp - Language server protocol (code intelligence)
68
+ python - Execute Python code (requires: ${APP_NAME} setup python)
69
+ notebook - Edit Jupyter notebooks
70
+ inspect_image - Analyze images with a vision model
71
+ browser - Browser automation (Puppeteer)
72
+ task - Launch sub-agents for parallel tasks
73
+ todo_write - Manage todo/task lists
74
+ web_search - Search the web
75
+ ask - Ask user questions (interactive mode only)
76
+
77
+ Useful Commands:
78
+ ${APP_NAME} --list-models - List configured provider models
79
+ ${APP_NAME} --help - Show this help`;
80
+ }
@@ -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,25 @@ 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
+ noWorktree?: boolean;
52
+ worktreeName?: string;
53
+ stateRoot?: string;
54
+ mutation?: string[];
55
+ artifactByteCap?: string;
56
+ serverKey?: string;
57
+ gjcCommand?: string;
58
+ target?: string;
59
+ profileDir?: string;
39
60
  };
40
61
  }
41
62
 
42
- const VALID_COMPONENTS: SetupComponent[] = ["defaults", "hooks", "provider", "python", "stt"];
63
+ const VALID_COMPONENTS: SetupComponent[] = ["defaults", "hermes", "hooks", "provider", "python", "stt"];
43
64
 
44
65
  function hasProviderSetupFlags(flags: SetupCommandArgs["flags"]): boolean {
45
66
  return (
@@ -88,6 +109,36 @@ export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
88
109
  flags.check = true;
89
110
  } else if (arg === "--force" || arg === "-f") {
90
111
  flags.force = true;
112
+ } else if (arg === "--smoke") {
113
+ flags.smoke = true;
114
+ } else if (arg === "--install") {
115
+ flags.install = true;
116
+ } else if (arg === "--root") {
117
+ flags.root = [...(flags.root ?? []), args[++i] ?? ""];
118
+ } else if (arg === "--repo") {
119
+ flags.repo = args[++i];
120
+ } else if (arg === "--profile") {
121
+ flags.profile = args[++i];
122
+ } else if (arg === "--session-command") {
123
+ flags.sessionCommand = args[++i];
124
+ } else if (arg === "--no-worktree") {
125
+ flags.noWorktree = true;
126
+ } else if (arg === "--worktree-name") {
127
+ flags.worktreeName = args[++i];
128
+ } else if (arg === "--state-root") {
129
+ flags.stateRoot = args[++i];
130
+ } else if (arg === "--mutation") {
131
+ flags.mutation = [...(flags.mutation ?? []), args[++i] ?? ""];
132
+ } else if (arg === "--artifact-byte-cap") {
133
+ flags.artifactByteCap = args[++i];
134
+ } else if (arg === "--server-key") {
135
+ flags.serverKey = args[++i];
136
+ } else if (arg === "--gjc-command") {
137
+ flags.gjcCommand = args[++i];
138
+ } else if (arg === "--target") {
139
+ flags.target = args[++i];
140
+ } else if (arg === "--profile-dir") {
141
+ flags.profileDir = args[++i];
91
142
  } else if (arg === "--compat") {
92
143
  flags.compat = args[++i];
93
144
  } else if (arg === "--preset") {
@@ -177,6 +228,9 @@ export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
177
228
  case "defaults":
178
229
  await handleDefaultsSetup(cmd.flags);
179
230
  break;
231
+ case "hermes":
232
+ await handleHermesSetup(cmd.flags);
233
+ break;
180
234
  case "hooks":
181
235
  await handleHooksSetup(cmd.flags);
182
236
  break;
@@ -192,6 +246,26 @@ export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
192
246
  }
193
247
  }
194
248
 
249
+ async function handleHermesSetup(flags: HermesSetupFlags): Promise<void> {
250
+ try {
251
+ const result = await runHermesSetup(flags);
252
+ if (flags.json) {
253
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
254
+ return;
255
+ }
256
+ process.stdout.write(`${chalk.green(`${theme.status.success} Hermes MCP setup ready`)}\n`);
257
+ process.stdout.write(`${chalk.dim(formatHermesSetupResult(result))}\n`);
258
+ } catch (error) {
259
+ const message = error instanceof Error ? error.message : String(error);
260
+ if (flags.json) {
261
+ process.stdout.write(`${JSON.stringify({ ok: false, error: message }, null, 2)}\n`);
262
+ } else {
263
+ process.stderr.write(`${chalk.red(`${theme.status.error} Hermes MCP setup failed`)}\n`);
264
+ process.stderr.write(`${chalk.dim(message)}\n`);
265
+ }
266
+ process.exit(hermesSetupExitCode(error));
267
+ }
268
+ }
195
269
  async function handleProviderSetup(flags: {
196
270
  json?: boolean;
197
271
  force?: boolean;
@@ -410,6 +484,7 @@ ${chalk.bold("Usage:")}
410
484
 
411
485
  ${chalk.bold("Components:")}
412
486
  defaults Install bundled GJC default workflow skills (default)
487
+ hermes Optional: render/install a Hermes MCP bridge setup package
413
488
  hooks Optional: install GJC native Codex UserPromptSubmit/Stop skill-state hooks
414
489
  provider Optional: add a preset, OpenAI-compatible, or Anthropic-compatible API provider
415
490
  python Optional: verify a Python 3 interpreter is reachable for code execution
@@ -421,6 +496,12 @@ ${chalk.bold("Provider example:")}
421
496
  ${APP_NAME} setup provider --preset glm
422
497
  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
498
 
499
+ ${chalk.bold("Hermes example:")}
500
+ ${APP_NAME} setup hermes --root /path/to/repo
501
+ ${APP_NAME} setup hermes --root /path/to/repo --profile my-bot --repo gajae-code --profile-dir /path/to/hermes/profile --install
502
+ ${APP_NAME} setup hermes --root /path/to/repo --worktree-name hermes-gajae-code
503
+ ${APP_NAME} setup hermes --root /path/to/repo --session-command "gjc --worktree hermes-custom --model <provider/model>"
504
+
424
505
  ${chalk.bold("Options:")}
425
506
  -c, --check Check if dependencies are installed without installing
426
507
  -f, --force Overwrite existing default workflow skill files
@@ -432,6 +513,17 @@ ${chalk.bold("Options:")}
432
513
  --api-key-env Read provider API key from this environment variable
433
514
  --model, --models Model id to add (repeat or comma-separate)
434
515
  --models-path Override models config path
516
+ --smoke Run Hermes MCP setup smoke checks
517
+ --install Install generated Hermes setup files
518
+ --root Allowed Hermes MCP workdir/artifact root (repeatable)
519
+ --profile Hermes MCP profile namespace
520
+ --repo Hermes MCP repo namespace
521
+ --session-command Explicit GJC session command; disables generated worktree flags
522
+ --no-worktree Disable default GJC --worktree isolation for Hermes sessions
523
+ --worktree-name Named GJC --worktree branch for Hermes sessions
524
+ --mutation Hermes MCP mutation classes: sessions,questions,reports,all
525
+ --target Hermes config file target for config-only install
526
+ --profile-dir Hermes profile directory for full setup install
435
527
 
436
528
  ${chalk.bold("Examples:")}
437
529
  ${APP_NAME} setup Install bundled GJC default workflow skills
@@ -439,6 +531,7 @@ ${chalk.bold("Examples:")}
439
531
  ${APP_NAME} setup defaults --check Check bundled GJC default workflow skills are installed
440
532
  ${APP_NAME} setup hooks Install native Codex skill-state hooks
441
533
  ${APP_NAME} setup hooks --check Check native Codex skill-state hooks
534
+ ${APP_NAME} setup hermes --root /path/to/repo Render a model-agnostic Hermes MCP setup preview
442
535
  ${APP_NAME} setup python Install Python execution dependencies
443
536
  ${APP_NAME} setup stt Install speech-to-text dependencies
444
537
  ${APP_NAME} setup stt --check Check if STT dependencies are available