@yu_robotics/remote-cli 1.6.139 → 1.6.145

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 (68) hide show
  1. package/README.md +53 -13
  2. package/dist/client/MessageHandler.d.ts +2 -0
  3. package/dist/client/MessageHandler.d.ts.map +1 -1
  4. package/dist/client/MessageHandler.js +86 -9
  5. package/dist/client/MessageHandler.js.map +1 -1
  6. package/dist/client/WebSocketClient.d.ts.map +1 -1
  7. package/dist/client/WebSocketClient.js +1 -0
  8. package/dist/client/WebSocketClient.js.map +1 -1
  9. package/dist/delegation/DelegatedWorkspaceManager.d.ts.map +1 -1
  10. package/dist/delegation/DelegatedWorkspaceManager.js +38 -4
  11. package/dist/delegation/DelegatedWorkspaceManager.js.map +1 -1
  12. package/dist/delegation/DelegationManager.d.ts +4 -0
  13. package/dist/delegation/DelegationManager.d.ts.map +1 -1
  14. package/dist/delegation/DelegationManager.js +195 -93
  15. package/dist/delegation/DelegationManager.js.map +1 -1
  16. package/dist/delegation/DelegationStatusFormatter.d.ts.map +1 -1
  17. package/dist/delegation/DelegationStatusFormatter.js +6 -5
  18. package/dist/delegation/DelegationStatusFormatter.js.map +1 -1
  19. package/dist/delegation/GitCheckpoint.d.ts +10 -2
  20. package/dist/delegation/GitCheckpoint.d.ts.map +1 -1
  21. package/dist/delegation/GitCheckpoint.js +286 -41
  22. package/dist/delegation/GitCheckpoint.js.map +1 -1
  23. package/dist/delegation/WorkerPolicy.d.ts +4 -1
  24. package/dist/delegation/WorkerPolicy.d.ts.map +1 -1
  25. package/dist/delegation/WorkerPolicy.js +17 -8
  26. package/dist/delegation/WorkerPolicy.js.map +1 -1
  27. package/dist/delegation/contract.d.ts +2 -2
  28. package/dist/delegation/contract.d.ts.map +1 -1
  29. package/dist/delegation/contract.js +3 -3
  30. package/dist/delegation/contract.js.map +1 -1
  31. package/dist/executor/AcpExecutor.d.ts +1 -0
  32. package/dist/executor/AcpExecutor.d.ts.map +1 -1
  33. package/dist/executor/AcpExecutor.js +21 -1
  34. package/dist/executor/AcpExecutor.js.map +1 -1
  35. package/dist/executor/Activity.d.ts +39 -0
  36. package/dist/executor/Activity.d.ts.map +1 -0
  37. package/dist/executor/Activity.js +266 -0
  38. package/dist/executor/Activity.js.map +1 -0
  39. package/dist/executor/AgyExecutor.d.ts +1 -0
  40. package/dist/executor/AgyExecutor.d.ts.map +1 -1
  41. package/dist/executor/AgyExecutor.js +8 -0
  42. package/dist/executor/AgyExecutor.js.map +1 -1
  43. package/dist/executor/ClaudePersistentExecutor.d.ts +4 -1
  44. package/dist/executor/ClaudePersistentExecutor.d.ts.map +1 -1
  45. package/dist/executor/ClaudePersistentExecutor.js +8 -0
  46. package/dist/executor/ClaudePersistentExecutor.js.map +1 -1
  47. package/dist/executor/CodexAppServerExecutor.d.ts +1 -0
  48. package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
  49. package/dist/executor/CodexAppServerExecutor.js +33 -9
  50. package/dist/executor/CodexAppServerExecutor.js.map +1 -1
  51. package/dist/executor/IExecutor.d.ts +3 -1
  52. package/dist/executor/IExecutor.d.ts.map +1 -1
  53. package/dist/executor/PiExecutor.d.ts +1 -0
  54. package/dist/executor/PiExecutor.d.ts.map +1 -1
  55. package/dist/executor/PiExecutor.js +10 -0
  56. package/dist/executor/PiExecutor.js.map +1 -1
  57. package/dist/executor/index.d.ts +1 -0
  58. package/dist/executor/index.d.ts.map +1 -1
  59. package/dist/executor/index.js +7 -1
  60. package/dist/executor/index.js.map +1 -1
  61. package/dist/maintenance/release-notes.json +1 -1
  62. package/dist/types/index.d.ts +12 -1
  63. package/dist/types/index.d.ts.map +1 -1
  64. package/dist/utils/ActivityProgress.d.ts +20 -0
  65. package/dist/utils/ActivityProgress.d.ts.map +1 -0
  66. package/dist/utils/ActivityProgress.js +69 -0
  67. package/dist/utils/ActivityProgress.js.map +1 -0
  68. package/package.json +1 -1
@@ -21,11 +21,13 @@ const WorkerPolicy_1 = require("./WorkerPolicy");
21
21
  const DelegationNotice_1 = require("./DelegationNotice");
22
22
  const WorkingDirectory_1 = require("../utils/WorkingDirectory");
23
23
  const DelegatedWorkspaceManager_1 = require("./DelegatedWorkspaceManager");
24
+ const ActivityProgress_1 = require("../utils/ActivityProgress");
25
+ const Activity_1 = require("../executor/Activity");
24
26
  exports.DELEGATION_LIMITS = { launches: 12, concurrent: 5, closeoutRounds: 2,
25
27
  idleTimeoutMs: 15 * 60_000, toolIdleTimeoutMs: 45 * 60_000,
26
28
  queueTimeoutMs: 60 * 60_000,
27
29
  resultBytes: 32 * 1024, continuationBytes: 64 * 1024,
28
- storageTimeoutMs: 10_000, calls: 500 };
30
+ storageTimeoutMs: 10_000, toolResponseWaitMs: 20_000, calls: 500 };
29
31
  /** Bounds and cadence for optional display-only delegated-worker text. */
30
32
  exports.DELEGATION_TEXT_PROGRESS = {
31
33
  flushMs: 2_500,
@@ -255,6 +257,7 @@ class DelegationManager {
255
257
  if (task.record.state === 'running')
256
258
  captureWorkerMetadata(task);
257
259
  return parent.onProgress?.({ taskId: task.record.id, backend: task.record.backend, ...progress,
260
+ ...(parent.onActivityProgress && task.activity ? { activity: { ...task.activity } } : {}),
258
261
  ...(task.lane ? { workerContext: { laneId: task.lane.id, generation: task.lane.contextGeneration ?? 0 } } : {}),
259
262
  ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}) }) === true;
260
263
  }
@@ -265,6 +268,8 @@ class DelegationManager {
265
268
  };
266
269
  const reportTextProgress = (task, latestText) => {
267
270
  try {
271
+ if (task.executor?.isWaitingInput?.())
272
+ return false;
268
273
  captureWorkerMetadata(task);
269
274
  return parent.onTextProgress?.({
270
275
  taskId: task.record.id,
@@ -272,6 +277,7 @@ class DelegationManager {
272
277
  phase: 'text',
273
278
  latestText,
274
279
  startedAt: task.record.startedAt,
280
+ ...(parent.onActivityProgress && task.executor?.isWaitingInput?.() === false ? { waitingForInput: false } : {}),
275
281
  ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}),
276
282
  }) === true;
277
283
  }
@@ -286,8 +292,28 @@ class DelegationManager {
286
292
  if (discardPending)
287
293
  task.textBuffer = undefined;
288
294
  };
295
+ const recordActivity = (task, value) => {
296
+ if (!parent.onActivityProgress || closed || task.record.state !== 'running' || task.executor?.isWaitingInput?.())
297
+ return;
298
+ const activity = (0, Activity_1.normalizeActivity)(value);
299
+ if (!activity)
300
+ return;
301
+ if (task.executor?.isWaitingInput?.() === false)
302
+ task.waitingInputText = undefined;
303
+ task.activity = activity;
304
+ task.activityProgress ??= new ActivityProgress_1.ActivityProgress(snapshot => {
305
+ if (closed || task.record.state !== 'running' || task.executor?.isWaitingInput?.())
306
+ return false;
307
+ captureWorkerMetadata(task);
308
+ return parent.onActivityProgress?.({ taskId: task.record.id, backend: task.record.backend,
309
+ phase: 'text', activity: snapshot, startedAt: task.record.startedAt,
310
+ ...(task.executor?.isWaitingInput?.() === false ? { waitingForInput: false } : {}),
311
+ ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}) }) === true;
312
+ });
313
+ task.activityProgress.offer(activity);
314
+ };
289
315
  const flushTextProgress = (task) => {
290
- if (closed || task.record.state !== 'running' || !task.textProgressEnabled)
316
+ if (closed || task.record.state !== 'running' || !task.textProgressEnabled || task.executor?.isWaitingInput?.())
291
317
  return;
292
318
  const latestText = task.textBuffer ? latestDisplayText(task.textBuffer) : undefined;
293
319
  if (!latestText || latestText === task.lastReportedText)
@@ -312,6 +338,8 @@ class DelegationManager {
312
338
  const recordDisplayText = (task, text) => {
313
339
  if (!task.textProgressEnabled || !text)
314
340
  return;
341
+ if (task.executor?.isWaitingInput?.() === false)
342
+ task.waitingInputText = undefined;
315
343
  task.textBuffer = tailText(`${task.textBuffer ?? ''}${text}`, exports.DELEGATION_TEXT_PROGRESS.bufferBytes);
316
344
  scheduleTextProgress(task);
317
345
  };
@@ -349,6 +377,7 @@ class DelegationManager {
349
377
  };
350
378
  const stop = (task) => task.stop ??= (async () => {
351
379
  clearTextProgress(task, true);
380
+ task.activityProgress?.dispose();
352
381
  if (!task.executor) {
353
382
  // A cancellation can race with durable lane acquisition. No worker
354
383
  // process owns this lane yet, so it must not become a reusable empty
@@ -404,6 +433,7 @@ class DelegationManager {
404
433
  };
405
434
  const finishTask = async (task) => {
406
435
  clearTimeout(task.queueTimer);
436
+ task.activityProgress?.dispose();
407
437
  task.record.finishedAt = Date.now();
408
438
  await deadline(async () => { await save(task); await this.store.prune(); }, this.cleanupMs)
409
439
  .catch(() => console.warn('[Delegation] Final task metadata could not be saved or pruned'));
@@ -466,7 +496,7 @@ class DelegationManager {
466
496
  }
467
497
  assertWorkspace();
468
498
  };
469
- const createWorker = async () => {
499
+ const prepareWorkerWorkspace = async () => {
470
500
  assertStartable();
471
501
  if (!task.lane) {
472
502
  const acquired = await this.laneStore.acquire(identity, source
@@ -480,17 +510,25 @@ class DelegationManager {
480
510
  await this.laneStore.markDirty(acquired.lane.id, 'Worker setup completed after its task ended.', true).catch(() => undefined);
481
511
  }
482
512
  }
513
+ assertStartable();
514
+ if (source && !task.workspace) {
515
+ baseline ??= this.workspaceManager.baseline(source);
516
+ const input = await baseline;
517
+ assertStartable();
518
+ task.workspacePreparationStarted = true;
519
+ task.workspace = await this.workspaceManager.prepare(task.lane, source, input, task.record.id);
520
+ assertStartable();
521
+ }
522
+ };
523
+ const createWorker = async () => {
483
524
  let worker;
484
525
  try {
526
+ // Shared-directory acquisition keeps its existing startup deadline.
527
+ if (!source)
528
+ await prepareWorkerWorkspace();
485
529
  assertStartable();
486
- if (source && !task.workspace) {
487
- baseline ??= this.workspaceManager.baseline(source);
488
- const input = await baseline;
489
- assertStartable();
490
- task.workspacePreparationStarted = true;
491
- task.workspace = await this.workspaceManager.prepare(task.lane, source, input, task.record.id);
492
- assertStartable();
493
- }
530
+ if (!task.lane)
531
+ throw new Error('Worker lane is unavailable before native startup');
494
532
  const executionCwd = task.workspace?.cwd ?? cwd;
495
533
  const workerGuard = task.workspace ? new DirectoryGuard_1.DirectoryGuard([...this.guard.getAllowedDirectories(), executionCwd]) : this.guard;
496
534
  worker = this.factory(workerGuard, configuration, executionCwd, task.lane.executorThreadId, (0, ThreadExecutorPool_1.resolveThreadModel)(parent.thread, configuration), parent.thread.efforts?.[backend], { lifecycleHooks: false, delegationWorker: true });
@@ -546,6 +584,11 @@ class DelegationManager {
546
584
  }
547
585
  let result;
548
586
  for (let attempt = 0; attempt < 2; attempt++) {
587
+ // Owned Git work and repository-lock waits are not native startup.
588
+ // Await them even on cancellation: no mutation may outlive its slot
589
+ // or source lease, and the final startable check prevents late launch.
590
+ if (source)
591
+ await prepareWorkerWorkspace();
549
592
  let setupPending = true;
550
593
  const setup = createWorker().finally(() => { setupPending = false; });
551
594
  let worker;
@@ -572,6 +615,7 @@ class DelegationManager {
572
615
  }
573
616
  // Native session IDs are distinct; policy was resolved against the real
574
617
  // parent thread before passing the synthetic worker identity to the factory.
618
+ let acceptingActivity = true;
575
619
  const execution = worker.execute(`You are executing one delegated task in an isolated worker session. This session may contain context from earlier delegated tasks in this thread and workspace. Complete only this objective and return a concise result with verification and remaining issues. Do not delegate to other agents.${task.workspace
576
620
  ? `\nThis task runs in an owned Git worktree at ${task.workspace.cwd}, with a fresh detached task checkout based on checkpoint ${task.workspace.input.commit}. Previous task changes are NOT implicitly present. Re-read current files; earlier conversation context may be stale. Stay within this checkout. Do not merge into or edit the delivery directory, push, change branches, or remove the worktree. Your file changes will be captured as an artifact for explicit coordinator integration. Ignored files and dependencies are not copied from the delivery directory. A worktree is not an OS sandbox.` : ''}
577
621
 
@@ -580,6 +624,10 @@ ${objective}`, {
580
624
  // backend-local limits that would otherwise ignore tool callbacks.
581
625
  timeout: 0,
582
626
  inactivityTimeout: 0,
627
+ onActivity: parent.onActivityProgress ? value => {
628
+ if (acceptingActivity && task.executor === worker)
629
+ recordActivity(task, value);
630
+ } : undefined,
583
631
  onStream: text => {
584
632
  // Generic streams may include reasoning or backend notices. Use
585
633
  // this channel only to detect interactive input requests.
@@ -588,6 +636,7 @@ ${objective}`, {
588
636
  return;
589
637
  if (task.executor?.isWaitingInput?.()) {
590
638
  clearTextProgress(task, true);
639
+ task.activityProgress?.clear();
591
640
  const prompt = bounded(text, 4000).text;
592
641
  if (task.waitingInputText === prompt)
593
642
  return;
@@ -633,20 +682,41 @@ ${prompt}`));
633
682
  tool_use_id: toolResult.tool_use_id, content: '', is_error: toolResult.is_error,
634
683
  }, startedAt: task.record.startedAt });
635
684
  },
636
- onApprovalRequest: request => !closed && task.record.state === 'running' && parent.onApproval({ ...request,
637
- description: `[${backend} delegated task] ${request.description}`, canRemember: false }, task.executor),
638
- onApprovalResolved: parent.onApprovalResolved,
685
+ onApprovalRequest: request => {
686
+ if (closed || task.record.state !== 'running')
687
+ return false;
688
+ task.activityProgress?.clear();
689
+ if (parent.onActivityProgress)
690
+ reportProgress(task, { phase: 'waiting_input', summary: 'Approval required', startedAt: task.record.startedAt });
691
+ return parent.onApproval({ ...request,
692
+ description: `[${backend} delegated task] ${request.description}`, canRemember: false }, task.executor);
693
+ },
694
+ onApprovalResolved: (id, status) => {
695
+ parent.onApprovalResolved(id, status);
696
+ if (acceptingActivity && task.executor === worker && worker.isWaitingInput?.() === false) {
697
+ task.waitingInputText = undefined;
698
+ recordActivity(task, { source: 'state', text: 'Working' });
699
+ }
700
+ },
639
701
  });
640
702
  // Start the inactivity window only once the worker has accepted the
641
703
  // objective and can emit its first real progress callback.
642
704
  task.signalDispatch();
643
705
  armIdleTimer(task);
644
- result = await Promise.race([task.interrupted, execution]);
706
+ try {
707
+ result = await Promise.race([task.interrupted, execution]);
708
+ }
709
+ finally {
710
+ acceptingActivity = false;
711
+ }
645
712
  captureWorkerMetadata(task);
646
713
  metadataCaptured = true;
647
714
  if (!result.success && attempt === 0 && worker.consumeSessionResumeFailure?.()) {
648
715
  clearTaskTimer(task);
649
716
  clearTextProgress(task, true);
717
+ task.activityProgress?.dispose();
718
+ task.activityProgress = undefined;
719
+ task.activity = undefined;
650
720
  task.lastReportedText = undefined;
651
721
  if (await discardMissingSessionLane())
652
722
  continue;
@@ -675,76 +745,97 @@ ${prompt}`));
675
745
  captureWorkerMetadata(task);
676
746
  clearTaskTimer(task);
677
747
  clearTextProgress(task, true);
748
+ task.activityProgress?.dispose();
678
749
  if (task.record.startedAt === undefined)
679
750
  task.discardLane = !task.preserveUnstartedLane;
680
751
  let released = !task.quarantineWorkspace;
752
+ let nativeExitConfirmed = false;
681
753
  let readyWorkspaceLane;
682
- try {
683
- await deadline(async () => {
684
- if (task.stop)
685
- await task.stop;
686
- else if (task.executor)
687
- await deadline(() => destroyWorker(task.executor), this.cleanupMs);
688
- else if (task.lane && !task.preserveUnstartedLane) {
689
- // A scope can close after durable acquisition but before construction.
690
- // No backend process was started, so discard the lane rather than
691
- // reserving the workspace indefinitely.
692
- task.discardLane = true;
754
+ const finalizeLane = async () => {
755
+ if (task.stop)
756
+ await deadline(() => task.stop, this.cleanupMs);
757
+ else if (task.executor)
758
+ await deadline(() => destroyWorker(task.executor), this.cleanupMs);
759
+ else if (task.lane && !task.preserveUnstartedLane) {
760
+ // A scope can close after durable acquisition but before construction.
761
+ // No backend process was started, so discard the lane rather than
762
+ // reserving the workspace indefinitely.
763
+ task.discardLane = true;
764
+ }
765
+ nativeExitConfirmed = true;
766
+ if (released && task.lane && task.preserveUnstartedLane) {
767
+ if (!task.workspacePreparationStarted) {
768
+ // Only acquisition changed: no checkout or executor was touched.
769
+ await this.laneStore.markReady(task.lane.id);
693
770
  }
694
- if (released && task.lane && task.preserveUnstartedLane) {
695
- if (!task.workspacePreparationStarted) {
696
- // Only acquisition changed: no checkout or executor was touched.
697
- await this.laneStore.markReady(task.lane.id);
771
+ else {
772
+ await this.workspaceManager.preserveLane(task.lane.id);
773
+ await this.laneStore.markDirty(task.lane.id, `${task.workspace ? 'Worker process setup did not complete' : 'Worker checkout preparation failed'}; existing context and files were retained for manual recovery.`);
774
+ }
775
+ }
776
+ if (released && task.lane && task.discardLane) {
777
+ const lane = task.lane;
778
+ await this.laneStore.markDirty(lane.id, 'Delegated worker setup ended before a reusable context was established.', true).catch(() => undefined);
779
+ await discardLane(lane);
780
+ task.lane = undefined;
781
+ }
782
+ if (released && task.lane && !task.preserveUnstartedLane) {
783
+ try {
784
+ if (task.workspace) {
785
+ task.artifact = await this.workspaceManager.collect(task.workspace, task.record.state === 'succeeded');
786
+ readyWorkspaceLane = task.lane;
698
787
  }
699
788
  else {
700
- await this.workspaceManager.preserveLane(task.lane.id);
701
- await this.laneStore.markDirty(task.lane.id, `${task.workspace ? 'Worker process setup did not complete' : 'Worker checkout preparation failed'}; existing context and files were retained for manual recovery.`);
789
+ await this.laneStore.markReady(task.lane.id);
702
790
  }
703
791
  }
704
- if (released && task.lane && task.discardLane) {
705
- const lane = task.lane;
706
- await this.laneStore.markDirty(lane.id, 'Delegated worker setup ended before a reusable context was established.', true).catch(() => undefined);
707
- await discardLane(lane);
708
- task.lane = undefined;
709
- }
710
- if (released && task.lane && !task.preserveUnstartedLane) {
711
- try {
712
- if (task.workspace) {
713
- task.artifact = await this.workspaceManager.collect(task.workspace, task.record.state === 'succeeded');
714
- readyWorkspaceLane = task.lane;
715
- }
716
- else {
717
- await this.laneStore.markReady(task.lane.id);
718
- }
792
+ catch (error) {
793
+ const detail = error instanceof Error ? error.message : 'Worker lane persistence failed';
794
+ if (task.workspace) {
795
+ // Exit was confirmed. Preserve files and context for recovery without blocking unrelated lanes.
796
+ await this.workspaceManager.preserveLane(task.lane.id).catch(() => undefined);
797
+ await this.laneStore.markDirty(task.lane.id, detail).catch(() => undefined);
798
+ if (task.record.state === 'succeeded')
799
+ task.record.state = 'failed';
800
+ task.record.error = bounded(`Worker artifact could not be finalized: ${detail}. Files were retained for manual recovery.`, 4000).text;
719
801
  }
720
- catch (error) {
721
- const detail = error instanceof Error ? error.message : 'Worker lane persistence failed';
722
- if (task.workspace) {
723
- // Exit was confirmed. Preserve files and context for recovery without blocking unrelated lanes.
724
- await this.workspaceManager.preserveLane(task.lane.id).catch(() => undefined);
725
- await this.laneStore.markDirty(task.lane.id, detail).catch(() => undefined);
726
- if (task.record.state === 'succeeded')
727
- task.record.state = 'failed';
728
- task.record.error = bounded(`Worker artifact could not be finalized: ${detail}. Files were retained for manual recovery.`, 4000).text;
729
- }
730
- else {
731
- await this.laneStore.markDirty(task.lane.id, detail, true).catch(() => undefined);
732
- await discardLane(task.lane);
733
- }
734
- console.warn('[Delegation] Worker lane could not be retained:', detail);
802
+ else {
803
+ await this.laneStore.markDirty(task.lane.id, detail, true).catch(() => undefined);
804
+ await discardLane(task.lane);
735
805
  }
806
+ console.warn('[Delegation] Worker lane could not be retained:', detail);
736
807
  }
737
- }, this.cleanupMs);
808
+ }
809
+ };
810
+ try {
811
+ // Collection owns Git mutations and must settle before releasing the
812
+ // lane, slot, or source lease. Individual Git commands remain bounded.
813
+ if (source)
814
+ await finalizeLane();
815
+ else
816
+ await deadline(finalizeLane, this.cleanupMs);
738
817
  }
739
818
  catch (error) {
740
- released = false;
741
- task.quarantineWorkspace = true;
742
- if (task.lane) {
743
- const detail = error instanceof Error ? error.message : 'Worker cleanup could not be confirmed';
744
- await deadline(() => this.laneStore.markDirty(task.lane.id, detail), this.cleanupMs).catch(() => undefined);
819
+ const detail = error instanceof Error ? error.message : 'Worker cleanup could not be confirmed';
820
+ if (source && released && nativeExitConfirmed) {
821
+ readyWorkspaceLane = undefined;
822
+ if (task.lane) {
823
+ await this.workspaceManager.preserveLane(task.lane.id).catch(() => undefined);
824
+ await this.laneStore.markDirty(task.lane.id, detail).catch(() => undefined);
825
+ }
826
+ if (task.record.state === 'succeeded')
827
+ task.record.state = 'failed';
828
+ task.record.error = bounded(`Worker lane could not be finalized: ${detail}. Files were retained for manual recovery.`, 4000).text;
829
+ }
830
+ else {
831
+ released = false;
832
+ task.quarantineWorkspace = true;
833
+ if (task.lane) {
834
+ await deadline(() => this.laneStore.markDirty(task.lane.id, detail), this.cleanupMs).catch(() => undefined);
835
+ }
836
+ task.record.state = 'interrupted';
837
+ task.record.error = 'Worker cleanup could not be confirmed. This workspace is blocked until the worker is stopped and the CLI restarts.';
745
838
  }
746
- task.record.state = 'interrupted';
747
- task.record.error = 'Worker cleanup could not be confirmed. This workspace is blocked until the worker is stopped and the CLI restarts.';
748
839
  console.warn('[Delegation] Worker cleanup failed:', error instanceof Error ? error.message : 'Cleanup failure');
749
840
  }
750
841
  if (released && readyWorkspaceLane) {
@@ -815,6 +906,18 @@ ${prompt}`));
815
906
  if (closed)
816
907
  throw new Error('Delegation turn has ended');
817
908
  const requestedDuring = execution;
909
+ const responseDeadline = Date.now() + exports.DELEGATION_LIMITS.toolResponseWaitMs;
910
+ const waitForResponse = async (pending) => {
911
+ let timer;
912
+ try {
913
+ await Promise.race([pending, new Promise(resolve => {
914
+ timer = setTimeout(resolve, Math.max(0, responseDeadline - Date.now()));
915
+ })]);
916
+ }
917
+ finally {
918
+ clearTimeout(timer);
919
+ }
920
+ };
818
921
  if (!callId || callId.length > 200)
819
922
  throw new Error('Invalid tool call ID');
820
923
  const signature = JSON.stringify({ name, args });
@@ -830,25 +933,8 @@ ${prompt}`));
830
933
  if (name === 'remote_cli_list_backends') {
831
934
  await discoverWorkspace();
832
935
  const backends = await this.registry.list(parent.config);
833
- return { backends: backends.map(item => {
834
- const configure = (mode) => (0, WorkerPolicy_1.workerConfiguration)(parent.config, this.guard, parent.thread.id, parent.backend, item.backend, mode);
835
- try {
836
- configure('inherit');
837
- }
838
- catch (error) {
839
- return { ...item, worker: false, readOnly: false, reason: error.message };
840
- }
841
- let readOnly = item.readOnly;
842
- if (readOnly) {
843
- try {
844
- configure('read_only');
845
- }
846
- catch {
847
- readOnly = false;
848
- }
849
- }
850
- return { ...item, readOnly };
851
- }), workspace: cwd, maxConcurrentChildren: maxChildren,
936
+ return { backends: backends.map(item => (0, WorkerPolicy_1.workerAvailability)(parent.config, this.guard, parent.thread.id, parent.backend, item)),
937
+ workspace: cwd, maxConcurrentChildren: maxChildren,
852
938
  maxTasksPerRequest: exports.DELEGATION_LIMITS.launches, scheduling: source ? 'isolated-worktrees' : 'serial',
853
939
  queueTimeoutSeconds: this.timeouts.queueTimeoutMs / 1000 };
854
940
  }
@@ -870,7 +956,16 @@ ${prompt}`));
870
956
  }
871
957
  if (name === 'remote_cli_cancel') {
872
958
  const task = getTask(args.taskId);
873
- await cancel(task);
959
+ const cancelling = cancel(task);
960
+ if (source)
961
+ await waitForResponse(cancelling);
962
+ else
963
+ await cancelling;
964
+ if (source && task.record.finishedAt === undefined) {
965
+ return { ...receive(task, requestedDuring), statusDetail: task.record.state === 'cancelled'
966
+ ? 'Cancellation requested; owned operations are still settling. Poll this taskId with remote_cli_result. The workspace remains reserved until cleanup completes.'
967
+ : 'Task finalization is still in progress. Poll this taskId with remote_cli_result. The workspace remains reserved until cleanup completes.' };
968
+ }
874
969
  return receive(task, requestedDuring);
875
970
  }
876
971
  if (name === 'remote_cli_integrate') {
@@ -1015,9 +1110,16 @@ ${prompt}`));
1015
1110
  start();
1016
1111
  }
1017
1112
  if (task.ownsSlot) {
1018
- // Preserve the existing first-task response after dispatch/setup,
1019
- // without holding up admission of followers while setup is pending.
1020
- await task.dispatched;
1113
+ // Preserve prompt dispatch responses, but do not let slow managed Git
1114
+ // work outlive the tool adapter's HTTP timeout. Returning an accepted
1115
+ // task ID never cancels its work or releases its lease/execution slot.
1116
+ if (source)
1117
+ await waitForResponse(task.dispatched);
1118
+ else
1119
+ await task.dispatched;
1120
+ if (source && task.record.finishedAt === undefined && task.record.startedAt === undefined) {
1121
+ return { ...receive(task, requestedDuring), statusDetail: 'Task accepted; workspace preparation is still in progress. Poll this taskId with remote_cli_result instead of submitting the task again.' };
1122
+ }
1021
1123
  }
1022
1124
  return receive(task, requestedDuring);
1023
1125
  })();