@gajae-code/coding-agent 0.17.6 → 0.17.7

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 (48) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/types/cli/public-command-errors.d.ts +2 -0
  3. package/dist/types/commands/sdk.d.ts +7 -1
  4. package/dist/types/config/model-profile-ownership.d.ts +74 -0
  5. package/dist/types/config/model-registry.d.ts +2 -1
  6. package/dist/types/config/settings-schema.d.ts +32 -0
  7. package/dist/types/sdk/broker/lifecycle.d.ts +4 -1
  8. package/dist/types/sdk/lifecycle/service.d.ts +2 -0
  9. package/dist/types/sdk/lifecycle-session.d.ts +5 -1
  10. package/dist/types/sdk/session-directory.d.ts +1 -0
  11. package/dist/types/sdk/session-list.d.ts +11 -0
  12. package/dist/types/sdk/startup-capability.d.ts +1 -1
  13. package/dist/types/session/agent-session.d.ts +6 -0
  14. package/dist/types/session/internal/managed-session-scope.d.ts +4 -0
  15. package/dist/types/session/session-manager.d.ts +6 -0
  16. package/dist/types/setup/model-onboarding-guidance.d.ts +18 -0
  17. package/dist/types/task/delegation-hint.d.ts +53 -0
  18. package/examples/README.md +3 -3
  19. package/examples/custom-tools/README.md +1 -1
  20. package/examples/hooks/README.md +1 -1
  21. package/package.json +7 -7
  22. package/scripts/generate-sdk-operation-inventory.ts +6 -0
  23. package/src/cli/config-cli.ts +2 -0
  24. package/src/cli/public-command-errors.ts +6 -0
  25. package/src/commands/sdk.ts +361 -117
  26. package/src/config/model-profile-ownership.ts +303 -0
  27. package/src/config/model-registry.ts +53 -14
  28. package/src/config/settings-schema.ts +81 -0
  29. package/src/internal-urls/docs-index.generated.ts +18 -17
  30. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  31. package/src/modes/controllers/selector-controller.ts +3 -29
  32. package/src/sdk/broker/broker.ts +120 -23
  33. package/src/sdk/broker/lifecycle.ts +89 -13
  34. package/src/sdk/cli/session-cli.ts +45 -9
  35. package/src/sdk/host/control/dispatch.ts +6 -0
  36. package/src/sdk/lifecycle/service.ts +186 -3
  37. package/src/sdk/lifecycle-session.ts +35 -8
  38. package/src/sdk/protocol/operation-inventory.generated.json +33 -0
  39. package/src/sdk/protocol/operation-registry.ts +5 -1
  40. package/src/sdk/session-directory.ts +1 -1
  41. package/src/sdk/session-list.ts +47 -0
  42. package/src/sdk/session.ts +290 -114
  43. package/src/sdk/startup-capability.ts +2 -1
  44. package/src/session/agent-session.ts +102 -4
  45. package/src/session/internal/managed-session-scope.ts +81 -17
  46. package/src/session/session-manager.ts +70 -11
  47. package/src/setup/model-onboarding-guidance.ts +26 -0
  48. package/src/task/delegation-hint.ts +578 -0
@@ -11,6 +11,9 @@ import {
11
11
  } from "../broker/session-scope";
12
12
  import type { SessionBindingAuthority } from "../session-authority";
13
13
  import {
14
+ SESSION_LIST_SAVED_SESSION_OMISSION_DETAIL_CODES,
15
+ type SessionListSavedSessionOmission,
16
+ type SessionListSavedSessionOmissionDetailCode,
14
17
  SessionListTraversalError,
15
18
  type SessionListTraversalPage,
16
19
  sessionListPageFromResponse,
@@ -238,6 +241,7 @@ export interface SessionLifecycleListResult {
238
241
  readonly sessions: readonly SessionLifecycleListEntry[];
239
242
  readonly warnings: readonly string[];
240
243
  readonly savedSession?: SessionLifecycleSavedSession;
244
+ readonly savedSessionOmission?: SessionListSavedSessionOmission;
241
245
  }
242
246
 
243
247
  export type SessionScopedListResult = SdkSearchResultV1;
@@ -652,11 +656,118 @@ function savedSessionTranscriptIdentity(value: unknown): SessionLifecycleSavedSe
652
656
  function savedSessionFromResult(value: unknown): SessionLifecycleSavedSession | undefined {
653
657
  if (!isRecord(value)) return undefined;
654
658
  const identity = savedSessionTranscriptIdentity(value.identity);
655
- if (typeof value.id !== "string" || typeof value.path !== "string" || value.path.length === 0 || !identity)
659
+ if (
660
+ typeof value.id !== "string" ||
661
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.id) ||
662
+ typeof value.path !== "string" ||
663
+ value.path.length === 0 ||
664
+ !identity
665
+ )
656
666
  return undefined;
657
667
  return { id: value.id, path: value.path, identity };
658
668
  }
659
669
 
670
+ const savedSessionOmissionFields = new Set([
671
+ "sessionId",
672
+ "reason",
673
+ "detailCode",
674
+ "candidateCount",
675
+ "missingIdentityFields",
676
+ ]);
677
+ const savedSessionOmissionDetailCodes = new Set<string>(SESSION_LIST_SAVED_SESSION_OMISSION_DETAIL_CODES);
678
+ const savedSessionScopeDetailCodes = new Set([
679
+ "cwd_missing",
680
+ "cwd_not_directory",
681
+ "identity_unavailable",
682
+ "network_unsupported",
683
+ "sessions_root_unavailable",
684
+ "binding_conflict",
685
+ "binding_invalid",
686
+ "migration_busy",
687
+ "atomic_unavailable",
688
+ "invalid_request",
689
+ "durability_failed",
690
+ "durability_not_provable",
691
+ "capacity_exceeded",
692
+ ]);
693
+ const savedSessionScanDetailCodes = new Set(["scan_failed", "unsafe_root", "invalid_candidate"]);
694
+ const savedSessionCandidateDetailCodes = new Set([
695
+ "invalid_header",
696
+ "unreadable_candidate",
697
+ "source_changed",
698
+ "cwd_not_found",
699
+ "cwd_not_directory",
700
+ "cwd_not_utf8",
701
+ "cwd_network_unsupported",
702
+ "cwd_identity_unavailable",
703
+ "cwd_io_error",
704
+ ]);
705
+
706
+ function isSafeSavedSessionOmissionDetailCode(value: unknown): value is SessionListSavedSessionOmissionDetailCode {
707
+ return typeof value === "string" && savedSessionOmissionDetailCodes.has(value);
708
+ }
709
+
710
+ function savedSessionOmissionDetailMatchesReason(
711
+ reason: SessionListSavedSessionOmission["reason"],
712
+ detailCode: SessionListSavedSessionOmissionDetailCode,
713
+ ): boolean {
714
+ if (reason === "scope_unavailable") return savedSessionScopeDetailCodes.has(detailCode);
715
+ if (reason === "candidate_scan_failed") return savedSessionScanDetailCodes.has(detailCode);
716
+ if (reason === "candidate_invalid") return savedSessionCandidateDetailCodes.has(detailCode);
717
+ return false;
718
+ }
719
+
720
+ function savedSessionOmissionFromResult(value: unknown): SessionListSavedSessionOmission | undefined {
721
+ if (!isRecord(value)) return undefined;
722
+ if (Object.keys(value).some(field => !savedSessionOmissionFields.has(field))) return undefined;
723
+ if (
724
+ typeof value.sessionId !== "string" ||
725
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(value.sessionId) ||
726
+ (value.reason !== "scope_unavailable" &&
727
+ value.reason !== "candidate_scan_failed" &&
728
+ value.reason !== "candidate_not_found" &&
729
+ value.reason !== "candidate_ambiguous" &&
730
+ value.reason !== "candidate_invalid" &&
731
+ value.reason !== "identity_incomplete")
732
+ )
733
+ return undefined;
734
+ if (
735
+ value.detailCode !== undefined &&
736
+ (!isSafeSavedSessionOmissionDetailCode(value.detailCode) ||
737
+ !savedSessionOmissionDetailMatchesReason(value.reason, value.detailCode))
738
+ )
739
+ return undefined;
740
+ if (
741
+ value.candidateCount !== undefined &&
742
+ (value.reason !== "candidate_ambiguous" ||
743
+ typeof value.candidateCount !== "number" ||
744
+ !Number.isSafeInteger(value.candidateCount) ||
745
+ value.candidateCount < 2)
746
+ )
747
+ return undefined;
748
+ let missingIdentityFields: ("nlink" | "ctimeNs")[] | undefined;
749
+ if (value.missingIdentityFields !== undefined) {
750
+ if (
751
+ value.reason !== "identity_incomplete" ||
752
+ !Array.isArray(value.missingIdentityFields) ||
753
+ value.missingIdentityFields.length === 0 ||
754
+ value.missingIdentityFields.some(field => field !== "nlink" && field !== "ctimeNs") ||
755
+ new Set(value.missingIdentityFields).size !== value.missingIdentityFields.length
756
+ )
757
+ return undefined;
758
+ missingIdentityFields = value.missingIdentityFields as ("nlink" | "ctimeNs")[];
759
+ }
760
+ if (value.reason === "candidate_ambiguous" && value.candidateCount === undefined) return undefined;
761
+ if (value.reason === "identity_incomplete" && missingIdentityFields === undefined) return undefined;
762
+ return {
763
+ sessionId: value.sessionId,
764
+ reason: value.reason,
765
+ ...(value.detailCode === undefined ? {} : { detailCode: value.detailCode }),
766
+ ...(value.candidateCount === undefined ? {} : { candidateCount: value.candidateCount }),
767
+ ...(missingIdentityFields === undefined ? {} : { missingIdentityFields }),
768
+ };
769
+ }
770
+
660
771
  function listResult(value: unknown): SessionLifecycleListResult | undefined {
661
772
  if (!isRecord(value) || !Array.isArray(value.sessions)) return undefined;
662
773
  const indexSeq = value.indexSeq;
@@ -702,8 +813,19 @@ function listResult(value: unknown): SessionLifecycleListResult | undefined {
702
813
  }
703
814
  const savedSessionPresent = Object.hasOwn(value, "savedSession");
704
815
  const savedSession = savedSessionPresent ? savedSessionFromResult(value.savedSession) : undefined;
705
- if (savedSessionPresent && !savedSession) return undefined;
706
- return { indexSeq, sessions, warnings, ...(savedSession ? { savedSession } : {}) };
816
+ const savedSessionOmissionPresent = Object.hasOwn(value, "savedSessionOmission");
817
+ const savedSessionOmission = savedSessionOmissionPresent
818
+ ? savedSessionOmissionFromResult(value.savedSessionOmission)
819
+ : undefined;
820
+ if (savedSessionPresent && (!savedSession || savedSessionOmissionPresent)) return undefined;
821
+ if (savedSessionOmissionPresent && !savedSessionOmission) return undefined;
822
+ return {
823
+ indexSeq,
824
+ sessions,
825
+ warnings,
826
+ ...(savedSession ? { savedSession } : {}),
827
+ ...(savedSessionOmission ? { savedSessionOmission } : {}),
828
+ };
707
829
  }
708
830
 
709
831
  function scopedPage(value: unknown): { scope: SdkSearchResultV1["scope"]; observedAt: string } | undefined {
@@ -993,6 +1115,13 @@ export class SessionLifecycleService {
993
1115
  "malformed_response",
994
1116
  "lifecycle broker returned a malformed scoped list result",
995
1117
  );
1118
+ if (result.savedSession !== undefined || result.savedSessionOmission !== undefined)
1119
+ return failure(
1120
+ "session.list",
1121
+ "uncertain",
1122
+ "malformed_response",
1123
+ "lifecycle broker attached an exact saved session to a scoped list result",
1124
+ );
996
1125
  if (JSON.stringify(pageScope.scope) !== JSON.stringify(locallyResolvedScope))
997
1126
  return failure(
998
1127
  "session.list",
@@ -1178,6 +1307,35 @@ export class SessionLifecycleService {
1178
1307
  const scopeRequest = target.scope === undefined ? undefined : scopeRequestV1(target.scope);
1179
1308
  if (target.scope !== undefined && !scopeRequest)
1180
1309
  return failure("session.list", "terminal", "invalid_request", "scope must be a valid ScopeRequestV1");
1310
+ if (target.cwd !== undefined && (typeof target.cwd !== "string" || target.cwd.length === 0))
1311
+ return failure("session.list", "terminal", "invalid_request", "cwd must be a non-empty string");
1312
+ if (target.cursor !== undefined && (typeof target.cursor !== "string" || target.cursor.length === 0))
1313
+ return failure("session.list", "terminal", "invalid_request", "cursor must be a non-empty opaque string");
1314
+ if (
1315
+ target.resolveSessionId !== undefined &&
1316
+ (typeof target.resolveSessionId !== "string" ||
1317
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(target.resolveSessionId))
1318
+ )
1319
+ return failure(
1320
+ "session.list",
1321
+ "terminal",
1322
+ "invalid_request",
1323
+ "resolveSessionId must be a canonical safe identifier",
1324
+ );
1325
+ if (scopeRequest !== undefined && (target.cwd !== undefined || target.resolveSessionId !== undefined))
1326
+ return failure(
1327
+ "session.list",
1328
+ "terminal",
1329
+ "invalid_request",
1330
+ "cwd and resolveSessionId cannot be combined with a scoped session.list target",
1331
+ );
1332
+ if (target.cursor !== undefined && target.resolveSessionId !== undefined)
1333
+ return failure(
1334
+ "session.list",
1335
+ "terminal",
1336
+ "invalid_request",
1337
+ "cursor cannot be combined with resolveSessionId",
1338
+ );
1181
1339
  let locallyResolvedScope: SdkSearchResultV1["scope"] | undefined;
1182
1340
  if (scopeRequest !== undefined) {
1183
1341
  try {
@@ -1253,6 +1411,30 @@ export class SessionLifecycleService {
1253
1411
  "lifecycle broker returned a malformed list result",
1254
1412
  );
1255
1413
  const { result } = firstPage.page;
1414
+ const requestedSessionId = typeof target.resolveSessionId === "string" ? target.resolveSessionId : undefined;
1415
+ const exactSavedSessionTarget = requestedSessionId !== undefined && typeof target.cwd === "string";
1416
+ if (requestedSessionId !== undefined && pages.length > 1)
1417
+ return failure(
1418
+ "session.list",
1419
+ "uncertain",
1420
+ "malformed_response",
1421
+ "lifecycle broker paginated an exact session.list result",
1422
+ );
1423
+ for (const page of pages) {
1424
+ const { savedSession, savedSessionOmission } = page.page.result;
1425
+ if (savedSession === undefined && savedSessionOmission === undefined) continue;
1426
+ if (
1427
+ !exactSavedSessionTarget ||
1428
+ (savedSession !== undefined && savedSession.id !== requestedSessionId) ||
1429
+ (savedSessionOmission !== undefined && savedSessionOmission.sessionId !== requestedSessionId)
1430
+ )
1431
+ return failure(
1432
+ "session.list",
1433
+ "uncertain",
1434
+ "malformed_response",
1435
+ "lifecycle broker returned a saved session that does not match the requested target",
1436
+ );
1437
+ }
1256
1438
  if (request.target?.scope !== undefined) {
1257
1439
  const scope = firstPage.page.scope;
1258
1440
  const observedAt = firstPage.page.observedAt;
@@ -1323,6 +1505,7 @@ export class SessionLifecycleService {
1323
1505
  sessions: pages.flatMap(page => page.page.result.sessions),
1324
1506
  warnings: result.warnings,
1325
1507
  ...(result.savedSession === undefined ? {} : { savedSession: result.savedSession }),
1508
+ ...(result.savedSessionOmission === undefined ? {} : { savedSessionOmission: result.savedSessionOmission }),
1326
1509
  },
1327
1510
  };
1328
1511
  }
@@ -59,13 +59,37 @@ export type CreateLifecycleAgentSessionOptions = Omit<CreateAgentSessionOptions,
59
59
  lifecycleRequestId?: string;
60
60
  };
61
61
 
62
+ export interface SdkLifecycleStartupOwner {
63
+ readonly capability: SdkStartupCapability;
64
+ readonly rollback: SdkStartupRollbackTracker;
65
+ }
66
+
67
+ async function disposePartialSession(session: AgentSession): Promise<void> {
68
+ try {
69
+ await session.dispose();
70
+ } catch (error) {
71
+ if (!isSessionDisposalIncompleteError(error)) throw error;
72
+ await session.awaitDisposeCompletion();
73
+ }
74
+ }
75
+
62
76
  /** Internal lifecycle-only session construction with an owner-bound SDK startup result. */
63
77
  export async function createLifecycleAgentSession(
64
78
  options: CreateLifecycleAgentSessionOptions = {},
79
+ owner?: SdkLifecycleStartupOwner,
65
80
  ): Promise<CreateLifecycleAgentSessionResult> {
66
- const rollback = new SdkStartupRollbackTracker();
67
- const capability = new SdkStartupCapability(rollback, options.readiness ?? "immediate", options.lifecycleRequestId);
81
+ const rollback = owner?.rollback ?? new SdkStartupRollbackTracker();
82
+ const capability =
83
+ owner?.capability ??
84
+ new SdkStartupCapability(rollback, options.readiness ?? "immediate", options.lifecycleRequestId);
68
85
  try {
86
+ if (capability.cancelled) {
87
+ const failure =
88
+ capability.result?.status === "failed"
89
+ ? capability.result.failure
90
+ : capability.normalizeFailure("startup", "pending");
91
+ return { capability, rollback, failure };
92
+ }
69
93
  const {
70
94
  modelId,
71
95
  mcpStartupTimeoutMs,
@@ -91,6 +115,14 @@ export async function createLifecycleAgentSession(
91
115
  [lifecycleMcpStartupTimeoutOption]?: number;
92
116
  };
93
117
  const result = await createAgentSession(internalOptions);
118
+ if (capability.cancelled) {
119
+ await disposePartialSession(result.session);
120
+ const failure =
121
+ capability.result?.status === "failed"
122
+ ? capability.result.failure
123
+ : capability.normalizeFailure("startup", "pending");
124
+ return { capability, rollback, failure };
125
+ }
94
126
  // Explicit model pin (#4707) is a guarantee, not a preference. The
95
127
  // coordinator validated the selector against its own registry; this child
96
128
  // owns the registry that actually serves requests, and the two can drift
@@ -106,12 +138,7 @@ export async function createLifecycleAgentSession(
106
138
  const expected = parseModelString(modelId);
107
139
  const expectedSelector = expected ? `${expected.provider}/${expected.id}` : modelId;
108
140
  if (!activeSelector || activeSelector.toLowerCase() !== expectedSelector.toLowerCase()) {
109
- try {
110
- await result.session.dispose();
111
- } catch (error) {
112
- if (!isSessionDisposalIncompleteError(error)) throw error;
113
- await result.session.awaitDisposeCompletion();
114
- }
141
+ await disposePartialSession(result.session);
115
142
  throw new Error(
116
143
  `Model "${modelId}" not found. Use --list-models to see available models.${
117
144
  activeSelector ? ` Session resolved ${activeSelector} instead.` : ""
@@ -2910,6 +2910,28 @@
2910
2910
  "testIds": "not_applicable"
2911
2911
  }
2912
2912
  },
2913
+ {
2914
+ "sourceId": "agent_session:setCoordinatorRuntimeStateFileForTests",
2915
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2916
+ "sourceKind": "agent_session",
2917
+ "decision": "exclude",
2918
+ "rationale": "test-only coordinator runtime state file setup seam, not a user-facing SDK control seam",
2919
+ "exclusionMetadata": {
2920
+ "adapterMappings": "not_applicable",
2921
+ "testIds": "not_applicable"
2922
+ }
2923
+ },
2924
+ {
2925
+ "sourceId": "agent_session:getCoordinatorRuntimeStateFileForTests",
2926
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2927
+ "sourceKind": "agent_session",
2928
+ "decision": "exclude",
2929
+ "rationale": "read-only test-only coordinator runtime state file accessor, not a user-facing SDK control seam",
2930
+ "exclusionMetadata": {
2931
+ "adapterMappings": "not_applicable",
2932
+ "testIds": "not_applicable"
2933
+ }
2934
+ },
2913
2935
  {
2914
2936
  "sourceId": "agent_session:trackPostPromptTaskForTests",
2915
2937
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -2987,6 +3009,17 @@
2987
3009
  "testIds": "not_applicable"
2988
3010
  }
2989
3011
  },
3012
+ {
3013
+ "sourceId": "agent_session:trackCoordinatorRuntimeStatePersistenceFailuresForTests",
3014
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
3015
+ "sourceKind": "agent_session",
3016
+ "decision": "exclude",
3017
+ "rationale": "test-only coordinator persistence failure observation seam, not a user-facing SDK control seam",
3018
+ "exclusionMetadata": {
3019
+ "adapterMappings": "not_applicable",
3020
+ "testIds": "not_applicable"
3021
+ }
3022
+ },
2990
3023
  {
2991
3024
  "sourceId": "agent_session:awaitCoordinatorRuntimeStatePersistenceForTests",
2992
3025
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -263,11 +263,15 @@ function controlDisposition(id: string): Record<Adapter, AdapterDisposition> {
263
263
 
264
264
  function controlErrors(id: string): string[] {
265
265
  const errors: Record<string, string[]> = {
266
- C01: ["client_ref_conflict", "reconciliation_capacity", "reconciliation_persist_failed"],
266
+ // C01/C05 submit a prompt and therefore run the prompt preflight, which
267
+ // refuses a session with no selected model before admission. The code is
268
+ // appended: ACP machine-only mapping reads errorCodes[0].
269
+ C01: ["client_ref_conflict", "reconciliation_capacity", "reconciliation_persist_failed", "model_not_selected"],
267
270
  C02: ["client_ref_conflict", "reconciliation_capacity", "reconciliation_persist_failed"],
268
271
  C09: ["client_ref_conflict", "reconciliation_capacity", "reconciliation_persist_failed"],
269
272
  C06: ["action_claimed"],
270
273
  C07: ["action_claimed", "terminal_uncertain"],
274
+ C05: ["invalid_request", "busy", "model_not_selected"],
271
275
  C08: ["action_claimed", "terminal_uncertain"],
272
276
  C13: ["invalid_request", "busy", "default_model_selection_recovery"],
273
277
  C36: ["revision_conflict", "secret_input_forbidden"],
@@ -51,7 +51,7 @@ export type ListManagedSessionCandidatesResult =
51
51
  scope: ManagedSessionScope;
52
52
  owned: readonly LogicalSessionCandidate[];
53
53
  foreignCount: number;
54
- invalid: readonly { code: string }[];
54
+ invalid: readonly { code: string; sessionId?: string }[];
55
55
  }
56
56
  | { kind: "error"; code: "scan_failed" | "unsafe_root" | "invalid_candidate"; message: string };
57
57
 
@@ -1,5 +1,52 @@
1
1
  export type SessionListPageRecord = Record<string, unknown>;
2
2
 
3
+ export type SessionListSavedSessionOmissionReason =
4
+ | "scope_unavailable"
5
+ | "candidate_scan_failed"
6
+ | "candidate_not_found"
7
+ | "candidate_ambiguous"
8
+ | "candidate_invalid"
9
+ | "identity_incomplete";
10
+
11
+ export const SESSION_LIST_SAVED_SESSION_OMISSION_DETAIL_CODES = [
12
+ "cwd_missing",
13
+ "cwd_not_directory",
14
+ "identity_unavailable",
15
+ "network_unsupported",
16
+ "sessions_root_unavailable",
17
+ "binding_conflict",
18
+ "binding_invalid",
19
+ "migration_busy",
20
+ "atomic_unavailable",
21
+ "invalid_request",
22
+ "durability_failed",
23
+ "durability_not_provable",
24
+ "capacity_exceeded",
25
+ "scan_failed",
26
+ "unsafe_root",
27
+ "invalid_candidate",
28
+ "invalid_header",
29
+ "unreadable_candidate",
30
+ "source_changed",
31
+ "cwd_not_found",
32
+ "cwd_not_utf8",
33
+ "cwd_network_unsupported",
34
+ "cwd_identity_unavailable",
35
+ "cwd_io_error",
36
+ ] as const;
37
+
38
+ export type SessionListSavedSessionOmissionDetailCode =
39
+ (typeof SESSION_LIST_SAVED_SESSION_OMISSION_DETAIL_CODES)[number];
40
+
41
+ /** Path-free diagnostic for a requested managed transcript that could not be selected safely. */
42
+ export interface SessionListSavedSessionOmission {
43
+ readonly sessionId: string;
44
+ readonly reason: SessionListSavedSessionOmissionReason;
45
+ readonly detailCode?: SessionListSavedSessionOmissionDetailCode;
46
+ readonly candidateCount?: number;
47
+ readonly missingIdentityFields?: readonly ("nlink" | "ctimeNs")[];
48
+ }
49
+
3
50
  export type SessionListTraversalErrorKind = "malformed_page" | "repeated_cursor" | "page_budget_exceeded";
4
51
 
5
52
  export class SessionListTraversalError extends Error {