@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
@@ -1,5 +1,14 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import path from "node:path";
3
+ import {
4
+ resolvedScopeV1,
5
+ resolveScopeRequest,
6
+ type ScopeRequestV1,
7
+ type SdkSearchResultV1,
8
+ type SdkSearchRowV1,
9
+ scopeRequestV1,
10
+ searchRowV1,
11
+ } from "../broker/session-scope";
3
12
  import {
4
13
  SessionListTraversalError,
5
14
  type SessionListTraversalPage,
@@ -141,6 +150,9 @@ export interface SessionDeleteTarget {
141
150
  export interface SessionListTarget {
142
151
  readonly cwd?: string;
143
152
  readonly resolveSessionId?: string;
153
+ readonly scope?: ScopeRequestV1;
154
+ readonly limit?: number;
155
+ readonly cursor?: string;
144
156
  }
145
157
 
146
158
  interface SessionLifecycleMutationRequestBase<
@@ -197,7 +209,8 @@ export interface SessionLifecycleListEntry {
197
209
  readonly live?: boolean;
198
210
  readonly endpointGeneration?: number;
199
211
  readonly terminalUncertain?: boolean;
200
- readonly repo?: string;
212
+ readonly cwd?: string;
213
+ readonly locator?: { readonly cwd: string; readonly worktreeRoot: string | null; readonly stateRoot: string };
201
214
  }
202
215
 
203
216
  export interface SessionLifecycleListResult {
@@ -207,6 +220,8 @@ export interface SessionLifecycleListResult {
207
220
  readonly savedSession?: SessionLifecycleSavedSession;
208
221
  }
209
222
 
223
+ export type SessionScopedListResult = SdkSearchResultV1;
224
+
210
225
  export interface SessionCreateResult {
211
226
  readonly ok: true;
212
227
  readonly operation: "session.create";
@@ -240,7 +255,7 @@ export interface SessionReconcileUncertainResult {
240
255
  export interface SessionListSuccessResult {
241
256
  readonly ok: true;
242
257
  readonly operation: "session.list";
243
- readonly result: SessionLifecycleListResult;
258
+ readonly result: SessionLifecycleListResult | SessionScopedListResult;
244
259
  }
245
260
 
246
261
  export interface SessionLifecycleError {
@@ -289,6 +304,7 @@ export type SessionListFailure = {
289
304
  readonly operation: "session.list";
290
305
  readonly certainty: SessionLifecycleCertainty;
291
306
  readonly error: SessionLifecycleError;
307
+ readonly result?: SessionScopedListResult;
292
308
  };
293
309
 
294
310
  export type SessionCreateOutcome = SessionCreateResult | SessionCreateFailure;
@@ -582,12 +598,24 @@ function listResult(value: unknown): SessionLifecycleListResult | undefined {
582
598
  live?: boolean;
583
599
  endpointGeneration?: number;
584
600
  terminalUncertain?: boolean;
585
- repo?: string;
601
+ cwd?: string;
602
+ locator?: { cwd: string; worktreeRoot: string | null; stateRoot: string };
586
603
  } = { sessionId: entry.sessionId };
587
604
  if (typeof entry.live === "boolean") item.live = entry.live;
588
605
  if (typeof entry.endpointGeneration === "number") item.endpointGeneration = entry.endpointGeneration;
589
606
  if (typeof entry.terminalUncertain === "boolean") item.terminalUncertain = entry.terminalUncertain;
590
- if (isRecord(entry.locator) && typeof entry.locator.repo === "string") item.repo = entry.locator.repo;
607
+ if (isRecord(entry.locator) && typeof entry.locator.cwd === "string") item.cwd = entry.locator.cwd;
608
+ if (
609
+ isRecord(entry.locator) &&
610
+ typeof entry.locator.cwd === "string" &&
611
+ (entry.locator.worktreeRoot === null || typeof entry.locator.worktreeRoot === "string") &&
612
+ typeof entry.locator.stateRoot === "string"
613
+ )
614
+ item.locator = {
615
+ cwd: entry.locator.cwd,
616
+ worktreeRoot: entry.locator.worktreeRoot,
617
+ stateRoot: entry.locator.stateRoot,
618
+ };
591
619
  sessions.push(item);
592
620
  }
593
621
  const warnings: string[] = [];
@@ -600,10 +628,40 @@ function listResult(value: unknown): SessionLifecycleListResult | undefined {
600
628
  if (savedSessionPresent && !savedSession) return undefined;
601
629
  return { indexSeq, sessions, warnings, ...(savedSession ? { savedSession } : {}) };
602
630
  }
631
+
632
+ function scopedPage(value: unknown): { scope: SdkSearchResultV1["scope"]; observedAt: string } | undefined {
633
+ if (!isRecord(value)) return undefined;
634
+ const scope = resolvedScopeV1(value.scope);
635
+ return scope && typeof value.observedAt === "string" ? { scope, observedAt: value.observedAt } : undefined;
636
+ }
637
+
638
+ function scopedSearchRows(sessions: readonly SessionLifecycleListEntry[]): SdkSearchRowV1[] | undefined {
639
+ const rows: SdkSearchRowV1[] = [];
640
+ for (const session of sessions) {
641
+ const locator = session.locator;
642
+ if (
643
+ locator === undefined ||
644
+ typeof locator.cwd !== "string" ||
645
+ (locator.worktreeRoot !== null && typeof locator.worktreeRoot !== "string") ||
646
+ typeof locator.stateRoot !== "string"
647
+ )
648
+ return undefined;
649
+ rows.push(
650
+ searchRowV1({
651
+ sessionId: session.sessionId,
652
+ locator: { cwd: locator.cwd, worktreeRoot: locator.worktreeRoot, stateRoot: locator.stateRoot },
653
+ live: session.live === true,
654
+ }),
655
+ );
656
+ }
657
+ return rows;
658
+ }
603
659
  type LifecycleListPage = {
604
660
  readonly result: SessionLifecycleListResult;
605
661
  readonly sessions: readonly SessionLifecycleListEntry[];
606
662
  readonly continuationCursor?: unknown;
663
+ readonly scope?: SdkSearchResultV1["scope"];
664
+ readonly observedAt?: string;
607
665
  };
608
666
 
609
667
  function brokerError(value: unknown): { code: string; message: string } | undefined {
@@ -647,6 +705,24 @@ function brokerErrorFromThrown(value: unknown): { code: string; message: string;
647
705
  return { code, message, ...(requestSent === undefined ? {} : { requestSent }) };
648
706
  }
649
707
 
708
+ function unavailableScopedList(scope: SdkSearchResultV1["scope"]): SessionListFailure {
709
+ return {
710
+ ok: false,
711
+ operation: "session.list",
712
+ certainty: "retryable",
713
+ error: { code: "unavailable", message: "broker search is unavailable" },
714
+ result: {
715
+ version: 1,
716
+ scope,
717
+ status: "unavailable",
718
+ observedAt: new Date().toISOString(),
719
+ rows: [],
720
+ warnings: [],
721
+ error: { code: "unavailable", message: "broker search is unavailable" },
722
+ },
723
+ };
724
+ }
725
+
650
726
  const TRANSPORT_ERROR_CODES = new Set([
651
727
  "timeout",
652
728
  "connection_closed",
@@ -678,7 +754,110 @@ export class SessionLifecycleService {
678
754
  this.#client = client;
679
755
  }
680
756
 
681
- async #execute(
757
+ async scopedList(scope: ScopeRequestV1, limit?: number, cursor?: string): Promise<SessionListOutcome> {
758
+ return await this.#list(
759
+ {
760
+ actor: { id: "gjc-sdk-search-cli", namespace: "sdk:search-cli" },
761
+ capability: "session.list",
762
+ target: {
763
+ scope,
764
+ ...(limit === undefined ? {} : { limit }),
765
+ ...(cursor === undefined ? {} : { cursor }),
766
+ },
767
+ },
768
+ true,
769
+ );
770
+ }
771
+
772
+ async #singleScopedList(
773
+ request: Omit<SessionListRequest, "operation">,
774
+ target: Readonly<Record<string, unknown>>,
775
+ locallyResolvedScope: SdkSearchResultV1["scope"],
776
+ ): Promise<SessionListOutcome> {
777
+ let response: unknown;
778
+ try {
779
+ response = await this.#client.global(
780
+ "session.list",
781
+ { ...target },
782
+ {
783
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
784
+ },
785
+ );
786
+ } catch (thrown) {
787
+ const error = brokerError(thrown) ?? brokerErrorFromThrown(thrown);
788
+ if (!error || error.code === "unavailable") return unavailableScopedList(locallyResolvedScope);
789
+ return failure("session.list", certaintyForThrownError(error), error.code, error.message);
790
+ }
791
+ const error = brokerError(response);
792
+ if (error) {
793
+ if (error.code === "unavailable") return unavailableScopedList(locallyResolvedScope);
794
+ return failure("session.list", certaintyForBrokerCode(error.code), error.code, error.message);
795
+ }
796
+ if (!isRecord(response) || response.ok !== true)
797
+ return failure(
798
+ "session.list",
799
+ "uncertain",
800
+ "malformed_response",
801
+ "lifecycle broker returned a malformed scoped list result",
802
+ );
803
+ const page = sessionListPageFromResponse(response);
804
+ const result = page === undefined ? undefined : listResult(page);
805
+ const pageScope = page === undefined ? undefined : scopedPage(page);
806
+ if (page === undefined || !result || !pageScope)
807
+ return failure(
808
+ "session.list",
809
+ "uncertain",
810
+ "malformed_response",
811
+ "lifecycle broker returned a malformed scoped list result",
812
+ );
813
+ if (JSON.stringify(pageScope.scope) !== JSON.stringify(locallyResolvedScope))
814
+ return failure(
815
+ "session.list",
816
+ "uncertain",
817
+ "scope_observation_drift",
818
+ "lifecycle broker scope does not match the locally resolved request scope",
819
+ );
820
+ const continuationCursor = page.continuationCursor;
821
+ if (
822
+ continuationCursor !== undefined &&
823
+ (typeof continuationCursor !== "string" || continuationCursor.length === 0)
824
+ )
825
+ return failure(
826
+ "session.list",
827
+ "uncertain",
828
+ "malformed_response",
829
+ "lifecycle broker returned a malformed scoped list result",
830
+ );
831
+ const rows = scopedSearchRows(result.sessions);
832
+ if (!rows)
833
+ return failure(
834
+ "session.list",
835
+ "uncertain",
836
+ "malformed_response",
837
+ "lifecycle broker returned a malformed scoped list result",
838
+ );
839
+ return {
840
+ ok: true,
841
+ operation: "session.list",
842
+ result: {
843
+ version: 1,
844
+ scope: pageScope.scope,
845
+ status:
846
+ pageScope.scope.resolution === "not-in-git-worktree"
847
+ ? "not-in-git-worktree"
848
+ : rows.length === 0
849
+ ? "empty"
850
+ : "populated",
851
+ observedAt: pageScope.observedAt,
852
+ indexSeq: result.indexSeq,
853
+ rows,
854
+ ...(continuationCursor === undefined ? {} : { cursor: continuationCursor }),
855
+ warnings: result.warnings,
856
+ },
857
+ };
858
+ }
859
+
860
+ async execute(
682
861
  request: SessionLifecycleMutationRequest,
683
862
  idempotencyKeyOverride?: string,
684
863
  ): Promise<
@@ -759,19 +938,6 @@ export class SessionLifecycleService {
759
938
  | SessionReconcileUncertainOutcome;
760
939
  }
761
940
 
762
- async execute(
763
- request: SessionLifecycleMutationRequest,
764
- ): Promise<
765
- | SessionCreateOutcome
766
- | SessionForkOutcome
767
- | SessionResumeOutcome
768
- | SessionCloseOutcome
769
- | SessionDeleteOutcome
770
- | SessionReconcileUncertainOutcome
771
- > {
772
- return this.#execute(request);
773
- }
774
-
775
941
  async executeWithIdempotencyKey(
776
942
  request: SessionLifecycleMutationRequest,
777
943
  idempotencyKey: string,
@@ -783,7 +949,7 @@ export class SessionLifecycleService {
783
949
  | SessionDeleteOutcome
784
950
  | SessionReconcileUncertainOutcome
785
951
  > {
786
- return this.#execute(request, idempotencyKey);
952
+ return this.execute(request, idempotencyKey);
787
953
  }
788
954
 
789
955
  async create(request: Omit<SessionCreateRequest, "operation">): Promise<SessionCreateOutcome> {
@@ -816,6 +982,9 @@ export class SessionLifecycleService {
816
982
  }
817
983
 
818
984
  async list(request: Omit<SessionListRequest, "operation">): Promise<SessionListOutcome> {
985
+ return await this.#list(request, false);
986
+ }
987
+ async #list(request: Omit<SessionListRequest, "operation">, singleScopedPage: boolean): Promise<SessionListOutcome> {
819
988
  if (!validActor((request as { actor?: unknown }).actor))
820
989
  return failure("session.list", "terminal", "unauthorized", "authenticated actor is required");
821
990
  if ((request as { capability?: unknown }).capability !== "session.list")
@@ -823,6 +992,24 @@ export class SessionLifecycleService {
823
992
  const target = request.target ?? {};
824
993
  if (!validTarget(target))
825
994
  return failure("session.list", "terminal", "invalid_request", "target must be an object");
995
+ const scopeRequest = target.scope === undefined ? undefined : scopeRequestV1(target.scope);
996
+ if (target.scope !== undefined && !scopeRequest)
997
+ return failure("session.list", "terminal", "invalid_request", "scope must be a valid ScopeRequestV1");
998
+ let locallyResolvedScope: SdkSearchResultV1["scope"] | undefined;
999
+ if (scopeRequest !== undefined) {
1000
+ try {
1001
+ locallyResolvedScope = await resolveScopeRequest(scopeRequest);
1002
+ } catch (cause) {
1003
+ return failure(
1004
+ "session.list",
1005
+ "terminal",
1006
+ "invalid_request",
1007
+ cause instanceof Error ? cause.message : "scope could not be resolved",
1008
+ );
1009
+ }
1010
+ }
1011
+ if (singleScopedPage && locallyResolvedScope !== undefined)
1012
+ return await this.#singleScopedList(request, target, locallyResolvedScope);
826
1013
  let pages: readonly SessionListTraversalPage<unknown, LifecycleListPage>[];
827
1014
  try {
828
1015
  pages = await traverseSessionList<Record<string, unknown>, unknown, LifecycleListPage>(
@@ -838,14 +1025,22 @@ export class SessionLifecycleService {
838
1025
  const page = sessionListPageFromResponse(response);
839
1026
  if (!page) return undefined;
840
1027
  const result = listResult(page);
1028
+ const scoped = scopedPage(page);
841
1029
  return result
842
- ? { result, sessions: result.sessions, continuationCursor: page.continuationCursor }
1030
+ ? {
1031
+ result,
1032
+ sessions: result.sessions,
1033
+ continuationCursor: page.continuationCursor,
1034
+ ...(scoped === undefined ? {} : scoped),
1035
+ }
843
1036
  : undefined;
844
1037
  },
845
1038
  );
846
1039
  } catch (thrown) {
847
1040
  if (thrown instanceof BrokerSessionListResponseError) {
848
1041
  const error = thrown.brokerError;
1042
+ if (error.code === "unavailable" && locallyResolvedScope)
1043
+ return unavailableScopedList(locallyResolvedScope);
849
1044
  return failure("session.list", certaintyForBrokerCode(error.code), error.code, error.message);
850
1045
  }
851
1046
  if (thrown instanceof SessionListTraversalError)
@@ -860,6 +1055,8 @@ export class SessionLifecycleService {
860
1055
  : "session.list exceeded the page budget",
861
1056
  );
862
1057
  const error = brokerError(thrown) ?? brokerErrorFromThrown(thrown);
1058
+ if (locallyResolvedScope && (!error || error.code === "unavailable"))
1059
+ return unavailableScopedList(locallyResolvedScope);
863
1060
  return error
864
1061
  ? failure("session.list", certaintyForThrownError(error), error.code, error.message)
865
1062
  : failure("session.list", "retryable", "unavailable", "lifecycle broker request was unavailable");
@@ -873,6 +1070,68 @@ export class SessionLifecycleService {
873
1070
  "lifecycle broker returned a malformed list result",
874
1071
  );
875
1072
  const { result } = firstPage.page;
1073
+ if (request.target?.scope !== undefined) {
1074
+ const scope = firstPage.page.scope;
1075
+ const observedAt = firstPage.page.observedAt;
1076
+ if (!scope || !observedAt)
1077
+ return failure(
1078
+ "session.list",
1079
+ "uncertain",
1080
+ "malformed_response",
1081
+ "lifecycle broker returned a malformed scoped list result",
1082
+ );
1083
+ const frozenScope = JSON.stringify(scope);
1084
+ const frozenIndexSeq = result.indexSeq;
1085
+ for (const page of pages) {
1086
+ const pageScope = page.page.scope;
1087
+ const pageObservedAt = page.page.observedAt;
1088
+ if (
1089
+ pageScope === undefined ||
1090
+ pageObservedAt !== observedAt ||
1091
+ page.page.result.indexSeq !== frozenIndexSeq ||
1092
+ JSON.stringify(pageScope) !== frozenScope
1093
+ )
1094
+ return failure(
1095
+ "session.list",
1096
+ "uncertain",
1097
+ "scope_observation_drift",
1098
+ "lifecycle broker changed the frozen scope observation across list pages",
1099
+ );
1100
+ }
1101
+ if (locallyResolvedScope && JSON.stringify(locallyResolvedScope) !== frozenScope)
1102
+ return failure(
1103
+ "session.list",
1104
+ "uncertain",
1105
+ "scope_observation_drift",
1106
+ "lifecycle broker scope does not match the locally resolved request scope",
1107
+ );
1108
+ const rows = scopedSearchRows(pages.flatMap(page => page.page.result.sessions));
1109
+ if (!rows)
1110
+ return failure(
1111
+ "session.list",
1112
+ "uncertain",
1113
+ "malformed_response",
1114
+ "lifecycle broker returned a malformed scoped list result",
1115
+ );
1116
+ return {
1117
+ ok: true,
1118
+ operation: "session.list",
1119
+ result: {
1120
+ version: 1,
1121
+ scope,
1122
+ status:
1123
+ scope.resolution === "not-in-git-worktree"
1124
+ ? "not-in-git-worktree"
1125
+ : rows.length === 0
1126
+ ? "empty"
1127
+ : "populated",
1128
+ observedAt,
1129
+ indexSeq: result.indexSeq,
1130
+ rows,
1131
+ warnings: result.warnings,
1132
+ },
1133
+ };
1134
+ }
876
1135
  return {
877
1136
  ok: true,
878
1137
  operation: "session.list",
@@ -55,6 +55,8 @@ export type CreateLifecycleAgentSessionOptions = Omit<CreateAgentSessionOptions,
55
55
  * a prepared signal instead of readiness until it is explicitly activated.
56
56
  */
57
57
  readiness?: "immediate" | "deferred";
58
+ /** Broker-issued lifecycle request identity published with host registration. */
59
+ lifecycleRequestId?: string;
58
60
  };
59
61
 
60
62
  /** Internal lifecycle-only session construction with an owner-bound SDK startup result. */
@@ -62,9 +64,15 @@ export async function createLifecycleAgentSession(
62
64
  options: CreateLifecycleAgentSessionOptions = {},
63
65
  ): Promise<CreateLifecycleAgentSessionResult> {
64
66
  const rollback = new SdkStartupRollbackTracker();
65
- const capability = new SdkStartupCapability(rollback, options.readiness ?? "immediate");
67
+ const capability = new SdkStartupCapability(rollback, options.readiness ?? "immediate", options.lifecycleRequestId);
66
68
  try {
67
- const { modelId, mcpStartupTimeoutMs, readiness: _readiness, ...sessionOptions } = options;
69
+ const {
70
+ modelId,
71
+ mcpStartupTimeoutMs,
72
+ readiness: _readiness,
73
+ lifecycleRequestId: _lifecycleRequestId,
74
+ ...sessionOptions
75
+ } = options;
68
76
  const internalOptions = {
69
77
  ...sessionOptions,
70
78
  // Explicit model pin (#4707): resolve through the staged selector
@@ -1097,6 +1097,24 @@
1097
1097
  "packages/coding-agent/test/sdk-operation-inventory.test.ts"
1098
1098
  ]
1099
1099
  },
1100
+ {
1101
+ "sourceId": "registry:G09",
1102
+ "sourceFile": "packages/coding-agent/src/sdk/protocol/operation-registry.ts",
1103
+ "sourceKind": "registry",
1104
+ "decision": "include",
1105
+ "sdkId": "session.spawn",
1106
+ "adapterMappings": {
1107
+ "telegram": "prohibited",
1108
+ "discord": "prohibited",
1109
+ "slack": "prohibited",
1110
+ "mcp": "prohibited",
1111
+ "acp": "prohibited",
1112
+ "daemonCli": "prohibited"
1113
+ },
1114
+ "testIds": [
1115
+ "packages/coding-agent/test/sdk-operation-inventory.test.ts"
1116
+ ]
1117
+ },
1100
1118
  {
1101
1119
  "sourceId": "registry:Q01",
1102
1120
  "sourceFile": "packages/coding-agent/src/sdk/protocol/operation-registry.ts",
@@ -2845,6 +2863,39 @@
2845
2863
  "testIds": "not_applicable"
2846
2864
  }
2847
2865
  },
2866
+ {
2867
+ "sourceId": "agent_session:queueCoordinatorRuntimeStatePersistForTests",
2868
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2869
+ "sourceKind": "agent_session",
2870
+ "decision": "exclude",
2871
+ "rationale": "test-only coordinator persistence ordering seam, not a user-facing SDK control seam",
2872
+ "exclusionMetadata": {
2873
+ "adapterMappings": "not_applicable",
2874
+ "testIds": "not_applicable"
2875
+ }
2876
+ },
2877
+ {
2878
+ "sourceId": "agent_session:parkAgentEndForCoordinatorPersistForTests",
2879
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2880
+ "sourceKind": "agent_session",
2881
+ "decision": "exclude",
2882
+ "rationale": "test-only parked terminal persistence seam, not a user-facing SDK control seam",
2883
+ "exclusionMetadata": {
2884
+ "adapterMappings": "not_applicable",
2885
+ "testIds": "not_applicable"
2886
+ }
2887
+ },
2888
+ {
2889
+ "sourceId": "agent_session:flushParkedAgentEndForCoordinatorPersistForTests",
2890
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2891
+ "sourceKind": "agent_session",
2892
+ "decision": "exclude",
2893
+ "rationale": "test-only parked terminal flush seam, not a user-facing SDK control seam",
2894
+ "exclusionMetadata": {
2895
+ "adapterMappings": "not_applicable",
2896
+ "testIds": "not_applicable"
2897
+ }
2898
+ },
2848
2899
  {
2849
2900
  "sourceId": "agent_session:drainAsyncJobDeliveriesForAcp",
2850
2901
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -136,6 +136,7 @@ const globals = [
136
136
  ["session.close", "Close a saved session."],
137
137
  ["session.delete", "Delete a saved session."],
138
138
  ["session.reconcile_uncertain", "Retire an indexed terminalUncertain create effect after dead-host proof."],
139
+ ["session.spawn", "Spawn a task-seeded background child session (local interactive master only)."],
139
140
  ] as const;
140
141
 
141
142
  const queries = [
@@ -353,9 +354,18 @@ export const OPERATIONS: readonly Operation[] = [
353
354
  acp: "machine_only",
354
355
  daemonCli: "machine_only",
355
356
  })
356
- : ["G03", "G04", "G05", "G06", "G07", "G08"].includes(id)
357
- ? dispositions({ telegram: "prohibited", discord: "prohibited", slack: "prohibited" })
358
- : dispositions(),
357
+ : id === "G09"
358
+ ? dispositions({
359
+ telegram: "prohibited",
360
+ discord: "prohibited",
361
+ slack: "prohibited",
362
+ mcp: "prohibited",
363
+ acp: "prohibited",
364
+ daemonCli: "prohibited",
365
+ })
366
+ : ["G03", "G04", "G05", "G06", "G07", "G08"].includes(id)
367
+ ? dispositions({ telegram: "prohibited", discord: "prohibited", slack: "prohibited" })
368
+ : dispositions(),
359
369
  testIds: ["packages/coding-agent/test/sdk-operation-inventory.test.ts"],
360
370
  };
361
371
  }),