@gajae-code/coding-agent 0.15.2 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/dist/types/config/autorouting-tier-map.d.ts +0 -9
  3. package/dist/types/config/file-lock.d.ts +7 -1
  4. package/dist/types/config/keybindings.d.ts +13 -3
  5. package/dist/types/config/model-registry.d.ts +5 -0
  6. package/dist/types/config/model-resolver.d.ts +1 -0
  7. package/dist/types/config/settings-schema.d.ts +65 -1
  8. package/dist/types/coordinator/contract.d.ts +1 -1
  9. package/dist/types/coordinator-mcp/question-state.d.ts +42 -2
  10. package/dist/types/extensibility/extensions/types.d.ts +2 -0
  11. package/dist/types/gjc-runtime/deep-interview-state.d.ts +12 -0
  12. package/dist/types/gjc-runtime/workflow-placeholder.d.ts +6 -0
  13. package/dist/types/main.d.ts +1 -1
  14. package/dist/types/modes/action-registry.d.ts +1 -0
  15. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  16. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +1 -1
  17. package/dist/types/modes/components/welcome.d.ts +2 -0
  18. package/dist/types/modes/controllers/input-controller.d.ts +16 -1
  19. package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +42 -0
  20. package/dist/types/modes/interactive-mode.d.ts +7 -0
  21. package/dist/types/modes/shared/oauth-url-copy.d.ts +9 -0
  22. package/dist/types/modes/types.d.ts +3 -0
  23. package/dist/types/modes/ui-language.d.ts +2 -1
  24. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  25. package/dist/types/sdk/broker/session-index.d.ts +18 -0
  26. package/dist/types/sdk/bus/index.d.ts +4 -1
  27. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  28. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  29. package/dist/types/sdk/cli/session-cli.d.ts +1 -1
  30. package/dist/types/sdk/lifecycle/service.d.ts +32 -5
  31. package/dist/types/sdk/session.d.ts +5 -0
  32. package/dist/types/session/agent-session.d.ts +4 -0
  33. package/dist/types/session/fallback-chain-controller.d.ts +8 -0
  34. package/dist/types/slash-commands/helpers/aside.d.ts +54 -0
  35. package/dist/types/tools/ask.d.ts +9 -0
  36. package/dist/types/utils/clipboard.d.ts +9 -1
  37. package/package.json +7 -7
  38. package/scripts/generate-sdk-adapter-parity-manifest.ts +5 -2
  39. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  40. package/src/commands/sdk.ts +2 -2
  41. package/src/config/autorouting-tier-map.ts +0 -9
  42. package/src/config/file-lock.ts +15 -1
  43. package/src/config/keybindings.ts +13 -3
  44. package/src/config/model-registry.ts +132 -21
  45. package/src/config/model-resolver.ts +2 -1
  46. package/src/config/models-config-schema.ts +2 -2
  47. package/src/config/settings-schema.ts +66 -1
  48. package/src/config/settings.ts +1 -1
  49. package/src/coordinator/contract.ts +1 -0
  50. package/src/coordinator-mcp/question-state.ts +235 -4
  51. package/src/coordinator-mcp/server.ts +402 -2
  52. package/src/dap/client.ts +19 -8
  53. package/src/deep-interview/render-middleware.ts +6 -2
  54. package/src/extensibility/extensions/types.ts +2 -0
  55. package/src/gjc-runtime/deep-interview-stage.ts +20 -1
  56. package/src/gjc-runtime/deep-interview-state.ts +3 -1
  57. package/src/gjc-runtime/ralplan-runtime.ts +6 -1
  58. package/src/gjc-runtime/session-state-lock.ts +11 -2
  59. package/src/gjc-runtime/ultragoal-runtime.ts +5 -0
  60. package/src/gjc-runtime/workflow-placeholder.ts +33 -0
  61. package/src/internal-urls/docs-index.generated.ts +11 -9
  62. package/src/lsp/client.ts +39 -4
  63. package/src/main.ts +42 -11
  64. package/src/modes/action-registry.ts +7 -0
  65. package/src/modes/components/hook-selector.ts +2 -1
  66. package/src/modes/components/iterm-pet-transport.ts +4 -2
  67. package/src/modes/components/runtime-mcp-add-wizard.ts +38 -1
  68. package/src/modes/components/settings-selector.ts +1 -0
  69. package/src/modes/components/tool-status-header.ts +149 -41
  70. package/src/modes/components/welcome.ts +8 -0
  71. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  72. package/src/modes/controllers/goal-mode-controller.ts +7 -2
  73. package/src/modes/controllers/input-controller.ts +141 -7
  74. package/src/modes/controllers/plan-mode-controller.ts +12 -4
  75. package/src/modes/controllers/runtime-mcp-command-controller.ts +164 -20
  76. package/src/modes/controllers/selector-controller.ts +11 -2
  77. package/src/modes/interactive-mode.ts +88 -1
  78. package/src/modes/shared/oauth-url-copy.ts +33 -0
  79. package/src/modes/types.ts +3 -0
  80. package/src/modes/ui-language.ts +44 -3
  81. package/src/runtime-mcp/oauth-flow.ts +19 -8
  82. package/src/runtime-mcp/transports/stdio.ts +27 -13
  83. package/src/sdk/acp/adapter.ts +29 -2
  84. package/src/sdk/broker/broker.ts +26 -6
  85. package/src/sdk/broker/lifecycle-ledger.ts +28 -1
  86. package/src/sdk/broker/lifecycle.ts +664 -5
  87. package/src/sdk/broker/session-index.ts +246 -0
  88. package/src/sdk/broker/transport.ts +1 -0
  89. package/src/sdk/bus/index.ts +135 -38
  90. package/src/sdk/bus/natives-augment.d.ts +20 -1
  91. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  92. package/src/sdk/bus/telegram-daemon.ts +3 -0
  93. package/src/sdk/cli/session-cli.ts +34 -7
  94. package/src/sdk/lifecycle/service.ts +166 -11
  95. package/src/sdk/mcp/server.ts +2 -1
  96. package/src/sdk/protocol/operation-inventory.generated.json +40 -0
  97. package/src/sdk/protocol/operation-registry.ts +2 -1
  98. package/src/sdk/session.ts +91 -7
  99. package/src/session/agent-session.ts +473 -66
  100. package/src/session/fallback-chain-controller.ts +17 -0
  101. package/src/setup/frictionless-onboarding.ts +4 -4
  102. package/src/slash-commands/aside-command.test.ts +366 -0
  103. package/src/slash-commands/builtin-registry.ts +19 -0
  104. package/src/slash-commands/helpers/aside.ts +299 -0
  105. package/src/tools/ask-contract.ts +54 -3
  106. package/src/tools/ask.ts +54 -6
  107. package/src/utils/clipboard.ts +37 -28
@@ -192,9 +192,11 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
192
192
  }
193
193
 
194
194
  async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
195
+ this.ctrl.signal?.throwIfAborted();
195
196
  if (!this.#resolvedClientId) {
196
- await this.#tryRegisterClient(redirectUri);
197
+ await this.#tryRegisterClient(redirectUri, this.ctrl.signal);
197
198
  }
199
+ this.ctrl.signal?.throwIfAborted();
198
200
 
199
201
  const authUrl = new URL(this.config.authorizationUrl);
200
202
  const params = authUrl.searchParams;
@@ -226,13 +228,14 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
226
228
  this.#codeVerifier = codeVerifier;
227
229
 
228
230
  if (!params.get("client_id")) {
229
- await this.#assertClientIdNotRequired(authUrl.toString());
231
+ await this.#assertClientIdNotRequired(authUrl.toString(), this.ctrl.signal);
230
232
  }
231
233
 
232
234
  return { url: authUrl.toString() };
233
235
  }
234
236
 
235
237
  async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
238
+ this.ctrl.signal?.throwIfAborted();
236
239
  const params = new URLSearchParams({
237
240
  grant_type: "authorization_code",
238
241
  code,
@@ -264,7 +267,9 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
264
267
  "Content-Type": "application/x-www-form-urlencoded",
265
268
  },
266
269
  body: params.toString(),
270
+ signal: this.ctrl.signal,
267
271
  });
272
+ this.ctrl.signal?.throwIfAborted();
268
273
 
269
274
  if (!response.ok) {
270
275
  const errorText = await response.text();
@@ -330,8 +335,8 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
330
335
  /**
331
336
  * Try OAuth dynamic client registration when provider requires a client_id.
332
337
  */
333
- async #tryRegisterClient(redirectUri: string): Promise<void> {
334
- const registrationEndpoint = await this.#resolveRegistrationEndpoint();
338
+ async #tryRegisterClient(redirectUri: string, signal?: AbortSignal): Promise<void> {
339
+ const registrationEndpoint = await this.#resolveRegistrationEndpoint(signal);
335
340
  if (!registrationEndpoint) return;
336
341
 
337
342
  try {
@@ -349,6 +354,7 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
349
354
  token_endpoint_auth_method: "none",
350
355
  application_type: "native",
351
356
  }),
357
+ signal,
352
358
  });
353
359
 
354
360
  if (!response.ok) return;
@@ -364,18 +370,20 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
364
370
  if (data.client_secret && data.client_secret.trim() !== "") {
365
371
  this.#registeredClientSecret = data.client_secret;
366
372
  }
367
- } catch {
373
+ } catch (error) {
374
+ if (signal?.aborted) throw error;
368
375
  // Ignore registration failures and continue without client registration.
369
376
  }
370
377
  }
371
378
 
372
- async #resolveRegistrationEndpoint(): Promise<string | null> {
379
+ async #resolveRegistrationEndpoint(signal?: AbortSignal): Promise<string | null> {
373
380
  try {
374
381
  const authorizationEndpoint = new URL(this.config.authorizationUrl);
375
382
  const metadataUrl = new URL("/.well-known/oauth-authorization-server", authorizationEndpoint.origin);
376
383
  const response = await fetch(metadataUrl.toString(), {
377
384
  method: "GET",
378
385
  headers: { Accept: "application/json" },
386
+ signal,
379
387
  });
380
388
 
381
389
  if (!response.ok) return null;
@@ -383,19 +391,21 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
383
391
  if (metadata.registration_endpoint && metadata.registration_endpoint.trim() !== "") {
384
392
  return metadata.registration_endpoint;
385
393
  }
386
- } catch {
394
+ } catch (error) {
395
+ if (signal?.aborted) throw error;
387
396
  // Ignore metadata discovery failures.
388
397
  }
389
398
 
390
399
  return null;
391
400
  }
392
401
 
393
- async #assertClientIdNotRequired(authorizationUrl: string): Promise<void> {
402
+ async #assertClientIdNotRequired(authorizationUrl: string, signal?: AbortSignal): Promise<void> {
394
403
  try {
395
404
  const response = await fetch(authorizationUrl, {
396
405
  method: "GET",
397
406
  redirect: "manual",
398
407
  headers: { Accept: "text/plain,text/html,application/json" },
408
+ signal,
399
409
  });
400
410
  if (response.status < 400) return;
401
411
  const body = await response.text();
@@ -403,6 +413,7 @@ export class MCPOAuthFlow extends OAuthCallbackFlow {
403
413
  throw new Error("OAuth provider requires client_id");
404
414
  }
405
415
  } catch (error) {
416
+ if (signal?.aborted) throw error;
406
417
  if (error instanceof Error && /client[_-]?id/i.test(error.message)) {
407
418
  throw error;
408
419
  }
@@ -239,8 +239,15 @@ export class StdioTransport implements MCPTransport {
239
239
  const response = error
240
240
  ? { jsonrpc: "2.0" as const, id, error }
241
241
  : { jsonrpc: "2.0" as const, id, result: result ?? {} };
242
- stdin.write(`${JSON.stringify(response)}\n`);
243
- stdin.flush();
242
+ // FileSink.write()/flush() return `number | Promise<number>` (flush may
243
+ // also return undefined): under backpressure a write returns a promise
244
+ // whose rejection — e.g. EPIPE when the server dies mid-write — must
245
+ // not escape as an unhandled rejection. Best-effort, like the caller.
246
+ Promise.resolve(stdin.write(`${JSON.stringify(response)}\n`))
247
+ .then(() => stdin.flush())
248
+ .catch(() => {
249
+ // Best-effort — process may have exited
250
+ });
244
251
  }
245
252
 
246
253
  #handleClose(failure?: MCPExpectedFailure): void {
@@ -276,6 +283,7 @@ export class StdioTransport implements MCPTransport {
276
283
  }
277
284
 
278
285
  const { promise, resolve, reject } = Promise.withResolvers<T>();
286
+ void promise.catch(() => {});
279
287
  let timer: NodeJS.Timeout | undefined;
280
288
  let settled = false;
281
289
 
@@ -319,14 +327,18 @@ export class StdioTransport implements MCPTransport {
319
327
  }, timeout);
320
328
 
321
329
  const message = `${JSON.stringify(request)}\n`;
322
- try {
323
- // Bun's FileSink has write() method directly
324
- stdin.write(message);
325
- stdin.flush();
326
- } catch (error: unknown) {
327
- cleanup();
328
- reject(new MCPExpectedFailure(error));
329
- }
330
+ void (async () => {
331
+ try {
332
+ // Bun's FileSink has write() method directly. Await both write and
333
+ // flush: under backpressure write() returns a promise, and either
334
+ // call can fail with EPIPE when the server dies mid-write.
335
+ await stdin.write(message);
336
+ await stdin.flush();
337
+ } catch (error: unknown) {
338
+ cleanup();
339
+ reject(new MCPExpectedFailure(error));
340
+ }
341
+ })();
330
342
 
331
343
  return promise;
332
344
  }
@@ -345,9 +357,11 @@ export class StdioTransport implements MCPTransport {
345
357
 
346
358
  const message = `${JSON.stringify(notification)}\n`;
347
359
  try {
348
- // Bun's FileSink has write() method directly
349
- stdin.write(message);
350
- stdin.flush();
360
+ // Bun's FileSink has write() method directly. Await both write and
361
+ // flush: under backpressure write() returns a promise, and either
362
+ // call can fail with EPIPE when the server dies mid-write.
363
+ await stdin.write(message);
364
+ await stdin.flush();
351
365
  } catch (error) {
352
366
  throw new MCPExpectedFailure(error);
353
367
  }
@@ -2,9 +2,13 @@ import { randomUUID } from "node:crypto";
2
2
  import { logger } from "@gajae-code/utils";
3
3
  import { lifecycleRequestTimeoutMs } from "../broker/startup-budget";
4
4
  import { type SdkClient, SdkClientError } from "../client";
5
-
6
5
  import type { AbortScope } from "../host/control/operations";
7
6
  import { assertReverseResponseFrame, ReverseLeaseError } from "../host/reverse-leases";
7
+ import {
8
+ SessionLifecycleService,
9
+ type SessionReconcileUncertainTarget,
10
+ validateSessionReconcileUncertainTarget,
11
+ } from "../lifecycle/service";
8
12
  import { validateAdapterControl, validateAdapterSecretFields } from "../protocol/adapter-validation";
9
13
  import { OPERATIONS } from "../protocol/operation-registry";
10
14
  import { type SessionAttachment, type SessionRouter, SessionRouterError } from "../router";
@@ -132,7 +136,8 @@ function isLifecycleOperation(operation: string): boolean {
132
136
  operation === "session.fork" ||
133
137
  operation === "session.resume" ||
134
138
  operation === "session.close" ||
135
- operation === "session.delete"
139
+ operation === "session.delete" ||
140
+ operation === "session.reconcile_uncertain"
136
141
  );
137
142
  }
138
143
 
@@ -449,8 +454,30 @@ export class AcpSdkAdapter {
449
454
  this.#assertGenericDisposition("global", operation);
450
455
  if (isLifecycleOperation(operation) && !idempotencyKey)
451
456
  throw new AcpSdkAdapterError("invalid_input", "idempotencyKey is required for lifecycle operations.");
457
+ if (operation === "session.reconcile_uncertain" && !validateSessionReconcileUncertainTarget(input))
458
+ throw new AcpSdkAdapterError(
459
+ "invalid_input",
460
+ "session.reconcile_uncertain requires complete identity-bound retirement proof.",
461
+ );
452
462
  if (!this.#client)
453
463
  throw new AcpSdkAdapterError("operation_prohibited", "Lifecycle operations require the Broker connection.");
464
+ const client = this.#client;
465
+ if (operation === "session.reconcile_uncertain") {
466
+ const outcome = await new SessionLifecycleService({
467
+ global: (lifecycleOperation, lifecycleInput, options) =>
468
+ client.global(lifecycleOperation, lifecycleInput, options),
469
+ }).executeWithIdempotencyKey(
470
+ {
471
+ operation,
472
+ actor: { id: "acp", namespace: "sdk:acp" },
473
+ capability: operation,
474
+ requestKey: idempotencyKey!,
475
+ target: input as unknown as SessionReconcileUncertainTarget,
476
+ },
477
+ idempotencyKey!,
478
+ );
479
+ return outcome.ok ? { ok: true, result: outcome.result } : { ok: false, error: outcome.error };
480
+ }
454
481
  // The broker may hold a startup in its admission queue before the readiness
455
482
  // clock even starts, so the caller deadline covers the queue wait too; sizing
456
483
  // it on readiness alone times out requests the broker is still running. A
@@ -190,15 +190,27 @@ export type BrokerResponse =
190
190
  const error = (code: BrokerErrorCode, message: string): BrokerResponse => ({ ok: false, error: { code, message } });
191
191
 
192
192
  function isCleanupPending(response: BrokerResponse): boolean {
193
- return !response.ok && response.error.code === "cleanup_pending" && response.error.cleanup !== undefined;
193
+ if (response.ok) return false;
194
+ const error = (response as { error?: unknown }).error;
195
+ if (typeof error !== "object" || error === null) return false;
196
+ const value = error as { code?: unknown; cleanup?: unknown };
197
+ return value.code === "cleanup_pending" && value.cleanup !== undefined;
194
198
  }
195
199
 
196
200
  function cleanupFromResponse(response: unknown): BrokerCleanupEvidence | undefined {
197
- return isBrokerResponse(response) && !response.ok ? response.error.cleanup : undefined;
201
+ if (!isBrokerResponse(response) || response.ok) return undefined;
202
+ const error = (response as { error?: unknown }).error;
203
+ if (typeof error !== "object" || error === null) return undefined;
204
+ const cleanup = (error as { cleanup?: unknown }).cleanup;
205
+ return cleanup && typeof cleanup === "object" ? (cleanup as BrokerCleanupEvidence) : undefined;
198
206
  }
199
207
  function pendingCleanupSessionId(response: BrokerResponse): string | undefined {
200
- if (response.ok || response.error.code !== "cleanup_pending") return undefined;
201
- return typeof response.error.cleanup?.sessionId === "string" ? response.error.cleanup.sessionId : undefined;
208
+ if (response.ok) return undefined;
209
+ const error = (response as { error?: unknown }).error;
210
+ if (typeof error !== "object" || error === null) return undefined;
211
+ const value = error as { code?: unknown; cleanup?: { sessionId?: unknown } };
212
+ if (value.code !== "cleanup_pending") return undefined;
213
+ return typeof value.cleanup?.sessionId === "string" ? value.cleanup.sessionId : undefined;
202
214
  }
203
215
 
204
216
  const LIFECYCLE_OPERATIONS = new Set([
@@ -207,6 +219,7 @@ const LIFECYCLE_OPERATIONS = new Set([
207
219
  "session.resume",
208
220
  "session.close",
209
221
  "session.delete",
222
+ "session.reconcile_uncertain",
210
223
  ]);
211
224
 
212
225
  /** Bootstrap signing material and its public candidate id authorize a launch but are not lifecycle request identity. */
@@ -225,7 +238,10 @@ function lifecycleFingerprint(operation: string, input: Record<string, unknown>)
225
238
  function lifecycleResponseState(response: BrokerResponse): LifecycleState {
226
239
  if (response.ok) return "terminal_ok";
227
240
  if (isCleanupPending(response)) return "effect_started";
228
- return response.error.code === "terminal_uncertain" ? "terminal_uncertain" : "terminal_error";
241
+ const error = (response as { error?: unknown }).error;
242
+ return typeof error === "object" && error !== null && (error as { code?: unknown }).code === "terminal_uncertain"
243
+ ? "terminal_uncertain"
244
+ : "terminal_error";
229
245
  }
230
246
 
231
247
  type InputNormalization = { input: Record<string, unknown> } | BrokerResponse;
@@ -307,7 +323,8 @@ function normalizeBrokerInput(operation: string, input: Record<string, unknown>)
307
323
  operation !== "session.fork" &&
308
324
  operation !== "session.resume" &&
309
325
  operation !== "session.close" &&
310
- operation !== "session.delete"
326
+ operation !== "session.delete" &&
327
+ operation !== "session.reconcile_uncertain"
311
328
  )
312
329
  return { input: normalized };
313
330
 
@@ -469,6 +486,7 @@ function lifecycleTarget(operation: string, input: Record<string, unknown>): unk
469
486
  case "session.resume":
470
487
  case "session.close":
471
488
  case "session.delete":
489
+ case "session.reconcile_uncertain":
472
490
  return { sessionId: id };
473
491
  default:
474
492
  return { operation, root, sessionId: id };
@@ -1575,6 +1593,8 @@ export class Broker {
1575
1593
  }
1576
1594
 
1577
1595
  if (!idempotencyKey) return error("invalid_input", "idempotencyKey is required for lifecycle operations");
1596
+ if (idempotencyKey.length > 256 || /[\u0000-\u001f\u007f]/u.test(idempotencyKey))
1597
+ return error("invalid_input", "idempotencyKey must be a bounded non-empty string");
1578
1598
  const target = createHash("sha256")
1579
1599
  .update(canonicalJson(lifecycleTarget(operation, input)))
1580
1600
  .digest("hex");
@@ -161,6 +161,7 @@ function isLifecycleLedgerEntry(value: unknown): value is LifecycleLedgerEntry {
161
161
  );
162
162
  }
163
163
  function canonicalJson(value: unknown): string {
164
+ if (typeof value === "bigint") return JSON.stringify(value.toString());
164
165
  if (value === null || typeof value !== "object") return JSON.stringify(value);
165
166
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
166
167
  const record = value as Record<string, unknown>;
@@ -698,7 +699,9 @@ export class LifecycleLedger {
698
699
  return this.#warnings;
699
700
  }
700
701
  async #append(entry: LifecycleLedgerEntry): Promise<LifecycleLedgerEntry> {
701
- const line = Buffer.from(`${JSON.stringify(entry)}\n`);
702
+ const line = Buffer.from(
703
+ `${JSON.stringify(entry, (_key, value: unknown) => (typeof value === "bigint" ? value.toString() : value))}\n`,
704
+ );
702
705
  if (line.length - 1 > this.#limits.maxLineBytes)
703
706
  throw new Error("Lifecycle ledger row exceeds the maximum byte length.");
704
707
  let replacementCompacted = false;
@@ -907,6 +910,30 @@ export class LifecycleLedger {
907
910
  return this.#byIdentity.get(identity);
908
911
  }
909
912
 
913
+ listUncertainCreatesBySessionId(
914
+ sessionId: string,
915
+ effectMarker?: string,
916
+ remoteCreateKey?: string,
917
+ ): LifecycleLedgerEntry[] {
918
+ const matches: LifecycleLedgerEntry[] = [];
919
+ for (const current of this.#byIdentity.values()) {
920
+ const effectIntent = current.effectIntent;
921
+ if (
922
+ current.state === "terminal_uncertain" &&
923
+ current.intendedSessionId === sessionId &&
924
+ current.operationKey?.startsWith("session.create\u0000") &&
925
+ effectIntent?.sessionId === sessionId &&
926
+ typeof effectIntent.stateRoot === "string" &&
927
+ path.isAbsolute(effectIntent.stateRoot) &&
928
+ effectIntent.childOwnershipEstablished === true &&
929
+ (effectMarker === undefined || current.effectMarker === effectMarker) &&
930
+ (remoteCreateKey === undefined || current.operationKey === `session.create\u0000${remoteCreateKey}`)
931
+ )
932
+ matches.push(current);
933
+ }
934
+ return matches;
935
+ }
936
+
910
937
  findPendingCleanupByTarget(
911
938
  sessionId: string,
912
939
  cwd: string,