codex-toys 0.140.2 → 0.140.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 (53) hide show
  1. package/README.md +6 -1
  2. package/dist/app-server/stdio-transport.js +0 -2
  3. package/dist/app-server/stdio-transport.js.map +1 -1
  4. package/dist/bin/codex-toys-proxy.js +16 -4
  5. package/dist/cli/args.d.ts +194 -1
  6. package/dist/cli/args.d.ts.map +1 -1
  7. package/dist/cli/args.js +461 -2
  8. package/dist/cli/args.js.map +1 -1
  9. package/dist/cli/index.js +446 -8
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/cli/pack.d.ts +1 -3
  12. package/dist/cli/pack.d.ts.map +1 -1
  13. package/dist/cli/pack.js +3 -138
  14. package/dist/cli/pack.js.map +1 -1
  15. package/dist/cli/toybox.d.ts.map +1 -1
  16. package/dist/cli/toybox.js +24 -2
  17. package/dist/cli/toybox.js.map +1 -1
  18. package/dist/cli/turn-automation.d.ts +4 -0
  19. package/dist/cli/turn-automation.d.ts.map +1 -1
  20. package/dist/cli/turn-automation.js +17 -0
  21. package/dist/cli/turn-automation.js.map +1 -1
  22. package/dist/cli/workspace-autonomy.d.ts +135 -0
  23. package/dist/cli/workspace-autonomy.d.ts.map +1 -1
  24. package/dist/cli/workspace-autonomy.js +476 -11
  25. package/dist/cli/workspace-autonomy.js.map +1 -1
  26. package/dist/host-overview.d.ts +112 -0
  27. package/dist/host-overview.d.ts.map +1 -0
  28. package/dist/host-overview.js +406 -0
  29. package/dist/host-overview.js.map +1 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/proxy.d.ts.map +1 -1
  35. package/dist/proxy.js +11 -1
  36. package/dist/proxy.js.map +1 -1
  37. package/dist/toybox/deferred-run-methods.d.ts +15 -0
  38. package/dist/toybox/deferred-run-methods.d.ts.map +1 -1
  39. package/dist/toybox/deferred-run-methods.js +261 -1
  40. package/dist/toybox/deferred-run-methods.js.map +1 -1
  41. package/dist/toybox/index.d.ts +2 -1
  42. package/dist/toybox/index.d.ts.map +1 -1
  43. package/dist/toybox/index.js +2 -1
  44. package/dist/toybox/index.js.map +1 -1
  45. package/dist/toybox/protocol.d.ts +1 -1
  46. package/dist/toybox/protocol.d.ts.map +1 -1
  47. package/dist/toybox/protocol.js +1 -0
  48. package/dist/toybox/protocol.js.map +1 -1
  49. package/dist/workspace-overview.d.ts +153 -0
  50. package/dist/workspace-overview.d.ts.map +1 -0
  51. package/dist/workspace-overview.js +584 -0
  52. package/dist/workspace-overview.js.map +1 -0
  53. package/package.json +1 -1
@@ -125,6 +125,7 @@ export async function collectWorkspaceDoctorInfo(context, options = {}) {
125
125
  const now = new Date();
126
126
  const latestDeferredRun = deferredRuns
127
127
  .toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
128
+ const deferredDueFlags = await Promise.all(deferredRuns.map(async (intent) => await isDeferredIntentDue(context, intent, now)));
128
129
  const failingCount = countFailingTasks(config?.tasks ?? [], runs);
129
130
  const includeRunner = options.includeRunner === true || options.runnerProbe !== undefined;
130
131
  const runner = includeRunner
@@ -149,7 +150,7 @@ export async function collectWorkspaceDoctorInfo(context, options = {}) {
149
150
  dueCount: dueTasks(config?.tasks ?? [], runs, new Date()).length,
150
151
  failingCount,
151
152
  deferredCount: deferredRuns.length,
152
- deferredDueCount: deferredRuns.filter((intent) => isDeferredIntentDue(intent, now)).length,
153
+ deferredDueCount: deferredDueFlags.filter(Boolean).length,
153
154
  deferredRunningCount: deferredRuns.filter((intent) => intent.status === "running").length,
154
155
  deferredFailedCount: deferredRuns.filter((intent) => intent.status === "failed").length,
155
156
  latestRun,
@@ -292,6 +293,7 @@ export async function createDeferredRunIntent(context, params) {
292
293
  createdBy: input.createdBy,
293
294
  reason: input.reason,
294
295
  source: input.source,
296
+ dependsOn: input.dependsOn,
295
297
  attemptIds: [],
296
298
  });
297
299
  await writeNewJsonFile(deferredIntentPath(context, intent.id), intent);
@@ -329,12 +331,141 @@ export async function readDeferredRun(context, intentId, options = {}) {
329
331
  : undefined;
330
332
  return compactUndefined({ intent, attempts, outputs });
331
333
  }
334
+ export async function enqueuePromptQueueIntent(context, params) {
335
+ const input = parsePromptQueueEnqueueParams(params);
336
+ const after = input.afterIntentId
337
+ ? {
338
+ kind: "deferred-run",
339
+ intentId: input.afterIntentId,
340
+ status: input.afterStatus,
341
+ }
342
+ : undefined;
343
+ return await createDeferredRunIntent(context, {
344
+ id: input.id,
345
+ runAt: input.runAt,
346
+ target: compactUndefined({
347
+ kind: "turn",
348
+ prompt: input.prompt,
349
+ threadId: input.threadId,
350
+ cwd: input.cwd,
351
+ model: input.model,
352
+ serviceTier: input.serviceTier,
353
+ effort: input.effort,
354
+ sandbox: input.sandbox,
355
+ approvalPolicy: input.approvalPolicy,
356
+ permissions: input.permissions,
357
+ responsesapiClientMetadata: input.responsesapiClientMetadata,
358
+ outputSchema: input.outputSchema,
359
+ }),
360
+ createdBy: input.createdBy ?? "workspace-prompt-queue",
361
+ reason: input.reason ?? input.title,
362
+ source: promptQueueSource(input, after),
363
+ dependsOn: after ? [after] : undefined,
364
+ });
365
+ }
366
+ export async function listPromptQueueIntents(context, options = {}) {
367
+ const intents = await listDeferredRunIntents(context, {
368
+ status: options.status,
369
+ });
370
+ return intents
371
+ .filter((intent) => isPromptQueueIntent(intent, options.queue))
372
+ .slice(0, clampLimit(options.limit, 500));
373
+ }
374
+ export async function collectPromptQueueRuns(context, options = {}) {
375
+ return await collectDeferredRuns(context, {
376
+ cursor: options.cursor,
377
+ defaultCursor: "prompt-queue",
378
+ now: options.now,
379
+ filter: (intent) => isPromptQueueIntent(intent, options.queue),
380
+ });
381
+ }
382
+ export async function runDuePromptQueueIntents(context, options) {
383
+ return await runDueDeferredRuns(context, {
384
+ ...options,
385
+ filter: (intent) => isPromptQueueIntent(intent, options.queue),
386
+ });
387
+ }
388
+ export async function enqueueLocalHandoffIntent(context, params) {
389
+ const input = parseLocalHandoffEnqueueParams(params);
390
+ const after = input.afterIntentId
391
+ ? {
392
+ kind: "deferred-run",
393
+ intentId: input.afterIntentId,
394
+ status: input.afterStatus,
395
+ }
396
+ : undefined;
397
+ return await createDeferredRunIntent(context, {
398
+ id: input.id,
399
+ runAt: input.runAt,
400
+ target: compactUndefined({
401
+ kind: "turn",
402
+ prompt: input.prompt,
403
+ threadId: input.threadId,
404
+ cwd: input.cwd,
405
+ model: input.model,
406
+ serviceTier: input.serviceTier,
407
+ effort: input.effort,
408
+ sandbox: input.sandbox,
409
+ approvalPolicy: input.approvalPolicy,
410
+ permissions: input.permissions,
411
+ responsesapiClientMetadata: input.responsesapiClientMetadata,
412
+ outputSchema: input.outputSchema,
413
+ }),
414
+ createdBy: input.createdBy ?? "workspace-local-handoff",
415
+ reason: input.reason ?? input.title,
416
+ source: localHandoffSource(input, after),
417
+ dependsOn: after ? [after] : undefined,
418
+ });
419
+ }
420
+ export async function listLocalHandoffIntents(context, options = {}) {
421
+ const intents = await listDeferredRunIntents(context, {
422
+ status: options.status,
423
+ });
424
+ return intents
425
+ .filter((intent) => isLocalHandoffIntent(intent, options))
426
+ .slice(0, clampLimit(options.limit, 500));
427
+ }
428
+ export async function collectLocalHandoffRuns(context, options = {}) {
429
+ return await collectDeferredRuns(context, {
430
+ cursor: options.cursor,
431
+ defaultCursor: "local-handoff",
432
+ now: options.now,
433
+ filter: (intent) => isLocalHandoffIntent(intent, options),
434
+ });
435
+ }
436
+ export async function drainLocalHandoffQueue(context, options) {
437
+ const action = options.action ?? "run";
438
+ const result = await runDueDeferredRuns(context, {
439
+ ...options,
440
+ includeLocalHandoffs: true,
441
+ localHandoffMaterialize: action === "materialize"
442
+ ? { queue: options.promptQueue }
443
+ : undefined,
444
+ filter: (intent) => isLocalHandoffIntent(intent, {
445
+ queue: options.queue,
446
+ targetHost: options.hostId ? undefined : "local-controller",
447
+ hostId: options.hostId,
448
+ capabilities: options.capabilities ?? [],
449
+ }),
450
+ });
451
+ return {
452
+ mode: result.mode,
453
+ action,
454
+ executions: result.executions,
455
+ };
456
+ }
332
457
  export async function collectDeferredRuns(context, options = {}) {
333
458
  await ensureDeferredRunDirs(context);
334
- const cursor = deferredCollectCursorName(options.cursor);
459
+ const cursor = deferredCollectCursorName(options.cursor, options.defaultCursor);
335
460
  const previousCursor = await readDeferredRunCollectCursor(context, cursor);
336
461
  const collectedAt = (options.now ?? new Date()).toISOString();
337
- const terminalIntents = (await listDeferredRunIntents(context))
462
+ const filteredIntents = [];
463
+ for (const intent of await listDeferredRunIntents(context)) {
464
+ if (!options.filter || await options.filter(intent)) {
465
+ filteredIntents.push(intent);
466
+ }
467
+ }
468
+ const terminalIntents = filteredIntents
338
469
  .filter((intent) => isTerminalDeferredRunStatus(intent.status))
339
470
  .toSorted((left, right) => left.updatedAt.localeCompare(right.updatedAt) || left.id.localeCompare(right.id))
340
471
  .filter((intent) => isAfterDeferredRunCollectCursor(intent, previousCursor));
@@ -371,13 +502,59 @@ export async function cancelDeferredRunIntent(context, intentId) {
371
502
  await writeJsonFileAtomic(deferredIntentPath(context, intentId), canceled);
372
503
  return canceled;
373
504
  }
505
+ export async function retryDeferredRunIntent(context, intentId, params = {}, options = {}) {
506
+ await ensureDeferredRunDirs(context);
507
+ const originalIntent = await readDeferredRunIntent(context, intentId);
508
+ if (!isTerminalDeferredRunStatus(originalIntent.status)) {
509
+ throw new Error(`Only terminal deferred runs can be retried: ${intentId} is ${originalIntent.status}`);
510
+ }
511
+ const input = parseDeferredRunRetryParams(params);
512
+ const now = (options.now ?? new Date()).toISOString();
513
+ const retrySource = retryDeferredRunSource(originalIntent, input.source);
514
+ let id = input.id ?? deferredRetryRunId(originalIntent.id, now);
515
+ for (let attempt = 0; attempt < 10; attempt += 1) {
516
+ const intent = compactUndefined({
517
+ id,
518
+ status: "pending",
519
+ mode: context.mode,
520
+ runAt: input.runAt ?? now,
521
+ target: originalIntent.target,
522
+ createdAt: now,
523
+ updatedAt: now,
524
+ createdBy: input.createdBy ?? "workspace-deferred-retry",
525
+ reason: input.reason ?? `Retry deferred run ${originalIntent.id}`,
526
+ source: retrySource,
527
+ dependsOn: originalIntent.dependsOn,
528
+ attemptIds: [],
529
+ });
530
+ try {
531
+ await writeNewJsonFile(deferredIntentPath(context, intent.id), intent);
532
+ return { intent, originalIntent };
533
+ }
534
+ catch (error) {
535
+ if (!isAlreadyExistsError(error)) {
536
+ throw error;
537
+ }
538
+ id = deferredRetryRunId(originalIntent.id, now);
539
+ }
540
+ }
541
+ throw new Error(`Could not allocate retry deferred run id for ${intentId}`);
542
+ }
374
543
  export async function runDueDeferredRuns(context, options) {
375
544
  await ensureStateDirs(context);
376
545
  await ensureDeferredRunDirs(context);
377
546
  const now = options.now ?? new Date();
378
- const due = (await listDeferredRunIntents(context))
379
- .filter((intent) => isDeferredIntentDue(intent, now))
380
- .slice(0, clampLimit(options.limit, 100));
547
+ const due = [];
548
+ for (const intent of await listDeferredRunIntents(context)) {
549
+ if (await isDeferredIntentDue(context, intent, now) &&
550
+ (options.includeLocalHandoffs === true || !isLocalHandoffIntent(intent)) &&
551
+ (!options.filter || await options.filter(intent))) {
552
+ due.push(intent);
553
+ }
554
+ if (due.length >= clampLimit(options.limit, 100)) {
555
+ break;
556
+ }
557
+ }
381
558
  const executions = [];
382
559
  for (const intent of due) {
383
560
  const claim = await claimDeferredRunIntent(context, intent, {
@@ -393,6 +570,7 @@ export async function runDueDeferredRuns(context, options) {
393
570
  const result = await executeDeferredRunTarget(context, claim.intent, {
394
571
  callToybox: options.callToybox,
395
572
  automationCwd: options.automationCwd,
573
+ localHandoffMaterialize: options.localHandoffMaterialize,
396
574
  });
397
575
  await writeJsonFileAtomic(outputPath, result.output);
398
576
  const finishedAt = new Date().toISOString();
@@ -535,6 +713,13 @@ async function runAutomationTask(context, config, task, runId, startedAt, option
535
713
  async function executeDeferredRunTarget(context, intent, options) {
536
714
  try {
537
715
  const target = intent.target;
716
+ if (options.localHandoffMaterialize && isLocalHandoffIntent(intent)) {
717
+ const materialized = await materializeLocalHandoffIntent(context, intent, options.localHandoffMaterialize);
718
+ return {
719
+ status: "completed",
720
+ output: { localHandoff: materialized },
721
+ };
722
+ }
538
723
  if (target.kind === "workspace-task") {
539
724
  const config = await loadWorkspaceConfig(context);
540
725
  const task = config.tasks.find((item) => item.id === target.taskId);
@@ -580,6 +765,42 @@ async function executeDeferredRunTarget(context, intent, options) {
580
765
  };
581
766
  }
582
767
  }
768
+ async function materializeLocalHandoffIntent(context, intent, options) {
769
+ if (intent.target.kind !== "turn") {
770
+ throw new Error(`Local handoff can only materialize turn targets: ${intent.id}`);
771
+ }
772
+ const source = recordOrUndefined(intent.source) ?? {};
773
+ const queue = options.queue ?? optionalString(source.queue) ?? "local";
774
+ const promptIntent = await enqueuePromptQueueIntent(context, {
775
+ prompt: intent.target.prompt,
776
+ title: optionalString(source.title) ?? intent.reason,
777
+ queue,
778
+ labels: stringArray(source.labels),
779
+ threadId: intent.target.threadId,
780
+ cwd: intent.target.cwd,
781
+ model: intent.target.model,
782
+ serviceTier: intent.target.serviceTier,
783
+ effort: intent.target.effort,
784
+ sandbox: intent.target.sandbox,
785
+ approvalPolicy: intent.target.approvalPolicy,
786
+ permissions: intent.target.permissions,
787
+ responsesapiClientMetadata: intent.target.responsesapiClientMetadata,
788
+ outputSchema: intent.target.outputSchema,
789
+ createdBy: "workspace-local-handoff-drain",
790
+ reason: intent.reason,
791
+ source: compactUndefined({
792
+ kind: "local-handoff-materialized",
793
+ handoffIntentId: intent.id,
794
+ handoffSource: source,
795
+ }),
796
+ });
797
+ return {
798
+ action: "materialized",
799
+ handoffIntentId: intent.id,
800
+ promptIntentId: promptIntent.id,
801
+ queue,
802
+ };
803
+ }
583
804
  async function runAutomationDeferredTarget(context, config, intent, options) {
584
805
  const target = await resolveTurnAutomationTarget(intent.target.automation, {
585
806
  cwd: context.repoRoot,
@@ -889,7 +1110,7 @@ async function readDeferredRunCollectCursor(context, cursor) {
889
1110
  }
890
1111
  async function claimDeferredRunIntent(context, intent, options) {
891
1112
  const current = await readDeferredRunIntent(context, intent.id);
892
- if (!isDeferredIntentDue(current, options.now)) {
1113
+ if (!await isDeferredIntentDue(context, current, options.now)) {
893
1114
  return undefined;
894
1115
  }
895
1116
  const claimPath = deferredClaimPath(context, current.id);
@@ -972,8 +1193,185 @@ function parseDeferredRunCreateParams(value) {
972
1193
  createdBy: optionalString(input.createdBy),
973
1194
  reason: optionalString(input.reason),
974
1195
  source,
1196
+ dependsOn: parseDeferredRunDependencies(input.dependsOn),
1197
+ });
1198
+ }
1199
+ function parsePromptQueueEnqueueParams(value) {
1200
+ const input = record(value);
1201
+ const runAt = optionalString(input.runAt);
1202
+ if (runAt && Number.isNaN(Date.parse(runAt))) {
1203
+ throw new Error(`Prompt queue runAt must be an ISO-compatible date: ${runAt}`);
1204
+ }
1205
+ const afterStatus = deferredDependencyStatusValue(input.afterStatus, "prompt queue afterStatus");
1206
+ return compactUndefined({
1207
+ id: optionalString(input.id),
1208
+ runAt,
1209
+ prompt: requiredString(input.prompt, "prompt queue prompt"),
1210
+ title: optionalString(input.title),
1211
+ queue: optionalString(input.queue),
1212
+ labels: stringArray(input.labels),
1213
+ threadId: optionalString(input.threadId),
1214
+ cwd: optionalString(input.cwd),
1215
+ model: optionalString(input.model),
1216
+ serviceTier: optionalString(input.serviceTier),
1217
+ effort: reasoningEffortValue(input.effort, "prompt queue effort"),
1218
+ sandbox: sandboxValue(input.sandbox, "prompt queue sandbox"),
1219
+ approvalPolicy: approvalPolicyValue(input.approvalPolicy, "prompt queue approvalPolicy"),
1220
+ permissions: optionalString(input.permissions),
1221
+ responsesapiClientMetadata: stringRecord(input.responsesapiClientMetadata),
1222
+ outputSchema: input.outputSchema,
1223
+ afterIntentId: optionalString(input.afterIntentId),
1224
+ afterStatus,
1225
+ createdBy: optionalString(input.createdBy),
1226
+ reason: optionalString(input.reason),
1227
+ source: recordOrUndefined(input.source),
975
1228
  });
976
1229
  }
1230
+ function parseLocalHandoffEnqueueParams(value) {
1231
+ const input = record(value);
1232
+ const runAt = optionalString(input.runAt);
1233
+ if (runAt && Number.isNaN(Date.parse(runAt))) {
1234
+ throw new Error(`Local handoff runAt must be an ISO-compatible date: ${runAt}`);
1235
+ }
1236
+ const afterStatus = deferredDependencyStatusValue(input.afterStatus, "local handoff afterStatus");
1237
+ return compactUndefined({
1238
+ id: optionalString(input.id),
1239
+ runAt,
1240
+ prompt: requiredString(input.prompt, "local handoff prompt"),
1241
+ title: optionalString(input.title),
1242
+ queue: optionalString(input.queue),
1243
+ labels: stringArray(input.labels),
1244
+ targetHost: optionalString(input.targetHost),
1245
+ requiredCapabilities: stringArray(input.requiredCapabilities),
1246
+ requesterHost: optionalString(input.requesterHost),
1247
+ requesterThreadId: optionalString(input.requesterThreadId),
1248
+ threadId: optionalString(input.threadId),
1249
+ cwd: optionalString(input.cwd),
1250
+ model: optionalString(input.model),
1251
+ serviceTier: optionalString(input.serviceTier),
1252
+ effort: reasoningEffortValue(input.effort, "local handoff effort"),
1253
+ sandbox: sandboxValue(input.sandbox, "local handoff sandbox"),
1254
+ approvalPolicy: approvalPolicyValue(input.approvalPolicy, "local handoff approvalPolicy"),
1255
+ permissions: optionalString(input.permissions),
1256
+ responsesapiClientMetadata: stringRecord(input.responsesapiClientMetadata),
1257
+ outputSchema: input.outputSchema,
1258
+ afterIntentId: optionalString(input.afterIntentId),
1259
+ afterStatus,
1260
+ createdBy: optionalString(input.createdBy),
1261
+ reason: optionalString(input.reason),
1262
+ source: recordOrUndefined(input.source),
1263
+ });
1264
+ }
1265
+ function parseDeferredRunDependencies(value) {
1266
+ if (!Array.isArray(value)) {
1267
+ return undefined;
1268
+ }
1269
+ const dependencies = value.map(parseDeferredRunDependency);
1270
+ return dependencies.length > 0 ? dependencies : undefined;
1271
+ }
1272
+ function parseDeferredRunDependency(value) {
1273
+ const input = record(value);
1274
+ const kind = requiredString(input.kind, "deferred run dependency kind");
1275
+ if (kind !== "deferred-run") {
1276
+ throw new Error(`Invalid deferred run dependency kind: ${kind}`);
1277
+ }
1278
+ return compactUndefined({
1279
+ kind: "deferred-run",
1280
+ intentId: requiredString(input.intentId, "deferred run dependency intentId"),
1281
+ status: deferredDependencyStatusValue(input.status, "deferred run dependency status"),
1282
+ });
1283
+ }
1284
+ function parseDeferredRunRetryParams(value) {
1285
+ const input = record(value);
1286
+ const runAt = optionalString(input.runAt);
1287
+ if (runAt && Number.isNaN(Date.parse(runAt))) {
1288
+ throw new Error(`Deferred run retry runAt must be an ISO-compatible date: ${runAt}`);
1289
+ }
1290
+ return compactUndefined({
1291
+ id: optionalString(input.id),
1292
+ runAt,
1293
+ createdBy: optionalString(input.createdBy),
1294
+ reason: optionalString(input.reason),
1295
+ source: recordOrUndefined(input.source),
1296
+ });
1297
+ }
1298
+ function retryDeferredRunSource(originalIntent, source) {
1299
+ const originalSource = recordOrUndefined(originalIntent.source) ?? {};
1300
+ return compactUndefined({
1301
+ ...originalSource,
1302
+ kind: optionalString(originalSource.kind) ?? "deferred-retry",
1303
+ retry: compactUndefined({
1304
+ kind: "deferred-retry",
1305
+ originalIntentId: originalIntent.id,
1306
+ originalStatus: originalIntent.status,
1307
+ originalRunAt: originalIntent.runAt,
1308
+ originalUpdatedAt: originalIntent.updatedAt,
1309
+ details: source,
1310
+ }),
1311
+ });
1312
+ }
1313
+ function promptQueueSource(input, after) {
1314
+ return compactUndefined({
1315
+ kind: "prompt-queue",
1316
+ queue: input.queue ?? "default",
1317
+ title: input.title,
1318
+ labels: input.labels,
1319
+ after,
1320
+ details: input.source,
1321
+ });
1322
+ }
1323
+ function localHandoffSource(input, after) {
1324
+ const requester = compactUndefined({
1325
+ host: input.requesterHost,
1326
+ threadId: input.requesterThreadId,
1327
+ });
1328
+ return compactUndefined({
1329
+ kind: "local-handoff",
1330
+ queue: input.queue ?? "local",
1331
+ title: input.title,
1332
+ labels: input.labels,
1333
+ targetHost: input.targetHost ?? "local-controller",
1334
+ requiredCapabilities: input.requiredCapabilities,
1335
+ requester: Object.keys(requester).length > 0 ? requester : undefined,
1336
+ after,
1337
+ details: input.source,
1338
+ });
1339
+ }
1340
+ function isPromptQueueIntent(intent, queue) {
1341
+ if (intent.target.kind !== "turn") {
1342
+ return false;
1343
+ }
1344
+ const source = recordOrUndefined(intent.source);
1345
+ if (source?.kind !== "prompt-queue") {
1346
+ return false;
1347
+ }
1348
+ return !queue || source.queue === queue;
1349
+ }
1350
+ function isLocalHandoffIntent(intent, options = {}) {
1351
+ if (intent.target.kind !== "turn") {
1352
+ return false;
1353
+ }
1354
+ const source = recordOrUndefined(intent.source);
1355
+ if (source?.kind !== "local-handoff") {
1356
+ return false;
1357
+ }
1358
+ if (options.queue && source.queue !== options.queue) {
1359
+ return false;
1360
+ }
1361
+ const targetHost = optionalString(source.targetHost) ?? "local-controller";
1362
+ if (options.targetHost && targetHost !== options.targetHost) {
1363
+ return false;
1364
+ }
1365
+ if (options.hostId && targetHost !== "local-controller" && targetHost !== options.hostId) {
1366
+ return false;
1367
+ }
1368
+ if (options.capabilities) {
1369
+ const requiredCapabilities = stringArray(source.requiredCapabilities) ?? [];
1370
+ const availableCapabilities = new Set(options.capabilities);
1371
+ return requiredCapabilities.every((capability) => availableCapabilities.has(capability));
1372
+ }
1373
+ return true;
1374
+ }
977
1375
  function parseDeferredRunTarget(value) {
978
1376
  const target = record(value);
979
1377
  const kind = requiredString(target.kind, "deferred run target kind");
@@ -1005,6 +1403,7 @@ function parseDeferredRunTarget(value) {
1005
1403
  cwd: optionalString(target.cwd),
1006
1404
  model: optionalString(target.model),
1007
1405
  serviceTier: optionalString(target.serviceTier),
1406
+ effort: reasoningEffortValue(target.effort, "deferred run turn target effort"),
1008
1407
  sandbox: sandboxValue(target.sandbox, "deferred run turn target sandbox"),
1009
1408
  approvalPolicy: approvalPolicyValue(target.approvalPolicy, "deferred run turn target approvalPolicy"),
1010
1409
  permissions: optionalString(target.permissions),
@@ -1027,6 +1426,7 @@ function normalizeDeferredRunIntent(value) {
1027
1426
  createdBy: optionalString(input.createdBy),
1028
1427
  reason: optionalString(input.reason),
1029
1428
  source: recordOrUndefined(input.source),
1429
+ dependsOn: parseDeferredRunDependencies(input.dependsOn),
1030
1430
  attemptIds: Array.isArray(input.attemptIds)
1031
1431
  ? input.attemptIds.filter((entry) => typeof entry === "string")
1032
1432
  : [],
@@ -1110,15 +1510,44 @@ function hasScheduledIntentForDate(taskId, intents, now) {
1110
1510
  intent.source?.kind === "workspace-task-schedule" &&
1111
1511
  intent.source.date === now.toISOString().slice(0, 10)));
1112
1512
  }
1113
- function isDeferredIntentDue(intent, now) {
1513
+ async function isDeferredIntentDue(context, intent, now) {
1114
1514
  if (intent.status === "pending") {
1115
- return intent.runAt <= now.toISOString();
1515
+ return intent.runAt <= now.toISOString() &&
1516
+ await areDeferredRunDependenciesSatisfied(context, intent.dependsOn);
1116
1517
  }
1117
1518
  if (intent.status === "running" && intent.lease?.expiresAt) {
1118
1519
  return intent.lease.expiresAt <= now.toISOString();
1119
1520
  }
1120
1521
  return false;
1121
1522
  }
1523
+ async function areDeferredRunDependenciesSatisfied(context, dependencies) {
1524
+ if (!dependencies || dependencies.length === 0) {
1525
+ return true;
1526
+ }
1527
+ for (const dependency of dependencies) {
1528
+ if (dependency.kind !== "deferred-run") {
1529
+ return false;
1530
+ }
1531
+ let intent;
1532
+ try {
1533
+ intent = await readDeferredRunIntent(context, dependency.intentId);
1534
+ }
1535
+ catch {
1536
+ return false;
1537
+ }
1538
+ const status = dependency.status ?? "completed";
1539
+ if (status === "terminal") {
1540
+ if (!isTerminalDeferredRunStatus(intent.status)) {
1541
+ return false;
1542
+ }
1543
+ continue;
1544
+ }
1545
+ if (intent.status !== status) {
1546
+ return false;
1547
+ }
1548
+ }
1549
+ return true;
1550
+ }
1122
1551
  function isTerminalDeferredRunStatus(status) {
1123
1552
  return status === "completed" || status === "failed" || status === "canceled";
1124
1553
  }
@@ -1395,8 +1824,8 @@ function deferredClaimPath(context, intentId) {
1395
1824
  function deferredCollectCursorPath(context, cursor) {
1396
1825
  return path.join(deferredCollectCursorDir(context), `${safeFileSegment(cursor)}.json`);
1397
1826
  }
1398
- function deferredCollectCursorName(value) {
1399
- const cursor = value?.trim() || "default";
1827
+ function deferredCollectCursorName(value, defaultCursor = "default") {
1828
+ const cursor = value?.trim() || defaultCursor;
1400
1829
  if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(cursor)) {
1401
1830
  throw new Error(`Invalid deferred collect cursor: ${cursor}`);
1402
1831
  }
@@ -1405,6 +1834,9 @@ function deferredCollectCursorName(value) {
1405
1834
  function deferredRunId(createdAt) {
1406
1835
  return `deferred-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
1407
1836
  }
1837
+ function deferredRetryRunId(originalIntentId, createdAt) {
1838
+ return `retry-${safeFileSegment(originalIntentId).slice(0, 40)}-${createdAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
1839
+ }
1408
1840
  function deferredAttemptId(intentId, startedAt) {
1409
1841
  return `${startedAt.replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}-${safeFileSegment(intentId).slice(0, 48)}`;
1410
1842
  }
@@ -1664,6 +2096,13 @@ function stringRecord(value) {
1664
2096
  const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
1665
2097
  return entries.length > 0 ? Object.fromEntries(entries) : undefined;
1666
2098
  }
2099
+ function stringArray(value) {
2100
+ if (!Array.isArray(value)) {
2101
+ return undefined;
2102
+ }
2103
+ const entries = value.filter((entry) => typeof entry === "string" && entry.length > 0);
2104
+ return entries.length > 0 ? entries : undefined;
2105
+ }
1667
2106
  function sandboxValue(value, label) {
1668
2107
  if (value === "danger-full-access" ||
1669
2108
  value === "read-only" ||
@@ -1687,6 +2126,32 @@ function approvalPolicyValue(value, label) {
1687
2126
  }
1688
2127
  return undefined;
1689
2128
  }
2129
+ function reasoningEffortValue(value, label) {
2130
+ if (value === "none" ||
2131
+ value === "minimal" ||
2132
+ value === "low" ||
2133
+ value === "medium" ||
2134
+ value === "high" ||
2135
+ value === "xhigh") {
2136
+ return value;
2137
+ }
2138
+ if (value !== undefined) {
2139
+ throw new Error(`${label} must be none, minimal, low, medium, high, or xhigh`);
2140
+ }
2141
+ return undefined;
2142
+ }
2143
+ function deferredDependencyStatusValue(value, label) {
2144
+ if (value === "completed" ||
2145
+ value === "failed" ||
2146
+ value === "canceled" ||
2147
+ value === "terminal") {
2148
+ return value;
2149
+ }
2150
+ if (value !== undefined) {
2151
+ throw new Error(`${label} must be completed, failed, canceled, or terminal`);
2152
+ }
2153
+ return undefined;
2154
+ }
1690
2155
  function deferredRunStatus(value) {
1691
2156
  if (value === "pending" ||
1692
2157
  value === "running" ||