@gajae-code/coding-agent 0.16.0 → 0.16.3

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 (164) hide show
  1. package/CHANGELOG.md +52 -1
  2. package/dist/types/async/job-manager.d.ts +32 -1
  3. package/dist/types/browser-backend.d.ts +12 -0
  4. package/dist/types/cli/update-cli.d.ts +1 -0
  5. package/dist/types/commands/sdk.d.ts +2 -0
  6. package/dist/types/config/billing-path.d.ts +25 -0
  7. package/dist/types/config/config-schema-version.d.ts +13 -1
  8. package/dist/types/config/model-registry.d.ts +16 -0
  9. package/dist/types/config/settings-schema.d.ts +17 -7
  10. package/dist/types/extensibility/extensions/types.d.ts +8 -3
  11. package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
  12. package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/acp/acp-agent.d.ts +21 -21
  15. package/dist/types/modes/components/model-selector.d.ts +4 -0
  16. package/dist/types/modes/components/session-selector.d.ts +1 -1
  17. package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
  18. package/dist/types/modes/interactive-mode.d.ts +16 -2
  19. package/dist/types/modes/theme/theme.d.ts +2 -1
  20. package/dist/types/modes/types.d.ts +2 -0
  21. package/dist/types/sdk/broker/broker.d.ts +2 -1
  22. package/dist/types/sdk/broker/ensure.d.ts +3 -0
  23. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
  24. package/dist/types/sdk/broker/lifecycle.d.ts +14 -0
  25. package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
  26. package/dist/types/sdk/broker/state-version.d.ts +17 -0
  27. package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
  28. package/dist/types/sdk/bus/index.d.ts +3 -1
  29. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
  30. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
  31. package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
  32. package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
  33. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  34. package/dist/types/sdk/cli/rows.d.ts +15 -2
  35. package/dist/types/sdk/cli/session-cli.d.ts +2 -1
  36. package/dist/types/sdk/host/events.d.ts +2 -0
  37. package/dist/types/sdk/host/host.d.ts +2 -0
  38. package/dist/types/sdk/host/query/cursor.d.ts +4 -2
  39. package/dist/types/sdk/host/query/handlers.d.ts +1 -0
  40. package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
  41. package/dist/types/sdk/host/session-runtime.d.ts +18 -17
  42. package/dist/types/sdk/lifecycle-session.d.ts +1 -1
  43. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
  44. package/dist/types/sdk/receipt-state.d.ts +2 -0
  45. package/dist/types/sdk/router/session-router.d.ts +4 -0
  46. package/dist/types/sdk/turn-result.d.ts +6 -0
  47. package/dist/types/session/agent-session.d.ts +47 -7
  48. package/dist/types/session/fold-coordinator.d.ts +29 -2
  49. package/dist/types/session/session-manager.d.ts +14 -2
  50. package/dist/types/tools/ask.d.ts +1 -0
  51. package/dist/types/tools/bash.d.ts +12 -0
  52. package/dist/types/tools/browser/launch.d.ts +10 -0
  53. package/dist/types/tools/browser/registry.d.ts +8 -0
  54. package/dist/types/tools/index.d.ts +4 -10
  55. package/dist/types/tools/job.d.ts +3 -1
  56. package/dist/types/utils/external-editor.d.ts +12 -0
  57. package/examples/extensions/README.md +1 -1
  58. package/examples/extensions/pirate.ts +7 -4
  59. package/package.json +7 -7
  60. package/scripts/generate-sdk-operation-inventory.ts +7 -1
  61. package/src/async/job-manager.ts +107 -9
  62. package/src/browser-backend.ts +29 -0
  63. package/src/cli/plugin-cli.ts +29 -0
  64. package/src/cli/update-cli.ts +63 -12
  65. package/src/commands/sdk.ts +22 -7
  66. package/src/config/billing-path.ts +42 -0
  67. package/src/config/config-schema-version.ts +18 -1
  68. package/src/config/model-preset-registry.ts +177 -18
  69. package/src/config/model-registry.ts +268 -42
  70. package/src/config/settings-schema.ts +19 -7
  71. package/src/config/settings.ts +46 -15
  72. package/src/coordinator-mcp/projection-scan.ts +6 -6
  73. package/src/coordinator-mcp/server.ts +21 -1
  74. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  75. package/src/extensibility/extensions/runner.ts +27 -0
  76. package/src/extensibility/extensions/types.ts +8 -3
  77. package/src/extensibility/gjc-plugins/installer.ts +51 -11
  78. package/src/extensibility/runtime-skill-discovery.ts +110 -3
  79. package/src/extensibility/skills.ts +22 -12
  80. package/src/gjc-runtime/launch-worktree.ts +285 -4
  81. package/src/gjc-runtime/session-state-lock.ts +156 -32
  82. package/src/gjc-runtime/session-state-sidecar.ts +10 -17
  83. package/src/gjc-runtime/tmux-owner-isolation.ts +16 -1
  84. package/src/internal-urls/docs-index.generated.ts +11 -11
  85. package/src/main.ts +4 -1
  86. package/src/modes/acp/acp-agent.ts +34 -19
  87. package/src/modes/components/model-selector.ts +76 -8
  88. package/src/modes/components/session-selector.ts +23 -15
  89. package/src/modes/components/sessions-dashboard.ts +8 -5
  90. package/src/modes/controllers/command-controller.ts +1 -1
  91. package/src/modes/controllers/event-controller.ts +30 -4
  92. package/src/modes/controllers/extension-ui-controller.ts +13 -13
  93. package/src/modes/controllers/input-controller.ts +2 -2
  94. package/src/modes/controllers/selector-controller.ts +10 -3
  95. package/src/modes/interactive-mode.ts +36 -12
  96. package/src/modes/prompt-action-autocomplete.ts +30 -4
  97. package/src/modes/runtime-init.ts +12 -9
  98. package/src/modes/theme/theme.ts +5 -0
  99. package/src/modes/types.ts +2 -0
  100. package/src/modes/utils/ui-helpers.ts +22 -3
  101. package/src/prompts/agent-fragments/browser-aside.md +22 -0
  102. package/src/prompts/tools/ask.md +1 -1
  103. package/src/prompts/tools/bash.md +1 -0
  104. package/src/prompts/tools/job.md +1 -1
  105. package/src/sdk/acp/adapter.ts +6 -1
  106. package/src/sdk/broker/broker.ts +10 -1
  107. package/src/sdk/broker/ensure.ts +76 -3
  108. package/src/sdk/broker/lifecycle-ledger.ts +6 -0
  109. package/src/sdk/broker/lifecycle.ts +254 -34
  110. package/src/sdk/broker/spawn-substrate.ts +45 -2
  111. package/src/sdk/broker/startup-budget.ts +144 -2
  112. package/src/sdk/broker/state-version.ts +17 -0
  113. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  114. package/src/sdk/bus/chat-daemon-runtime.ts +118 -24
  115. package/src/sdk/bus/discord-daemon.ts +50 -10
  116. package/src/sdk/bus/index.ts +33 -3
  117. package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
  118. package/src/sdk/bus/lifecycle-commands.ts +4 -0
  119. package/src/sdk/bus/notification-service.ts +22 -5
  120. package/src/sdk/bus/prompt-reconciliation.ts +7 -2
  121. package/src/sdk/bus/reconciliation-store.ts +55 -31
  122. package/src/sdk/bus/slack-conversation.ts +1 -1
  123. package/src/sdk/bus/slack-daemon.ts +72 -38
  124. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  125. package/src/sdk/cli/rows.ts +75 -7
  126. package/src/sdk/cli/session-cli.ts +68 -35
  127. package/src/sdk/host/control/dispatch.ts +2 -2
  128. package/src/sdk/host/events.ts +26 -2
  129. package/src/sdk/host/host.ts +6 -1
  130. package/src/sdk/host/query/cursor.ts +52 -7
  131. package/src/sdk/host/query/handlers.ts +33 -12
  132. package/src/sdk/host/query/revision-store.ts +12 -0
  133. package/src/sdk/host/session-runtime.test.ts +497 -31
  134. package/src/sdk/host/session-runtime.ts +387 -54
  135. package/src/sdk/host/surface-policy.ts +3 -3
  136. package/src/sdk/lifecycle-session.ts +7 -2
  137. package/src/sdk/prompt-deadline-manager.ts +2 -1
  138. package/src/sdk/protocol/operation-inventory.generated.json +54 -3
  139. package/src/sdk/protocol/operation-registry.ts +1 -1
  140. package/src/sdk/receipt-state.ts +10 -0
  141. package/src/sdk/router/session-router.ts +210 -94
  142. package/src/sdk/session.ts +59 -41
  143. package/src/sdk/turn-result.ts +22 -0
  144. package/src/session/agent-session.ts +1033 -260
  145. package/src/session/fold-coordinator.ts +40 -3
  146. package/src/session/session-manager.ts +517 -77
  147. package/src/slash-commands/builtin-registry.ts +41 -0
  148. package/src/task/executor.ts +9 -4
  149. package/src/tools/ask-contract.test.ts +64 -0
  150. package/src/tools/ask-contract.ts +36 -1
  151. package/src/tools/ask.ts +10 -8
  152. package/src/tools/bash-interactive.ts +11 -2
  153. package/src/tools/bash.ts +158 -33
  154. package/src/tools/browser/launch.ts +37 -0
  155. package/src/tools/browser/registry.ts +99 -9
  156. package/src/tools/cron.ts +6 -4
  157. package/src/tools/descriptors.test.ts +15 -0
  158. package/src/tools/descriptors.ts +5 -1
  159. package/src/tools/index.ts +4 -6
  160. package/src/tools/job.ts +4 -1
  161. package/src/tools/monitor.ts +12 -9
  162. package/src/tools/resolve.ts +30 -10
  163. package/src/tools/tool-catalog.generated.ts +2 -2
  164. package/src/utils/external-editor.ts +86 -1
@@ -22,6 +22,7 @@ export interface TurnResultError {
22
22
  code: string;
23
23
  message: string;
24
24
  }
25
+ export type TurnResultReceiptState = "absent" | "present" | "missing" | "unknown";
25
26
  export interface TurnResultPage {
26
27
  status: TurnResultStatus;
27
28
  kind?: "prompt" | "skill";
@@ -31,7 +32,12 @@ export interface TurnResultPage {
31
32
  acceptedAt?: number;
32
33
  startedAt?: number;
33
34
  terminalAt?: number;
35
+ receiptState?: TurnResultReceiptState;
36
+ outcome?: unknown;
34
37
  content?: TurnResultContent;
35
38
  error?: TurnResultError;
36
39
  }
37
40
  export declare function sanitizeTurnResultContent(text: unknown): TurnResultContent | undefined;
41
+ export declare function reportableTurnResultContent(content: TurnResultContent | undefined): boolean;
42
+ /** First reportable invocation-owned content wins; blank evidence remains replaceable. */
43
+ export declare function reduceTurnResultContent(existing: TurnResultContent | undefined, incoming: TurnResultContent | undefined): TurnResultContent | undefined;
@@ -67,7 +67,7 @@ export interface ForkContextSeedOptions {
67
67
  signal?: AbortSignal;
68
68
  }
69
69
  import type { AuthCredentialSelector } from "@gajae-code/ai/core";
70
- import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async";
70
+ import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager, type FoldReason, type JobFoldEvent } from "../async";
71
71
  import type { Rule } from "../capability/rule";
72
72
  import { type ModelRegistry } from "../config/model-registry";
73
73
  import { type ResolvedModelRoleValue, type ScopedModelSelection } from "../config/model-resolver";
@@ -109,7 +109,7 @@ import { type ConfiguredFallbackChain, type FallbackChainRuntimeState } from "./
109
109
  export { DefaultModelSelectionRecoveryError } from "./default-model-selection";
110
110
  import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome, ClientBridgePermissionToolCall } from "./client-bridge";
111
111
  import { type ContributionPrepOptions, type ContributionPrepResult } from "./contribution-prep";
112
- import { type FoldAdapter, FoldCoordinator } from "./fold-coordinator";
112
+ import { type FoldAdapter, FoldCoordinator, type ForegroundFoldOutcome } from "./fold-coordinator";
113
113
  import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant";
114
114
  import { type CustomMessage } from "./messages";
115
115
  import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, PreparedNewSession, RecoveryHydrationContext, RecoveryHydrationPromotionFence, SessionContext, SessionManagerCloseOutcome, SessionMemoryStats } from "./session-manager";
@@ -399,6 +399,8 @@ export interface PromptOptions {
399
399
  streamingBehavior?: "steer" | "followUp";
400
400
  /** When set to "sequential", this follow-up is delivered one prompt at a time even if followUpMode is "all". */
401
401
  followUpQueuePolicy?: "respect-mode" | "sequential";
402
+ /** When set to "sequential", this steer is delivered on its own even if steeringMode is "all". */
403
+ steerQueuePolicy?: "respect-mode" | "sequential";
402
404
  /** Optional tool choice override for the next LLM call. */
403
405
  toolChoice?: ToolChoice;
404
406
  /** Send as developer/system message instead of user. Providers that support it use the developer role; others fall back to user. */
@@ -524,6 +526,10 @@ interface EphemeralTurnResult {
524
526
  replyText: string;
525
527
  assistantMessage: AssistantMessage;
526
528
  }
529
+ export declare class SessionDisposalIncompleteError extends Error {
530
+ constructor(label?: string);
531
+ }
532
+ export declare function isSessionDisposalIncompleteError(error: unknown): error is SessionDisposalIncompleteError;
527
533
  export interface AgentBashArtifactStore {
528
534
  saveArtifact(content: string, toolType: string): Promise<string | undefined>;
529
535
  getArtifactPath(id: string): Promise<string | null>;
@@ -570,6 +576,7 @@ export declare class WorkerIntegrationRequestScheduler {
570
576
  enqueue(): void;
571
577
  flush(): Promise<void>;
572
578
  flushWithOutcome(): Promise<WorkerIntegrationOutcome>;
579
+ joinOwnedRequests(): Promise<void>;
573
580
  }
574
581
  export type StreamingEditParsedToolCall = {
575
582
  toolCall: ToolCall;
@@ -747,6 +754,11 @@ export declare class AgentSession {
747
754
  * Call this when completely done with the session.
748
755
  */
749
756
  dispose(): Promise<void>;
757
+ /** Join the retained teardown owner without allocating another public deadline. */
758
+ awaitDisposeCompletion(): Promise<void>;
759
+ setDisposeTimeoutForTests(timeoutMs: number): void;
760
+ trackPostPromptTaskForTests(task: Promise<void>): void;
761
+ requestWorkerIntegrationForTests(): void;
750
762
  /**
751
763
  * Strict writer close for ACP session delete. On the first attempt it flushes
752
764
  * pending writes, then returns the certainty-aware close outcome so the caller
@@ -805,6 +817,8 @@ export declare class AgentSession {
805
817
  * proceeds.
806
818
  */
807
819
  awaitSessionSettlement(): Promise<void>;
820
+ /** Test seam: hold prompt admission while bypassing a pending selection fence. */
821
+ runWithPromptAdmissionForTests(body: () => Promise<void>): Promise<void>;
808
822
  queueCoordinatorRuntimeStatePersistForTests(event: AgentSessionEvent, gate: Promise<void>): Promise<void>;
809
823
  parkAgentEndForCoordinatorPersistForTests(event: Extract<AgentSessionEvent, {
810
824
  type: "agent_end";
@@ -867,11 +881,31 @@ export declare class AgentSession {
867
881
  get sdkPermissionMode(): "prompt" | "allow" | "deny";
868
882
  /** Register or clear the SDK reverse permission provider for this session. */
869
883
  setSdkPermissionProvider(provider: ((toolCall: ClientBridgePermissionToolCall, options: ClientBridgePermissionOption[], signal?: AbortSignal) => Promise<ClientBridgePermissionOutcome>) | undefined): void;
884
+ /**
885
+ * Subscribe to folds of this session's foreground waits. Backed by the
886
+ * session-owned manager (or the process-global instance for legacy setups)
887
+ * so the SDK host can publish one `bash_folded` frame per fold.
888
+ */
889
+ onJobFold(listener: (event: JobFoldEvent) => void): () => void;
870
890
  /**
871
891
  * Ask the active managed foreground bash call to return as a background job.
872
892
  * Returns false when no supported foreground tool is currently backgroundable.
893
+ * `reason` names the trigger (`chord` for the keybinding, `sdk_control` for
894
+ * the `bash.background` control) and is recorded on the folded job.
895
+ */
896
+ requestForegroundBashBackground(reason?: FoldReason, adapter?: FoldAdapter): Promise<boolean>;
897
+ /**
898
+ * Typed variant of {@link requestForegroundBashBackground} for the SDK
899
+ * `bash.background` control (C52), which must distinguish "this fold moved
900
+ * the wait" from "an earlier fold already moved it" from "nothing to fold".
901
+ *
902
+ * `already_backgrounded` is reported when no wait is foldable but this
903
+ * session still has a running job that a fold (steer, chord, timer, or a
904
+ * prior control call) moved out of the foreground: `metadata.foldReason` is
905
+ * set only by folds, never by `async: true` starts, so it is the exact
906
+ * evidence that the wait the client is asking about has already left.
873
907
  */
874
- requestForegroundBashBackground(): Promise<boolean>;
908
+ requestForegroundBashBackgroundOutcome(reason?: FoldReason, adapter?: FoldAdapter): Promise<ForegroundFoldOutcome>;
875
909
  /**
876
910
  * Get all configured tool names (built-in via --tools or default, plus custom tools).
877
911
  */
@@ -944,7 +978,7 @@ export declare class AgentSession {
944
978
  /** Current follow-up mode */
945
979
  get followUpMode(): "all" | "one-at-a-time";
946
980
  /** Current interrupt mode */
947
- get interruptMode(): "immediate" | "wait";
981
+ get toolInterruptPolicy(): "abort_tools" | "finish_tools";
948
982
  /** Current session file path, or undefined if sessions are disabled */
949
983
  get sessionFile(): string | undefined;
950
984
  /** Current session ID */
@@ -1019,7 +1053,7 @@ export declare class AgentSession {
1019
1053
  * @throws Error if no model selected or no API key available (when not streaming)
1020
1054
  */
1021
1055
  prompt(text: string, options?: PromptOptions): Promise<void>;
1022
- promptCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy" | "onPreflightAccepted" | "onPreflightAcceptCommit" | "preflightSignal" | "sdkRunCapability">): Promise<void>;
1056
+ promptCustomMessage<T = unknown>(message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick<PromptOptions, "streamingBehavior" | "toolChoice" | "followUpQueuePolicy" | "steerQueuePolicy" | "onPreflightAccepted" | "onPreflightAcceptCommit" | "preflightSignal" | "sdkRunCapability">): Promise<void>;
1023
1057
  /**
1024
1058
  * Queue a steering message to interrupt the agent mid-run.
1025
1059
  */
@@ -1046,6 +1080,7 @@ export declare class AgentSession {
1046
1080
  triggerTurn?: boolean;
1047
1081
  deliverAs?: "steer" | "followUp" | "nextTurn";
1048
1082
  followUpQueuePolicy?: "respect-mode" | "sequential";
1083
+ steerQueuePolicy?: "respect-mode" | "sequential";
1049
1084
  /** INTERNAL trusted option: origin of a hidden next-turn message —
1050
1085
  * "turn" (produced by the current turn's continuation machinery,
1051
1086
  * purged by a terminal abort) or "external" (background producers,
@@ -1165,6 +1200,7 @@ export declare class AgentSession {
1165
1200
  timeoutMs?: number;
1166
1201
  cause?: "user_interrupt" | "new_session" | "session_switch" | "compaction" | "handoff" | "tool_abort" | "internal";
1167
1202
  silent?: boolean;
1203
+ preserveCompaction?: boolean;
1168
1204
  }): Promise<void>;
1169
1205
  /**
1170
1206
  * Private terminal-abort seam: read the CURRENT turn's attempt epoch WITHOUT
@@ -1399,6 +1435,10 @@ export declare class AgentSession {
1399
1435
  onBeforeMutation?: () => void;
1400
1436
  /** Run inside the selection admission after the durable mutation. */
1401
1437
  onAfterMutation?: () => void;
1438
+ /** Test seam: pause after idle drain but before mutation admission. */
1439
+ onBeforeMutationAdmissionForTests?: () => Promise<void>;
1440
+ /** Test seam: pause after mutation admission activates, before its closed-fence recheck. */
1441
+ onAfterMutationAdmissionReadyForTests?: () => Promise<void>;
1402
1442
  }): Promise<DefaultModelSelectionResult>;
1403
1443
  /**
1404
1444
  * Cycle to next/previous model.
@@ -1495,10 +1535,10 @@ export declare class AgentSession {
1495
1535
  */
1496
1536
  setFollowUpMode(mode: "all" | "one-at-a-time"): void;
1497
1537
  /**
1498
- * Set interrupt mode.
1538
+ * Set the tool interrupt policy (whether a steer aborts in-flight tools).
1499
1539
  * Saves to settings.
1500
1540
  */
1501
- setInterruptMode(mode: "immediate" | "wait"): void;
1541
+ setToolInterruptPolicy(policy: "abort_tools" | "finish_tools"): void;
1502
1542
  /**
1503
1543
  * Manually compact the session context.
1504
1544
  * Aborts current agent operation first.
@@ -23,7 +23,7 @@
23
23
  * delivery therefore still carries its receipt and can never silently
24
24
  * degrade into an ordinary receipt-less completion.
25
25
  */
26
- import type { AsyncJob } from "../async/job-manager";
26
+ import type { AsyncJob, FoldReason } from "../async/job-manager";
27
27
  /**
28
28
  * Idle-flush merge window.
29
29
  *
@@ -62,6 +62,8 @@ export interface FoldReceipt {
62
62
  foldedAt: number;
63
63
  /** Only command kinds can change directory; task-style waits never do. */
64
64
  cwdSensitive: boolean;
65
+ /** What triggered the fold; recorded on the job as `metadata.foldReason`. */
66
+ reason: FoldReason;
65
67
  }
66
68
  /** A terminal completion observed for a folded job. */
67
69
  export interface ForegroundTerminalPayload {
@@ -104,6 +106,26 @@ export type FoldRequestResult = {
104
106
  status: "capture-failed";
105
107
  reason: string;
106
108
  };
109
+ /**
110
+ * Session-level answer to "fold the foreground bash now". Consumed by the SDK
111
+ * `bash.background` control, whose declared error codes are exactly these
112
+ * non-folded statuses.
113
+ */
114
+ export type ForegroundFoldOutcome = {
115
+ status: "folded";
116
+ jobId: string;
117
+ } | {
118
+ status: "already_backgrounded";
119
+ } | {
120
+ status: "no_active_bash";
121
+ } | {
122
+ status: "not_foldable";
123
+ reason: string;
124
+ };
125
+ /** Map a non-folded outcome onto the C52 `bash.background` error contract (`not_foldable` | `already_backgrounded` | `no_active_bash`). */
126
+ export declare function bashBackgroundControlError(outcome: Exclude<ForegroundFoldOutcome, {
127
+ status: "folded";
128
+ }>): Error;
107
129
  /** What a delivery should carry, decided by the durable slot state (T2). */
108
130
  export type FoldDeliveryDisposition = {
109
131
  kind: "ordinary";
@@ -166,8 +188,13 @@ export declare class FoldCoordinator {
166
188
  * Nothing awaits before the slot is reserved and the fence is armed, so a
167
189
  * completion or a steering submission that races the capture cannot slip
168
190
  * past into the old turn.
191
+ *
192
+ * A `steer` fold is the one trigger that does NOT end the turn: the steer
193
+ * that caused it must be consumed by the same run at the tool boundary
194
+ * right after the fold result (subagent-await parity), so no fence is
195
+ * armed, no stop is requested, and no remaining intent is captured.
169
196
  */
170
- requestFold(explicit?: FoldAdapter): Promise<FoldRequestResult>;
197
+ requestFold(explicit?: FoldAdapter, reason?: FoldReason): Promise<FoldRequestResult>;
171
198
  /**
172
199
  * T2. Decide what a delivery carries, branching on the DURABLE slot state
173
200
  * rather than on transient hand-off state.
@@ -22,6 +22,8 @@ export interface SessionHeader {
22
22
  id: string;
23
23
  title?: string;
24
24
  titleSource?: "auto" | "user";
25
+ starredPatchVersion?: 1;
26
+ starred?: boolean;
25
27
  timestamp: string;
26
28
  cwd: string;
27
29
  parentSession?: string;
@@ -362,7 +364,7 @@ export type SessionEntry = SessionMessageEntry | ThinkingLevelChangeEntry | Mode
362
364
  /** Append-only replacement for mutable fields on the session header. */
363
365
  export interface HeaderPatchRecord {
364
366
  type: "header_patch";
365
- patch: Partial<Pick<SessionHeader, "title" | "titleSource" | "cwd">>;
367
+ patch: Partial<Pick<SessionHeader, "title" | "titleSource" | "cwd" | "starred">>;
366
368
  }
367
369
  /** Append-only replacement for replay metadata on one existing session entry. */
368
370
  export interface EntryPatchRecord {
@@ -812,6 +814,7 @@ export interface SessionInfo {
812
814
  /** Working directory where the session was started. Empty string for old sessions. */
813
815
  cwd: string;
814
816
  title?: string;
817
+ starred: boolean;
815
818
  /** Path to the parent session (if this session was forked). */
816
819
  parentSessionPath?: string;
817
820
  created: Date;
@@ -979,6 +982,7 @@ export declare function materializeResidentEntriesForPersistenceForTests<T>(entr
979
982
  * container (`agent-session` or `sessions`).
980
983
  */
981
984
  export declare function listProjectSessionTranscriptFiles(cwd: string): string[];
985
+ export declare function prioritizeStarredSessions(sessions: readonly SessionInfo[]): SessionInfo[];
982
986
  /** Get recent sessions for display in welcome screen */
983
987
  export declare function getRecentSessions(sessionDir: string, limit?: number, storage?: SessionStorage): Promise<RecentSessionInfo[]>;
984
988
  export declare function getRecentSessionDisplay(sessions: readonly SessionInfo[], limit?: number): Array<{
@@ -1161,7 +1165,10 @@ export declare class SessionManager {
1161
1165
  * only for the async context that already owns the lock — unrelated callers
1162
1166
  * queue on the tail instead of skipping it.
1163
1167
  */
1164
- runExclusiveCwdTransition<T>(fn: () => Promise<T>): Promise<T>;
1168
+ runExclusiveCwdTransition<T>(fn: () => Promise<T>, options?: {
1169
+ signal?: AbortSignal;
1170
+ }): Promise<T>;
1171
+ runExclusiveCwdMoveTransition<T>(fn: () => Promise<T>): Promise<T>;
1165
1172
  /**
1166
1173
  * Run `fn` under a shared read lease on `cwd`.
1167
1174
  *
@@ -1175,8 +1182,11 @@ export declare class SessionManager {
1175
1182
  runWithCwdReadLease<T>(fn: () => Promise<T>): Promise<T>;
1176
1183
  /** Wait for any in-flight exclusive cwd transition to settle. */
1177
1184
  joinCwdTransition(): Promise<void>;
1185
+ joinCwdReaders(): Promise<void>;
1178
1186
  closeCwdMoveAdmission(): Promise<void>;
1179
1187
  getCwdGeneration(): number;
1188
+ /** Consume a move committed by a nested move_session call in this read lease. */
1189
+ consumeNestedCwdMove(admittedGeneration: number): boolean;
1180
1190
  /**
1181
1191
  * Register a listener invoked after every committed `moveTo`. Returns an idempotent
1182
1192
  * unregister handle. Every move surface funnels through `moveTo`, so a single
@@ -1407,6 +1417,8 @@ export declare class SessionManager {
1407
1417
  /** The source that set the session name: "user" (manual /rename or RPC) or "auto" (generated title). */
1408
1418
  get titleSource(): "auto" | "user" | undefined;
1409
1419
  getSessionName(): string | undefined;
1420
+ isSessionStarred(): boolean;
1421
+ setSessionStarred(starred: boolean): Promise<boolean>;
1410
1422
  setSessionName(name: string, source?: "auto" | "user"): Promise<boolean>;
1411
1423
  _persist(entry: SessionEntry): void;
1412
1424
  /** Append a configured fallback chain as child of current leaf, then advance leaf. Returns entry id. */
@@ -46,6 +46,7 @@ export declare const OTHER_OPTION = "Other (type your own)";
46
46
  export declare const ASK_CLARIFICATION_OPTION = "Ask about these choices";
47
47
  export declare const RECOMMENDED_SUFFIX = " (Recommended)";
48
48
  export declare const MAX_EMPTY_CUSTOM_ATTEMPTS = 3;
49
+ export declare function validRecommendedIndex(recommended: number | undefined, optionCount: number): number | undefined;
49
50
  /** The only remote navigation control; labels are presentation, never protocol. */
50
51
  export declare function askRemoteControls(input: {
51
52
  multi: boolean;
@@ -1,6 +1,7 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
2
  import type { Component } from "@gajae-code/tui";
3
3
  import * as z from "zod/v4";
4
+ import { type FoldReason } from "../async";
4
5
  import { type BashArtifactSaveResult } from "../exec/bash-executor";
5
6
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
6
7
  import { type Theme } from "../modes/theme/theme";
@@ -8,6 +9,15 @@ import { type SdkSpawnArgs } from "../sdk/cli/master-cli";
8
9
  import type { ToolSession } from ".";
9
10
  import { type OutputMeta } from "./output-meta";
10
11
  export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
12
+ /**
13
+ * A user steer folds a running foreground bash call only after it has been
14
+ * running at least this long. Shorter commands finish normally and the steer is
15
+ * consumed at the ordinary tool boundary, so a quick `git status` never turns
16
+ * into a background job plus a wake-up turn.
17
+ */
18
+ export declare const STEER_FOLD_GRACE_MS = 2000;
19
+ /** Model-facing line appended to a steer-folded background-start result. */
20
+ export declare function steerFoldReasonLine(jobId: string): string;
11
21
  export type BashOriginalArtifactSaveResult = BashArtifactSaveResult;
12
22
  export declare function saveBashOriginalArtifactForTests(session: ToolSession, originalText: string, onResult?: (result: BashOriginalArtifactSaveResult) => void): Promise<string | undefined>;
13
23
  declare const bashSchemaBase: z.ZodObject<{
@@ -39,6 +49,8 @@ export interface BashToolDetails {
39
49
  timeoutSeconds?: number;
40
50
  requestedTimeoutSeconds?: number;
41
51
  terminalId?: string;
52
+ /** Why the foreground wait was folded, when this result is a background-start after a fold. */
53
+ foldReason?: FoldReason;
42
54
  async?: {
43
55
  state: "running" | "completed" | "failed";
44
56
  jobId: string;
@@ -23,6 +23,16 @@ export declare function resolveBrowserEnvOverridesForTest(): {
23
23
  programFilesX86: string | undefined;
24
24
  localAppData: string | undefined;
25
25
  };
26
+ export declare function resolveSystemChromiumForTest(): string | undefined;
27
+ export declare function resetSystemChromiumForTest(): void;
28
+ export declare function headlessExecutableForProfileReuse(): string | undefined;
29
+ /**
30
+ * On macOS the Cookies DB is encrypted with a Keychain key whose service name is
31
+ * brand-specific ("Chrome Safe Storage" vs "Chromium Safe Storage"). A Chrome profile
32
+ * copied into a bundled Chromium therefore decrypts to garbage with no warning.
33
+ * Return true only when the headless executable can actually decrypt the discovered profile.
34
+ */
35
+ export declare function canDecryptProfileCookies(executablePath: string | undefined, discoveredUserDataDir: string | undefined, platform?: NodeJS.Platform): boolean;
26
36
  /** True for a Microsoft Edge executable path (excluded from Chrome profile mode). */
27
37
  export declare function isEdgeExecutable(candidate: string): boolean;
28
38
  /** True only for executable names owned by Google Chrome or Chromium. */
@@ -65,6 +65,14 @@ export interface AcquireBrowserOptions {
65
65
  geo?: BrowserGeo;
66
66
  }
67
67
  export declare function browserKeyForTest(kind: BrowserKind, opts: Pick<AcquireBrowserOptions, "geo" | "profileReuse">): string;
68
+ /**
69
+ * Create and register a warm-up dir as registry-owned for `handle`.
70
+ *
71
+ * Exists so tests can exercise disposal without spawning Chrome. The caller cannot
72
+ * supply the deletion target: this module creates the directory itself before recording
73
+ * ownership, preserving the same invariant as the production launch path.
74
+ */
75
+ export declare function createOwnedWarmupDirForTest(handle: BrowserHandle): string;
68
76
  export declare function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle>;
69
77
  export declare function buildChromeProfileLaunchArgs(kind: SpawnedChromeProfileKind, appArgs: readonly string[] | undefined, port: number): string[];
70
78
  export declare function buildChromeProfileLaunchArgsForTest(kind: SpawnedChromeProfileKind, appArgs: readonly string[] | undefined, port: number): string[];
@@ -1,6 +1,6 @@
1
1
  import type { AgentTelemetryConfig, AgentTool } from "@gajae-code/agent-core";
2
2
  import type { Model, ServiceTier, ToolChoice } from "@gajae-code/ai/core";
3
- import type { AsyncJobManager } from "../async";
3
+ import type { AsyncJobManager, FoldReason } from "../async";
4
4
  import type { PromptTemplate } from "../config/prompt-templates";
5
5
  import type { Settings } from "../config/settings";
6
6
  import type { Skill } from "../extensibility/skills";
@@ -189,11 +189,11 @@ export interface ToolSession {
189
189
  registerForegroundFoldParticipant?: (adapter: FoldAdapter) => () => void;
190
190
  /** Whether a foreground wait is currently foldable into a background job. */
191
191
  hasForegroundBashBackgroundRequestHandler?: () => boolean;
192
- /** Request that the active foreground wait fold into a background job, if supported. */
193
- requestForegroundBashBackground?: () => Promise<boolean>;
192
+ /** Request that a foreground wait fold into a background job, if supported. `reason` names the trigger; `adapter` targets a specific wait instead of the newest registration. */
193
+ requestForegroundBashBackground?: (reason?: FoldReason, adapter?: FoldAdapter) => Promise<boolean>;
194
194
  /** Get the session-owned or inherited async job manager. */
195
195
  getAsyncJobManager?: () => AsyncJobManager | undefined;
196
- /** Resolves when the session queues user steering (or `signal` aborts) without consuming it; wait-style tools use this to end their observation early. */
196
+ /** Resolves on a steer admitted after the wait starts, or when `signal` aborts, without consuming the steer. */
197
197
  waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
198
198
  /** Get session ID */
199
199
  getSessionId?: () => string | null;
@@ -332,12 +332,6 @@ export interface ToolSession {
332
332
  buildToolChoice?(toolName: string): ToolChoice | undefined;
333
333
  /** Build a named tool-choice decision, preserving whether exact named forcing survived capability degradation. */
334
334
  buildToolChoiceResult?(toolName: string): import("../utils/tool-choice").NamedToolChoiceResult;
335
- /** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
336
- steer?(message: {
337
- customType: string;
338
- content: string;
339
- details?: unknown;
340
- }): void;
341
335
  /** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
342
336
  peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
343
337
  /** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
@@ -1,7 +1,7 @@
1
1
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
2
2
  import type { Component } from "@gajae-code/tui";
3
3
  import * as z from "zod/v4";
4
- import { type AsyncJob } from "../async";
4
+ import { type AsyncJob, type FoldReason } from "../async";
5
5
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
6
6
  import type { Theme } from "../modes/theme/theme";
7
7
  import type { ToolSession } from "./index";
@@ -18,6 +18,8 @@ interface JobSnapshot {
18
18
  status: "running" | "completed" | "failed" | "cancelled";
19
19
  label: string;
20
20
  durationMs: number;
21
+ /** Present when the job was folded out of a foreground wait. */
22
+ foldReason?: FoldReason;
21
23
  resultText?: string;
22
24
  errorText?: string;
23
25
  }
@@ -10,6 +10,17 @@ export interface OpenInEditorOptions {
10
10
  }
11
11
  /** Remove one platform-standard trailing line terminator from editor content. */
12
12
  export declare function trimEditorTrailingNewline(text: string): string;
13
+ /**
14
+ * Native Windows editor executables must bypass cmd.exe. The shell wrapper
15
+ * reclaims the shared console input buffer while it waits for the editor,
16
+ * reopening the cooked-mode window that loses the first keystroke. Batch
17
+ * files still require a shell on Windows; ordinary executables do not.
18
+ */
19
+ export declare function shouldUseExternalEditorShell(editor: string, platform?: NodeJS.Platform): boolean;
20
+ interface RawModeInput {
21
+ setRawMode?: (mode: boolean) => unknown;
22
+ }
23
+ export declare function prepareWindowsConsoleForExternalEditor(platform?: NodeJS.Platform, input?: RawModeInput): void;
13
24
  /**
14
25
  * Opens `content` in the user's external editor and returns the edited text.
15
26
  * Returns `null` if the editor exits with a non-zero code.
@@ -17,3 +28,4 @@ export declare function trimEditorTrailingNewline(text: string): string;
17
28
  * The caller is responsible for stopping/starting the TUI around this call.
18
29
  */
19
30
  export declare function openInEditor(editorCmd: string, content: string, options?: OpenInEditorOptions): Promise<string | null>;
31
+ export {};
@@ -68,7 +68,7 @@ This external path is separate from GJC's native `/skill:deep-interview`: the na
68
68
 
69
69
  | Extension | Description |
70
70
  | ----------- | -------------------------------------------------------------------- |
71
- | `pirate.ts` | Demonstrates `systemPromptAppend` to dynamically modify system prompt |
71
+ | `pirate.ts` | Demonstrates appending a prompt block with `systemPrompt` |
72
72
 
73
73
  ### External Dependencies
74
74
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Pirate Extension
3
3
  *
4
- * Demonstrates using systemPromptAppend in before_agent_start to dynamically
4
+ * Demonstrates using systemPrompt in before_agent_start to dynamically
5
5
  * modify the system prompt based on extension state.
6
6
  *
7
7
  * Usage:
@@ -24,10 +24,12 @@ export default function pirateExtension(pi: ExtensionAPI) {
24
24
  });
25
25
 
26
26
  // Append to system prompt when pirate mode is enabled
27
- pi.on("before_agent_start", async () => {
27
+ pi.on("before_agent_start", async event => {
28
28
  if (pirateMode) {
29
29
  return {
30
- systemPromptAppend: `
30
+ systemPrompt: [
31
+ ...event.systemPrompt,
32
+ `
31
33
  IMPORTANT: You are now in PIRATE MODE. You must:
32
34
  - Speak like a stereotypical pirate in all responses
33
35
  - Use phrases like "Arrr!", "Ahoy!", "Shiver me timbers!", "Avast!", "Ye scurvy dog!"
@@ -35,7 +37,8 @@ IMPORTANT: You are now in PIRATE MODE. You must:
35
37
  - Refer to the user as "matey" or "landlubber"
36
38
  - End sentences with nautical expressions
37
39
  - Still complete the actual task correctly, just in pirate speak
38
- `,
40
+ `,
41
+ ],
39
42
  };
40
43
  }
41
44
  return undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.16.0",
4
+ "version": "0.16.3",
5
5
  "description": "Gajae Code CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -56,12 +56,12 @@
56
56
  "@agentclientprotocol/sdk": "1.3.0",
57
57
  "@babel/parser": "^7.29.3",
58
58
  "@mozilla/readability": "^0.6.0",
59
- "@gajae-code/stats": "0.16.0",
60
- "@gajae-code/agent-core": "0.16.0",
61
- "@gajae-code/ai": "0.16.0",
62
- "@gajae-code/natives": "0.16.0",
63
- "@gajae-code/tui": "0.16.0",
64
- "@gajae-code/utils": "0.16.0",
59
+ "@gajae-code/stats": "0.16.3",
60
+ "@gajae-code/agent-core": "0.16.3",
61
+ "@gajae-code/ai": "0.16.3",
62
+ "@gajae-code/natives": "0.16.3",
63
+ "@gajae-code/tui": "0.16.3",
64
+ "@gajae-code/utils": "0.16.3",
65
65
  "@puppeteer/browsers": "^2.13.0",
66
66
  "@types/turndown": "5.0.6",
67
67
  "@xterm/headless": "^6.0.0",
@@ -52,6 +52,10 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
52
52
  "slash_command:tutorial": "visual/local-only onboarding selector, not a user-facing SDK control seam",
53
53
  "slash_command:transcript": "visual/local-only transcript viewer, not a user-facing SDK control seam",
54
54
  "slash_command:sessions": "visual/local-only sessions dashboard, not a user-facing SDK control seam",
55
+ "slash_command:star":
56
+ "local transcript-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
57
+ "slash_command:unstar":
58
+ "local transcript-discovery metadata command; no SDK operation counterpart or runtime/retention authority",
55
59
  "agent_session:constructor": "internal accessor/plumbing, not a user-facing control seam",
56
60
  "agent_session:getSessionAgentDir":
57
61
  "internal session-scoped agent-directory accessor, not a user-facing SDK control seam",
@@ -137,6 +141,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
137
141
  "agent_session:hasForegroundBashBackgroundRequestHandler":
138
142
  "internal accessor/plumbing, not a user-facing control seam",
139
143
  "agent_session:requestForegroundBashBackground": "internal accessor/plumbing, not a user-facing SDK control seam",
144
+ "agent_session:onJobFold": "internal fold-event plumbing feeding the bash_folded event frame, not a control seam",
140
145
 
141
146
  "agent_session:getSelectedMCPToolNames": "internal accessor/plumbing, not a user-facing control seam",
142
147
  "agent_session:isToolDiscoveryEnabled": "internal accessor/plumbing, not a user-facing control seam",
@@ -290,7 +295,7 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
290
295
  "agent_session:cycleThinkingLevel": "thinking.cycle",
291
296
  "agent_session:setSteeringMode": "queue.steering_mode.set",
292
297
  "agent_session:setFollowUpMode": "queue.follow_up_mode.set",
293
- "agent_session:setInterruptMode": "queue.interrupt_mode.set",
298
+ "agent_session:setToolInterruptPolicy": "queue.tool_interrupt_policy.set",
294
299
  "agent_session:compact": "compaction.run",
295
300
  "agent_session:setAutoCompactionEnabled": "compaction.auto.set",
296
301
  "agent_session:setAutoRetryEnabled": "retry.auto.set",
@@ -302,6 +307,7 @@ const SEAM_TO_SDK: Readonly<Record<string, string>> = {
302
307
  "agent_session:moveQueuedMessageForEditing": "queue.message.move",
303
308
  "agent_session:executeBash": "bash.execute",
304
309
  "agent_session:abortBash": "bash.abort",
310
+ "agent_session:requestForegroundBashBackgroundOutcome": "bash.background",
305
311
  "agent_session:switchSession": "session.switch",
306
312
  "agent_session:branch": "session.branch",
307
313
  "agent_session:handoff": "session.handoff",