@gajae-code/coding-agent 0.4.3 → 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.
Files changed (45) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/types/async/job-manager.d.ts +19 -1
  3. package/dist/types/cli/setup-cli.d.ts +14 -1
  4. package/dist/types/commands/coordinator.d.ts +19 -0
  5. package/dist/types/commands/mcp-serve.d.ts +24 -0
  6. package/dist/types/commands/setup.d.ts +41 -0
  7. package/dist/types/coordinator/contract.d.ts +4 -0
  8. package/dist/types/coordinator-mcp/policy.d.ts +24 -0
  9. package/dist/types/coordinator-mcp/safety.d.ts +26 -0
  10. package/dist/types/coordinator-mcp/server.d.ts +52 -0
  11. package/dist/types/extensibility/extensions/types.d.ts +13 -0
  12. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  13. package/dist/types/modes/components/hook-selector.d.ts +11 -0
  14. package/dist/types/setup/hermes-setup.d.ts +71 -0
  15. package/dist/types/task/render.d.ts +7 -1
  16. package/dist/types/tools/subagent-render.d.ts +25 -0
  17. package/dist/types/tools/subagent.d.ts +5 -1
  18. package/package.json +7 -7
  19. package/src/async/job-manager.ts +43 -1
  20. package/src/cli/setup-cli.ts +86 -2
  21. package/src/cli.ts +2 -0
  22. package/src/commands/coordinator.ts +70 -0
  23. package/src/commands/mcp-serve.ts +62 -0
  24. package/src/commands/setup.ts +30 -1
  25. package/src/coordinator/contract.ts +20 -0
  26. package/src/coordinator-mcp/policy.ts +160 -0
  27. package/src/coordinator-mcp/safety.ts +80 -0
  28. package/src/coordinator-mcp/server.ts +1316 -0
  29. package/src/extensibility/extensions/types.ts +13 -0
  30. package/src/gjc-runtime/session-state-sidecar.ts +79 -0
  31. package/src/internal-urls/docs-index.generated.ts +3 -2
  32. package/src/modes/components/hook-selector.ts +109 -5
  33. package/src/modes/controllers/extension-ui-controller.ts +16 -1
  34. package/src/prompts/agents/architect.md +6 -0
  35. package/src/prompts/agents/critic.md +6 -0
  36. package/src/prompts/agents/planner.md +8 -1
  37. package/src/session/agent-session.ts +6 -0
  38. package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
  39. package/src/setup/hermes-setup.ts +429 -0
  40. package/src/task/index.ts +2 -0
  41. package/src/task/render.ts +14 -0
  42. package/src/tools/ask.ts +30 -10
  43. package/src/tools/renderers.ts +2 -0
  44. package/src/tools/subagent-render.ts +160 -0
  45. package/src/tools/subagent.ts +49 -7
package/CHANGELOG.md CHANGED
@@ -17,9 +17,11 @@
17
17
  ### Changed
18
18
 
19
19
  - Added conservative `timeout-minutes` values to all CI workflow jobs to prevent indefinite hangs.
20
+ - Made coordinator MCP turn waiting state-backed by durable turn/session files, with runtime session sidecar updates for running/completed/error states and Meeseeks guidance that avoids fixed sleep/capture-pane loops.
20
21
 
21
22
  ### Fixed
22
23
 
24
+ - Failed stale coordinator turns quickly when their recorded tmux session is gone, clearing active-turn state instead of burning await timeouts.
23
25
  - Improved the grep limit-reached message to show the current limit value and suggest using `--limit` for more results.
24
26
  - Passed the active model's `maxTokens` (reserved completion budget) into the auto-compaction threshold and context-usage reserve so prompt packing reserves output for large-window models, keeping the safe input budget below the total context window (e.g. ~272K for a 400K/128K model) instead of filling the whole window ([#442](https://github.com/Yeachan-Heo/gajae-code/issues/442)).
25
27
  - Fixed a `gjc harness` recovery deadlock where a session created by `start` without `--detach` (persisted as `started` with no owner lease/endpoint) could never get a live owner: `recover` refused to spawn one because no prior endpoint existed, while `start` reported `session-already-exists`. `recover` now bootstraps a fresh owner for a never-started session (no lease, no endpoint, no owner-run evidence) without writing a misleading `vanish` receipt, reported via `bootstrappedOwner: true`. Bootstrap is independent of the vanish classifier's `ownerRequired` verdict (nothing has vanished), so a session started in a non-git workspace (git delta `unknown`) is recovered too, while a deleted worktree is still refused (#421).
@@ -1,4 +1,4 @@
1
- import type { AgentSource } from "../task/types";
1
+ import type { AgentProgress, AgentSource } from "../task/types";
2
2
  export interface AsyncJob {
3
3
  id: string;
4
4
  type: "bash" | "task";
@@ -189,6 +189,24 @@ export declare class AsyncJobManager {
189
189
  registerLiveHandle(subagentId: string, handle: SubagentLiveHandle): void;
190
190
  getLiveHandle(subagentId: string): SubagentLiveHandle | undefined;
191
191
  removeLiveHandle(subagentId: string): void;
192
+ /**
193
+ * Retain the latest live `AgentProgress` for a subagent (deep-cloned so later
194
+ * mutation of the live object cannot corrupt retained state). Read by the
195
+ * `subagent` await panel; cleared on terminal/cancel/purge/dispose.
196
+ *
197
+ * Ignored for ids without a canonical `SubagentRecord` (e.g. foreground/inline
198
+ * task runs that share the executor path) so the map only holds detached
199
+ * subagent progress and never accumulates untracked foreground task state.
200
+ */
201
+ recordSubagentProgress(subagentId: string, progress: AgentProgress): void;
202
+ getSubagentProgress(subagentId: string): AgentProgress | undefined;
203
+ /**
204
+ * True only when a live, in-session progress producer exists for this id: a
205
+ * canonical registered record with a live handle or an in-memory running job.
206
+ * False for `SubagentTool` backward-compat job synthesis and resumed-from-disk
207
+ * records, which have no live producer to stream from.
208
+ */
209
+ hasLiveSubagent(subagentId: string, filter?: AsyncJobFilter): boolean;
192
210
  /** Install the TaskTool-owned resume runner. Returns the new job id, or undefined on failure. */
193
211
  setResumeRunner(runner: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined): void;
194
212
  registerResumeDescriptor(descriptor: ResumeDescriptor): void;
@@ -1,4 +1,4 @@
1
- export type SetupComponent = "defaults" | "hooks" | "provider" | "python" | "stt";
1
+ export type SetupComponent = "defaults" | "hermes" | "hooks" | "provider" | "python" | "stt";
2
2
  export interface SetupCommandArgs {
3
3
  component: SetupComponent;
4
4
  flags: {
@@ -12,6 +12,19 @@ export interface SetupCommandArgs {
12
12
  apiKeyEnv?: string;
13
13
  model?: string[];
14
14
  modelsPath?: string;
15
+ smoke?: boolean;
16
+ install?: boolean;
17
+ root?: string[];
18
+ repo?: string;
19
+ profile?: string;
20
+ sessionCommand?: string;
21
+ stateRoot?: string;
22
+ mutation?: string[];
23
+ artifactByteCap?: string;
24
+ serverKey?: string;
25
+ gjcCommand?: string;
26
+ target?: string;
27
+ profileDir?: string;
15
28
  };
16
29
  }
17
30
  /**
@@ -0,0 +1,19 @@
1
+ import { Command } from "@gajae-code/utils/cli";
2
+ export default class Coordinator extends Command {
3
+ static description: string;
4
+ static strict: boolean;
5
+ static args: {
6
+ action: import("@gajae-code/utils/cli").ArgDescriptor & {
7
+ description: string;
8
+ required: false;
9
+ };
10
+ };
11
+ static flags: {
12
+ json: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
13
+ char: string;
14
+ description: string;
15
+ default: boolean;
16
+ };
17
+ };
18
+ run(): Promise<void>;
19
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from "@gajae-code/utils/cli";
2
+ export declare function validateMcpServeSubcommandForTest(server: string | undefined): void;
3
+ export default class McpServe extends Command {
4
+ static description: string;
5
+ static strict: boolean;
6
+ static args: {
7
+ server: import("@gajae-code/utils/cli").ArgDescriptor & {
8
+ description: string;
9
+ required: false;
10
+ };
11
+ };
12
+ static flags: {
13
+ json: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
14
+ char: string;
15
+ description: string;
16
+ default: boolean;
17
+ };
18
+ check: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
19
+ description: string;
20
+ default: boolean;
21
+ };
22
+ };
23
+ run(): Promise<void>;
24
+ }
@@ -24,6 +24,47 @@ export default class Setup extends Command {
24
24
  json: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
25
25
  description: string;
26
26
  };
27
+ smoke: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
28
+ description: string;
29
+ };
30
+ install: import("@gajae-code/utils/cli").FlagDescriptor<"boolean"> & {
31
+ description: string;
32
+ };
33
+ root: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
34
+ description: string;
35
+ multiple: true;
36
+ };
37
+ repo: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
38
+ description: string;
39
+ };
40
+ profile: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
41
+ description: string;
42
+ };
43
+ "session-command": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
44
+ description: string;
45
+ };
46
+ "state-root": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
47
+ description: string;
48
+ };
49
+ mutation: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
50
+ description: string;
51
+ multiple: true;
52
+ };
53
+ "artifact-byte-cap": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
54
+ description: string;
55
+ };
56
+ "server-key": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
57
+ description: string;
58
+ };
59
+ "gjc-command": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
60
+ description: string;
61
+ };
62
+ target: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
63
+ description: string;
64
+ };
65
+ "profile-dir": import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
66
+ description: string;
67
+ };
27
68
  preset: import("@gajae-code/utils/cli").FlagDescriptor<"string"> & {
28
69
  description: string;
29
70
  };
@@ -0,0 +1,4 @@
1
+ export declare const COORDINATOR_MCP_PROTOCOL_VERSION = "2024-11-05";
2
+ export declare const COORDINATOR_MCP_SERVER_NAME = "gjc-coordinator-mcp";
3
+ export declare const COORDINATOR_MCP_TOOL_NAMES: readonly ["gjc_coordinator_list_sessions", "gjc_coordinator_read_status", "gjc_coordinator_read_tail", "gjc_coordinator_list_questions", "gjc_coordinator_list_artifacts", "gjc_coordinator_read_artifact", "gjc_coordinator_read_coordination_status", "gjc_coordinator_start_session", "gjc_coordinator_send_prompt", "gjc_coordinator_submit_question_answer", "gjc_coordinator_read_turn", "gjc_coordinator_await_turn", "gjc_coordinator_report_status"];
4
+ export type CoordinatorToolName = (typeof COORDINATOR_MCP_TOOL_NAMES)[number];
@@ -0,0 +1,24 @@
1
+ export type CoordinatorMutationClass = "sessions" | "questions" | "reports";
2
+ export interface CoordinatorNamespace {
3
+ profile: string | null;
4
+ repo: string | null;
5
+ }
6
+ export interface CoordinatorMcpConfig {
7
+ allowedRoots: string[];
8
+ mutationClasses: Set<CoordinatorMutationClass>;
9
+ artifactByteCap: number;
10
+ namespace: CoordinatorNamespace;
11
+ stateRoot: string;
12
+ sessionCommand: string | null;
13
+ }
14
+ export interface CoordinatorMutationRequest {
15
+ allow_mutation?: boolean;
16
+ }
17
+ export declare function buildCoordinatorMcpConfig(env?: NodeJS.ProcessEnv): CoordinatorMcpConfig;
18
+ export declare function assertCoordinatorWorkdir(config: CoordinatorMcpConfig, cwd: unknown): Promise<string>;
19
+ export declare function assertCoordinatorArtifactPath(config: CoordinatorMcpConfig, artifactPath: unknown): Promise<{
20
+ path: string;
21
+ byteCap: number;
22
+ }>;
23
+ export declare function requireCoordinatorMutation(config: CoordinatorMcpConfig, mutationClass: CoordinatorMutationClass, request: CoordinatorMutationRequest): void;
24
+ export declare function coordinatorNamespacePath(config: CoordinatorMcpConfig): string;
@@ -0,0 +1,26 @@
1
+ import { type CoordinatorMutationClass } from "./policy";
2
+ export declare const COORDINATOR_MUTATION_CLASSES: readonly ["sessions", "questions", "reports"];
3
+ export type { CoordinatorMutationClass };
4
+ export interface CoordinatorSafetyConfig {
5
+ allowedRoots: string[];
6
+ artifactMaxBytes: number;
7
+ enabledMutationClasses: Set<CoordinatorMutationClass>;
8
+ repo?: string;
9
+ profile?: string;
10
+ }
11
+ export interface CoordinatorSafetyPolicy {
12
+ config: CoordinatorSafetyConfig;
13
+ resolveWorkdir(input: unknown): Promise<string>;
14
+ resolveArtifactPath(input: unknown): Promise<string>;
15
+ assertMutationAllowed(mutationClass: CoordinatorMutationClass, args: Record<string, unknown>): {
16
+ ok: true;
17
+ } | CoordinatorFailure;
18
+ }
19
+ export interface CoordinatorFailure {
20
+ ok: false;
21
+ reason: string;
22
+ [key: string]: unknown;
23
+ }
24
+ export declare function createCoordinatorSafetyPolicy(options?: {
25
+ env?: NodeJS.ProcessEnv;
26
+ }): Promise<CoordinatorSafetyPolicy>;
@@ -0,0 +1,52 @@
1
+ import { COORDINATOR_MCP_PROTOCOL_VERSION, COORDINATOR_MCP_SERVER_NAME, COORDINATOR_MCP_TOOL_NAMES, type CoordinatorToolName } from "../coordinator/contract";
2
+ import { type CoordinatorMcpConfig } from "./policy";
3
+ export type { CoordinatorToolName };
4
+ export { COORDINATOR_MCP_PROTOCOL_VERSION, COORDINATOR_MCP_SERVER_NAME, COORDINATOR_MCP_TOOL_NAMES };
5
+ interface JsonRpcRequest {
6
+ jsonrpc: "2.0";
7
+ id?: string | number | null;
8
+ method: string;
9
+ params?: unknown;
10
+ }
11
+ interface JsonRpcResponse {
12
+ jsonrpc: "2.0";
13
+ id: string | number | null;
14
+ result?: any;
15
+ error?: {
16
+ code: number;
17
+ message: string;
18
+ data?: unknown;
19
+ };
20
+ }
21
+ interface SessionStartInput {
22
+ cwd: string;
23
+ prompt?: string;
24
+ namespace: {
25
+ profile: string | null;
26
+ repo: string | null;
27
+ };
28
+ worktree: true;
29
+ }
30
+ interface CoordinatorServices {
31
+ listSessions?: () => unknown[] | Promise<unknown[]>;
32
+ startSession?: (input: SessionStartInput) => unknown | Promise<unknown>;
33
+ }
34
+ interface CoordinatorMcpServerOptions {
35
+ env?: NodeJS.ProcessEnv;
36
+ services?: CoordinatorServices;
37
+ }
38
+ interface LegacyHandlerOptions {
39
+ env?: NodeJS.ProcessEnv;
40
+ createSession?: () => unknown;
41
+ }
42
+ export declare function readCoordinatorArtifact(config: CoordinatorMcpConfig, args: {
43
+ path: unknown;
44
+ }): Promise<Record<string, unknown>>;
45
+ export declare function createCoordinatorMcpServer(options?: CoordinatorMcpServerOptions): {
46
+ config: CoordinatorMcpConfig;
47
+ callTool: (name: string, args?: Record<string, unknown>) => Promise<Record<string, unknown>>;
48
+ handleJsonRpc: (request: JsonRpcRequest) => Promise<JsonRpcResponse>;
49
+ handle: (request: JsonRpcRequest) => Promise<JsonRpcResponse>;
50
+ };
51
+ export declare function handleCoordinatorMcpRequest(request: JsonRpcRequest, options?: LegacyHandlerOptions): Promise<any>;
52
+ export declare function runCoordinatorMcpStdio(options?: CoordinatorMcpServerOptions): Promise<void>;
@@ -62,6 +62,19 @@ export interface ExtensionUIDialogOptions {
62
62
  * select-only rendering hint; non-TUI bridges drop it and do not serialize it.
63
63
  */
64
64
  scrollTitleRows?: number;
65
+ /**
66
+ * For interactive TUI select dialogs, handle the option with `optionLabel`
67
+ * inline: selecting it keeps the title and option list on screen and opens
68
+ * a free-text input below the list. Submitting calls `onSubmit` with the
69
+ * typed text and resolves the select with `optionLabel`; Escape returns to
70
+ * option selection. Non-TUI bridges (RPC, ACP) drop it; callers must keep
71
+ * a fallback path for selects that resolve `optionLabel` without invoking
72
+ * `onSubmit`.
73
+ */
74
+ customInput?: {
75
+ optionLabel: string;
76
+ onSubmit: (text: string) => void;
77
+ };
65
78
  }
66
79
  /** Raw terminal input listener for extensions. */
67
80
  export type TerminalInputHandler = (data: string) => {
@@ -0,0 +1,13 @@
1
+ export declare const GJC_COORDINATOR_SESSION_STATE_FILE_ENV = "GJC_COORDINATOR_SESSION_STATE_FILE";
2
+ export declare const GJC_COORDINATOR_SESSION_ID_ENV = "GJC_COORDINATOR_SESSION_ID";
3
+ interface RuntimeStateEvent {
4
+ type: string;
5
+ messages?: unknown[];
6
+ }
7
+ interface RuntimeStateContext {
8
+ sessionId: string;
9
+ cwd: string;
10
+ sessionFile?: string | null;
11
+ }
12
+ export declare function persistCoordinatorRuntimeStateFromEvent(event: RuntimeStateEvent, context: RuntimeStateContext): Promise<void>;
13
+ export {};
@@ -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,71 @@
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
+ stateRoot?: string;
15
+ mutation?: string[];
16
+ artifactByteCap?: string;
17
+ serverKey?: string;
18
+ gjcCommand?: string;
19
+ target?: string;
20
+ profileDir?: string;
21
+ }
22
+ export interface CoordinatorSetupSpec {
23
+ schemaVersion: 1;
24
+ coordinator: "hermes";
25
+ serverKey: string;
26
+ serverName: typeof COORDINATOR_MCP_SERVER_NAME;
27
+ protocolVersion: typeof COORDINATOR_MCP_PROTOCOL_VERSION;
28
+ gjcCommand: string;
29
+ args: ["mcp-serve", "coordinator"];
30
+ roots: string[];
31
+ namespace: {
32
+ profile?: string;
33
+ repo?: string;
34
+ };
35
+ sessionCommand?: string;
36
+ stateRoot?: string;
37
+ mutationPolicy: {
38
+ classes: HermesMutationClass[];
39
+ perCallConsentRequired: true;
40
+ };
41
+ artifactByteCap?: number;
42
+ installTarget?: {
43
+ kind: "profile-dir" | "config-file";
44
+ path: string;
45
+ };
46
+ operatorTemplateVersion: 1;
47
+ contractDocVersion: 1;
48
+ }
49
+ export interface HermesSetupResult {
50
+ ok: boolean;
51
+ mode: HermesSetupMode;
52
+ files_written: string[];
53
+ previews: Array<{
54
+ path: string;
55
+ content: string;
56
+ }>;
57
+ warnings: string[];
58
+ smoke: null | {
59
+ ok: boolean;
60
+ protocolVersion: string;
61
+ serverName: string;
62
+ requiredTools: string[];
63
+ missingTools: string[];
64
+ };
65
+ }
66
+ export declare function buildHermesSetupSpec(flags: HermesSetupFlags): CoordinatorSetupSpec;
67
+ export declare function computeHermesSetupSignature(spec: CoordinatorSetupSpec): string;
68
+ export declare function renderHermesServerBlock(spec: CoordinatorSetupSpec): Record<string, unknown>;
69
+ export declare function runHermesSetup(flags: HermesSetupFlags): Promise<HermesSetupResult>;
70
+ export declare function formatHermesSetupResult(result: HermesSetupResult): string;
71
+ export declare function hermesSetupExitCode(error: unknown): number;
@@ -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,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.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.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",
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",
@@ -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();