@gajae-code/coding-agent 0.15.6 → 0.16.0

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 (167) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/types/cli/args.d.ts +2 -0
  3. package/dist/types/cli/root-flags.d.ts +14 -0
  4. package/dist/types/cli/update-cli.d.ts +13 -0
  5. package/dist/types/cli.d.ts +7 -0
  6. package/dist/types/commands/launch.d.ts +10 -0
  7. package/dist/types/config/autorouting-tier-map.d.ts +24 -0
  8. package/dist/types/config/file-lock.d.ts +5 -0
  9. package/dist/types/config/model-registry.d.ts +6 -0
  10. package/dist/types/config/settings-schema.d.ts +10 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
  13. package/dist/types/crash/record-loader.d.ts +15 -0
  14. package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
  15. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
  16. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
  17. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
  18. package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
  19. package/dist/types/master-mode/context.d.ts +21 -0
  20. package/dist/types/master-mode/first-request.d.ts +34 -0
  21. package/dist/types/master-mode/snapshot.d.ts +32 -0
  22. package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
  23. package/dist/types/modes/components/oauth-selector.d.ts +1 -0
  24. package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
  25. package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
  26. package/dist/types/modes/components/status-line/segments.d.ts +10 -0
  27. package/dist/types/modes/print-mode.d.ts +17 -0
  28. package/dist/types/modes/ui-language.d.ts +7 -1
  29. package/dist/types/sdk/broker/broker.d.ts +77 -1
  30. package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
  31. package/dist/types/sdk/broker/index.d.ts +3 -0
  32. package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
  33. package/dist/types/sdk/broker/master-capability.d.ts +10 -0
  34. package/dist/types/sdk/broker/session-index.d.ts +40 -11
  35. package/dist/types/sdk/broker/session-scope.d.ts +72 -0
  36. package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
  37. package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
  38. package/dist/types/sdk/broker/state-version.d.ts +4 -1
  39. package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
  40. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
  41. package/dist/types/sdk/bus/index.d.ts +6 -0
  42. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
  43. package/dist/types/sdk/cli/index.d.ts +1 -0
  44. package/dist/types/sdk/cli/master-cli.d.ts +63 -0
  45. package/dist/types/sdk/cli/rows.d.ts +4 -3
  46. package/dist/types/sdk/cli/session-cli.d.ts +18 -2
  47. package/dist/types/sdk/client/client.d.ts +3 -2
  48. package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
  49. package/dist/types/sdk/host/control/index.d.ts +1 -1
  50. package/dist/types/sdk/host/control/operations.d.ts +2 -0
  51. package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
  52. package/dist/types/sdk/host/host.d.ts +10 -0
  53. package/dist/types/sdk/host/session-runtime.d.ts +52 -0
  54. package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
  55. package/dist/types/sdk/lifecycle/service.d.ts +15 -3
  56. package/dist/types/sdk/lifecycle-session.d.ts +2 -0
  57. package/dist/types/sdk/router/session-router.d.ts +2 -0
  58. package/dist/types/sdk/session.d.ts +16 -0
  59. package/dist/types/sdk/startup-capability.d.ts +2 -1
  60. package/dist/types/session/agent-session.d.ts +5 -0
  61. package/dist/types/session/credential-store-errors.d.ts +1 -0
  62. package/dist/types/session/session-manager.d.ts +65 -0
  63. package/dist/types/session/yield-queue.d.ts +5 -4
  64. package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
  65. package/dist/types/tools/bash.d.ts +4 -0
  66. package/dist/types/tools/gh.d.ts +8 -0
  67. package/dist/types/tools/index.d.ts +4 -0
  68. package/package.json +7 -7
  69. package/scripts/generate-sdk-operation-inventory.ts +6 -0
  70. package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
  71. package/src/cli/accounts-cli.ts +9 -5
  72. package/src/cli/args.ts +17 -0
  73. package/src/cli/fast-help.ts +1 -0
  74. package/src/cli/root-flags.ts +5 -0
  75. package/src/cli/update-cli.ts +77 -11
  76. package/src/commands/launch.ts +86 -18
  77. package/src/commands/sdk.ts +113 -21
  78. package/src/config/autorouting-tier-map.ts +12 -0
  79. package/src/config/file-lock.ts +158 -83
  80. package/src/config/model-preset-registry.ts +1 -5
  81. package/src/config/model-profiles.ts +5 -5
  82. package/src/config/model-registry.ts +23 -0
  83. package/src/config/model-resolver.ts +5 -0
  84. package/src/config/settings-schema.ts +8 -1
  85. package/src/config/settings.ts +5 -0
  86. package/src/coordinator-mcp/projection-scan.ts +21 -0
  87. package/src/coordinator-mcp/question-state.ts +9 -11
  88. package/src/coordinator-mcp/server.ts +94 -20
  89. package/src/crash/index-store.ts +2 -0
  90. package/src/crash/record-loader.ts +10 -0
  91. package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
  92. package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
  93. package/src/gjc-runtime/session-state-lock.ts +79 -6
  94. package/src/gjc-runtime/session-state-sidecar.ts +929 -23
  95. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
  96. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
  97. package/src/gjc-runtime/tmux-sessions.ts +252 -13
  98. package/src/hooks/skill-state.ts +6 -0
  99. package/src/internal-urls/docs-index.generated.ts +7 -7
  100. package/src/internal-urls/issue-pr-protocol.ts +4 -2
  101. package/src/main.ts +57 -2
  102. package/src/master-mode/context.ts +50 -0
  103. package/src/master-mode/first-request.ts +65 -0
  104. package/src/master-mode/snapshot.ts +205 -0
  105. package/src/modes/acp/acp-agent.ts +605 -156
  106. package/src/modes/components/footer.ts +4 -2
  107. package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
  108. package/src/modes/components/oauth-selector.ts +6 -1
  109. package/src/modes/components/settings-selector.ts +1 -0
  110. package/src/modes/components/status-line/model-name.ts +53 -0
  111. package/src/modes/components/status-line/priority-row.ts +107 -0
  112. package/src/modes/components/status-line/segments.ts +23 -9
  113. package/src/modes/components/tool-status-header.ts +151 -15
  114. package/src/modes/controllers/input-controller.ts +28 -0
  115. package/src/modes/controllers/selector-controller.ts +191 -96
  116. package/src/modes/print-mode.ts +64 -0
  117. package/src/modes/ui-language.ts +49 -2
  118. package/src/prompts/system/master-mode.md +28 -0
  119. package/src/sdk/broker/broker.ts +1636 -26
  120. package/src/sdk/broker/endpoint-authority.ts +86 -0
  121. package/src/sdk/broker/ensure.ts +5 -0
  122. package/src/sdk/broker/index.ts +3 -0
  123. package/src/sdk/broker/lifecycle.ts +186 -41
  124. package/src/sdk/broker/master-capability.ts +232 -0
  125. package/src/sdk/broker/session-index.ts +121 -17
  126. package/src/sdk/broker/session-scope.ts +272 -0
  127. package/src/sdk/broker/spawn-authority.ts +1031 -0
  128. package/src/sdk/broker/spawn-substrate.ts +548 -0
  129. package/src/sdk/broker/state-version.ts +26 -14
  130. package/src/sdk/broker/transport.ts +1 -0
  131. package/src/sdk/broker/worktree-occupancy.ts +90 -0
  132. package/src/sdk/bus/chat-daemon-control.ts +5 -2
  133. package/src/sdk/bus/chat-daemon-runtime.ts +35 -4
  134. package/src/sdk/bus/index.ts +51 -4
  135. package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
  136. package/src/sdk/cli/index.ts +1 -0
  137. package/src/sdk/cli/master-cli.ts +259 -0
  138. package/src/sdk/cli/rows.ts +11 -8
  139. package/src/sdk/cli/session-cli.ts +264 -5
  140. package/src/sdk/client/client.ts +14 -8
  141. package/src/sdk/host/control/dispatch.ts +99 -38
  142. package/src/sdk/host/control/index.ts +2 -0
  143. package/src/sdk/host/control/operations.ts +2 -0
  144. package/src/sdk/host/control/runtime-gate.ts +203 -11
  145. package/src/sdk/host/host.ts +27 -2
  146. package/src/sdk/host/session-runtime.test.ts +210 -6
  147. package/src/sdk/host/session-runtime.ts +291 -49
  148. package/src/sdk/lifecycle/broker-client.ts +28 -0
  149. package/src/sdk/lifecycle/service.ts +279 -20
  150. package/src/sdk/lifecycle-session.ts +10 -2
  151. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  152. package/src/sdk/protocol/operation-registry.ts +13 -3
  153. package/src/sdk/router/session-router.ts +67 -59
  154. package/src/sdk/session.ts +357 -155
  155. package/src/sdk/startup-capability.ts +1 -0
  156. package/src/session/agent-session.ts +640 -103
  157. package/src/session/blob-store.ts +15 -5
  158. package/src/session/credential-store-errors.ts +2 -0
  159. package/src/session/internal/managed-session-storage.ts +153 -78
  160. package/src/session/session-manager.ts +337 -24
  161. package/src/session/yield-queue.ts +24 -10
  162. package/src/setup/frictionless-onboarding.ts +2 -0
  163. package/src/telemetry/events.ts +1138 -12
  164. package/src/tools/bash.ts +256 -42
  165. package/src/tools/gh.ts +104 -48
  166. package/src/tools/github-cache.ts +2 -2
  167. package/src/tools/index.ts +4 -0
@@ -6,3 +6,9 @@ export declare class AgentDirSessionLifecycleClient implements SessionLifecycleC
6
6
  global(operation: SessionLifecycleOperation, input: Record<string, unknown>, options: SessionLifecycleClientRequestOptions): Promise<unknown>;
7
7
  }
8
8
  export declare function createBrokerSessionLifecycleService(agentDir: string): SessionLifecycleService;
9
+ /**
10
+ * Dedicated dispatch for the local master `session.spawn` surface. It shares
11
+ * the lifecycle client's credential boundary but bypasses the generic mutation
12
+ * outcome types: spawn responses are validated by the caller's allowlist.
13
+ */
14
+ export declare function dispatchSpawnGlobal(agentDir: string, input: Record<string, unknown>, idempotencyKey: string, timeoutMs: number): Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ import { type ScopeRequestV1, type SdkSearchResultV1 } from "../broker/session-scope";
1
2
  export type SessionLifecycleOperation = "session.create" | "session.fork" | "session.resume" | "session.close" | "session.delete" | "session.reconcile_uncertain" | "session.list";
2
3
  export interface SessionLifecycleActor {
3
4
  readonly id: string;
@@ -104,6 +105,9 @@ export interface SessionDeleteTarget {
104
105
  export interface SessionListTarget {
105
106
  readonly cwd?: string;
106
107
  readonly resolveSessionId?: string;
108
+ readonly scope?: ScopeRequestV1;
109
+ readonly limit?: number;
110
+ readonly cursor?: string;
107
111
  }
108
112
  interface SessionLifecycleMutationRequestBase<TOperation extends Exclude<SessionLifecycleOperation, "session.list">, TTarget> {
109
113
  readonly operation: TOperation;
@@ -141,7 +145,12 @@ export interface SessionLifecycleListEntry {
141
145
  readonly live?: boolean;
142
146
  readonly endpointGeneration?: number;
143
147
  readonly terminalUncertain?: boolean;
144
- readonly repo?: string;
148
+ readonly cwd?: string;
149
+ readonly locator?: {
150
+ readonly cwd: string;
151
+ readonly worktreeRoot: string | null;
152
+ readonly stateRoot: string;
153
+ };
145
154
  }
146
155
  export interface SessionLifecycleListResult {
147
156
  readonly indexSeq: number;
@@ -149,6 +158,7 @@ export interface SessionLifecycleListResult {
149
158
  readonly warnings: readonly string[];
150
159
  readonly savedSession?: SessionLifecycleSavedSession;
151
160
  }
161
+ export type SessionScopedListResult = SdkSearchResultV1;
152
162
  export interface SessionCreateResult {
153
163
  readonly ok: true;
154
164
  readonly operation: "session.create";
@@ -182,7 +192,7 @@ export interface SessionReconcileUncertainResult {
182
192
  export interface SessionListSuccessResult {
183
193
  readonly ok: true;
184
194
  readonly operation: "session.list";
185
- readonly result: SessionLifecycleListResult;
195
+ readonly result: SessionLifecycleListResult | SessionScopedListResult;
186
196
  }
187
197
  export interface SessionLifecycleError {
188
198
  readonly code: string;
@@ -229,6 +239,7 @@ export type SessionListFailure = {
229
239
  readonly operation: "session.list";
230
240
  readonly certainty: SessionLifecycleCertainty;
231
241
  readonly error: SessionLifecycleError;
242
+ readonly result?: SessionScopedListResult;
232
243
  };
233
244
  export type SessionCreateOutcome = SessionCreateResult | SessionCreateFailure;
234
245
  export type SessionForkOutcome = SessionForkResult | SessionForkFailure;
@@ -254,7 +265,8 @@ export declare function validateSessionLifecycleMutationRequest(request: unknown
254
265
  export declare class SessionLifecycleService {
255
266
  #private;
256
267
  constructor(client: SessionLifecycleClient);
257
- execute(request: SessionLifecycleMutationRequest): Promise<SessionCreateOutcome | SessionForkOutcome | SessionResumeOutcome | SessionCloseOutcome | SessionDeleteOutcome | SessionReconcileUncertainOutcome>;
268
+ scopedList(scope: ScopeRequestV1, limit?: number, cursor?: string): Promise<SessionListOutcome>;
269
+ execute(request: SessionLifecycleMutationRequest, idempotencyKeyOverride?: string): Promise<SessionCreateOutcome | SessionForkOutcome | SessionResumeOutcome | SessionCloseOutcome | SessionDeleteOutcome | SessionReconcileUncertainOutcome>;
258
270
  executeWithIdempotencyKey(request: SessionLifecycleMutationRequest, idempotencyKey: string): Promise<SessionCreateOutcome | SessionForkOutcome | SessionResumeOutcome | SessionCloseOutcome | SessionDeleteOutcome | SessionReconcileUncertainOutcome>;
259
271
  create(request: Omit<SessionCreateRequest, "operation">): Promise<SessionCreateOutcome>;
260
272
  fork(request: Omit<SessionForkRequest, "operation">): Promise<SessionForkOutcome>;
@@ -48,6 +48,8 @@ export type CreateLifecycleAgentSessionOptions = Omit<CreateAgentSessionOptions,
48
48
  * a prepared signal instead of readiness until it is explicitly activated.
49
49
  */
50
50
  readiness?: "immediate" | "deferred";
51
+ /** Broker-issued lifecycle request identity published with host registration. */
52
+ lifecycleRequestId?: string;
51
53
  };
52
54
  /** Internal lifecycle-only session construction with an owner-bound SDK startup result. */
53
55
  export declare function createLifecycleAgentSession(options?: CreateLifecycleAgentSessionOptions): Promise<CreateLifecycleAgentSessionResult>;
@@ -164,6 +164,8 @@ export interface SessionRouterDeps {
164
164
  export type SessionRouterProviderDeps = Pick<SessionRouterDeps, "createClient" | "createIndex" | "createBrokerClient" | "setInterval" | "clearInterval" | "setTimeout" | "clearTimeout" | "onReconciled">;
165
165
  export interface SessionRouterOptions {
166
166
  agentDir: string;
167
+ /** Limits attachment to exact ids selected by a Broker-scoped operation. */
168
+ sessionIds?: readonly string[];
167
169
  deps?: SessionRouterDeps;
168
170
  /** Runtime-specific identity validation; Router supplies a conservative fallback. */
169
171
  correlateFrame?: SessionRouterFrameCorrelator;
@@ -7,6 +7,7 @@ import { ModelRegistry } from "../config/model-registry";
7
7
  import { type ScopedModelSelection } from "../config/model-resolver";
8
8
  import { type PromptTemplate } from "../config/prompt-templates";
9
9
  import { Settings, type SkillsSettings } from "../config/settings";
10
+ import type { MasterModeContext } from "../master-mode/context";
10
11
  import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
11
12
  import "../discovery";
12
13
  import type { CustomCommandsLoadResult } from "../extensibility/custom-commands";
@@ -176,6 +177,8 @@ export interface CreateAgentSessionOptions {
176
177
  * @internal CLI-only startup optimization; SDK callers retain synchronous loading by default.
177
178
  */
178
179
  deferMcpConfigStartup?: boolean;
180
+ /** Process-local master authority. Never persist or pass to lifecycle children. */
181
+ masterModeContext?: MasterModeContext;
179
182
  /**
180
183
  * Defer memory backend startup until the caller has applied startup model profiles.
181
184
  * @internal CLI-only ordering guard; SDK callers retain immediate startup by default.
@@ -384,4 +387,17 @@ export declare function createPluginHooksExtension(hooks: ConstrainedPluginHook[
384
387
  export declare function resolveIntentTracingEnabled(intentTracingSetting: boolean | undefined, options: {
385
388
  subSession: boolean;
386
389
  }): boolean;
390
+ /**
391
+ * Order candidates so each known provider's curated default model is tried
392
+ * before the rest of the catalog.
393
+ *
394
+ * Provider catalogs are not ranked by fitness — a withdrawn model whose ID
395
+ * carries an older date suffix sorts ahead of its current replacement — so
396
+ * picking the first credentialed entry can start an unconfigured install on a
397
+ * model the provider no longer serves. `DEFAULT_MODEL_PER_PROVIDER` is the
398
+ * curated table `findInitialModel` already sweeps for the same purpose;
399
+ * reusing it keeps both unconfigured paths on one source of truth. Candidates
400
+ * that are not a provider default keep their original relative order.
401
+ */
402
+ export declare function orderByProviderDefaultFirst(candidates: readonly Model[], providerOrder?: readonly string[]): Model[];
387
403
  export declare function createAgentSession(options?: CreateAgentSessionOptions): Promise<CreateAgentSessionResult>;
@@ -49,6 +49,7 @@ export declare class SdkStartupCapability {
49
49
  #private;
50
50
  readonly rollback?: SdkStartupRollbackTracker | undefined;
51
51
  readonly readiness: "immediate" | "deferred";
52
+ readonly lifecycleRequestId?: string | undefined;
52
53
  get cancelled(): boolean;
53
54
  cancel(): void;
54
55
  /**
@@ -57,7 +58,7 @@ export declare class SdkStartupCapability {
57
58
  * environment so bus wiring can never mistake an inherited process-global
58
59
  * flag for a broker-managed preparation.
59
60
  */
60
- constructor(rollback?: SdkStartupRollbackTracker | undefined, readiness?: "immediate" | "deferred");
61
+ constructor(rollback?: SdkStartupRollbackTracker | undefined, readiness?: "immediate" | "deferred", lifecycleRequestId?: string | undefined);
61
62
  normalizeFailure(phase: SdkStartupPhase, reason: SdkStartupReason, error?: unknown): SdkStartupFailure;
62
63
  get result(): SdkStartupResult | undefined;
63
64
  get promise(): Promise<SdkStartupResult>;
@@ -805,6 +805,11 @@ export declare class AgentSession {
805
805
  * proceeds.
806
806
  */
807
807
  awaitSessionSettlement(): Promise<void>;
808
+ queueCoordinatorRuntimeStatePersistForTests(event: AgentSessionEvent, gate: Promise<void>): Promise<void>;
809
+ parkAgentEndForCoordinatorPersistForTests(event: Extract<AgentSessionEvent, {
810
+ type: "agent_end";
811
+ }>): void;
812
+ flushParkedAgentEndForCoordinatorPersistForTests(): void;
808
813
  drainAsyncJobDeliveriesForAcp(options?: {
809
814
  timeoutMs?: number;
810
815
  }): Promise<boolean>;
@@ -0,0 +1 @@
1
+ export declare const CREDENTIAL_STORE_UNREADABLE_MESSAGE = "Credential store is unreadable. Back up the database, then have an operator run PRAGMA integrity_check or REINDEX; no automatic repair was attempted.";
@@ -1175,7 +1175,63 @@ export declare class SessionManager {
1175
1175
  runWithCwdReadLease<T>(fn: () => Promise<T>): Promise<T>;
1176
1176
  /** Wait for any in-flight exclusive cwd transition to settle. */
1177
1177
  joinCwdTransition(): Promise<void>;
1178
+ closeCwdMoveAdmission(): Promise<void>;
1178
1179
  getCwdGeneration(): number;
1180
+ /**
1181
+ * Register a listener invoked after every committed `moveTo`. Returns an idempotent
1182
+ * unregister handle. Every move surface funnels through `moveTo`, so a single
1183
+ * registration covers `move_session`, `/move`, and SDK/ACP `session.cwd.move`.
1184
+ */
1185
+ registerAfterMoveListener(listener: (move: {
1186
+ previousCwd: string;
1187
+ newCwd: string;
1188
+ previousSessionFile: string | undefined;
1189
+ }) => void | Promise<void>): () => void;
1190
+ registerBeforeMoveListener(listener: (move: {
1191
+ previousCwd: string;
1192
+ newCwd: string;
1193
+ previousSessionFile: string | undefined;
1194
+ newSessionFile: string | undefined;
1195
+ previousCwdIdentity: {
1196
+ dev: bigint;
1197
+ ino: bigint;
1198
+ };
1199
+ newCwdIdentity: {
1200
+ dev: bigint;
1201
+ ino: bigint;
1202
+ };
1203
+ preserveRecoveryJournal?: boolean;
1204
+ }) => void | Promise<void>): () => void;
1205
+ registerMovePublicationListener(listener: (move: {
1206
+ previousCwd: string;
1207
+ newCwd: string;
1208
+ previousSessionFile: string | undefined;
1209
+ newSessionFile: string | undefined;
1210
+ previousCwdIdentity: {
1211
+ dev: bigint;
1212
+ ino: bigint;
1213
+ };
1214
+ newCwdIdentity: {
1215
+ dev: bigint;
1216
+ ino: bigint;
1217
+ };
1218
+ preserveRecoveryJournal?: boolean;
1219
+ }) => void | Promise<void>): () => void;
1220
+ registerMoveAbortListener(listener: (move: {
1221
+ previousCwd: string;
1222
+ newCwd: string;
1223
+ previousSessionFile: string | undefined;
1224
+ newSessionFile: string | undefined;
1225
+ previousCwdIdentity: {
1226
+ dev: bigint;
1227
+ ino: bigint;
1228
+ };
1229
+ newCwdIdentity: {
1230
+ dev: bigint;
1231
+ ino: bigint;
1232
+ };
1233
+ preserveRecoveryJournal?: boolean;
1234
+ }) => void | Promise<void>): () => void;
1179
1235
  static openNoFollowDirectory(dir: string): Promise<fs.promises.FileHandle>;
1180
1236
  /**
1181
1237
  * Claim process-cwd authority for `manager` when it is unowned or the prior
@@ -1215,6 +1271,15 @@ export declare class SessionManager {
1215
1271
  bigint: true;
1216
1272
  }) => Promise<fs.BigIntStats>;
1217
1273
  };
1274
+ expectedSourceIdentity?: {
1275
+ dev: bigint;
1276
+ ino: bigint;
1277
+ };
1278
+ sourceHandle?: {
1279
+ stat: (opts: {
1280
+ bigint: true;
1281
+ }) => Promise<fs.BigIntStats>;
1282
+ };
1218
1283
  }): Promise<void>;
1219
1284
  isPersisted(): boolean;
1220
1285
  stageDefaultModelSelection(model: string, thinkingLevel: string | undefined, options?: {
@@ -15,8 +15,9 @@ export interface YieldDispatcher<P> {
15
15
  export interface YieldQueueOptions {
16
16
  isStreaming: () => boolean;
17
17
  injectStreaming(msg: AgentMessage): void;
18
- injectIdle(messages: AgentMessage[]): Promise<void>;
19
- scheduleIdleFlush(run: () => Promise<void>): void;
18
+ injectIdle(messages: AgentMessage[], signal?: AbortSignal): Promise<void>;
19
+ scheduleIdleFlush(run: (signal?: AbortSignal) => Promise<void>, onSkip: () => void): void;
20
+ getIdleFlushSignal?(): AbortSignal | undefined;
20
21
  }
21
22
  type YieldFlushMode = "streaming" | "idle";
22
23
  export declare class YieldQueue {
@@ -25,8 +26,8 @@ export declare class YieldQueue {
25
26
  register<P>(kind: string, dispatcher: YieldDispatcher<P>): () => void;
26
27
  enqueue<P>(kind: string, entry: P): void;
27
28
  has(kind?: string): boolean;
28
- flush(mode: YieldFlushMode): Promise<void>;
29
- clear(): void;
29
+ flush(mode: YieldFlushMode, signal?: AbortSignal): Promise<void>;
30
+ clear(onDrop?: (kind: string, entries: readonly unknown[]) => void): void;
30
31
  /** Drop only the queued entries of a single kind, leaving other kinds intact. */
31
32
  clearKind(kind: string): void;
32
33
  /**
@@ -68,6 +68,7 @@ export declare function hasCorroboratedRecentEvidence(evidence: readonly Onboard
68
68
  export declare function shouldOfferOnboarding(state: FrictionlessOnboardingState): boolean;
69
69
  export interface AutomaticOnboardingInput {
70
70
  normalInteractive: boolean;
71
+ automation?: boolean;
71
72
  initialMessage?: string;
72
73
  initialMessages: readonly string[];
73
74
  initialImages?: readonly unknown[];
@@ -4,6 +4,7 @@ import * as z from "zod/v4";
4
4
  import { type BashArtifactSaveResult } from "../exec/bash-executor";
5
5
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
6
6
  import { type Theme } from "../modes/theme/theme";
7
+ import { type SdkSpawnArgs } from "../sdk/cli/master-cli";
7
8
  import type { ToolSession } from ".";
8
9
  import { type OutputMeta } from "./output-meta";
9
10
  export declare const BASH_DEFAULT_PREVIEW_LINES = 10;
@@ -48,6 +49,9 @@ export interface BashToolDetails {
48
49
  export declare function summarizeBashToolActivity(kind: "args" | "result", value: unknown): string | undefined;
49
50
  export interface BashToolOptions {
50
51
  }
52
+ export declare function isStrictDirectSdkSpawnCommand(command: string): boolean;
53
+ export declare function parseDirectSdkSpawnArgs(command: string): SdkSpawnArgs;
54
+ export declare function masterCommandEnvOverrides(env: Record<string, string> | undefined, directMasterSpawn: boolean): Record<string, string>;
51
55
  export declare class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
52
56
  #private;
53
57
  private readonly session;
@@ -322,6 +322,14 @@ export interface PrDiffFile {
322
322
  changeType: "modified" | "added" | "deleted" | "renamed" | "binary";
323
323
  /** Pre-image path for renames/deletes; same as `path` otherwise. */
324
324
  oldPath?: string;
325
+ /**
326
+ * Set when `path` kept its C-style escaped source form because the quoted
327
+ * token held invalid UTF-8 or a malformed escape; distinguishes it from a
328
+ * valid path whose decoded text looks identical.
329
+ */
330
+ pathEscaped?: boolean;
331
+ /** Same as {@link PrDiffFile.pathEscaped}, for `oldPath`. */
332
+ oldPathEscaped?: boolean;
325
333
  /** Byte offset of the section's `diff --git` line in the unified diff. */
326
334
  startOffset: number;
327
335
  /** Byte offset of the next section (or end-of-text). */
@@ -219,6 +219,10 @@ export interface ToolSession {
219
219
  bashAllowedPrefixes?: string[];
220
220
  /** Restriction policy for sessions that deliberately expose a narrow bash surface. */
221
221
  bashRestrictionProfile?: BashRestrictionProfile;
222
+ /** Process-local master capability injected into direct Bash children only. */
223
+ getMasterBashCapability?: () => string | undefined;
224
+ /** Stable master lineage identity used across session switches and branches. */
225
+ getMasterOwnerSessionId?: () => string | undefined;
222
226
  /** Optional per-session allowlist for tools exposed through search_tool_bm25. */
223
227
  discoverableToolAllowedNames?: readonly string[];
224
228
  /** Throw instead of warn when toolNames contains an unknown name. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.15.6",
4
+ "version": "0.16.0",
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.15.6",
60
- "@gajae-code/agent-core": "0.15.6",
61
- "@gajae-code/ai": "0.15.6",
62
- "@gajae-code/natives": "0.15.6",
63
- "@gajae-code/tui": "0.15.6",
64
- "@gajae-code/utils": "0.15.6",
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",
65
65
  "@puppeteer/browsers": "^2.13.0",
66
66
  "@types/turndown": "5.0.6",
67
67
  "@xterm/headless": "^6.0.0",
@@ -107,6 +107,12 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
107
107
  "agent_session:activeMidRunBarrierCountForTests": "read-only test seam, not a user-facing SDK control seam",
108
108
  "agent_session:activeMidRunMaintenanceCountForTests": "read-only test seam, not a user-facing SDK control seam",
109
109
  "agent_session:getPendingNextTurnMessagesForTests": "read-only test seam, not a user-facing SDK control seam",
110
+ "agent_session:queueCoordinatorRuntimeStatePersistForTests":
111
+ "test-only coordinator persistence ordering seam, not a user-facing SDK control seam",
112
+ "agent_session:parkAgentEndForCoordinatorPersistForTests":
113
+ "test-only parked terminal persistence seam, not a user-facing SDK control seam",
114
+ "agent_session:flushParkedAgentEndForCoordinatorPersistForTests":
115
+ "test-only parked terminal flush seam, not a user-facing SDK control seam",
110
116
  "agent_session:setCancelAndSubmitAbortOutcomeProviderForTests":
111
117
  "test-only cancellation seam, not a user-facing SDK control seam",
112
118
  "agent_session:getAgentId": "internal accessor/plumbing, not a user-facing control seam",
@@ -1567,6 +1567,24 @@ function isTypeOnlyTmuxPrimitiveOccurrence(contents: string, occurrence: TmuxPri
1567
1567
  return /^\s*(?:export\s+)?type\b/.test(contents.slice(lineStart, lineEnd === -1 ? contents.length : lineEnd));
1568
1568
  }
1569
1569
 
1570
+ function isExactTmuxSelfInjectionGuardVerb(
1571
+ file: string,
1572
+ contents: string,
1573
+ occurrence: TmuxPrimitiveOccurrence,
1574
+ ): boolean {
1575
+ if (file !== "packages/coding-agent/src/tools/tmux-self-injection-guard.ts") return false;
1576
+ const declaration = 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);';
1577
+ const declarationStart = contents.indexOf(declaration);
1578
+ if (declarationStart === -1 || contents.indexOf(declaration, declarationStart + 1) !== -1) return false;
1579
+ const references = [...contents.matchAll(/\bINPUT_VERBS\b/g)];
1580
+ if (references.length !== 2) return false;
1581
+ const guardUse = "INPUT_VERBS.has(invocation.verb)";
1582
+ const guardUseStart = contents.indexOf(guardUse);
1583
+ if (guardUseStart === -1 || contents.indexOf(guardUse, guardUseStart + 1) !== -1) return false;
1584
+ if (!references.some(reference => reference.index === guardUseStart)) return false;
1585
+ return occurrence.start >= declarationStart && occurrence.end <= declarationStart + declaration.length;
1586
+ }
1587
+
1570
1588
  function tmuxMachineBusViolations(file: string, contents: string): string[] {
1571
1589
  if (isGeneratedDocumentationIndex(file)) return [];
1572
1590
  const allowedTeamFallbackRanges =
@@ -1581,7 +1599,8 @@ function tmuxMachineBusViolations(file: string, contents: string): string[] {
1581
1599
  if (
1582
1600
  isExactTeamFallback ||
1583
1601
  isExactTmuxScrollCopyModeSendKeys(file, contents, occurrence) ||
1584
- isTypeOnlyTmuxPrimitiveOccurrence(contents, occurrence)
1602
+ isTypeOnlyTmuxPrimitiveOccurrence(contents, occurrence) ||
1603
+ isExactTmuxSelfInjectionGuardVerb(file, contents, occurrence)
1585
1604
  )
1586
1605
  continue;
1587
1606
  const detail =
@@ -3147,6 +3166,21 @@ fi
3147
3166
  1,
3148
3167
  "tmux send-keys content injection is outside sanctioned process lifecycle",
3149
3168
  );
3169
+ await runSelfTestFixture(
3170
+ {
3171
+ "packages/coding-agent/src/tools/tmux-self-injection-guard.ts":
3172
+ 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);\nfunction guarded(invocation: { verb: string }): void { if (!INPUT_VERBS.has(invocation.verb)) return; }\n',
3173
+ },
3174
+ 0,
3175
+ );
3176
+ await runSelfTestFixture(
3177
+ {
3178
+ "packages/coding-agent/src/tools/tmux-self-injection-guard.ts":
3179
+ 'const INPUT_VERBS = new Set(["send-keys", "paste-buffer", "send-prefix"]);\nfunction guarded(invocation: { verb: string }): void { if (!INPUT_VERBS.has(invocation.verb)) return; }\nfor (const verb of INPUT_VERBS) Bun.spawnSync(["tmux", verb, "-t", pane, prompt]);\n',
3180
+ },
3181
+ 1,
3182
+ "tmux send-keys content injection is outside sanctioned process lifecycle",
3183
+ );
3150
3184
  await runSelfTestFixture(
3151
3185
  {
3152
3186
  "packages/coding-agent/src/gjc-runtime/team-runtime.ts":
@@ -10,6 +10,7 @@ import {
10
10
  type AuthStorage,
11
11
  type CredentialInventoryRecord,
12
12
  type CredentialRemovalTarget,
13
+ isSqliteCorruptionError,
13
14
  OAuthCredentialSelectorError,
14
15
  type OAuthPinTarget,
15
16
  resolveOAuthStorageProvider,
@@ -23,6 +24,7 @@ import {
23
24
  checkAccountInventory,
24
25
  clearPersistentPinForRemovedRows,
25
26
  } from "../session/account-inventory";
27
+ import { CREDENTIAL_STORE_UNREADABLE_MESSAGE } from "../session/credential-store-errors";
26
28
  import { resolveStartupAuthConfig, type StartupAuthConfigSnapshot } from "../session/startup-auth-config";
27
29
 
28
30
  export const ACCOUNTS_ACTIONS = ["list", "check", "pin", "logout"] as const;
@@ -48,6 +50,7 @@ class AccountsCommandError extends Error {
48
50
  }
49
51
  }
50
52
  export function toAccountsCommandError(error: unknown): AccountsCommandError | undefined {
53
+ if (isSqliteCorruptionError(error)) return new AccountsCommandError(CREDENTIAL_STORE_UNREADABLE_MESSAGE);
51
54
  if (error instanceof OAuthCredentialSelectorError) return new AccountsCommandError(error.message);
52
55
  return undefined;
53
56
  }
@@ -391,16 +394,17 @@ export async function runAccountsCommand(cmd: AccountsCommandArgs): Promise<void
391
394
  }
392
395
  }
393
396
  } catch (error) {
397
+ const normalizedError = toAccountsCommandError(error) ?? error;
394
398
  if (cmd.flags.json) {
395
- if (!(error instanceof AccountsCommandError)) {
396
- process.stderr.write(`accounts command failed: ${cleanReason(error) ?? "Command failed."}\n`);
399
+ if (!(normalizedError instanceof AccountsCommandError)) {
400
+ process.stderr.write(`accounts command failed: ${cleanReason(normalizedError) ?? "Command failed."}\n`);
397
401
  }
398
402
  process.exitCode = 1;
399
- writeJsonFailure(error);
403
+ writeJsonFailure(normalizedError);
400
404
  return;
401
405
  }
402
- if (error instanceof AccountsCommandError) {
403
- process.stderr.write(`${error.message}\n`);
406
+ if (normalizedError instanceof AccountsCommandError) {
407
+ process.stderr.write(`${normalizedError.message}\n`);
404
408
  process.exitCode = 1;
405
409
  return;
406
410
  }
package/src/cli/args.ts CHANGED
@@ -84,6 +84,8 @@ export interface Args {
84
84
  noRules?: boolean;
85
85
  listModels?: string | true;
86
86
  noTitle?: boolean;
87
+ master?: true;
88
+ masterScope?: "repo" | "pwd" | "global";
87
89
  messages: string[];
88
90
  fileArgs: string[];
89
91
  /** Flags unknown to every launch parser; their consumer supplies the diagnostic. */
@@ -178,6 +180,17 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
178
180
  result.help = true;
179
181
  } else if (arg === "--version" || arg === "-v") {
180
182
  result.version = true;
183
+ } else if (arg === "--master") {
184
+ result.master = true;
185
+ } else if (arg === "--scope") {
186
+ const scope = takeFlagValue(args, i++, "--scope");
187
+ if (scope !== "repo" && scope !== "pwd" && scope !== "global") {
188
+ throw new CliParseError("invalid --scope value (expected repo, pwd, or global)");
189
+ }
190
+ if (result.masterScope !== undefined && result.masterScope !== scope) {
191
+ throw new CliParseError("--scope cannot be specified with conflicting values");
192
+ }
193
+ result.masterScope = scope;
181
194
  } else if (arg === "--allow-home") {
182
195
  result.allowHome = true;
183
196
  } else if (arg === "--mode") {
@@ -371,6 +384,10 @@ export function parseArgs(args: string[], authority: ParseArgsAuthority = "local
371
384
  }
372
385
  }
373
386
 
387
+ if (result.masterScope !== undefined && result.master !== true) {
388
+ throw new CliParseError("--scope requires --master");
389
+ }
390
+ if (result.master === true && result.masterScope === undefined) result.masterScope = "repo";
374
391
  if (result.default && !result.mpreset) {
375
392
  throw new CliParseError("--default requires --mpreset <name>");
376
393
  }
@@ -79,6 +79,7 @@ Environment Variables:
79
79
 
80
80
  # Configuration
81
81
  GJC_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
82
+ GJC_UI_LANGUAGE - Explicit first-use onboarding language (en, ko, zh, or ja); invalid values use English without prompting
82
83
  GJC_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
83
84
  GJC_SMOL_MODEL - Override smol/fast model (see --smol)
84
85
  GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
@@ -73,6 +73,11 @@ export const ROOT_LAUNCH_FLAGS = {
73
73
  optionalValue: true,
74
74
  }),
75
75
  "no-title": Flags.boolean({ description: "Disable title auto-generation" }),
76
+ master: Flags.boolean({ description: "Launch an interactive master session" }),
77
+ scope: Flags.string({
78
+ description: "Master scope: repo (default), pwd, or global",
79
+ options: ["repo", "pwd", "global"],
80
+ }),
76
81
  };
77
82
 
78
83
  export type ConsumerLaunchFlagName = "hook" | "extension" | "no-extensions" | "no-skills" | "skills";
@@ -9,7 +9,7 @@ import * as fs from "node:fs";
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
11
  import { pipeline } from "node:stream/promises";
12
- import { $which, APP_NAME, isCompiledBinary, isEnoent, VERSION } from "@gajae-code/utils";
12
+ import { $which, APP_NAME, isCompiledBinary, isEnoent, redactCrashSecrets, VERSION } from "@gajae-code/utils";
13
13
  import { $ } from "bun";
14
14
  import chalk from "chalk";
15
15
  import { Settings } from "../config/settings";
@@ -55,6 +55,7 @@ export interface InstalledVersionVerification {
55
55
  ok: boolean;
56
56
  actual?: string;
57
57
  path?: string;
58
+ versionOutput?: string;
58
59
  smokeTestFailed?: boolean;
59
60
  smokeTestOutput?: string;
60
61
  cleanupWarning?: string;
@@ -462,6 +463,71 @@ export function parseReportedVersionForTest(output: string): string | undefined
462
463
  return parseReportedVersion(output);
463
464
  }
464
465
 
466
+ const VERIFICATION_OUTPUT_MAX_LENGTH = 512;
467
+ const ANSI_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/gu;
468
+ const UNSAFE_CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
469
+
470
+ function sanitizeVerificationOutput(...streams: Array<string | undefined>): string | undefined {
471
+ const output = streams
472
+ .map(stream => stream?.replace(ANSI_ESCAPE, "").replace(UNSAFE_CONTROL, " ").replace(/\s+/g, " ").trim() ?? "")
473
+ .filter(Boolean)
474
+ .filter((stream, index, all) => all.indexOf(stream) === index)
475
+ .join(" ");
476
+ if (!output) return undefined;
477
+ const redacted = redactCrashSecrets(output);
478
+ if (redacted.length <= VERIFICATION_OUTPUT_MAX_LENGTH) return redacted;
479
+ return `${redacted.slice(0, VERIFICATION_OUTPUT_MAX_LENGTH - 3)}...`;
480
+ }
481
+
482
+ export function sanitizeVerificationOutputForTest(
483
+ stderr: string | undefined,
484
+ stdout: string | undefined,
485
+ ): string | undefined {
486
+ return sanitizeVerificationOutput(stderr, stdout);
487
+ }
488
+
489
+ interface InstalledVersionCommandResult {
490
+ exitCode: number | null;
491
+ stdout: string;
492
+ stderr: string;
493
+ }
494
+
495
+ type InstalledVersionCommandRunner = (runtimePath: string) => Promise<InstalledVersionCommandResult>;
496
+
497
+ async function verifyInstalledVersionWith(
498
+ expectedVersion: string,
499
+ runtimePath: string | undefined,
500
+ runVersion: InstalledVersionCommandRunner,
501
+ ): Promise<InstalledVersionVerification> {
502
+ if (!runtimePath) return { ok: false };
503
+ try {
504
+ const result = await runVersion(runtimePath);
505
+ if (result.exitCode !== 0) {
506
+ return {
507
+ ok: false,
508
+ path: runtimePath,
509
+ versionOutput: sanitizeVerificationOutput(result.stderr, result.stdout),
510
+ };
511
+ }
512
+ const actual = parseReportedVersion(result.stdout);
513
+ return { ok: actual === expectedVersion, actual, path: runtimePath };
514
+ } catch (error) {
515
+ return {
516
+ ok: false,
517
+ path: runtimePath,
518
+ versionOutput: sanitizeVerificationOutput(error instanceof Error ? error.message : String(error)),
519
+ };
520
+ }
521
+ }
522
+
523
+ export async function verifyInstalledVersionForTest(options: {
524
+ expectedVersion: string;
525
+ runtimePath: string | undefined;
526
+ runVersion: InstalledVersionCommandRunner;
527
+ }): Promise<InstalledVersionVerification> {
528
+ return await verifyInstalledVersionWith(options.expectedVersion, options.runtimePath, options.runVersion);
529
+ }
530
+
465
531
  /**
466
532
  * Run the resolved gjc binary and check if it reports the expected version.
467
533
  */
@@ -469,15 +535,14 @@ async function verifyInstalledVersion(
469
535
  expectedVersion: string,
470
536
  runtimePath: string | undefined = resolveGjcPath(),
471
537
  ): Promise<InstalledVersionVerification> {
472
- if (!runtimePath) return { ok: false };
473
- try {
474
- const result = await $`${runtimePath} --version`.quiet().nothrow();
475
- if (result.exitCode !== 0) return { ok: false, path: runtimePath };
476
- const actual = parseReportedVersion(result.text());
477
- return { ok: actual === expectedVersion, actual, path: runtimePath };
478
- } catch {
479
- return { ok: false, path: runtimePath };
480
- }
538
+ return await verifyInstalledVersionWith(expectedVersion, runtimePath, async resolvedPath => {
539
+ const result = await $`${resolvedPath} --version`.quiet().nothrow();
540
+ return {
541
+ exitCode: result.exitCode,
542
+ stdout: result.stdout.toString(),
543
+ stderr: result.stderr.toString(),
544
+ };
545
+ });
481
546
  }
482
547
 
483
548
  async function verifyInstalledRuntime(
@@ -665,7 +730,8 @@ function formatVerificationFailure(result: InstalledVersionVerification, expecte
665
730
  if (result.actual) {
666
731
  return `${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`;
667
732
  }
668
- return `could not verify updated version${result.path ? ` at ${result.path}` : ""}`;
733
+ const outputSuffix = result.versionOutput ? `: ${result.versionOutput}` : "";
734
+ return `could not verify updated version${result.path ? ` at ${result.path}` : ""}${outputSuffix}`;
669
735
  }
670
736
 
671
737
  export function formatVerificationFailureForTest(