@yu_robotics/remote-cli 1.6.121 → 1.6.133

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 (124) hide show
  1. package/README.md +36 -3
  2. package/dist/client/MessageHandler.d.ts.map +1 -1
  3. package/dist/client/MessageHandler.js +37 -3
  4. package/dist/client/MessageHandler.js.map +1 -1
  5. package/dist/client/TaskRecovery.d.ts.map +1 -1
  6. package/dist/client/TaskRecovery.js +5 -0
  7. package/dist/client/TaskRecovery.js.map +1 -1
  8. package/dist/client/WebSocketClient.d.ts +6 -0
  9. package/dist/client/WebSocketClient.d.ts.map +1 -1
  10. package/dist/client/WebSocketClient.js +4 -0
  11. package/dist/client/WebSocketClient.js.map +1 -1
  12. package/dist/commands/config.js +7 -0
  13. package/dist/commands/config.js.map +1 -1
  14. package/dist/commands/start.d.ts.map +1 -1
  15. package/dist/commands/start.js +11 -0
  16. package/dist/commands/start.js.map +1 -1
  17. package/dist/delegation/DelegatedWorkerSessionStore.d.ts +4 -1
  18. package/dist/delegation/DelegatedWorkerSessionStore.d.ts.map +1 -1
  19. package/dist/delegation/DelegatedWorkerSessionStore.js +6 -7
  20. package/dist/delegation/DelegatedWorkerSessionStore.js.map +1 -1
  21. package/dist/delegation/DelegatedWorkspaceManager.d.ts +91 -0
  22. package/dist/delegation/DelegatedWorkspaceManager.d.ts.map +1 -0
  23. package/dist/delegation/DelegatedWorkspaceManager.js +377 -0
  24. package/dist/delegation/DelegatedWorkspaceManager.js.map +1 -0
  25. package/dist/delegation/DelegationManager.d.ts +13 -4
  26. package/dist/delegation/DelegationManager.d.ts.map +1 -1
  27. package/dist/delegation/DelegationManager.js +388 -114
  28. package/dist/delegation/DelegationManager.js.map +1 -1
  29. package/dist/delegation/DelegationNotice.d.ts.map +1 -1
  30. package/dist/delegation/DelegationNotice.js +2 -0
  31. package/dist/delegation/DelegationNotice.js.map +1 -1
  32. package/dist/delegation/DelegationStore.d.ts +6 -2
  33. package/dist/delegation/DelegationStore.d.ts.map +1 -1
  34. package/dist/delegation/DelegationStore.js +12 -5
  35. package/dist/delegation/DelegationStore.js.map +1 -1
  36. package/dist/delegation/GitCheckpoint.d.ts +27 -0
  37. package/dist/delegation/GitCheckpoint.d.ts.map +1 -0
  38. package/dist/delegation/GitCheckpoint.js +226 -0
  39. package/dist/delegation/GitCheckpoint.js.map +1 -0
  40. package/dist/delegation/contract.d.ts +18 -4
  41. package/dist/delegation/contract.d.ts.map +1 -1
  42. package/dist/delegation/contract.js +13 -4
  43. package/dist/delegation/contract.js.map +1 -1
  44. package/dist/executor/AcpExecutor.d.ts +2 -0
  45. package/dist/executor/AcpExecutor.d.ts.map +1 -1
  46. package/dist/executor/AcpExecutor.js +22 -2
  47. package/dist/executor/AcpExecutor.js.map +1 -1
  48. package/dist/executor/AgyExecutor.d.ts +1 -0
  49. package/dist/executor/AgyExecutor.d.ts.map +1 -1
  50. package/dist/executor/AgyExecutor.js +4 -0
  51. package/dist/executor/AgyExecutor.js.map +1 -1
  52. package/dist/executor/ClaudePersistentExecutor.d.ts +2 -0
  53. package/dist/executor/ClaudePersistentExecutor.d.ts.map +1 -1
  54. package/dist/executor/ClaudePersistentExecutor.js +19 -0
  55. package/dist/executor/ClaudePersistentExecutor.js.map +1 -1
  56. package/dist/executor/CodexAppServerClient.d.ts +5 -0
  57. package/dist/executor/CodexAppServerClient.d.ts.map +1 -1
  58. package/dist/executor/CodexAppServerClient.js +6 -2
  59. package/dist/executor/CodexAppServerClient.js.map +1 -1
  60. package/dist/executor/CodexAppServerExecutor.d.ts +4 -0
  61. package/dist/executor/CodexAppServerExecutor.d.ts.map +1 -1
  62. package/dist/executor/CodexAppServerExecutor.js +32 -0
  63. package/dist/executor/CodexAppServerExecutor.js.map +1 -1
  64. package/dist/executor/ExecutionMetadata.d.ts +9 -0
  65. package/dist/executor/ExecutionMetadata.d.ts.map +1 -0
  66. package/dist/executor/ExecutionMetadata.js +57 -0
  67. package/dist/executor/ExecutionMetadata.js.map +1 -0
  68. package/dist/executor/IExecutor.d.ts +4 -1
  69. package/dist/executor/IExecutor.d.ts.map +1 -1
  70. package/dist/executor/PiExecutor.d.ts +3 -0
  71. package/dist/executor/PiExecutor.d.ts.map +1 -1
  72. package/dist/executor/PiExecutor.js +36 -0
  73. package/dist/executor/PiExecutor.js.map +1 -1
  74. package/dist/executor/claude/ClaudeStderrFilter.d.ts +1 -1
  75. package/dist/executor/claude/ClaudeStderrFilter.d.ts.map +1 -1
  76. package/dist/executor/claude/ClaudeStderrFilter.js +3 -2
  77. package/dist/executor/claude/ClaudeStderrFilter.js.map +1 -1
  78. package/dist/maintenance/CodexStatusProbe.d.ts +26 -0
  79. package/dist/maintenance/CodexStatusProbe.d.ts.map +1 -0
  80. package/dist/maintenance/CodexStatusProbe.js +124 -0
  81. package/dist/maintenance/CodexStatusProbe.js.map +1 -0
  82. package/dist/maintenance/CodexWeekly.d.ts +17 -0
  83. package/dist/maintenance/CodexWeekly.d.ts.map +1 -0
  84. package/dist/maintenance/CodexWeekly.js +60 -0
  85. package/dist/maintenance/CodexWeekly.js.map +1 -0
  86. package/dist/maintenance/MaintenanceClient.d.ts +23 -0
  87. package/dist/maintenance/MaintenanceClient.d.ts.map +1 -0
  88. package/dist/maintenance/MaintenanceClient.js +69 -0
  89. package/dist/maintenance/MaintenanceClient.js.map +1 -0
  90. package/dist/maintenance/PrivateStore.d.ts +10 -0
  91. package/dist/maintenance/PrivateStore.d.ts.map +1 -0
  92. package/dist/maintenance/PrivateStore.js +54 -0
  93. package/dist/maintenance/PrivateStore.js.map +1 -0
  94. package/dist/maintenance/ReleaseNotes.d.ts +30 -0
  95. package/dist/maintenance/ReleaseNotes.d.ts.map +1 -0
  96. package/dist/maintenance/ReleaseNotes.js +139 -0
  97. package/dist/maintenance/ReleaseNotes.js.map +1 -0
  98. package/dist/maintenance/ReleaseOverview.d.ts +23 -0
  99. package/dist/maintenance/ReleaseOverview.d.ts.map +1 -0
  100. package/dist/maintenance/ReleaseOverview.js +56 -0
  101. package/dist/maintenance/ReleaseOverview.js.map +1 -0
  102. package/dist/maintenance/SubscriptionInspection.d.ts +49 -0
  103. package/dist/maintenance/SubscriptionInspection.d.ts.map +1 -0
  104. package/dist/maintenance/SubscriptionInspection.js +193 -0
  105. package/dist/maintenance/SubscriptionInspection.js.map +1 -0
  106. package/dist/maintenance/SubscriptionReminderStore.d.ts +12 -0
  107. package/dist/maintenance/SubscriptionReminderStore.d.ts.map +1 -0
  108. package/dist/maintenance/SubscriptionReminderStore.js +60 -0
  109. package/dist/maintenance/SubscriptionReminderStore.js.map +1 -0
  110. package/dist/maintenance/UpdateNotices.d.ts +21 -0
  111. package/dist/maintenance/UpdateNotices.d.ts.map +1 -0
  112. package/dist/maintenance/UpdateNotices.js +104 -0
  113. package/dist/maintenance/UpdateNotices.js.map +1 -0
  114. package/dist/maintenance/Version.d.ts +3 -0
  115. package/dist/maintenance/Version.d.ts.map +1 -0
  116. package/dist/maintenance/Version.js +42 -0
  117. package/dist/maintenance/Version.js.map +1 -0
  118. package/dist/maintenance/release-notes.json +1 -0
  119. package/dist/types/config.d.ts +5 -0
  120. package/dist/types/config.d.ts.map +1 -1
  121. package/dist/types/config.js.map +1 -1
  122. package/dist/types/index.d.ts +15 -0
  123. package/dist/types/index.d.ts.map +1 -1
  124. package/package.json +2 -2
@@ -8,7 +8,9 @@ exports.workspacesOverlap = workspacesOverlap;
8
8
  const crypto_1 = require("crypto");
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
+ const DirectoryGuard_1 = require("../security/DirectoryGuard");
11
12
  const ThreadExecutorPool_1 = require("../thread/ThreadExecutorPool");
13
+ const ExecutionMetadata_1 = require("../executor/ExecutionMetadata");
12
14
  const executor_1 = require("../executor");
13
15
  const BackendRegistry_1 = require("./BackendRegistry");
14
16
  const DelegationStore_1 = require("./DelegationStore");
@@ -18,8 +20,10 @@ const contract_1 = require("./contract");
18
20
  const WorkerPolicy_1 = require("./WorkerPolicy");
19
21
  const DelegationNotice_1 = require("./DelegationNotice");
20
22
  const WorkingDirectory_1 = require("../utils/WorkingDirectory");
21
- exports.DELEGATION_LIMITS = { launches: 12, concurrent: 3,
23
+ const DelegatedWorkspaceManager_1 = require("./DelegatedWorkspaceManager");
24
+ exports.DELEGATION_LIMITS = { launches: 12, concurrent: 5,
22
25
  idleTimeoutMs: 15 * 60_000, toolIdleTimeoutMs: 45 * 60_000,
26
+ queueTimeoutMs: 60 * 60_000,
23
27
  resultBytes: 32 * 1024, continuationBytes: 64 * 1024,
24
28
  storageTimeoutMs: 10_000, calls: 500 };
25
29
  /** Bounds and cadence for optional display-only delegated-worker text. */
@@ -34,11 +38,12 @@ function validDuration(value, fallback) {
34
38
  function timeoutPolicy(input) {
35
39
  if (typeof input === 'number') {
36
40
  const duration = validDuration(input, exports.DELEGATION_LIMITS.idleTimeoutMs);
37
- return { idleTimeoutMs: duration, toolIdleTimeoutMs: duration };
41
+ return { idleTimeoutMs: duration, toolIdleTimeoutMs: duration, queueTimeoutMs: exports.DELEGATION_LIMITS.queueTimeoutMs };
38
42
  }
39
43
  return {
40
44
  idleTimeoutMs: validDuration(input.idleTimeoutMs, exports.DELEGATION_LIMITS.idleTimeoutMs),
41
45
  toolIdleTimeoutMs: validDuration(input.toolIdleTimeoutMs, exports.DELEGATION_LIMITS.toolIdleTimeoutMs),
46
+ queueTimeoutMs: validDuration(input.queueTimeoutMs, exports.DELEGATION_LIMITS.queueTimeoutMs),
42
47
  };
43
48
  }
44
49
  function formatDuration(milliseconds) {
@@ -122,17 +127,20 @@ class DelegationManager {
122
127
  registry;
123
128
  store;
124
129
  laneStore;
130
+ workspaceManager;
125
131
  running = 0;
126
132
  workspaces = new Map();
133
+ pumps = new Set();
127
134
  scopes = new Map();
128
135
  timeouts;
129
- constructor(guard, factory = executor_1.createExecutor, registry = new BackendRegistry_1.BackendRegistry(), store = new DelegationStore_1.DelegationStore(), timeoutInput = exports.DELEGATION_LIMITS, cleanupMs = 10_000, laneStore = new DelegatedWorkerSessionStore_1.DelegatedWorkerSessionStore()) {
136
+ constructor(guard, factory = executor_1.createExecutor, registry = new BackendRegistry_1.BackendRegistry(), store = new DelegationStore_1.DelegationStore(), timeoutInput = exports.DELEGATION_LIMITS, cleanupMs = 10_000, laneStore = new DelegatedWorkerSessionStore_1.DelegatedWorkerSessionStore(), workspaceManager = new DelegatedWorkspaceManager_1.DelegatedWorkspaceManager()) {
130
137
  this.guard = guard;
131
138
  this.factory = factory;
132
139
  this.cleanupMs = cleanupMs;
133
140
  this.registry = registry;
134
141
  this.store = store;
135
142
  this.laneStore = laneStore;
143
+ this.workspaceManager = workspaceManager;
136
144
  this.timeouts = timeoutPolicy(timeoutInput);
137
145
  }
138
146
  begin(parent) {
@@ -147,12 +155,32 @@ class DelegationManager {
147
155
  let closed = false;
148
156
  let launches = 0;
149
157
  let launchRevision = 0;
150
- let starting = false;
158
+ let admissions = 0;
159
+ let admissionTail = Promise.resolve();
160
+ const activeTasks = new Set();
161
+ let integrating = false;
162
+ let integrationPending;
163
+ let source;
164
+ let sourcePromise;
165
+ let baseline;
166
+ let maxChildren = 1;
167
+ const workspaceOwner = { id: (0, crypto_1.randomUUID)(), threadId: parent.thread.id, quarantined: false };
168
+ const workspaceGeneration = parent.thread.delegationWorkspaceGeneration ?? 0;
151
169
  let execution = 0;
152
170
  let acceptingResults = true;
153
171
  const stagedResults = new Set();
154
172
  const cwd = fs_1.default.realpathSync(this.guard.resolveWorkingDirectory(parent.cwd));
173
+ let leasePath = cwd;
155
174
  this.guard.resolveWorkingDirectory(cwd);
175
+ const discoverWorkspace = () => sourcePromise ??= this.workspaceManager.discover(cwd).then(found => {
176
+ if (found) {
177
+ // A full repository checkpoint must not read outside the directory-selection policy.
178
+ this.guard.resolveWorkingDirectory(found.root);
179
+ source = found;
180
+ leasePath = found.root;
181
+ maxChildren = exports.DELEGATION_LIMITS.concurrent;
182
+ }
183
+ });
156
184
  const getTask = (value) => {
157
185
  const task = typeof value === 'string' ? tasks.get(value) : undefined;
158
186
  if (!task)
@@ -160,12 +188,15 @@ class DelegationManager {
160
188
  return task;
161
189
  };
162
190
  const view = (task) => ({ taskId: task.record.id, backend: task.record.backend,
163
- state: task.record.finishedAt ? task.record.state : 'running',
164
- output: task.record.finishedAt ? task.record.output : undefined,
165
- error: task.record.finishedAt ? task.record.error : undefined,
166
- truncated: task.record.truncated ?? false });
191
+ state: task.record.finishedAt !== undefined ? task.record.state : task.record.startedAt === undefined ? 'queued' : 'running',
192
+ output: task.record.finishedAt !== undefined ? task.record.output : undefined,
193
+ error: task.record.finishedAt !== undefined ? task.record.error : undefined,
194
+ truncated: task.record.truncated ?? false,
195
+ ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}),
196
+ ...(task.artifact ? { artifact: { taskId: task.artifact.taskId,
197
+ disposition: task.artifact.disposition, outputCommit: task.artifact.outputCommit } } : {}) });
167
198
  const receive = (task, requestedDuring) => {
168
- if (task.record.finishedAt && acceptingResults && requestedDuring === execution)
199
+ if (task.record.finishedAt !== undefined && acceptingResults && requestedDuring === execution)
169
200
  stagedResults.add(task.record.id);
170
201
  return view(task);
171
202
  };
@@ -180,9 +211,35 @@ class DelegationManager {
180
211
  console.warn('[Delegation] Progress could not be delivered');
181
212
  }
182
213
  };
214
+ const releaseWorkspaceIfIdle = () => {
215
+ if (integrating || admissions || activeTasks.size || [...tasks.values()].some(task => !task.settled))
216
+ return;
217
+ // A new idle cohort must observe coordinator edits even if the last task was read-only.
218
+ baseline = undefined;
219
+ if (this.workspaces.get(leasePath) === workspaceOwner && !workspaceOwner.quarantined)
220
+ this.workspaces.delete(leasePath);
221
+ };
222
+ const assertWorkspace = () => {
223
+ (0, WorkingDirectory_1.assertWorkingDirectoryExists)(parent.cwd);
224
+ if (fs_1.default.realpathSync(this.guard.resolveWorkingDirectory(parent.cwd)) !== cwd
225
+ || (parent.thread.delegationWorkspaceGeneration ?? 0) !== workspaceGeneration) {
226
+ throw new Error('Delegated workspace changed before the task could start');
227
+ }
228
+ if (this.workspaces.get(leasePath) !== workspaceOwner || workspaceOwner.quarantined || parent.isWorkspaceBusy?.(leasePath)) {
229
+ throw new Error('Delegation capacity or workspace is busy; task was not started');
230
+ }
231
+ };
232
+ const captureWorkerMetadata = (task) => {
233
+ if (!task.executor || task.record.startedAt === undefined)
234
+ return;
235
+ task.executionMetadata = (0, ExecutionMetadata_1.mergeReportedExecutionMetadata)(task.executionMetadata, (0, ExecutionMetadata_1.captureExecutionMetadata)(task.executor, task.record.backend));
236
+ };
183
237
  const reportProgress = (task, progress) => {
184
238
  try {
185
- return parent.onProgress?.({ taskId: task.record.id, backend: task.record.backend, ...progress }) === true;
239
+ if (task.record.state === 'running')
240
+ captureWorkerMetadata(task);
241
+ return parent.onProgress?.({ taskId: task.record.id, backend: task.record.backend, ...progress,
242
+ ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}) }) === true;
186
243
  }
187
244
  catch {
188
245
  console.warn('[Delegation] Progress could not be delivered');
@@ -191,12 +248,14 @@ class DelegationManager {
191
248
  };
192
249
  const reportTextProgress = (task, latestText) => {
193
250
  try {
251
+ captureWorkerMetadata(task);
194
252
  return parent.onTextProgress?.({
195
253
  taskId: task.record.id,
196
254
  backend: task.record.backend,
197
255
  phase: 'text',
198
256
  latestText,
199
257
  startedAt: task.record.startedAt,
258
+ ...(task.executionMetadata ? { executionMetadata: { ...task.executionMetadata } } : {}),
200
259
  }) === true;
201
260
  }
202
261
  catch {
@@ -240,7 +299,7 @@ class DelegationManager {
240
299
  scheduleTextProgress(task);
241
300
  };
242
301
  const reportTerminalProgress = (task) => {
243
- if (task.record.state === 'running')
302
+ if (!(0, DelegationStore_1.isTerminalTaskState)(task.record.state) || task.record.startedAt === undefined)
244
303
  return false;
245
304
  return reportProgress(task, {
246
305
  phase: task.record.state,
@@ -258,6 +317,8 @@ class DelegationManager {
258
317
  };
259
318
  const discardLane = async (lane) => {
260
319
  try {
320
+ if (source)
321
+ await this.workspaceManager.preserveLane(lane.id);
261
322
  await (0, DelegatedWorkerLaneCleanup_1.cleanupDelegatedWorkerLane)(lane);
262
323
  await this.laneStore.remove(lane.id);
263
324
  return true;
@@ -280,6 +341,7 @@ class DelegationManager {
280
341
  return;
281
342
  }
282
343
  try {
344
+ captureWorkerMetadata(task);
283
345
  try {
284
346
  await deadline(() => task.executor.abort(), Math.min(2000, this.cleanupMs));
285
347
  }
@@ -323,8 +385,45 @@ class DelegationManager {
323
385
  task.record.lastActivityKind = kind;
324
386
  armIdleTimer(task);
325
387
  };
388
+ const finishTask = async (task) => {
389
+ clearTimeout(task.queueTimer);
390
+ task.record.finishedAt = Date.now();
391
+ await deadline(async () => { await save(task); await this.store.prune(); }, this.cleanupMs)
392
+ .catch(() => console.warn('[Delegation] Final task metadata could not be saved or pruned'));
393
+ if (!reportTerminalProgress(task)) {
394
+ // A never-started task has no tool/progress card to close on older peers.
395
+ if (task.record.startedAt !== undefined) {
396
+ notify(() => parent.onToolResult({ tool_use_id: task.record.id, content: JSON.stringify(view(task)), is_error: task.record.state !== 'succeeded' }));
397
+ }
398
+ notify(() => parent.onNotice((0, DelegationNotice_1.formatDelegationNotice)(task.record)));
399
+ }
400
+ task.settle();
401
+ activeTasks.delete(task);
402
+ releaseWorkspaceIfIdle();
403
+ for (const start of this.pumps)
404
+ start();
405
+ };
406
+ const completeUnstarted = (task, state, error) => {
407
+ if (task.finishUnstarted)
408
+ return task.finishUnstarted;
409
+ task.record.state = state;
410
+ task.record.error = error;
411
+ return task.finishUnstarted = finishTask(task);
412
+ };
413
+ const armQueueTimer = (task) => {
414
+ const remaining = Math.max(0, task.record.acceptedAt + this.timeouts.queueTimeoutMs - Date.now());
415
+ task.queueTimer = setTimeout(() => {
416
+ if (task.record.state !== 'queued' || task.ownsSlot)
417
+ return;
418
+ void completeUnstarted(task, 'timed_out', `Task was not started within ${formatDuration(this.timeouts.queueTimeoutMs)} of admission.`);
419
+ }, remaining);
420
+ };
326
421
  const cancel = async (task) => {
327
- if (task.record.state === 'running') {
422
+ if (task.record.state === 'queued' && !task.ownsSlot) {
423
+ await completeUnstarted(task, 'cancelled', 'Cancelled before the worker started.');
424
+ }
425
+ else if (!(0, DelegationStore_1.isTerminalTaskState)(task.record.state)) {
426
+ clearTimeout(task.queueTimer);
328
427
  clearTaskTimer(task);
329
428
  clearTextProgress(task, true);
330
429
  task.record.state = 'cancelled';
@@ -334,27 +433,53 @@ class DelegationManager {
334
433
  await task.done;
335
434
  return view(task);
336
435
  };
337
- const run = async (task, configuration, objective) => {
436
+ const run = async (task) => {
437
+ const { configuration, objective } = task;
438
+ let metadataCaptured = false;
338
439
  const backend = task.record.backend;
339
440
  const identity = {
340
441
  threadId: parent.thread.id,
341
442
  backend,
342
443
  workingDirectory: cwd,
343
- workspaceGeneration: parent.thread.delegationWorkspaceGeneration ?? 0,
444
+ workspaceGeneration,
445
+ };
446
+ const assertStartable = () => {
447
+ if (closed || (0, DelegationStore_1.isTerminalTaskState)(task.record.state) || task.quarantineWorkspace) {
448
+ throw new Error('Delegated task ended before execution could start');
449
+ }
450
+ assertWorkspace();
344
451
  };
345
452
  const createWorker = async () => {
453
+ assertStartable();
346
454
  if (!task.lane) {
347
- const acquired = await this.laneStore.acquire(identity);
455
+ const acquired = await this.laneStore.acquire(identity, source
456
+ ? { pooled: true, excluded: await this.workspaceManager.unavailableLanes() } : undefined);
348
457
  task.lane = acquired.lane;
458
+ if (task.settled || task.quarantineWorkspace) {
459
+ // Setup can finish after its deadline, but must never start a late
460
+ // worker or leave that acquisition resumable.
461
+ await this.laneStore.markDirty(acquired.lane.id, 'Worker setup completed after its task ended.', true).catch(() => undefined);
462
+ }
349
463
  }
350
464
  let worker;
351
465
  try {
352
- worker = this.factory(this.guard, configuration, cwd, task.lane.executorThreadId, (0, ThreadExecutorPool_1.resolveThreadModel)(parent.thread, configuration), parent.thread.efforts?.[backend], { lifecycleHooks: false, delegationWorker: true });
466
+ assertStartable();
467
+ if (source && !task.workspace) {
468
+ baseline ??= this.workspaceManager.baseline(source);
469
+ const input = await baseline;
470
+ assertStartable();
471
+ task.workspace = await this.workspaceManager.prepare(task.lane, source, input, task.record.id);
472
+ assertStartable();
473
+ }
474
+ const executionCwd = task.workspace?.cwd ?? cwd;
475
+ const workerGuard = task.workspace ? new DirectoryGuard_1.DirectoryGuard([...this.guard.getAllowedDirectories(), executionCwd]) : this.guard;
476
+ worker = this.factory(workerGuard, configuration, executionCwd, task.lane.executorThreadId, (0, ThreadExecutorPool_1.resolveThreadModel)(parent.thread, configuration), parent.thread.efforts?.[backend], { lifecycleHooks: false, delegationWorker: true });
353
477
  task.executor = worker;
354
- if (fs_1.default.realpathSync(worker.getCurrentWorkingDirectory()) !== cwd) {
478
+ if (fs_1.default.realpathSync(worker.getCurrentWorkingDirectory()) !== fs_1.default.realpathSync(executionCwd)) {
355
479
  throw new Error('Worker refused the delegated workspace; refusing a fallback directory');
356
480
  }
357
481
  await this.laneStore.markRunning(task.lane.id);
482
+ assertStartable();
358
483
  return worker;
359
484
  }
360
485
  catch (error) {
@@ -386,17 +511,42 @@ class DelegationManager {
386
511
  await this.laneStore.markDirty(lane.id, 'Native worker session was unavailable before the objective was dispatched.', true);
387
512
  const discarded = await discardLane(lane);
388
513
  task.lane = undefined;
514
+ task.workspace = undefined;
389
515
  return discarded;
390
516
  };
391
517
  try {
392
- if (task.record.state !== 'running')
518
+ if ((0, DelegationStore_1.isTerminalTaskState)(task.record.state))
393
519
  return;
394
520
  let result;
395
521
  for (let attempt = 0; attempt < 2; attempt++) {
396
- const worker = await createWorker();
522
+ let setupPending = true;
523
+ const setup = createWorker().finally(() => { setupPending = false; });
524
+ let worker;
525
+ try {
526
+ worker = await deadline(() => setup, exports.DELEGATION_LIMITS.storageTimeoutMs, 'Worker setup timed out');
527
+ }
528
+ catch (error) {
529
+ if (setupPending)
530
+ task.quarantineWorkspace = true;
531
+ throw error;
532
+ }
533
+ assertStartable();
534
+ task.executionMetadata = (0, ExecutionMetadata_1.captureExecutionMetadata)(worker, backend);
535
+ metadataCaptured = false;
536
+ if (task.record.startedAt === undefined) {
537
+ task.record.state = 'running';
538
+ task.record.startedAt = Date.now();
539
+ void save(task);
540
+ if (!reportProgress(task, { phase: 'started', objective: task.record.objective, startedAt: task.record.startedAt })) {
541
+ notify(() => parent.onToolUse({ id: task.record.id, name: 'Task', input: {
542
+ description: `${backend}: ${objective.slice(0, 120)}`, prompt: objective.slice(0, 1000), subagent_type: backend,
543
+ } }));
544
+ }
545
+ }
397
546
  // Native session IDs are distinct; policy was resolved against the real
398
547
  // parent thread before passing the synthetic worker identity to the factory.
399
- 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.
548
+ 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
549
+ ? `\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.` : ''}
400
550
 
401
551
  ${objective}`, {
402
552
  // The manager owns delegated-task liveness. Zero disables optional
@@ -462,8 +612,11 @@ ${prompt}`));
462
612
  });
463
613
  // Start the inactivity window only once the worker has accepted the
464
614
  // objective and can emit its first real progress callback.
615
+ task.signalDispatch();
465
616
  armIdleTimer(task);
466
617
  result = await Promise.race([task.interrupted, execution]);
618
+ captureWorkerMetadata(task);
619
+ metadataCaptured = true;
467
620
  if (!result.success && attempt === 0 && worker.consumeSessionResumeFailure?.()) {
468
621
  clearTaskTimer(task);
469
622
  clearTextProgress(task, true);
@@ -484,70 +637,120 @@ ${prompt}`));
484
637
  }
485
638
  }
486
639
  catch (error) {
487
- if (task.record.state === 'running') {
640
+ if (!(0, DelegationStore_1.isTerminalTaskState)(task.record.state)) {
488
641
  task.record.state = 'failed';
489
642
  task.record.error = bounded(error instanceof Error ? error.message : 'Worker failed', 4000).text;
490
643
  }
491
644
  this.registry.invalidate();
492
645
  }
493
646
  finally {
647
+ if (!metadataCaptured)
648
+ captureWorkerMetadata(task);
494
649
  clearTaskTimer(task);
495
650
  clearTextProgress(task, true);
651
+ if (task.record.startedAt === undefined)
652
+ task.discardLane = true;
496
653
  let released = !task.quarantineWorkspace;
497
654
  try {
498
- if (task.stop)
499
- await task.stop;
500
- else if (task.executor)
501
- await deadline(() => destroyWorker(task.executor), this.cleanupMs);
502
- else if (task.lane) {
503
- // A scope can close after durable acquisition but before construction.
504
- // No backend process was started, so discard the lane rather than
505
- // reserving the workspace indefinitely.
506
- task.discardLane = true;
507
- }
508
- if (released && task.lane && task.discardLane) {
509
- const lane = task.lane;
510
- await this.laneStore.markDirty(lane.id, 'Delegated worker setup ended before a reusable context was established.', true).catch(() => undefined);
511
- await discardLane(lane);
512
- task.lane = undefined;
513
- }
514
- if (released && task.lane) {
515
- try {
516
- await this.laneStore.markReady(task.lane.id);
655
+ await deadline(async () => {
656
+ if (task.stop)
657
+ await task.stop;
658
+ else if (task.executor)
659
+ await deadline(() => destroyWorker(task.executor), this.cleanupMs);
660
+ else if (task.lane) {
661
+ // A scope can close after durable acquisition but before construction.
662
+ // No backend process was started, so discard the lane rather than
663
+ // reserving the workspace indefinitely.
664
+ task.discardLane = true;
517
665
  }
518
- catch (error) {
519
- const detail = error instanceof Error ? error.message : 'Worker lane persistence failed';
520
- await this.laneStore.markDirty(task.lane.id, detail, true).catch(() => undefined);
521
- await discardLane(task.lane);
522
- console.warn('[Delegation] Worker lane could not be retained:', detail);
666
+ if (released && task.lane && task.discardLane) {
667
+ const lane = task.lane;
668
+ await this.laneStore.markDirty(lane.id, 'Delegated worker setup ended before a reusable context was established.', true).catch(() => undefined);
669
+ await discardLane(lane);
670
+ task.lane = undefined;
523
671
  }
524
- }
672
+ if (released && task.lane) {
673
+ try {
674
+ if (task.workspace) {
675
+ task.artifact = await this.workspaceManager.collect(task.workspace, task.record.state === 'succeeded');
676
+ }
677
+ await this.laneStore.markReady(task.lane.id);
678
+ }
679
+ catch (error) {
680
+ const detail = error instanceof Error ? error.message : 'Worker lane persistence failed';
681
+ if (task.workspace) {
682
+ // Exit was confirmed. Preserve files and context for recovery without blocking unrelated lanes.
683
+ await this.workspaceManager.preserveLane(task.lane.id).catch(() => undefined);
684
+ await this.laneStore.markDirty(task.lane.id, detail).catch(() => undefined);
685
+ if (task.record.state === 'succeeded')
686
+ task.record.state = 'failed';
687
+ task.record.error = bounded(`Worker artifact could not be finalized: ${detail}. Files were retained for manual recovery.`, 4000).text;
688
+ }
689
+ else {
690
+ await this.laneStore.markDirty(task.lane.id, detail, true).catch(() => undefined);
691
+ await discardLane(task.lane);
692
+ }
693
+ console.warn('[Delegation] Worker lane could not be retained:', detail);
694
+ }
695
+ }
696
+ }, this.cleanupMs);
525
697
  }
526
698
  catch (error) {
527
699
  released = false;
528
700
  task.quarantineWorkspace = true;
529
701
  if (task.lane) {
530
702
  const detail = error instanceof Error ? error.message : 'Worker cleanup could not be confirmed';
531
- await this.laneStore.markDirty(task.lane.id, detail).catch(() => undefined);
703
+ await deadline(() => this.laneStore.markDirty(task.lane.id, detail), this.cleanupMs).catch(() => undefined);
532
704
  }
533
705
  task.record.state = 'interrupted';
534
706
  task.record.error = 'Worker cleanup could not be confirmed. This workspace is blocked until the worker is stopped and the CLI restarts.';
535
707
  console.warn('[Delegation] Worker cleanup failed:', error instanceof Error ? error.message : 'Cleanup failure');
536
708
  }
537
- if (released) {
709
+ if (released && task.ownsSlot) {
710
+ task.ownsSlot = false;
538
711
  this.running--;
539
- this.workspaces.delete(cwd);
540
712
  }
541
- else
542
- this.workspaces.set(cwd, `quarantined:${parent.thread.id}`);
543
- task.record.finishedAt = Date.now();
544
- await deadline(async () => { await save(task); await this.store.prune(); }, this.cleanupMs)
545
- .catch(() => console.warn('[Delegation] Final task metadata could not be saved or pruned'));
546
- if (!reportTerminalProgress(task)) {
547
- notify(() => parent.onToolResult({ tool_use_id: task.record.id, content: JSON.stringify(view(task)), is_error: task.record.state !== 'succeeded' }));
548
- notify(() => parent.onNotice((0, DelegationNotice_1.formatDelegationNotice)(task.record)));
713
+ else if (!released) {
714
+ workspaceOwner.quarantined = true;
715
+ task.record.state = 'interrupted';
716
+ task.record.error = 'Worker cleanup could not be confirmed. This workspace is blocked until the worker is stopped and the CLI restarts.';
549
717
  }
550
- task.settle();
718
+ await finishTask(task);
719
+ }
720
+ };
721
+ const startTask = (task) => {
722
+ clearTimeout(task.queueTimer);
723
+ activeTasks.add(task);
724
+ task.ownsSlot = true;
725
+ this.running++;
726
+ void run(task).catch(() => undefined);
727
+ };
728
+ const pump = () => {
729
+ if (closed || integrating || activeTasks.size >= maxChildren)
730
+ return;
731
+ const queued = [...tasks.values()].filter(task => task.record.state === 'queued' && !task.ownsSlot && !task.finishUnstarted);
732
+ if (workspaceOwner.quarantined) {
733
+ for (const task of queued)
734
+ void completeUnstarted(task, 'interrupted', 'Task was not started because the previous worker exit could not be confirmed.');
735
+ return;
736
+ }
737
+ const task = queued[0];
738
+ if (!task)
739
+ return;
740
+ if (Date.now() >= task.record.acceptedAt + this.timeouts.queueTimeoutMs) {
741
+ void completeUnstarted(task, 'timed_out', `Task was not started within ${formatDuration(this.timeouts.queueTimeoutMs)} of admission.`);
742
+ return;
743
+ }
744
+ try {
745
+ assertWorkspace();
746
+ }
747
+ catch (error) {
748
+ void completeUnstarted(task, 'failed', bounded(error instanceof Error ? error.message : 'Workspace unavailable', 4000).text);
749
+ return;
750
+ }
751
+ if (this.running < exports.DELEGATION_LIMITS.concurrent) {
752
+ startTask(task);
753
+ pump();
551
754
  }
552
755
  };
553
756
  const invoke = async (name, args, callId) => {
@@ -567,6 +770,7 @@ ${prompt}`));
567
770
  throw new Error('Delegation tool-call limit reached');
568
771
  const result = (async () => {
569
772
  if (name === 'remote_cli_list_backends') {
773
+ await discoverWorkspace();
570
774
  const backends = await this.registry.list(parent.config);
571
775
  return { backends: backends.map(item => {
572
776
  const configure = (mode) => (0, WorkerPolicy_1.workerConfiguration)(parent.config, this.guard, parent.thread.id, parent.backend, item.backend, mode);
@@ -586,14 +790,16 @@ ${prompt}`));
586
790
  }
587
791
  }
588
792
  return { ...item, readOnly };
589
- }), workspace: cwd, maxConcurrentChildren: 1 };
793
+ }), workspace: cwd, maxConcurrentChildren: maxChildren,
794
+ maxTasksPerRequest: exports.DELEGATION_LIMITS.launches, scheduling: source ? 'isolated-worktrees' : 'serial',
795
+ queueTimeoutSeconds: this.timeouts.queueTimeoutMs / 1000 };
590
796
  }
591
797
  if (name === 'remote_cli_result') {
592
798
  const task = getTask(args.taskId);
593
799
  const seconds = args.waitSeconds ?? 25;
594
800
  if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0 || seconds > 25)
595
801
  throw new Error('waitSeconds must be between 0 and 25');
596
- if (!task.record.finishedAt && seconds > 0) {
802
+ if (task.record.finishedAt === undefined && seconds > 0) {
597
803
  let timer;
598
804
  try {
599
805
  await Promise.race([task.done, new Promise(resolve => { timer = setTimeout(resolve, seconds * 1000); })]);
@@ -609,14 +815,42 @@ ${prompt}`));
609
815
  await cancel(task);
610
816
  return receive(task, requestedDuring);
611
817
  }
818
+ if (name === 'remote_cli_integrate') {
819
+ if (!acceptingResults)
820
+ throw new Error('The coordinator execution has ended');
821
+ await discoverWorkspace();
822
+ if (!source)
823
+ throw new Error('Non-Git delegation uses the shared workspace and has no worktree artifacts');
824
+ if (integrating || admissions || activeTasks.size || [...tasks.values()].some(task => !task.settled)) {
825
+ throw new Error('Wait for all accepted workers to finish before integrating artifacts');
826
+ }
827
+ if (workspaceOwner.quarantined || [...this.workspaces].some(([active, owner]) => owner !== workspaceOwner && workspacesOverlap(active, leasePath)) || parent.isWorkspaceBusy?.(leasePath)) {
828
+ throw new Error('Delivery workspace is busy; integration was not started');
829
+ }
830
+ integrating = true;
831
+ this.workspaces.set(leasePath, workspaceOwner);
832
+ try {
833
+ assertWorkspace();
834
+ integrationPending = this.workspaceManager.integrate({ threadId: parent.thread.id, cwd, workspaceGeneration,
835
+ checkActive: () => { if (closed)
836
+ throw new Error('Integration was cancelled before delivery'); assertWorkspace(); } }, String(args.taskId ?? ''), args.action ?? 'inspect', args.expectedRevision);
837
+ const result = await integrationPending;
838
+ baseline = undefined;
839
+ const task = tasks.get(String(args.taskId));
840
+ if (task)
841
+ task.artifact = await this.workspaceManager.describe(task.record.id);
842
+ return result;
843
+ }
844
+ finally {
845
+ integrationPending = undefined;
846
+ integrating = false;
847
+ releaseWorkspaceIfIdle();
848
+ }
849
+ }
612
850
  if (name !== 'remote_cli_delegate')
613
851
  throw new Error('Unknown delegation tool');
614
852
  if (!acceptingResults)
615
853
  throw new Error('The coordinator execution has ended');
616
- if (starting || [...tasks.values()].some(task => !task.record.finishedAt))
617
- throw new Error('Wait for the current delegated task before starting another');
618
- if (launches >= exports.DELEGATION_LIMITS.launches)
619
- throw new Error('Delegated task limit reached');
620
854
  if (!contract_1.DELEGATION_BACKENDS.includes(args.backend))
621
855
  throw new Error('Unsupported worker backend');
622
856
  if (typeof args.objective !== 'string' || !args.objective.trim() || args.objective.length > 24_000)
@@ -627,77 +861,107 @@ ${prompt}`));
627
861
  throw new Error('Unsupported execution mode');
628
862
  const backend = args.backend;
629
863
  const configuration = (0, WorkerPolicy_1.workerConfiguration)(parent.config, this.guard, parent.thread.id, parent.backend, backend, mode);
864
+ if (integrating)
865
+ throw new Error('Artifact integration is in progress; wait before delegating');
630
866
  launchRevision++;
631
- starting = true;
632
- try {
633
- const available = await this.registry.get(backend, parent.config);
867
+ admissions++;
868
+ const discovery = this.registry.get(backend, parent.config);
869
+ void discovery.catch(() => undefined);
870
+ // Serialize admission, not worker execution. Replayed calls share the
871
+ // original promise; simultaneous new calls cannot overdraw the quota.
872
+ const admitted = admissionTail.then(async () => {
873
+ if (closed)
874
+ throw new Error('Delegation turn has ended');
875
+ if (launches >= exports.DELEGATION_LIMITS.launches)
876
+ throw new Error('Delegated task limit reached');
877
+ const available = await discovery;
634
878
  if (!available.installed)
635
879
  throw new Error(`${backend}: ${available.reason}`);
880
+ await discoverWorkspace();
881
+ if (integrating)
882
+ throw new Error('Artifact integration is in progress; wait before delegating');
636
883
  await deadline(() => this.store.initialize(), exports.DELEGATION_LIMITS.storageTimeoutMs, 'Task record initialization timed out');
637
884
  if (closed)
638
885
  throw new Error('Delegation turn has ended');
639
- if (this.running >= exports.DELEGATION_LIMITS.concurrent
640
- || [...this.workspaces.keys()].some(active => workspacesOverlap(active, cwd))
641
- || parent.isWorkspaceBusy?.(cwd))
886
+ const idle = !activeTasks.size && ![...tasks.values()].some(task => !task.settled);
887
+ const first = activeTasks.size < maxChildren && this.running < exports.DELEGATION_LIMITS.concurrent
888
+ && ![...tasks.values()].some(task => task.record.state === 'queued' && !task.ownsSlot && !task.finishUnstarted);
889
+ if ((idle && this.running >= exports.DELEGATION_LIMITS.concurrent)
890
+ || workspaceOwner.quarantined
891
+ || [...this.workspaces].some(([active, owner]) => owner !== workspaceOwner && workspacesOverlap(active, leasePath))
892
+ || parent.isWorkspaceBusy?.(leasePath))
642
893
  throw new Error('Delegation capacity or workspace is busy; wait before retrying');
894
+ this.workspaces.set(leasePath, workspaceOwner);
895
+ assertWorkspace();
643
896
  launches++;
644
- this.running++;
645
- this.workspaces.set(cwd, parent.thread.id);
646
- let settle;
897
+ let resolveDone;
898
+ let resolveDispatch;
647
899
  let interrupt;
648
900
  const interrupted = new Promise(resolve => { interrupt = () => resolve({ success: false }); });
649
901
  const task = { record: { id: (0, crypto_1.randomUUID)(), threadId: parent.thread.id,
650
902
  parentMessageId: parent.messageId, backend, objective: objective.slice(0, 1000),
651
- state: 'running', startedAt: Date.now() }, resultDelivered: false,
903
+ state: 'queued', acceptedAt: Date.now() }, configuration, objective,
904
+ resultDelivered: false, settled: false, ownsSlot: first,
652
905
  textProgressEnabled: parent.onTextProgress !== undefined,
653
- activeToolIds: new Set(), done: new Promise(resolve => { settle = resolve; }), settle, interrupted, interrupt };
654
- tasks.set(task.record.id, task);
655
- try {
656
- await deadline(() => this.store.write(task.record), exports.DELEGATION_LIMITS.storageTimeoutMs, 'Initial task record write timed out');
657
- }
658
- catch (error) {
659
- tasks.delete(task.record.id);
660
- this.running--;
661
- this.workspaces.delete(cwd);
662
- launches--;
663
- task.settle();
664
- throw error;
906
+ activeToolIds: new Set(), done: new Promise(resolve => { resolveDone = resolve; }),
907
+ dispatched: new Promise(resolve => { resolveDispatch = resolve; }), signalDispatch: () => resolveDispatch(),
908
+ settle: () => { if (task.settled)
909
+ return; task.settled = true; resolveDone(); resolveDispatch(); }, interrupted, interrupt };
910
+ if (first) {
911
+ activeTasks.add(task);
912
+ this.running++;
665
913
  }
666
914
  try {
667
- const lane = await this.laneStore.acquire({
668
- threadId: parent.thread.id,
669
- backend,
670
- workingDirectory: cwd,
671
- workspaceGeneration: parent.thread.delegationWorkspaceGeneration ?? 0,
672
- });
673
- task.lane = lane.lane;
915
+ await deadline(() => this.store.write(task.record), exports.DELEGATION_LIMITS.storageTimeoutMs, 'Initial task record write timed out');
674
916
  }
675
917
  catch (error) {
676
- task.record.state = 'failed';
677
- task.record.error = bounded(error instanceof Error ? error.message : 'Worker lane storage failed', 4000).text;
678
- task.record.finishedAt = Date.now();
679
- await this.store.write(task.record).catch(() => undefined);
680
- tasks.delete(task.record.id);
681
- this.running--;
682
- this.workspaces.delete(cwd);
918
+ if (task.ownsSlot) {
919
+ this.running--;
920
+ task.ownsSlot = false;
921
+ activeTasks.delete(task);
922
+ }
683
923
  launches--;
684
924
  task.settle();
685
925
  throw error;
686
926
  }
927
+ // The durable queued record is the acceptance boundary. Every task
928
+ // after it consumes quota, including setup failures and cancellation.
929
+ tasks.set(task.record.id, task);
687
930
  if (closed) {
688
- task.record.state = 'cancelled';
931
+ if (task.ownsSlot) {
932
+ this.running--;
933
+ task.ownsSlot = false;
934
+ activeTasks.delete(task);
935
+ }
936
+ await completeUnstarted(task, 'cancelled', 'Cancelled before the worker started.');
689
937
  }
690
- if (!reportProgress(task, { phase: 'started', objective: task.record.objective, startedAt: task.record.startedAt })) {
691
- notify(() => parent.onToolUse({ id: task.record.id, name: 'Task', input: {
692
- description: `${backend}: ${objective.slice(0, 120)}`, prompt: objective.slice(0, 1000), subagent_type: backend,
693
- } }));
938
+ else if (task.ownsSlot) {
939
+ void run(task).catch(() => undefined);
694
940
  }
695
- void run(task, configuration, objective).catch(() => undefined);
696
- return receive(task, requestedDuring);
941
+ else {
942
+ armQueueTimer(task);
943
+ notify(() => parent.onNotice(`\n\n⏳ ${backend} delegated task ${task.record.id.slice(0, 8)} queued. It will start after earlier tasks finish; queue wait is limited to ${formatDuration(this.timeouts.queueTimeoutMs)}.\n\n`));
944
+ pump();
945
+ }
946
+ return task;
947
+ });
948
+ admissionTail = admitted.catch(() => undefined);
949
+ let task;
950
+ try {
951
+ task = await admitted;
697
952
  }
698
953
  finally {
699
- starting = false;
954
+ admissions--;
955
+ releaseWorkspaceIfIdle();
956
+ for (const start of this.pumps)
957
+ start();
700
958
  }
959
+ if (task.ownsSlot) {
960
+ // Preserve the existing first-task response after dispatch/setup,
961
+ // without holding up admission of followers while setup is pending.
962
+ await task.dispatched;
963
+ }
964
+ return receive(task, requestedDuring);
701
965
  })();
702
966
  calls.set(callId, { signature, result });
703
967
  return result;
@@ -705,7 +969,7 @@ ${prompt}`));
705
969
  const scope = {
706
970
  invoke,
707
971
  isClosed: () => closed,
708
- hasTasks: () => starting || tasks.size > 0,
972
+ hasTasks: () => admissions > 0 || tasks.size > 0,
709
973
  getLaunchRevision: () => launchRevision,
710
974
  beginExecution: (taskIds = []) => {
711
975
  if (closed)
@@ -714,7 +978,7 @@ ${prompt}`));
714
978
  acceptingResults = true;
715
979
  stagedResults.clear();
716
980
  for (const id of taskIds) {
717
- if (tasks.get(id)?.record.finishedAt)
981
+ if (tasks.get(id)?.record.finishedAt !== undefined)
718
982
  stagedResults.add(id);
719
983
  }
720
984
  },
@@ -726,9 +990,9 @@ ${prompt}`));
726
990
  }
727
991
  stagedResults.clear();
728
992
  },
729
- hasPendingResults: () => starting || [...tasks.values()].some(task => !task.resultDelivered && !stagedResults.has(task.record.id)),
993
+ hasPendingResults: () => admissions > 0 || [...tasks.values()].some(task => !task.resultDelivered && !stagedResults.has(task.record.id)),
730
994
  getRetainedResults: () => boundedResults([...tasks.values()]
731
- .filter(task => task.record.finishedAt && !task.resultDelivered).map(view)),
995
+ .filter(task => task.record.finishedAt !== undefined && !task.resultDelivered).map(view)),
732
996
  collectPendingResults: async () => {
733
997
  if (closed)
734
998
  throw new Error('Delegation was cancelled');
@@ -743,19 +1007,28 @@ ${prompt}`));
743
1007
  throw new Error('Delegation was cancelled');
744
1008
  return boundedResults(pending.map(view));
745
1009
  },
746
- waitingExecutor: () => [...tasks.values()].find(task => task.executor?.isWaitingInput?.())?.executor,
1010
+ waitingExecutor: () => {
1011
+ const waiting = [...activeTasks].filter(task => task.record.state === 'running' && task.executor?.isWaitingInput?.());
1012
+ return !closed && waiting.length === 1 ? waiting[0].executor : undefined;
1013
+ },
1014
+ waitingInputCount: () => closed ? 0 : [...activeTasks].filter(task => task.record.state === 'running' && task.executor?.isWaitingInput?.()).length,
747
1015
  close: async () => {
748
1016
  closed = true;
1017
+ this.pumps.delete(pump);
749
1018
  await Promise.allSettled([...tasks.values()].map(cancel));
1019
+ await integrationPending?.catch(() => undefined);
1020
+ releaseWorkspaceIfIdle();
750
1021
  // A version probe may still be pending. Its closed check prevents launch.
751
1022
  if (this.scopes.get(parent.thread.id) === scope)
752
1023
  this.scopes.delete(parent.thread.id);
753
1024
  },
754
1025
  };
1026
+ this.pumps.add(pump);
755
1027
  this.scopes.set(parent.thread.id, scope);
756
1028
  return scope;
757
1029
  }
758
1030
  waitingExecutor(threadId) { return this.scopes.get(threadId)?.waitingExecutor(); }
1031
+ hasAmbiguousInput(threadId) { return (this.scopes.get(threadId)?.waitingInputCount() ?? 0) > 1; }
759
1032
  hasActiveTasks(threadId) { return this.scopes.get(threadId)?.hasTasks() ?? false; }
760
1033
  /** Retry previously confirmed cleanup requests without touching uncertain interrupted workers. */
761
1034
  async reconcilePendingWorkerLanes() {
@@ -789,6 +1062,7 @@ ${prompt}`));
789
1062
  let failed = 0;
790
1063
  for (const lane of lanes) {
791
1064
  try {
1065
+ await this.workspaceManager.preserveLane(lane.id);
792
1066
  await (0, DelegatedWorkerLaneCleanup_1.cleanupDelegatedWorkerLane)(lane);
793
1067
  await this.laneStore.remove(lane.id);
794
1068
  removed++;
@@ -809,7 +1083,7 @@ ${prompt}`));
809
1083
  if (this.workspaces.size === 0)
810
1084
  return false;
811
1085
  const canonical = fs_1.default.realpathSync(cwd);
812
- return [...this.workspaces].some(([active, owner]) => owner !== threadId && workspacesOverlap(active, canonical));
1086
+ return [...this.workspaces].some(([active, owner]) => (owner.quarantined || owner.threadId !== threadId) && workspacesOverlap(active, canonical));
813
1087
  }
814
1088
  async cancelThread(threadId) { await this.scopes.get(threadId)?.close(); }
815
1089
  async destroy() { await Promise.allSettled([...this.scopes.values()].map(scope => scope.close())); }