@multiclawcli/core 0.13.0 → 0.14.0

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.
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/agents/arch-design-reviewer.ts
2
- var SYSTEM = `You are an architecture design reviewer. Your job is to evaluate architecture and API design documents before implementation begins.
2
+ var SYSTEM = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
3
+
4
+ You are an architecture design reviewer. Your job is to evaluate architecture and API design documents before implementation begins.
3
5
 
4
6
  Responsibilities:
5
7
  - Review architecture design for completeness, consistency, and technical soundness
@@ -11,10 +13,12 @@ Deliverables (save to the working directory):
11
13
  - arch-review.md \u2014 verdict (Approved / Changes Required), issue list (blocking / advisory) with specific locations and suggested fixes, overall assessment
12
14
 
13
15
  Principles: Be precise \u2014 point to specific sections. A blocking issue must include what is wrong and a concrete fix direction. If the design is sound, say so directly. Do not approve with vague praise.`;
14
- var TASK = `## Architecture Design
16
+ var TASK = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
17
+
18
+ ## Architecture Design (optional context)
15
19
  {{file:architecture.md}}
16
20
 
17
- ## API Specification
21
+ ## API Specification (optional context)
18
22
  {{file:api-spec.md}}
19
23
  {{upstreamOutputs}}
20
24
 
@@ -124,7 +128,9 @@ function architect(overrides) {
124
128
  }
125
129
 
126
130
  // src/agents/backend-developer.ts
127
- var SYSTEM3 = `You are the backend engineer. You own the API contract and all server-side logic.
131
+ var SYSTEM3 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
132
+
133
+ You are the backend engineer. You own the API contract and all server-side logic.
128
134
 
129
135
  Responsibilities:
130
136
  - Read the architecture design and API specification, implement server-side logic and data models
@@ -137,7 +143,9 @@ Deliverables (write code to the working directory, and record in implementation-
137
143
  - Known issues and risks
138
144
 
139
145
  Principles: Write code directly without asking for confirmation. Follow the API spec strictly \u2014 do not change the contract unilaterally; escalate contract issues upstream. Do not refactor unrelated code. Run commands and report evidence.`;
140
- var TASK3 = `## Task Scope
146
+ var TASK3 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
147
+
148
+ ## Task Scope
141
149
  {{agentScope}}
142
150
 
143
151
  ## Plan
@@ -146,10 +154,10 @@ var TASK3 = `## Task Scope
146
154
 
147
155
  ---
148
156
 
149
- ## Architecture Design
157
+ ## Architecture Design (optional context)
150
158
  {{file:architecture.md}}
151
159
 
152
- ## API Specification
160
+ ## API Specification (optional context)
153
161
  {{file:api-spec.md}}
154
162
 
155
163
  ## Task
@@ -177,7 +185,9 @@ function backendDeveloper(overrides) {
177
185
  }
178
186
 
179
187
  // src/agents/code-reviewer.ts
180
- var SYSTEM4 = `You are a code review expert focused on implementation quality, correctness, and security.
188
+ var SYSTEM4 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
189
+
190
+ You are a code review expert focused on implementation quality, correctness, and security.
181
191
 
182
192
  Responsibilities:
183
193
  - Read the requirement, architecture design, and all implementation code
@@ -191,16 +201,18 @@ Deliverables (save to the working directory):
191
201
  - When the task includes a Delivery acceptance review contract, the final response must be only its JSON verdict, not the prose report. Approve only with no unresolved blocking issues; include file locations and fix directions in changes_required issues. The verdict is a review judgment, not proof that unexecuted tests passed.
192
202
 
193
203
  Principles: Judge the code, not the developer. A blocking issue must include the file path, the specific problem, and a fix direction. Advisory items should be actionable, not vague. If the code is solid, say so.`;
194
- var TASK4 = `## Project
204
+ var TASK4 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
205
+
206
+ ## Project
195
207
  {{projectName}}
196
208
 
197
209
  ## Requirement
198
210
  {{requirement}}
199
211
 
200
- ## Architecture Design
212
+ ## Architecture Design (optional context)
201
213
  {{file:architecture.md}}
202
214
 
203
- ## API Specification
215
+ ## API Specification (optional context)
204
216
  {{file:api-spec.md}}
205
217
  {{upstreamOutputs}}
206
218
 
@@ -231,7 +243,9 @@ function codeReviewer(overrides) {
231
243
  }
232
244
 
233
245
  // src/agents/devops.ts
234
- var SYSTEM5 = `You are the DevOps engineer. Your job is to containerize the application, set up CI/CD pipelines, and produce deployment-ready infrastructure configuration.
246
+ var SYSTEM5 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
247
+
248
+ You are the DevOps engineer. Your job is to containerize the application, set up CI/CD pipelines, and produce deployment-ready infrastructure configuration.
235
249
 
236
250
  Responsibilities:
237
251
  - Read the architecture design and implementation to understand the application structure
@@ -249,7 +263,9 @@ Deliverables (save to the working directory):
249
263
  - devops-notes.md \u2014 deployment procedure, environment requirements, known limitations
250
264
 
251
265
  Principles: Follow security best practices (non-root user, minimal base image, no secrets in image). Make CI fail fast. Document every non-obvious decision in devops-notes.md.`;
252
- var TASK5 = `## Task Scope
266
+ var TASK5 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
267
+
268
+ ## Task Scope
253
269
  {{agentScope}}
254
270
 
255
271
  ## Plan
@@ -261,7 +277,7 @@ var TASK5 = `## Task Scope
261
277
  ## Project
262
278
  {{projectName}}
263
279
 
264
- ## Architecture Design
280
+ ## Architecture Design (optional context)
265
281
  {{file:architecture.md}}
266
282
 
267
283
  ## Task
@@ -289,7 +305,9 @@ function devops(overrides) {
289
305
  }
290
306
 
291
307
  // src/agents/frontend-developer.ts
292
- var SYSTEM6 = `You are the frontend engineer. Your responsibility is to turn design specs and API contracts into a working user interface.
308
+ var SYSTEM6 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
309
+
310
+ You are the frontend engineer. Your responsibility is to turn design specs and API contracts into a working user interface.
293
311
 
294
312
  Responsibilities:
295
313
  - Read the architecture design and API specification, implement pages, components, and interactions
@@ -299,7 +317,9 @@ Responsibilities:
299
317
  Deliverables (write code to the working directory, and record results in implementation-notes.md).
300
318
 
301
319
  Principles: Write code directly without asking for confirmation. Do not change the API contract \u2014 escalate contract issues upstream. Do not refactor unrelated code. Run commands and report evidence.`;
302
- var TASK6 = `## Task Scope
320
+ var TASK6 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
321
+
322
+ ## Task Scope
303
323
  {{agentScope}}
304
324
 
305
325
  ## Plan
@@ -311,10 +331,10 @@ var TASK6 = `## Task Scope
311
331
  ## UI Design Specification
312
332
  {{file:ui-design.md}}
313
333
 
314
- ## Architecture Design
334
+ ## Architecture Design (optional context)
315
335
  {{file:architecture.md}}
316
336
 
317
- ## API Specification
337
+ ## API Specification (optional context)
318
338
  {{file:api-spec.md}}
319
339
 
320
340
  ## Task
@@ -346,31 +366,38 @@ function frontendDeveloper(overrides) {
346
366
  // src/agents/leader.ts
347
367
  var SYSTEM7 = `You are the project leader. Decide what the requirement actually needs before anyone else starts.
348
368
 
349
- There are three outcomes, and the first one matters most:
369
+ Choose one routing outcome:
350
370
  - "reply" \u2014 the message is a greeting, a question, or anything you can answer on the spot. Answer it and stop; do not spend the team on it.
351
371
  - "ask" \u2014 it is a real task, but too vague to plan. Ask the one question that would unblock it.
352
- - "run" \u2014 it is a task you can scope. Name the minimum set of agents needed.
372
+ - "run" \u2014 new work. Name the minimum set of agents needed.
373
+ - "continue" \u2014 a follow-up to the Latest Work shown below. Include its exact workId and name the agents needed now.
353
374
 
354
- Do not apply fixed rules \u2014 use your judgment based on the actual requirement and the code. A minor bug fix may need only a developer and code-reviewer; a complex new feature may need the full pipeline. Read the code to understand the impact before deciding.
375
+ Do not apply fixed rules \u2014 use your judgment based on the actual requirement and the code. A minor bug fix may need only a developer and code-reviewer; a complex new feature may need the full pipeline. Use role descriptions and work summaries first; read code only if routing needs it. Do not write technical implementation plans. An architect/planner is optional; workers investigate and plan within their assigned scope when none participates.
355
376
 
356
377
  The console hands you the turns that came before this one. A requirement is often only interpretable next to them: "Postgres" is an answer, not a task, and the turn list is what tells you which question it answers. Read it as your own conversation \u2014 "you asked" is you. If you asked and got an answer, decide now; do not ask the same question again. For every "run" decision, include a complete, standalone "requirement" that combines the original task goal with all clarification answers and constraints. Never pass only the latest short answer to the team.
357
378
 
379
+ Preserve the original goal and confirmed constraints; never invent requirements. Continue only with explicit reference or unique clear context; similar subject alone is new work. If the target is ambiguous, ask. A failed/interrupted task requires an explicit user request to continue; cancelled work cannot be automatically continued. Optional tasks are [{"agentId":"selected-id","scope":"assigned responsibility"}], never technical steps. During handoff rerouting preserve the complete goal and do not select agents that handed it back. If no suitable role is available ask or explain why work cannot proceed.
380
+
358
381
  Output ONLY a single JSON object on its own line \u2014 no prose, no markdown fences:
359
382
  {"mode": "reply", "message": "your answer"}
360
383
  {"mode": "ask", "message": "your question"}
361
- {"mode": "run", "reason": "one sentence on why this set", "requirement": "the complete task and constraints", "run": ["agent-id-1", "agent-id-2"]}`;
384
+ {"mode": "run", "reason": "one sentence on why this set", "requirement": "the complete task and constraints", "run": ["agent-id-1", "agent-id-2"]}
385
+ {"mode": "continue", "workId": "id-from-Latest-Work", "requirement": "original goal plus follow-up constraints", "run": ["agent-id"]}`;
362
386
  var TASK7 = `## Project
363
387
  {{projectName}}
364
388
 
365
389
  ## Requirement
366
390
  {{requirement}}
367
391
  {{conversation}}
392
+ ## Latest Work
393
+ {{latestWork}}
394
+
368
395
  ## Available Agents
369
396
  {{availableAgents}}
370
397
 
371
398
  ## Task
372
- Read the codebase to understand the current state, then decide how to handle the requirement.
373
- Output a single JSON object \u2014 exactly one of:
399
+ Decide new work, continuation, answer or clarification. Preserve source requirements, choose minimum roles, and provide responsibility scopes when no planner participates.
400
+ Output a single JSON object \u2014 run, continue (with exact workId), reply or ask:
374
401
  {"mode": "reply", "message": "your answer"}
375
402
  {"mode": "ask", "message": "your question"}
376
403
  {"mode": "run", "reason": "one sentence on why this set", "requirement": "the complete task and constraints", "run": ["agent-id", ...]}
@@ -433,7 +460,9 @@ function productManager(overrides) {
433
460
  }
434
461
 
435
462
  // src/agents/reviewer.ts
436
- var SYSTEM9 = `You are an independent code review expert. You evaluate both business correctness and technical quality.
463
+ var SYSTEM9 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
464
+
465
+ You are an independent code review expert. You evaluate both business correctness and technical quality.
437
466
 
438
467
  Responsibilities:
439
468
  - Read requirements, design documents, and implementation code
@@ -444,7 +473,9 @@ Deliverables (save to the working directory):
444
473
  - review.md \u2014 score (1\u201310), issue list (blocking / advisory), improvement directions, conclusion (Approved / Changes Required)
445
474
 
446
475
  Principles: Review independently \u2014 do not approve just because the developer says it is fine. Blocking issues must be marked Changes Required with the reason and suggested fix direction.`;
447
- var TASK9 = `## Project
476
+ var TASK9 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
477
+
478
+ ## Project
448
479
  {{projectName}}
449
480
 
450
481
  ## Requirement
@@ -479,7 +510,9 @@ function reviewer(overrides) {
479
510
  }
480
511
 
481
512
  // src/agents/tester.ts
482
- var SYSTEM10 = `You are the test engineer. Your responsibility is to verify that requirements have actually been implemented.
513
+ var SYSTEM10 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
514
+
515
+ You are the test engineer. Your responsibility is to verify that requirements have actually been implemented.
483
516
 
484
517
  Responsibilities:
485
518
  - Read requirements (acceptance criteria AC-) and the implementation code
@@ -490,7 +523,9 @@ Deliverables (save to the working directory):
490
523
  - test-report.md \u2014 overall verdict (PASS / FAIL / BLOCKED), test results per feature, reproduction steps and evidence for failures
491
524
 
492
525
  Principles: Do not PASS just because the code compiles or because a developer says it is fine. Every core feature must be verified with actual execution. When BLOCKED, state exactly what is missing.`;
493
- var TASK10 = `## Task Scope
526
+ var TASK10 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
527
+
528
+ ## Task Scope
494
529
  {{agentScope}}
495
530
 
496
531
  ## Plan
@@ -531,7 +566,9 @@ function tester(overrides) {
531
566
  }
532
567
 
533
568
  // src/agents/ui-design-reviewer.ts
534
- var SYSTEM11 = `You are a UI/UX design reviewer. Your job is to evaluate a UI design specification before implementation begins.
569
+ var SYSTEM11 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
570
+
571
+ You are a UI/UX design reviewer. Your job is to evaluate a UI design specification before implementation begins.
535
572
 
536
573
  Responsibilities:
537
574
  - Review the design specification for completeness, consistency, and feasibility
@@ -544,7 +581,9 @@ Deliverables (save to the working directory):
544
581
  - ui-review.md \u2014 verdict (Approved / Changes Required), issue list (blocking / advisory) with component names and fix directions
545
582
 
546
583
  Principles: Be specific \u2014 reference component names or section titles in ui-design.md. A blocking issue must include what is missing or wrong and a fix direction. If the design is thorough and implementable, say so directly.`;
547
- var TASK11 = `## Requirement
584
+ var TASK11 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
585
+
586
+ ## Requirement
548
587
  {{requirement}}
549
588
 
550
589
  ## UI Design Specification
@@ -578,7 +617,9 @@ function uiDesignReviewer(overrides) {
578
617
  }
579
618
 
580
619
  // src/agents/ui-designer.ts
581
- var SYSTEM12 = `You are a UI/UX designer. Your job is to translate architecture and requirements into a detailed, developer-ready UI design specification.
620
+ var SYSTEM12 = `The complete requirement and assigned responsibility govern your work. Role deliverable lists apply only within that scope. Existing architecture, API and UI designs are optional context; when absent, work from the requirement and existing code, investigate, plan and verify without requiring an architect or creating missing architecture documents.
621
+
622
+ You are a UI/UX designer. Your job is to translate architecture and requirements into a detailed, developer-ready UI design specification.
582
623
 
583
624
  Responsibilities:
584
625
  - Define the component inventory: every page, section, and reusable component
@@ -592,7 +633,9 @@ Deliverables (save to the working directory):
592
633
  - ui-design.md \u2014 full UI design specification: component inventory, page layouts, interaction flows, design tokens, accessibility requirements
593
634
 
594
635
  Principles: Be specific enough that a developer can implement without guessing. Every component gets a name, purpose, props/variants, and states. Flag anything that depends on backend data.`;
595
- var TASK12 = `## Task Scope
636
+ var TASK12 = `The current requirement and assigned scope govern this work. The role checklist below is guidance, not additional work. Architecture, API and UI design documents are optional context: use them if present and relevant; otherwise investigate the existing code, plan within your role, implement and verify. Do not require an architect or create design documents just because they are absent.
637
+
638
+ ## Task Scope
596
639
  {{agentScope}}
597
640
 
598
641
  ## Plan
@@ -607,10 +650,10 @@ var TASK12 = `## Task Scope
607
650
  ## Requirement
608
651
  {{requirement}}
609
652
 
610
- ## Architecture Design
653
+ ## Architecture Design (optional context)
611
654
  {{file:architecture.md}}
612
655
 
613
- ## API Specification
656
+ ## API Specification (optional context)
614
657
  {{file:api-spec.md}}
615
658
 
616
659
  ## Task
@@ -686,6 +729,9 @@ var EventBus = class {
686
729
  }
687
730
  };
688
731
 
732
+ // src/orchestrator/AgentSessionManager.ts
733
+ import { randomUUID } from "crypto";
734
+
689
735
  // src/utils/abort.ts
690
736
  var ABORT_ERROR_NAME = "AbortError";
691
737
  function abortError(message = "Operation aborted") {
@@ -697,83 +743,287 @@ function isAbortError(err) {
697
743
  return err instanceof Error && err.name === ABORT_ERROR_NAME;
698
744
  }
699
745
 
746
+ // src/utils/errors.ts
747
+ var PermanentError = class extends Error {
748
+ constructor(message) {
749
+ super(message);
750
+ this.name = "PermanentError";
751
+ }
752
+ };
753
+ function isPermanent(err) {
754
+ return err instanceof PermanentError;
755
+ }
756
+
757
+ // src/utils/session.ts
758
+ import { createHash } from "crypto";
759
+ import * as path from "path";
760
+ function sessionCompatibilityKey(runtime, task) {
761
+ return createHash("sha256").update(
762
+ JSON.stringify([
763
+ runtime.name,
764
+ task.provider,
765
+ task.command,
766
+ task.args,
767
+ task.model,
768
+ path.resolve(task.workDir),
769
+ task.systemPrompt,
770
+ Boolean(task.onQuestion)
771
+ ])
772
+ ).digest("hex");
773
+ }
774
+ function isSessionRecord(value) {
775
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
776
+ const record = value;
777
+ const capabilities = record.capabilities;
778
+ const failure = record.failure;
779
+ return ["bindingId", "agentId", "runtime", "compatibilityKey", "observedAt"].every(
780
+ (key) => typeof record[key] === "string" && !!record[key]
781
+ ) && (record.sessionId === void 0 || typeof record.sessionId === "string" && !!record.sessionId) && (record.workId === void 0 || typeof record.workId === "string") && (record.reason === void 0 || typeof record.reason === "string") && ["opening", "idle", "running", "cancelling", "closing", "closed", "unknown"].includes(
782
+ String(record.state)
783
+ ) && ["created", "loaded", "reused"].includes(String(record.action)) && !!capabilities && ["continuation", "load", "supplement"].every((key) => typeof capabilities[key] === "boolean") && (failure === void 0 || !!failure && [
784
+ "incompatible",
785
+ "unsupported",
786
+ "opening_failed",
787
+ "load_failed",
788
+ "turn_failed",
789
+ "cleanup_failed"
790
+ ].includes(String(failure.code)) && typeof failure.message === "string");
791
+ }
792
+ function isSessionEvent(value) {
793
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
794
+ const event = value;
795
+ return isSessionRecord(event.record) && [
796
+ "opening",
797
+ "created",
798
+ "load_requested",
799
+ "loaded",
800
+ "reused",
801
+ "state_changed",
802
+ "opening_failed",
803
+ "load_failed",
804
+ "closing",
805
+ "closed",
806
+ "cleanup_failed"
807
+ ].includes(String(event.action)) && typeof event.executionId === "string" && !!event.executionId && Number.isInteger(event.attempt) && event.attempt > 0 && (event.requestedSessionId === void 0 || typeof event.requestedSessionId === "string") && (event.reason === void 0 || typeof event.reason === "string");
808
+ }
809
+ function describeSession(record) {
810
+ if (record.failure)
811
+ return "Session " + record.failure.code.replaceAll("_", " ") + ": " + record.failure.message;
812
+ if (record.state === "opening") return "Session opening";
813
+ if (!record.sessionId) return "Session not established \xB7 " + record.state;
814
+ return "Session " + record.action + (record.sessionId ? " " + record.sessionId : "") + " \xB7 " + record.state;
815
+ }
816
+
700
817
  // src/orchestrator/AgentSessionManager.ts
701
818
  var AgentSessionManager = class {
702
819
  constructor() {
820
+ this.listeners = /* @__PURE__ */ new Set();
703
821
  this.entries = /* @__PURE__ */ new Map();
704
822
  this.closed = false;
705
823
  }
706
- async execute(runtime, task) {
824
+ subscribe(listener) {
825
+ this.listeners.add(listener);
826
+ return () => this.listeners.delete(listener);
827
+ }
828
+ publish(entry3, action, requestedSessionId) {
829
+ entry3.record.observedAt = (/* @__PURE__ */ new Date()).toISOString();
830
+ if (entry3.session && action !== "closing" && action !== "closed" && entry3.record.failure?.code !== "cleanup_failed")
831
+ entry3.record.state = entry3.session.state;
832
+ const event = {
833
+ action,
834
+ executionId: entry3.executionId,
835
+ attempt: entry3.attempt,
836
+ record: structuredClone(entry3.record),
837
+ ...requestedSessionId ? { requestedSessionId } : {},
838
+ reason: entry3.record.reason
839
+ };
840
+ for (const listener of this.listeners) listener(event);
841
+ }
842
+ compatible(record, runtime, task, key) {
843
+ return record.agentId === task.agentId && record.workId === task.workId && record.runtime === runtime.name && record.compatibilityKey === key;
844
+ }
845
+ inspect(runtime, task, context = {}) {
846
+ const intent = (action, reason) => ({
847
+ agentId: task.agentId,
848
+ action,
849
+ reason
850
+ });
851
+ const entry3 = this.entries.get(task.agentId);
852
+ if (this.closed || this.resetting || entry3?.busy || entry3?.closing || entry3?.session && !["idle", "closed"].includes(entry3.session.state))
853
+ return intent("unavailable", "A Session is busy or cleanup is pending.");
854
+ let candidate = context.session;
855
+ if (candidate?.failure?.code === "cleanup_failed" || entry3?.record.failure?.code === "cleanup_failed")
856
+ return intent("unavailable", "Previous Session cleanup was not acknowledged.");
857
+ if (!candidate?.sessionId) candidate = void 0;
858
+ const key = sessionCompatibilityKey(runtime, task);
859
+ const compatible = (record) => this.compatible(record, runtime, task, key);
860
+ if (candidate && !compatible(candidate))
861
+ return intent(
862
+ context.strict ? "unavailable" : "create",
863
+ "Recorded work identity or immutable Session settings differ."
864
+ );
865
+ if (entry3 && compatible(entry3.record)) {
866
+ if (runtime.openSession && entry3.runtime === runtime && entry3.session?.state === "idle" && entry3.session.capabilities.continuation && (!candidate?.sessionId || candidate.sessionId === entry3.session.sessionId))
867
+ return intent("reuse", "Will reuse the matching live Session if still idle.");
868
+ candidate ??= entry3.record;
869
+ }
870
+ if (candidate) {
871
+ if (!runtime.openSession || !candidate.capabilities.load)
872
+ return intent(
873
+ context.strict ? "unavailable" : "create",
874
+ "Recorded Session loading is unsupported."
875
+ );
876
+ return intent(
877
+ "load",
878
+ "Will attempt to load the recorded conversation; restoration is not yet confirmed."
879
+ );
880
+ }
881
+ return intent(
882
+ "create",
883
+ "Will start a new executor conversation; any previous worker must first acknowledge cleanup."
884
+ );
885
+ }
886
+ async execute(runtime, task, context = {}) {
707
887
  if (this.closed) throw new Error("AgentSessionManager is closed");
708
888
  if (this.resetting) throw new Error("AgentSessionManager is resetting");
709
889
  if (task.signal?.aborted) throw abortError();
890
+ const compatibilityKey = sessionCompatibilityKey(runtime, task);
891
+ const executionId = context.executionId ?? randomUUID();
892
+ const attempt = context.attempt ?? 1;
710
893
  let entry3 = this.entries.get(task.agentId);
711
- if (entry3?.busy || entry3?.session && entry3.session.state !== "idle" && entry3.session.state !== "closed") {
712
- throw new Error(`Agent session ${task.agentId} is busy`);
713
- }
894
+ if (entry3?.busy || entry3?.session && !["idle", "closed"].includes(entry3.session.state))
895
+ throw new Error("Agent session is busy: " + task.agentId);
714
896
  if (entry3?.closing) await entry3.closing;
715
- const fingerprint = JSON.stringify([
716
- task.command,
717
- task.args,
718
- task.model,
719
- task.workDir,
720
- task.systemPrompt,
721
- task.sessionId,
722
- task.resume,
723
- Boolean(task.onQuestion)
724
- ]);
725
- if (entry3 && (entry3.runtime !== runtime || entry3.fingerprint !== fingerprint || entry3.session?.state === "closed" || !runtime.openSession)) {
726
- entry3.busy = true;
727
- try {
728
- await this.closeEntry(entry3);
729
- this.entries.delete(task.agentId);
730
- } finally {
731
- entry3.busy = false;
732
- }
897
+ let candidate = context.session;
898
+ let reason;
899
+ const compatible = (record) => this.compatible(record, runtime, task, compatibilityKey);
900
+ const observation = () => ({
901
+ record: {
902
+ bindingId: randomUUID(),
903
+ agentId: task.agentId,
904
+ workId: task.workId,
905
+ runtime: runtime.name,
906
+ compatibilityKey,
907
+ state: "opening",
908
+ capabilities: { continuation: false, load: false, supplement: false },
909
+ action: "created",
910
+ observedAt: (/* @__PURE__ */ new Date()).toISOString(),
911
+ reason
912
+ },
913
+ executionId,
914
+ attempt
915
+ });
916
+ const rejectTarget = (code, message) => {
917
+ const failure = observation();
918
+ failure.record.state = "unknown";
919
+ failure.record.failure = { code, message };
920
+ this.publish(failure, "load_failed", candidate?.sessionId);
921
+ throw new PermanentError(message);
922
+ };
923
+ if (candidate?.failure?.code === "cleanup_failed")
924
+ rejectTarget(
925
+ "unsupported",
926
+ "Previous Session cleanup was not acknowledged; refusing to start a replacement executor."
927
+ );
928
+ if (!candidate?.sessionId) candidate = void 0;
929
+ if (candidate && !compatible(candidate)) {
930
+ reason = "The recorded Session belongs to different work or has incompatible creation settings.";
931
+ if (context.strict) rejectTarget("incompatible", reason);
932
+ candidate = void 0;
933
+ }
934
+ const requested = candidate?.sessionId;
935
+ if (entry3 && (entry3.runtime !== runtime || !compatible(entry3.record) || entry3.session?.state === "closed" || !entry3.session?.capabilities.continuation || requested && requested !== entry3.session.sessionId || task.resume === "fork" || !runtime.openSession)) {
936
+ reason ??= !compatible(entry3.record) ? "Work identity or immutable Session settings changed." : "The previous live Session cannot continue.";
937
+ if (!candidate && compatible(entry3.record) && entry3.record.failure?.code !== "cleanup_failed")
938
+ candidate = structuredClone(entry3.record);
939
+ await this.closeEntry(entry3);
940
+ this.entries.delete(task.agentId);
733
941
  entry3 = void 0;
734
- if (this.closed || this.resetting) throw abortError();
942
+ if (this.closed || this.resetting || task.signal?.aborted) throw abortError();
943
+ }
944
+ if (!entry3 && candidate && !candidate.capabilities.load) {
945
+ reason = "The recorded provider Session does not support loading.";
946
+ if (context.strict) rejectTarget("unsupported", reason);
947
+ candidate = void 0;
948
+ }
949
+ if (!runtime.openSession) {
950
+ if (candidate) rejectTarget("unsupported", "This runtime cannot load a live Session.");
951
+ const output = await runtime.execute(task);
952
+ if (output.sessionId) {
953
+ const observed = observation();
954
+ observed.record.sessionId = output.sessionId;
955
+ observed.record.state = "unknown";
956
+ observed.record.reason = "The execute-only runtime reported an ID without a managed Session.";
957
+ this.publish(observed, "state_changed");
958
+ }
959
+ return output;
735
960
  }
736
- if (task.signal?.aborted) throw abortError();
737
- const openSession = runtime.openSession;
738
- if (!openSession) return runtime.execute(task);
739
961
  let detach;
740
962
  if (!entry3) {
741
- const startup = new AbortController();
742
- const forwardAbort = () => startup.abort(task.signal?.reason);
743
- task.signal?.addEventListener("abort", forwardAbort, { once: true });
744
- detach = () => task.signal?.removeEventListener("abort", forwardAbort);
963
+ const sessionId = candidate?.sessionId;
745
964
  const created = {
965
+ ...observation(),
746
966
  runtime,
747
- fingerprint,
748
- startup,
967
+ startup: new AbortController(),
749
968
  busy: true,
750
969
  onWarning: task.onWarning,
751
- // Defer opening until the cache entry is installed, including synchronous failures.
752
- opening: Promise.resolve().then(
753
- () => openSession.call(runtime, {
970
+ opening: Promise.resolve().then(() => open())
971
+ };
972
+ const open = async () => {
973
+ try {
974
+ const session = await runtime.openSession({
754
975
  agentId: task.agentId,
976
+ provider: task.provider,
755
977
  systemPrompt: task.systemPrompt,
756
978
  workDir: task.workDir,
757
979
  command: task.command,
758
980
  args: task.args,
759
981
  model: task.model,
760
- sessionId: task.sessionId,
982
+ sessionId,
761
983
  resume: task.resume,
762
984
  timeout: task.timeout,
763
985
  questionForms: Boolean(task.onQuestion),
764
- signal: startup.signal,
986
+ signal: created.startup.signal,
765
987
  onWarning: (message) => created.onWarning?.(message)
766
- })
767
- ).then((session) => {
988
+ });
768
989
  created.session = session;
990
+ created.record.capabilities = { ...session.capabilities };
991
+ if (sessionId && session.sessionId !== sessionId) {
992
+ created.record.failure = {
993
+ code: "load_failed",
994
+ message: "Provider returned a different Session ID; the requested conversation was not restored."
995
+ };
996
+ await this.closeSession(created, session);
997
+ throw new PermanentError(created.record.failure.message);
998
+ }
999
+ created.record.sessionId = session.sessionId;
1000
+ this.publish(created, sessionId ? "loaded" : "created", sessionId);
769
1001
  return session;
770
- })
1002
+ } catch (error) {
1003
+ created.record.failure ??= {
1004
+ code: sessionId ? "load_failed" : "opening_failed",
1005
+ message: error instanceof Error ? error.message : String(error)
1006
+ };
1007
+ if (!created.session) created.record.state = "unknown";
1008
+ this.publish(created, sessionId ? "load_failed" : "opening_failed", sessionId);
1009
+ throw error;
1010
+ }
771
1011
  };
1012
+ created.record.action = sessionId ? "loaded" : "created";
1013
+ const forwardAbort = () => created.startup.abort(task.signal?.reason);
1014
+ task.signal?.addEventListener("abort", forwardAbort, { once: true });
1015
+ detach = () => task.signal?.removeEventListener("abort", forwardAbort);
1016
+ this.entries.set(task.agentId, created);
1017
+ this.publish(created, sessionId ? "load_requested" : "opening", sessionId);
772
1018
  entry3 = created;
773
- this.entries.set(task.agentId, entry3);
774
1019
  } else {
775
1020
  entry3.busy = true;
776
1021
  entry3.onWarning = task.onWarning;
1022
+ entry3.executionId = executionId;
1023
+ entry3.attempt = attempt;
1024
+ entry3.record.action = "reused";
1025
+ entry3.record.failure = void 0;
1026
+ this.publish(entry3, "reused");
777
1027
  }
778
1028
  try {
779
1029
  const session = await entry3.opening;
@@ -783,8 +1033,9 @@ var AgentSessionManager = class {
783
1033
  await this.closeEntry(entry3);
784
1034
  throw abortError();
785
1035
  }
786
- if (session.state !== "idle") throw new Error(`Agent session ${task.agentId} is busy`);
787
- return await session.prompt(task.prompt, {
1036
+ if (session.state !== "idle") throw new Error("Agent session is busy: " + task.agentId);
1037
+ const prompt = task.workId ? task.prompt + "\n\n## Session continuity\n" + (entry3.record.action === "created" ? "A new executor Session was created; no old conversation was restored. Inspect the workspace before repeating operations." : entry3.record.action === "loaded" ? "The recorded provider conversation was loaded successfully. Inspect current workspace state before continuing." : "The matching live executor conversation is being reused.") + (entry3.record.reason ? "\n" + entry3.record.reason : "") : task.prompt;
1038
+ const turn = session.prompt(prompt, {
788
1039
  signal: task.signal,
789
1040
  timeout: task.timeout,
790
1041
  onEvent: task.onEvent,
@@ -792,6 +1043,17 @@ var AgentSessionManager = class {
792
1043
  onPermission: task.onPermission,
793
1044
  onQuestion: task.onQuestion
794
1045
  });
1046
+ this.publish(entry3, "state_changed");
1047
+ return await turn;
1048
+ } catch (error) {
1049
+ if (entry3.session) {
1050
+ entry3.record.failure ??= {
1051
+ code: "turn_failed",
1052
+ message: error instanceof Error ? error.message : String(error)
1053
+ };
1054
+ this.publish(entry3, "state_changed");
1055
+ }
1056
+ throw error;
795
1057
  } finally {
796
1058
  detach?.();
797
1059
  entry3.onWarning = void 0;
@@ -801,25 +1063,12 @@ var AgentSessionManager = class {
801
1063
  if (this.entries.get(task.agentId) === entry3) this.entries.delete(task.agentId);
802
1064
  } else if (!entry3.session) {
803
1065
  if (this.entries.get(task.agentId) === entry3) this.entries.delete(task.agentId);
804
- }
1066
+ } else this.publish(entry3, "state_changed");
805
1067
  } finally {
806
1068
  entry3.busy = false;
807
1069
  }
808
1070
  }
809
1071
  }
810
- /**
811
- * Whether this manager still holds a session for `agentId`.
812
- *
813
- * A caller about to continue a past conversation uses this to avoid naming a
814
- * session it already has open: passing the id would change the entry's
815
- * fingerprint, and the manager would close a perfectly good session and
816
- * `session/load` it straight back.
817
- */
818
- has(agentId) {
819
- if (this.closed) return false;
820
- const entry3 = this.entries.get(agentId);
821
- return entry3 !== void 0 && !entry3.closing && entry3.session?.state !== "closed";
822
- }
823
1072
  /**
824
1073
  * Deliver a message into every session that is running a turn right now.
825
1074
  *
@@ -858,6 +1107,16 @@ var AgentSessionManager = class {
858
1107
  })
859
1108
  );
860
1109
  }
1110
+ /** Close selected idle conversations without resetting the leader. */
1111
+ async resetAgents(agentIds) {
1112
+ for (const id of agentIds) {
1113
+ const entry3 = this.entries.get(id);
1114
+ if (!entry3) continue;
1115
+ if (entry3.busy) throw new Error("Cannot reset a busy agent: " + id);
1116
+ await this.closeEntry(entry3);
1117
+ if (this.entries.get(id) === entry3) this.entries.delete(id);
1118
+ }
1119
+ }
861
1120
  /** Closes all sessions and permits a fresh conversation after cleanup completes. */
862
1121
  reset() {
863
1122
  if (this.closing) return this.closing;
@@ -894,26 +1153,49 @@ var AgentSessionManager = class {
894
1153
  entry3.closing = (async () => {
895
1154
  let session;
896
1155
  try {
897
- session = await entry3.opening;
1156
+ session = entry3.session ?? await entry3.opening;
898
1157
  } catch (error) {
899
- if (isAbortError(error)) return;
900
- throw error;
1158
+ if (entry3.session) session = entry3.session;
1159
+ else if (isAbortError(error)) return;
1160
+ else throw error;
901
1161
  }
902
- await session.close();
1162
+ await this.closeSession(entry3, session);
903
1163
  })();
904
1164
  }
905
1165
  return entry3.closing;
906
1166
  }
1167
+ async closeSession(entry3, session) {
1168
+ if (entry3.cleanupComplete) return;
1169
+ if (entry3.record.failure?.code === "cleanup_failed")
1170
+ throw new Error(entry3.record.failure.message);
1171
+ entry3.record.state = "closing";
1172
+ this.publish(entry3, "closing");
1173
+ try {
1174
+ await session.close();
1175
+ entry3.cleanupComplete = true;
1176
+ entry3.record.state = "closed";
1177
+ this.publish(entry3, "closed");
1178
+ } catch (error) {
1179
+ entry3.record.state = "unknown";
1180
+ entry3.record.failure = {
1181
+ code: "cleanup_failed",
1182
+ message: error instanceof Error ? error.message : String(error)
1183
+ };
1184
+ this.publish(entry3, "cleanup_failed");
1185
+ throw error;
1186
+ }
1187
+ }
907
1188
  };
908
1189
 
909
1190
  // src/orchestrator/Orchestrator.ts
1191
+ import { randomUUID as randomUUID4 } from "crypto";
910
1192
  import * as fs4 from "fs";
911
- import * as path5 from "path";
1193
+ import * as path6 from "path";
912
1194
 
913
1195
  // src/providers/catalog.ts
914
- import * as path from "path";
1196
+ import * as path2 from "path";
915
1197
  function normalizeCommandKey(command) {
916
- return path.basename(command).toLowerCase().replace(/\.(cmd|exe|bat)$/i, "");
1198
+ return path2.basename(command).toLowerCase().replace(/\.(cmd|exe|bat)$/i, "");
917
1199
  }
918
1200
  var PROVIDERS = [
919
1201
  {
@@ -1199,14 +1481,25 @@ function asDecision(value) {
1199
1481
  const message = typeof record.message === "string" ? record.message.trim() : "";
1200
1482
  return message ? { mode, message } : void 0;
1201
1483
  }
1202
- if (mode === "run" || mode === void 0) {
1484
+ if (mode === "run" || mode === "continue" || mode === void 0) {
1203
1485
  const run = record.run;
1204
1486
  if (!Array.isArray(run) || !run.every((id) => typeof id === "string")) return void 0;
1205
1487
  const reason = typeof record.reason === "string" ? record.reason.trim() : "";
1206
1488
  const requirement = typeof record.requirement === "string" ? record.requirement.trim() : "";
1207
1489
  if ("requirement" in record && !requirement) return void 0;
1490
+ const workId = typeof record.workId === "string" ? record.workId.trim() : "";
1491
+ if (mode === "continue" && !workId) return void 0;
1492
+ const tasks = record.tasks;
1493
+ if (tasks !== void 0 && (!Array.isArray(tasks) || !tasks.every(
1494
+ (task) => typeof task === "object" && task !== null && typeof task.agentId === "string" && task.agentId.trim() && typeof task.scope === "string" && task.scope.trim()
1495
+ )))
1496
+ return void 0;
1497
+ if (Array.isArray(tasks) && (new Set(tasks.map((task) => task.agentId)).size !== tasks.length || tasks.some((task) => !run.includes(task.agentId))))
1498
+ return void 0;
1208
1499
  return {
1209
- mode: "run",
1500
+ mode: mode === "continue" ? "continue" : "run",
1501
+ ...workId ? { workId } : {},
1502
+ ...Array.isArray(tasks) ? { tasks: tasks.map((task) => ({ agentId: task.agentId, scope: task.scope.trim() })) } : {},
1210
1503
  run,
1211
1504
  ...reason ? { reason } : {},
1212
1505
  ...requirement ? { requirement } : {}
@@ -1255,32 +1548,61 @@ function renderUpstreamOutputs(outputs) {
1255
1548
  return block(entries);
1256
1549
  }
1257
1550
 
1551
+ // src/utils/work.ts
1552
+ function isWorkContext(value) {
1553
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1554
+ const work = value;
1555
+ if (typeof work.id !== "string" || !work.id.trim() || typeof work.originalRequirement !== "string" || typeof work.requirement !== "string" || !Array.isArray(work.agents))
1556
+ return false;
1557
+ if (!work.agents.every((item) => {
1558
+ if (typeof item !== "object" || item === null) return false;
1559
+ const agent = item;
1560
+ return typeof agent.agentId === "string" && !!agent.agentId.trim() && ["scope", "plan", "taskTitle"].every(
1561
+ (key) => agent[key] === void 0 || typeof agent[key] === "string"
1562
+ ) && (agent.session === void 0 || isSessionRecord(agent.session) && agent.session.agentId === agent.agentId && agent.session.workId === work.id) && (agent.dependsOn === void 0 || Array.isArray(agent.dependsOn) && agent.dependsOn.every((id) => typeof id === "string"));
1563
+ }))
1564
+ return false;
1565
+ if (new Set(work.agents.map((item) => item.agentId)).size !== work.agents.length) return false;
1566
+ if (work.outcome !== void 0) {
1567
+ if (typeof work.outcome !== "object" || work.outcome === null) return false;
1568
+ const outcome = work.outcome;
1569
+ if (!["success", "failed", "cancelled", "interrupted", "unknown"].includes(
1570
+ String(outcome.execution)
1571
+ ) || !Array.isArray(outcome.completedAgents) || !outcome.completedAgents.every((id) => typeof id === "string") || !Array.isArray(outcome.failedAgents) || !outcome.failedAgents.every(
1572
+ (item) => item && typeof item.agentId === "string" && (item.reason === void 0 || typeof item.reason === "string")
1573
+ ))
1574
+ return false;
1575
+ }
1576
+ return true;
1577
+ }
1578
+
1258
1579
  // src/orchestrator/AgentRunner.ts
1580
+ import { randomUUID as randomUUID3 } from "crypto";
1259
1581
  import * as fs3 from "fs";
1260
- import * as path4 from "path";
1582
+ import * as path5 from "path";
1261
1583
 
1262
1584
  // src/runtime/AcpRuntime.ts
1263
1585
  import { spawn } from "child_process";
1264
- import { randomUUID } from "crypto";
1586
+ import { randomUUID as randomUUID2 } from "crypto";
1265
1587
  import { Readable, Writable } from "stream";
1266
1588
  import { StringDecoder } from "string_decoder";
1267
1589
 
1268
1590
  // src/providers/bridge.ts
1269
1591
  import * as fs from "fs";
1270
1592
  import { createRequire } from "module";
1271
- import * as path2 from "path";
1593
+ import * as path3 from "path";
1272
1594
  var BRIDGE_PACKAGES = {
1273
1595
  "claude-agent-acp": "@agentclientprotocol/claude-agent-acp",
1274
1596
  "codex-acp": "@agentclientprotocol/codex-acp"
1275
1597
  };
1276
1598
  function resolvesOnPath(command) {
1277
- const dirs = (process.env.PATH ?? "").split(path2.delimiter).filter(Boolean);
1599
+ const dirs = (process.env.PATH ?? "").split(path3.delimiter).filter(Boolean);
1278
1600
  const exts = (process.env.PATHEXT ?? "").split(";").filter(Boolean);
1279
1601
  const suffixes = exts.length > 0 ? exts : [""];
1280
1602
  for (const dir of dirs) {
1281
1603
  for (const ext of suffixes) {
1282
1604
  try {
1283
- fs.accessSync(path2.join(dir, command + ext), fs.constants.X_OK);
1605
+ fs.accessSync(path3.join(dir, command + ext), fs.constants.X_OK);
1284
1606
  return true;
1285
1607
  } catch {
1286
1608
  }
@@ -1297,7 +1619,7 @@ function resolveBridgeBinary(command) {
1297
1619
  const bin = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8")).bin;
1298
1620
  const binRel = typeof bin === "string" ? bin : bin?.[command] ?? Object.values(bin)[0];
1299
1621
  if (!binRel) return null;
1300
- return path2.join(path2.dirname(pkgJsonPath), binRel);
1622
+ return path3.join(path3.dirname(pkgJsonPath), binRel);
1301
1623
  } catch {
1302
1624
  return null;
1303
1625
  }
@@ -1307,17 +1629,6 @@ function resolveExecutable(command) {
1307
1629
  return resolveBridgeBinary(command);
1308
1630
  }
1309
1631
 
1310
- // src/utils/errors.ts
1311
- var PermanentError = class extends Error {
1312
- constructor(message) {
1313
- super(message);
1314
- this.name = "PermanentError";
1315
- }
1316
- };
1317
- function isPermanent(err) {
1318
- return err instanceof PermanentError;
1319
- }
1320
-
1321
1632
  // src/utils/toolSummary.ts
1322
1633
  var MAX_SUMMARY_COLUMNS = 120;
1323
1634
  function stringifySafe(value) {
@@ -1582,6 +1893,7 @@ var AcpRuntime = class extends RuntimeBase {
1582
1893
  let state = "idle";
1583
1894
  let sessionId = "";
1584
1895
  let steeringSupported = false;
1896
+ let loadSupported = false;
1585
1897
  let active;
1586
1898
  let childClosed = false;
1587
1899
  let terminalError;
@@ -1641,8 +1953,8 @@ var AcpRuntime = class extends RuntimeBase {
1641
1953
  };
1642
1954
  const close = () => {
1643
1955
  if (closePromise) return closePromise;
1644
- closePromise = new Promise((resolve4, reject) => {
1645
- closeResolve = resolve4;
1956
+ closePromise = new Promise((resolve5, reject) => {
1957
+ closeResolve = resolve5;
1646
1958
  closeReject = reject;
1647
1959
  });
1648
1960
  options.signal?.removeEventListener("abort", onSessionAbort);
@@ -1806,7 +2118,7 @@ var AcpRuntime = class extends RuntimeBase {
1806
2118
  const signal = AbortSignal.any([turn.decisions.signal, context.signal]);
1807
2119
  const offered = new Set(params.options.map((option) => option.optionId));
1808
2120
  const request = {
1809
- requestId: randomUUID(),
2121
+ requestId: randomUUID2(),
1810
2122
  runId: turn.runId,
1811
2123
  agentId: options.agentId,
1812
2124
  seq: turn.seq++,
@@ -1819,8 +2131,8 @@ var AcpRuntime = class extends RuntimeBase {
1819
2131
  options: params.options.map(({ optionId, name, kind }) => ({ optionId, name, kind }))
1820
2132
  };
1821
2133
  let onAbort;
1822
- const aborted = new Promise((resolve4) => {
1823
- onAbort = () => resolve4(void 0);
2134
+ const aborted = new Promise((resolve5) => {
2135
+ onAbort = () => resolve5(void 0);
1824
2136
  signal.addEventListener("abort", onAbort, { once: true });
1825
2137
  if (signal.aborted) onAbort();
1826
2138
  });
@@ -1865,7 +2177,7 @@ var AcpRuntime = class extends RuntimeBase {
1865
2177
  const schema = structuredClone(params.requestedSchema);
1866
2178
  const signal = AbortSignal.any([turn.decisions.signal, context.signal]);
1867
2179
  const request = {
1868
- requestId: randomUUID(),
2180
+ requestId: randomUUID2(),
1869
2181
  runId: turn.runId,
1870
2182
  agentId: options.agentId,
1871
2183
  seq: turn.seq++,
@@ -1875,8 +2187,8 @@ var AcpRuntime = class extends RuntimeBase {
1875
2187
  requestedSchema: structuredClone(schema)
1876
2188
  };
1877
2189
  let onAbort;
1878
- const aborted = new Promise((resolve4) => {
1879
- onAbort = () => resolve4(void 0);
2190
+ const aborted = new Promise((resolve5) => {
2191
+ onAbort = () => resolve5(void 0);
1880
2192
  signal.addEventListener("abort", onAbort, { once: true });
1881
2193
  if (signal.aborted) onAbort();
1882
2194
  });
@@ -1986,20 +2298,20 @@ var AcpRuntime = class extends RuntimeBase {
1986
2298
  } catch (error) {
1987
2299
  return Promise.reject(error);
1988
2300
  }
1989
- let resolve4;
2301
+ let resolve5;
1990
2302
  let reject;
1991
2303
  const promise = new Promise((res, rej) => {
1992
- resolve4 = res;
2304
+ resolve5 = res;
1993
2305
  reject = rej;
1994
2306
  });
1995
2307
  const turn = {
1996
- runId: randomUUID(),
2308
+ runId: randomUUID2(),
1997
2309
  seq: 0,
1998
2310
  options: promptOptions,
1999
2311
  output: "",
2000
2312
  decisions: new AbortController(),
2001
2313
  promise,
2002
- resolve: resolve4,
2314
+ resolve: resolve5,
2003
2315
  reject
2004
2316
  };
2005
2317
  active = turn;
@@ -2059,7 +2371,7 @@ ${text}` : text
2059
2371
  return state;
2060
2372
  },
2061
2373
  get capabilities() {
2062
- return { continuation: true, supplement: steeringSupported };
2374
+ return { continuation: true, load: loadSupported, supplement: steeringSupported };
2063
2375
  },
2064
2376
  prompt,
2065
2377
  supplement,
@@ -2078,6 +2390,7 @@ ${text}` : text
2078
2390
  });
2079
2391
  if (terminalError) throw terminalError;
2080
2392
  steeringSupported = advertisesSteering(init);
2393
+ loadSupported = init.agentCapabilities?.loadSession === true;
2081
2394
  if (options.sessionId) {
2082
2395
  if (init.agentCapabilities?.loadSession !== true) {
2083
2396
  throw new PermanentError(
@@ -2178,13 +2491,44 @@ var RuntimeRegistry = class {
2178
2491
  };
2179
2492
  var runtimeRegistry = new RuntimeRegistry();
2180
2493
 
2494
+ // src/utils/handoff.ts
2495
+ var HANDOFF_INSTRUCTION = [
2496
+ "## Routing handoff",
2497
+ "If this assignment belongs to another role or needs rerouting beyond your scope, explain what remains and end with exactly:",
2498
+ '<multiclaw-handoff>{"reason":"specific reason and remaining work"}</multiclaw-handoff>',
2499
+ "Do not use this for routine implementation choices, progress, or provider errors. Do not rewrite the original requirement. Otherwise finish normally."
2500
+ ].join("\n");
2501
+ function parseHandoff(output) {
2502
+ const text = output.trim();
2503
+ const unfenced = text.replace(/```[\s\S]*?```/g, "[quoted example]");
2504
+ const lastLine = unfenced.trim().split("\n").slice(-1)[0] ?? "";
2505
+ const terminal = unfenced.match(/(?:^|\n)<multiclaw-handoff>([\s\S]*?)<\/multiclaw-handoff>$/);
2506
+ const unclosed = /(?:^|\n)<multiclaw-handoff>(?:(?!<\/multiclaw-handoff>)[\s\S])*$/.test(unfenced);
2507
+ if (!terminal && !unclosed && !/^<\/?multiclaw-handoff(?:>|\s)/.test(lastLine.trim()))
2508
+ return void 0;
2509
+ const lines = unfenced.split("\n").filter((line) => /^<multiclaw-handoff(?:>|\s)/.test(line.trim()));
2510
+ if (text.split(String.fromCharCode(96).repeat(3)).length % 2 === 0) return void 0;
2511
+ const match = text.match(/(?:^|\n)<multiclaw-handoff>([\s\S]*?)<\/multiclaw-handoff>$/);
2512
+ if (lines.length !== 1 || !match) return { error: "Invalid terminal handoff marker." };
2513
+ try {
2514
+ const item = JSON.parse(match[1]);
2515
+ if (typeof item !== "object" || item === null || Array.isArray(item)) throw new Error();
2516
+ const fields = item;
2517
+ if (Object.keys(fields).length !== 1 || typeof fields.reason !== "string" || !fields.reason.trim())
2518
+ throw new Error();
2519
+ return { reason: fields.reason.trim() };
2520
+ } catch {
2521
+ return { error: "Handoff requires a JSON object with a non-empty reason." };
2522
+ }
2523
+ }
2524
+
2181
2525
  // src/utils/prompt.ts
2182
2526
  import * as fs2 from "fs/promises";
2183
- import * as path3 from "path";
2527
+ import * as path4 from "path";
2184
2528
  function resolveInWorkspace(workDir, reference) {
2185
- const root = path3.resolve(workDir);
2186
- const resolved = path3.resolve(root, reference);
2187
- if (resolved !== root && !resolved.startsWith(root + path3.sep)) return void 0;
2529
+ const root = path4.resolve(workDir);
2530
+ const resolved = path4.resolve(root, reference);
2531
+ if (resolved !== root && !resolved.startsWith(root + path4.sep)) return void 0;
2188
2532
  return resolved;
2189
2533
  }
2190
2534
  async function renderPrompt(template, context, workDir) {
@@ -2213,14 +2557,14 @@ async function renderPrompt(template, context, workDir) {
2213
2557
  var DEFAULT_RETRY_DELAY_MS = 2e3;
2214
2558
  function sleep(ms, signal) {
2215
2559
  if (signal?.aborted) return Promise.reject(abortError());
2216
- return new Promise((resolve4, reject) => {
2560
+ return new Promise((resolve5, reject) => {
2217
2561
  const onAbort = () => {
2218
2562
  clearTimeout(timer);
2219
2563
  reject(abortError());
2220
2564
  };
2221
2565
  const timer = setTimeout(() => {
2222
2566
  signal?.removeEventListener("abort", onAbort);
2223
- resolve4();
2567
+ resolve5();
2224
2568
  }, ms);
2225
2569
  signal?.addEventListener("abort", onAbort, { once: true });
2226
2570
  });
@@ -2295,8 +2639,45 @@ var AgentRunner = class {
2295
2639
  this.config = config;
2296
2640
  this.sessionManager = sessionManager;
2297
2641
  }
2642
+ sessionTask(agent) {
2643
+ return {
2644
+ agentId: agent.id,
2645
+ workId: agent.id === this.config.leader?.id ? void 0 : this.contextManager.get("workId"),
2646
+ systemPrompt: agent.systemPrompt,
2647
+ prompt: "",
2648
+ workDir: agent.workDir ? path5.resolve(this.globalWorkDir, agent.workDir) : this.globalWorkDir,
2649
+ timeout: agent.timeout ?? 6e5,
2650
+ model: resolveModel(agent, this.config),
2651
+ provider: resolveProvider(agent, this.config),
2652
+ command: resolveCommand(agent, this.config),
2653
+ args: resolveArgs(agent, this.config),
2654
+ onQuestion: this.config.questions?.onRequest
2655
+ };
2656
+ }
2657
+ previewSession(agent) {
2658
+ const task = this.sessionTask(agent);
2659
+ return (this.sessionManager ?? new AgentSessionManager()).inspect(
2660
+ runtimeRegistry.get(agent.runtime ?? "acp"),
2661
+ task,
2662
+ { session: this.config.resume?.sessions?.[agent.id], strict: !!this.config.resume?.agents }
2663
+ );
2664
+ }
2298
2665
  async run(agent, signal, instruction) {
2299
2666
  const startTime = /* @__PURE__ */ new Date();
2667
+ const executionId = randomUUID3();
2668
+ const manager = this.sessionManager ?? new AgentSessionManager();
2669
+ let session;
2670
+ let finalResult;
2671
+ const offSession = manager.subscribe((event) => {
2672
+ if (event.executionId !== executionId) return;
2673
+ session = event.record;
2674
+ if (!this.sessionManager)
2675
+ this.eventBus.emit({
2676
+ type: "agent:session",
2677
+ timestamp: event.record.observedAt,
2678
+ payload: event
2679
+ });
2680
+ });
2300
2681
  const model = resolveModel(agent, this.config);
2301
2682
  this.eventBus.emit({
2302
2683
  type: "agent:start",
@@ -2314,7 +2695,7 @@ var AgentRunner = class {
2314
2695
  let usage;
2315
2696
  let costUsd;
2316
2697
  try {
2317
- const workDir = agent.workDir ? path4.resolve(this.globalWorkDir, agent.workDir) : this.globalWorkDir;
2698
+ const workDir = agent.workDir ? path5.resolve(this.globalWorkDir, agent.workDir) : this.globalWorkDir;
2318
2699
  fs3.mkdirSync(workDir, { recursive: true });
2319
2700
  const runtime = runtimeRegistry.get(agent.runtime ?? "acp");
2320
2701
  const retries = agent.retries ?? 0;
@@ -2332,8 +2713,8 @@ var AgentRunner = class {
2332
2713
  // agent, and the no-leader path has no plan at all — so without a
2333
2714
  // default the model is handed the literal text `{{agentScope}}`
2334
2715
  // and has to guess what it was supposed to say.
2335
- agentScope: agent.agentScope ?? "Not narrowed down \u2014 cover the whole task.",
2336
- agentPlan: agent.agentPlan ?? "No step-by-step plan was provided \u2014 use your judgement."
2716
+ agentScope: agent.agentScope?.trim() || "Fulfil the current requirement within your role: " + (agent.description ?? agent.name),
2717
+ agentPlan: agent.agentPlan?.trim() || "No step-by-step plan was provided \u2014 use your judgement."
2337
2718
  };
2338
2719
  let prompt = await renderPrompt(agent.taskPrompt, agentContext, this.globalWorkDir);
2339
2720
  const requirement = this.contextManager.get("requirement") ?? this.config.context?.requirement;
@@ -2343,6 +2724,10 @@ ${requirement}
2343
2724
 
2344
2725
  ${prompt}`;
2345
2726
  }
2727
+ if (agent.id !== this.config.leader?.id && agent.id !== this.config.review?.reviewer)
2728
+ prompt = "## Assigned responsibility\n" + agentContext.agentScope + "\n\n## Planning\n" + agentContext.agentPlan + "\n\n" + prompt;
2729
+ if (agent.id !== this.config.leader?.id && agent.id !== this.config.review?.reviewer)
2730
+ prompt += "\n\n" + HANDOFF_INSTRUCTION;
2346
2731
  if (instruction) prompt += `
2347
2732
 
2348
2733
  ${instruction}`;
@@ -2357,28 +2742,14 @@ ${instruction}`;
2357
2742
  `, "tool");
2358
2743
  };
2359
2744
  const task = {
2360
- agentId: agent.id,
2361
- systemPrompt: agent.systemPrompt,
2745
+ ...this.sessionTask(agent),
2362
2746
  prompt,
2363
2747
  permissionMode,
2364
2748
  onPermission: this.config.permissions?.onRequest,
2365
2749
  onQuestion: this.config.questions?.onRequest,
2366
- workDir,
2367
- timeout,
2368
2750
  signal: runtime.openSession && signal ? AbortSignal.any([innerSignal, signal]) : innerSignal,
2369
2751
  model,
2370
- // Only the caller knows which session to continue; absent means a
2371
- // fresh one. It must stay `undefined` rather than `""` — the ACP
2372
- // runtime tests `if (task.sessionId)`, so an empty string would
2373
- // read as "load this session" for a session that does not exist.
2374
- sessionId: this.config.resume?.sessions?.[agent.id],
2375
- // Resolve through provider/defaults exactly as the CLI's
2376
- // availability gate does — an agent that pins `provider` (or
2377
- // leans on `defaults`) must launch that provider's bridge, not
2378
- // the runtime's built-in default. Passing the raw `agent.command`
2379
- // here sent a `provider: "claude"` config straight to `opencode`.
2380
- command: resolveCommand(agent, this.config),
2381
- args: resolveArgs(agent, this.config),
2752
+ provider: resolveProvider(agent, this.config),
2382
2753
  // A runtime's diagnostics travel on the event bus rather than
2383
2754
  // stderr: under the TUI's alternate screen a stderr write is
2384
2755
  // never seen. The agent name prefixes the line so it is
@@ -2413,7 +2784,12 @@ ${instruction}`;
2413
2784
  }
2414
2785
  }
2415
2786
  };
2416
- return await (this.sessionManager ? this.sessionManager.execute(runtime, task) : runtime.execute(task));
2787
+ return await manager.execute(runtime, task, {
2788
+ session: this.config.resume?.sessions?.[agent.id],
2789
+ strict: !!this.config.resume?.agents,
2790
+ executionId,
2791
+ attempt
2792
+ });
2417
2793
  },
2418
2794
  retries,
2419
2795
  (attempt, err) => {
@@ -2446,16 +2822,17 @@ ${instruction}`;
2446
2822
  output = completed.output;
2447
2823
  usage = completed.usage;
2448
2824
  costUsd = completed.costUsd;
2449
- const sessionId = completed.sessionId;
2450
2825
  const incomplete = completed.stopReason !== void 0 && completed.stopReason !== "success";
2451
2826
  const cancelled = completed.stopReason === "cancelled";
2452
- const failed = completed.exitCode !== 0 || incomplete;
2453
- const error = failed ? `Runtime turn did not complete (${completed.stopReason ?? `exit code ${completed.exitCode}`})` : void 0;
2827
+ const handoff = completed.exitCode === 0 && !incomplete && agent.id !== this.config.leader?.id && agent.id !== this.config.review?.reviewer ? parseHandoff(output) : void 0;
2828
+ const failed = completed.exitCode !== 0 || incomplete || !!(handoff && "error" in handoff);
2829
+ const error = handoff && "error" in handoff ? handoff.error : failed ? `Runtime turn did not complete (${completed.stopReason ?? `exit code ${completed.exitCode}`})` : void 0;
2454
2830
  const endTime = /* @__PURE__ */ new Date();
2455
2831
  const result = {
2456
2832
  agentId: agent.id,
2457
2833
  agentName: agent.name,
2458
- status: cancelled ? "cancelled" : failed ? "failed" : "success",
2834
+ status: cancelled ? "cancelled" : failed ? "failed" : handoff ? "handed_back" : "success",
2835
+ ...handoff && "reason" in handoff ? { handoffReason: handoff.reason } : {},
2459
2836
  ...error ? { error } : {},
2460
2837
  output,
2461
2838
  duration: endTime.getTime() - startTime.getTime(),
@@ -2465,13 +2842,9 @@ ${instruction}`;
2465
2842
  model,
2466
2843
  ...usage ? { usage } : {},
2467
2844
  ...costUsd !== void 0 ? { costUsd } : {},
2468
- ...sessionId ? { sessionId } : {}
2845
+ ...session ? { session } : {}
2469
2846
  };
2470
- this.eventBus.emit({
2471
- type: failed ? "agent:failed" : "agent:complete",
2472
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2473
- payload: result
2474
- });
2847
+ finalResult = result;
2475
2848
  return result;
2476
2849
  } catch (err) {
2477
2850
  const endTime = /* @__PURE__ */ new Date();
@@ -2487,14 +2860,29 @@ ${instruction}`;
2487
2860
  error: err.message,
2488
2861
  startTime: startTime.toISOString(),
2489
2862
  endTime: endTime.toISOString(),
2490
- model
2863
+ model,
2864
+ ...session ? { session } : {}
2491
2865
  };
2492
- this.eventBus.emit({
2493
- type: "agent:failed",
2494
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2495
- payload: result
2496
- });
2866
+ finalResult = result;
2497
2867
  return result;
2868
+ } finally {
2869
+ try {
2870
+ if (!this.sessionManager) await manager.close();
2871
+ } catch (error) {
2872
+ if (finalResult) {
2873
+ finalResult.status = "failed";
2874
+ finalResult.error = (finalResult.error ? finalResult.error + "; " : "") + "Session cleanup failed: " + String(error);
2875
+ }
2876
+ } finally {
2877
+ if (finalResult && session) finalResult.session = session;
2878
+ offSession();
2879
+ if (finalResult)
2880
+ this.eventBus.emit({
2881
+ type: finalResult.status === "success" ? "agent:complete" : finalResult.status === "handed_back" ? "agent:handed-back" : "agent:failed",
2882
+ timestamp: finalResult.endTime,
2883
+ payload: finalResult
2884
+ });
2885
+ }
2498
2886
  }
2499
2887
  }
2500
2888
  /**
@@ -2559,10 +2947,51 @@ function buildStages(agents2) {
2559
2947
  var Orchestrator = class {
2560
2948
  constructor(config, sessions) {
2561
2949
  this.config = config;
2950
+ this.sessions = sessions;
2562
2951
  this.eventBus = new EventBus();
2563
2952
  /** Which configured checkpoints have already been triggered this run. */
2564
2953
  this.checkpointTriggered = /* @__PURE__ */ new Set();
2565
- this.workDir = path5.resolve(config.workDir);
2954
+ this.assignments = /* @__PURE__ */ new Map();
2955
+ this.excluded = /* @__PURE__ */ new Set();
2956
+ this.rerouting = false;
2957
+ this.repairSpent = 0;
2958
+ this.leaderCosts = [];
2959
+ this.cleanupAttempted = false;
2960
+ this.sessionRecords = /* @__PURE__ */ new Map();
2961
+ this.recovery = "";
2962
+ config = {
2963
+ ...config,
2964
+ latestWork: isWorkContext(config.latestWork) ? config.latestWork : void 0,
2965
+ resume: config.resume ? { ...config.resume } : void 0
2966
+ };
2967
+ this.config = config;
2968
+ this.ownsSessions = !sessions;
2969
+ this.sessions = sessions ?? new AgentSessionManager();
2970
+ this.offSessions = this.sessions.subscribe((event) => {
2971
+ this.sessionRecords.set(event.record.bindingId, event.record);
2972
+ if (event.record.sessionId && this.work && this.work.id === event.record.workId) {
2973
+ let entry3 = this.work.agents.find((item) => item.agentId === event.record.agentId);
2974
+ if (!entry3) {
2975
+ entry3 = { agentId: event.record.agentId };
2976
+ this.work.agents.push(entry3);
2977
+ }
2978
+ if (!entry3.session || entry3.session.bindingId === event.record.bindingId || event.action === "created" || event.action === "loaded")
2979
+ entry3.session = event.record;
2980
+ }
2981
+ this.eventBus.emit({
2982
+ type: "agent:session",
2983
+ timestamp: event.record.observedAt,
2984
+ payload: event
2985
+ });
2986
+ if (event.record.sessionId && this.work && this.work.id === event.record.workId && this.routing)
2987
+ this.eventBus.emit({
2988
+ type: "orchestration:route",
2989
+ timestamp: event.record.observedAt,
2990
+ payload: { routing: { ...this.routing }, work: structuredClone(this.work) }
2991
+ });
2992
+ });
2993
+ this.originalRequirement = config.context?.requirement ?? "";
2994
+ this.workDir = path6.resolve(config.workDir);
2566
2995
  fs4.mkdirSync(this.workDir, { recursive: true });
2567
2996
  this.contextManager = new ContextManager(config.context);
2568
2997
  this.contextManager.set("conversation", renderConversation(config.history));
@@ -2572,21 +3001,32 @@ var Orchestrator = class {
2572
3001
  this.contextManager,
2573
3002
  this.workDir,
2574
3003
  config,
2575
- sessions
3004
+ this.sessions
2576
3005
  );
2577
3006
  }
2578
3007
  async preview(options = {}) {
3008
+ try {
3009
+ return await this.previewManaged(options);
3010
+ } finally {
3011
+ try {
3012
+ if (this.ownsSessions) await this.sessions?.close();
3013
+ } finally {
3014
+ this.offSessions();
3015
+ }
3016
+ }
3017
+ }
3018
+ async previewManaged(options = {}) {
2579
3019
  if (options.signal?.aborted)
2580
3020
  return { name: this.config.name, totalAgents: 0, stages: [], ...this.requirementField() };
2581
3021
  const policyError = this.reviewPolicyError();
2582
3022
  if (policyError) throw new Error(policyError);
2583
3023
  if (options.signal?.aborted)
2584
3024
  return { name: this.config.name, totalAgents: 0, stages: [], ...this.requirementField() };
2585
- this.prepareTaskPlan();
2586
3025
  const resolution = await this.resolveTurn(options.signal);
2587
3026
  if (resolution.kind === "cancelled" || options.signal?.aborted) {
2588
3027
  return { name: this.config.name, totalAgents: 0, stages: [], ...this.requirementField() };
2589
3028
  }
3029
+ if (resolution.kind === "error") throw new Error(resolution.message);
2590
3030
  if (resolution.kind === "message") {
2591
3031
  return {
2592
3032
  name: this.config.name,
@@ -2606,11 +3046,135 @@ var Orchestrator = class {
2606
3046
  name: this.config.name,
2607
3047
  totalAgents: agents2.length + (reviewer2 && agents2.length ? 1 : 0),
2608
3048
  stages: stageInfos,
3049
+ sessions: [...agents2, ...reviewer2 && agents2.length ? [reviewer2] : []].map(
3050
+ (agent) => this.runner.previewSession(agent)
3051
+ ),
2609
3052
  ...this.reasonField(resolution.reason),
2610
- ...this.requirementField()
3053
+ ...this.requirementField(),
3054
+ originalRequirement: this.originalRequirement,
3055
+ routing: this.routing
2611
3056
  };
2612
3057
  }
2613
3058
  async run(options = {}) {
3059
+ try {
3060
+ return await this.runManaged(options);
3061
+ } finally {
3062
+ try {
3063
+ if (this.ownsSessions && !this.cleanupAttempted) await this.sessions?.close();
3064
+ } finally {
3065
+ this.offSessions();
3066
+ }
3067
+ }
3068
+ }
3069
+ async runManaged(options = {}) {
3070
+ const started = /* @__PURE__ */ new Date();
3071
+ const audit = [];
3072
+ let result;
3073
+ for (; ; ) {
3074
+ this.checkpointTriggered.clear();
3075
+ result = await this.runPass(options);
3076
+ const effective2 = [
3077
+ ...new Map(result.agentResults.map((item) => [item.agentId, item])).values()
3078
+ ];
3079
+ audit.push(...result.agentResults);
3080
+ const handbacks = effective2.filter((item) => item.status === "handed_back");
3081
+ const failure = effective2.some(
3082
+ (item) => item.status === "failed" || item.status === "cancelled"
3083
+ );
3084
+ if (!handbacks.length || failure || result.cancelled || options.signal?.aborted) break;
3085
+ if (this.rerouting || !this.config.leader || this.config.useLeader === false || this.config.resume?.agents) {
3086
+ this.routeError = this.rerouting ? "Work handed back again; rerouting budget exhausted." : "Work handed back; automatic rerouting is unavailable for this fixed team.";
3087
+ result.success = false;
3088
+ break;
3089
+ }
3090
+ this.rerouting = true;
3091
+ for (const item of handbacks) this.excluded.add(item.agentId);
3092
+ if (this.routing) this.routing.reroutes = 1;
3093
+ this.recovery = "## Rerouting after worker handoff\nPreserve the complete requirement. Check the workspace before repeating completed operations.\n" + JSON.stringify({
3094
+ originalRequirement: this.work?.originalRequirement,
3095
+ requirement: this.contextManager.get("requirement"),
3096
+ handbacks: handbacks.map((item) => ({
3097
+ agentId: item.agentId,
3098
+ handoffReason: item.handoffReason,
3099
+ session: item.session
3100
+ })),
3101
+ completed: effective2.filter((item) => item.status === "success").map((item) => ({ agentId: item.agentId, output: item.output.slice(-4e3) })),
3102
+ assignments: this.work?.agents
3103
+ });
3104
+ this.contextManager.set("rerouting", this.recovery);
3105
+ this.contextManager.set("upstreamOutputs", "");
3106
+ }
3107
+ const effective = [...new Map(result.agentResults.map((item) => [item.agentId, item])).values()];
3108
+ result.effectiveAgentResults = effective;
3109
+ result.agentResults = audit;
3110
+ if (this.rerouting && result.reply)
3111
+ this.routeError = "Work remains incomplete: " + result.reply.message;
3112
+ result.originalRequirement = this.originalRequirement;
3113
+ result.routing = this.routing;
3114
+ if (this.routeError) {
3115
+ result.success = false;
3116
+ result.routing = {
3117
+ mode: this.routing?.mode ?? "run",
3118
+ workId: this.work?.id,
3119
+ reroutes: this.rerouting ? 1 : 0,
3120
+ error: this.routeError
3121
+ };
3122
+ this.eventBus.emit({
3123
+ type: "orchestration:routing-error",
3124
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3125
+ payload: { message: this.routeError }
3126
+ });
3127
+ }
3128
+ if (this.work) {
3129
+ this.work.requirement = this.contextManager.get("requirement") ?? this.work.requirement;
3130
+ const workers = effective.filter((item) => item.agentId !== this.config.review?.reviewer);
3131
+ this.work.outcome = {
3132
+ execution: result.cancelled ? "cancelled" : !this.routeError && workers.length > 0 && workers.every((item) => item.status === "success") ? "success" : "failed",
3133
+ completedAgents: effective.filter(
3134
+ (item) => item.status === "success" && item.agentId !== this.config.review?.reviewer
3135
+ ).map((item) => item.agentId),
3136
+ failedAgents: workers.filter((item) => item.status !== "success").map((item) => ({ agentId: item.agentId, reason: item.error ?? item.handoffReason })),
3137
+ ...result.acceptance ? { acceptance: { status: result.acceptance.status, reason: result.acceptance.reason } } : {}
3138
+ };
3139
+ result.work = this.work;
3140
+ }
3141
+ if (this.ownsSessions) {
3142
+ this.cleanupAttempted = true;
3143
+ try {
3144
+ await this.sessions?.close();
3145
+ } catch (error) {
3146
+ result.success = false;
3147
+ result.routing = {
3148
+ mode: this.routing?.mode ?? "run",
3149
+ workId: this.work?.id,
3150
+ reroutes: this.rerouting ? 1 : 0,
3151
+ error: "Session cleanup failed: " + errorMessage2(error)
3152
+ };
3153
+ if (this.work?.outcome) this.work.outcome.execution = "failed";
3154
+ this.eventBus.emit({
3155
+ type: "orchestration:routing-error",
3156
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3157
+ payload: { message: result.routing.error }
3158
+ });
3159
+ }
3160
+ for (const item of [...result.agentResults, ...result.effectiveAgentResults ?? []]) {
3161
+ const latest = item.session && this.sessionRecords.get(item.session.bindingId);
3162
+ if (latest) item.session = { ...latest, action: item.session.action };
3163
+ }
3164
+ }
3165
+ const cost = sumCosts([...audit, ...this.leaderCosts]);
3166
+ if (cost !== void 0) result.totalCostUsd = cost;
3167
+ result.startTime = started.toISOString();
3168
+ result.endTime = (/* @__PURE__ */ new Date()).toISOString();
3169
+ result.totalDuration = Date.now() - started.getTime();
3170
+ this.eventBus.emit({
3171
+ type: "orchestration:complete",
3172
+ timestamp: result.endTime,
3173
+ payload: result
3174
+ });
3175
+ return result;
3176
+ }
3177
+ async runPass(options) {
2614
3178
  const startTime = /* @__PURE__ */ new Date();
2615
3179
  if (options.signal?.aborted) return this.finishCancelled(startTime);
2616
3180
  const policyError = this.reviewPolicyError();
@@ -2626,16 +3190,38 @@ var Orchestrator = class {
2626
3190
  return result2;
2627
3191
  }
2628
3192
  if (options.signal?.aborted) return this.finishCancelled(startTime);
2629
- this.prepareTaskPlan();
2630
3193
  const resolution = await this.resolveTurn(options.signal);
2631
3194
  if (resolution.kind === "cancelled" || options.signal?.aborted) {
2632
3195
  return this.finishCancelled(startTime);
2633
3196
  }
3197
+ if (resolution.kind === "error") {
3198
+ this.routeError = resolution.message;
3199
+ return this.finishEmpty(startTime, void 0, false);
3200
+ }
2634
3201
  if (resolution.kind === "message") {
2635
3202
  return this.finishWithMessage(resolution.mode, resolution.message, startTime);
2636
3203
  }
2637
3204
  const agents2 = resolution.agents;
2638
- if (this.config.resume) this.applyResumeTaskPlan(agents2);
3205
+ if (this.routing?.mode === "run" && !this.rerouting && !this.config.resume?.agents) {
3206
+ try {
3207
+ await this.sessions?.resetAgents(
3208
+ this.config.agents.filter((agent) => agent.id !== this.config.leader?.id).map((agent) => agent.id)
3209
+ );
3210
+ this.prepareTaskPlan();
3211
+ } catch (error) {
3212
+ this.routeError = "Routing incomplete: could not prepare new work: " + errorMessage2(error);
3213
+ return this.finishEmpty(startTime, void 0, false);
3214
+ }
3215
+ }
3216
+ if (!this.config.resume?.agents && (this.rerouting || this.routing?.mode === "continue") && agents2.some((agent) => this.isPlanner(agent))) {
3217
+ try {
3218
+ fs4.rmSync(path6.join(this.workDir, "task-plan.json"), { force: true });
3219
+ } catch (error) {
3220
+ this.routeError = "Routing incomplete: could not clear the previous plan: " + errorMessage2(error);
3221
+ return this.finishEmpty(startTime, void 0, false);
3222
+ }
3223
+ }
3224
+ if (this.config.resume?.agents && !this.config.latestWork) this.applyResumeTaskPlan(agents2);
2639
3225
  if (agents2.length === 0)
2640
3226
  return this.finishEmpty(
2641
3227
  startTime,
@@ -2666,6 +3252,7 @@ var Orchestrator = class {
2666
3252
  let cancelled = false;
2667
3253
  let planApplied = false;
2668
3254
  const executedIds = /* @__PURE__ */ new Set();
3255
+ let handedBack = false;
2669
3256
  for (let i = 0; i < stages.length; i++) {
2670
3257
  if (options.signal?.aborted) {
2671
3258
  cancelled = true;
@@ -2692,11 +3279,29 @@ var Orchestrator = class {
2692
3279
  allSuccess = false;
2693
3280
  break;
2694
3281
  }
3282
+ this.captureWork(agents2, results);
3283
+ handedBack = stageResults.some((item) => item.status === "handed_back");
3284
+ if (handedBack) {
3285
+ allSuccess = false;
3286
+ this.eventBus.emit({
3287
+ type: "stage:complete",
3288
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3289
+ payload: { stageIndex: i }
3290
+ });
3291
+ for (const pending of stages.slice(i + 1).flat())
3292
+ this.eventBus.emit({
3293
+ type: "agent:skipped",
3294
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3295
+ payload: { agentId: pending.id, agentName: pending.name, icon: pending.icon }
3296
+ });
3297
+ break;
3298
+ }
2695
3299
  if (!planApplied && stage.some((a) => this.isPlanner(a)) && stageResults.some(
2696
3300
  (r) => r.status === "success" && stage.some((a) => a.id === r.agentId && this.isPlanner(a))
2697
3301
  )) {
2698
3302
  planApplied = true;
2699
3303
  await this.applyTaskPlan(agents2, executedIds);
3304
+ this.captureWork(agents2, results);
2700
3305
  const remaining = agents2.filter((a) => !executedIds.has(a.id)).map((a) => ({
2701
3306
  ...a,
2702
3307
  dependsOn: a.dependsOn?.filter((dep) => !executedIds.has(dep))
@@ -2761,7 +3366,7 @@ var Orchestrator = class {
2761
3366
  }
2762
3367
  }
2763
3368
  let acceptance;
2764
- if (this.config.review) {
3369
+ if (this.config.review && !handedBack) {
2765
3370
  acceptance = await this.reviewDelivery(
2766
3371
  agents2,
2767
3372
  results,
@@ -2773,6 +3378,7 @@ var Orchestrator = class {
2773
3378
  cancelled ||= acceptance.status === "cancelled";
2774
3379
  allSuccess = allSuccess && acceptance.status === "approved";
2775
3380
  }
3381
+ this.captureWork(agents2, results);
2776
3382
  const endTime = /* @__PURE__ */ new Date();
2777
3383
  const totalCostUsd = sumCosts(results);
2778
3384
  const result = {
@@ -2788,11 +3394,6 @@ var Orchestrator = class {
2788
3394
  ...this.reasonField(resolution.reason),
2789
3395
  ...this.requirementField()
2790
3396
  };
2791
- this.eventBus.emit({
2792
- type: "orchestration:complete",
2793
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2794
- payload: result
2795
- });
2796
3397
  return result;
2797
3398
  }
2798
3399
  reviewPolicyError() {
@@ -2820,7 +3421,7 @@ var Orchestrator = class {
2820
3421
  const acceptance = {
2821
3422
  status: "failed",
2822
3423
  reviewer: policy.reviewer,
2823
- repairRounds: 0,
3424
+ repairRounds: this.repairSpent,
2824
3425
  reviews: []
2825
3426
  };
2826
3427
  if (cancelled || signal?.aborted) return { ...acceptance, status: "cancelled" };
@@ -2866,7 +3467,8 @@ var Orchestrator = class {
2866
3467
  const result = await this.runner.run(
2867
3468
  agent,
2868
3469
  signal,
2869
- `${instruction}
3470
+ `${this.recovery}
3471
+ ${instruction}
2870
3472
 
2871
3473
  ## Latest worker outputs
2872
3474
  ${this.contextManager.get("upstreamOutputs") ?? ""}`
@@ -2916,6 +3518,7 @@ Approval requires an empty issues array; changes_required requires at least one
2916
3518
  return acceptance;
2917
3519
  }
2918
3520
  acceptance.repairRounds++;
3521
+ this.repairSpent++;
2919
3522
  this.warn(
2920
3523
  `Delivery requires changes \u2014 repair pass ${acceptance.repairRounds}/${policy.maxRepairRounds ?? 1}.`
2921
3524
  );
@@ -2986,8 +3589,8 @@ Address these findings within your assigned scope, preserve the complete task re
2986
3589
  * generic "no plan was provided" fallback.
2987
3590
  */
2988
3591
  prepareTaskPlan() {
2989
- if (this.config.resume) return;
2990
- fs4.rmSync(path5.join(this.workDir, "task-plan.json"), { force: true });
3592
+ if (this.config.resume?.agents) return;
3593
+ fs4.rmSync(path6.join(this.workDir, "task-plan.json"), { force: true });
2991
3594
  }
2992
3595
  /**
2993
3596
  * Decide what this turn is: a pipeline, an answer, or a question.
@@ -3000,38 +3603,132 @@ Address these findings within your assigned scope, preserve the complete task re
3000
3603
  async resolveTurn(signal) {
3001
3604
  if (signal?.aborted) return { kind: "cancelled" };
3002
3605
  if (this.config.resume?.agents) {
3003
- return {
3004
- kind: "run",
3005
- agents: this.selectAgents(this.config.resume.agents, "The resume list")
3006
- };
3606
+ const agents2 = this.selectAgents(this.config.resume.agents, "The resume list");
3607
+ this.beginWork(agents2, "continue", this.config.latestWork);
3608
+ return { kind: "run", agents: agents2 };
3007
3609
  }
3008
3610
  if (!this.config.leader || this.config.useLeader === false) {
3009
- return { kind: "run", agents: this.configuredAgents() };
3611
+ const agents2 = this.configuredAgents();
3612
+ this.beginWork(agents2, "run");
3613
+ return { kind: "run", agents: agents2 };
3010
3614
  }
3615
+ const available = this.configuredAgents().filter((agent) => !this.excluded.has(agent.id));
3011
3616
  this.contextManager.set(
3012
3617
  "availableAgents",
3013
- this.configuredAgents().map((a) => `${a.id}: ${a.name}`).join("\n")
3618
+ available.map((agent) => agent.id + ": " + agent.name + " \u2014 " + (agent.description ?? "")).join("\n")
3619
+ );
3620
+ this.contextManager.set(
3621
+ "latestWork",
3622
+ JSON.stringify(this.rerouting ? this.work : this.config.latestWork) ?? "No previous work."
3014
3623
  );
3015
- const result = await this.runner.run(this.config.leader, signal);
3016
- if (signal?.aborted || result.status === "cancelled") return { kind: "cancelled" };
3017
- if (result.status !== "success") {
3018
- this.warn("The leader could not be reached \u2014 running the full configured team.");
3019
- return { kind: "run", agents: this.configuredAgents() };
3020
- }
3021
- const decision = parseLeaderDecision(result.output);
3022
- if (decision) {
3023
- if (decision.mode !== "run") {
3624
+ const leader2 = this.config.leader;
3625
+ for (let attempt = 0; attempt < 2; attempt++) {
3626
+ const result = await this.runner.run(
3627
+ leader2,
3628
+ signal,
3629
+ (this.recovery || "") + (attempt ? "\nYour previous routing decision was invalid or unavailable. Return exactly one valid run, continue, reply or ask decision. Do not select unavailable agents." : "")
3630
+ );
3631
+ this.leaderCosts.push(result);
3632
+ if (signal?.aborted || result.status === "cancelled") return { kind: "cancelled" };
3633
+ const decision = result.status === "success" ? parseLeaderDecision(result.output) : void 0;
3634
+ if (decision && (decision.mode === "run" || decision.mode === "continue")) {
3635
+ const known = new Set(available.map((agent) => agent.id));
3636
+ if (!decision.run.length || decision.run.some((id) => !known.has(id)) || new Set(decision.run).size !== decision.run.length) {
3637
+ this.warn("Invalid routing agents: " + decision.run.join(", "));
3638
+ continue;
3639
+ }
3640
+ const previous = this.rerouting ? this.work : this.config.latestWork;
3641
+ if (decision.mode === "continue" && (!previous || previous.outcome?.execution === "cancelled" || decision.workId !== previous.id))
3642
+ continue;
3643
+ if (decision.requirement) this.contextManager.set("requirement", decision.requirement);
3644
+ else if (decision.mode === "continue" && previous)
3645
+ this.contextManager.set(
3646
+ "requirement",
3647
+ previous.requirement + "\n\n[Follow-up] " + this.originalRequirement
3648
+ );
3649
+ const agents2 = this.selectAgents(decision.run);
3650
+ this.beginWork(agents2, decision.mode, previous);
3651
+ this.assignments = new Map((decision.tasks ?? []).map((task) => [task.agentId, task.scope]));
3652
+ for (const task of decision.tasks ?? []) {
3653
+ const agent = agents2.find((item) => item.id === task.agentId);
3654
+ if (agent)
3655
+ agent.agentScope = this.assignments.get(agent.id) ?? task.scope ?? agent.agentScope;
3656
+ }
3657
+ this.captureWork(agents2, []);
3658
+ return { kind: "run", agents: agents2, reason: decision.reason };
3659
+ }
3660
+ if (decision && (decision.mode === "reply" || decision.mode === "ask"))
3024
3661
  return { kind: "message", mode: decision.mode, message: decision.message };
3662
+ const message = result.output.trim();
3663
+ if (result.status === "success" && message && !/^[{[]|"(?:mode|run|tasks|workId)"\s*:|multiclaw-handoff/.test(message))
3664
+ return { kind: "message", mode: "reply", message };
3665
+ }
3666
+ return {
3667
+ kind: "error",
3668
+ message: "Routing incomplete: the leader did not return a valid decision after one retry."
3669
+ };
3670
+ }
3671
+ beginWork(agents2, mode, previous) {
3672
+ const continuing = mode === "continue" || this.rerouting || !!this.config.resume?.agents;
3673
+ this.work = continuing && previous ? structuredClone(previous) : {
3674
+ id: randomUUID4(),
3675
+ originalRequirement: this.originalRequirement,
3676
+ requirement: this.contextManager.get("requirement") ?? this.originalRequirement,
3677
+ agents: []
3678
+ };
3679
+ this.routing = {
3680
+ mode: continuing ? "continue" : "run",
3681
+ workId: this.work.id,
3682
+ reroutes: this.rerouting ? 1 : 0
3683
+ };
3684
+ this.contextManager.set("workId", this.work.id);
3685
+ if (continuing) {
3686
+ const sessions = {
3687
+ ...this.config.resume?.agents ? this.config.resume.sessions : {}
3688
+ };
3689
+ for (const agent of agents2) {
3690
+ const prior = this.work.agents.find((item) => item.agentId === agent.id);
3691
+ if (prior) {
3692
+ agent.agentScope = prior.scope ?? agent.agentScope;
3693
+ agent.agentPlan = prior.plan ?? agent.agentPlan;
3694
+ agent.taskTitle = prior.taskTitle ?? agent.taskTitle;
3695
+ if (prior.dependsOn)
3696
+ agent.dependsOn = prior.dependsOn.filter((id) => agents2.some((item) => item.id === id));
3697
+ if (prior.session) sessions[agent.id] = prior.session;
3698
+ }
3025
3699
  }
3026
- if (decision.requirement) this.contextManager.set("requirement", decision.requirement);
3027
- return { kind: "run", agents: this.selectAgents(decision.run), reason: decision.reason };
3700
+ this.config.resume = {
3701
+ ...this.config.resume?.agents ? { agents: this.config.resume.agents } : {},
3702
+ sessions
3703
+ };
3704
+ this.recovery = this.recovery || "## Continued work\nInspect the workspace and git state before repeating operations. Previous execution and acceptance facts (partial output is not verified):\n" + JSON.stringify(this.work);
3705
+ } else {
3706
+ this.config.resume = void 0;
3707
+ this.recovery = "";
3028
3708
  }
3029
- const message = result.output.trim();
3030
- if (!message) {
3031
- this.warn("The leader returned no output \u2014 running the full configured team.");
3032
- return { kind: "run", agents: this.configuredAgents() };
3709
+ }
3710
+ captureWork(agents2, results) {
3711
+ if (!this.work || !this.routing) return;
3712
+ this.work.requirement = this.contextManager.get("requirement") ?? this.work.requirement;
3713
+ for (const agent of agents2) {
3714
+ const result = [...results].reverse().find((item) => item.agentId === agent.id);
3715
+ const old = this.work.agents.find((item) => item.agentId === agent.id);
3716
+ const entry3 = {
3717
+ agentId: agent.id,
3718
+ scope: agent.agentScope,
3719
+ plan: agent.agentPlan,
3720
+ taskTitle: agent.taskTitle,
3721
+ dependsOn: agent.dependsOn ? [...agent.dependsOn] : void 0,
3722
+ session: result?.session?.sessionId || result?.session?.failure?.code === "cleanup_failed" ? result.session : old?.session ?? result?.session
3723
+ };
3724
+ this.work.agents = this.work.agents.filter((item) => item.agentId !== agent.id);
3725
+ this.work.agents.push(entry3);
3033
3726
  }
3034
- return { kind: "message", mode: "reply", message };
3727
+ this.eventBus.emit({
3728
+ type: "orchestration:route",
3729
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3730
+ payload: { routing: { ...this.routing }, work: structuredClone(this.work) }
3731
+ });
3035
3732
  }
3036
3733
  /**
3037
3734
  * The configured team, as copies.
@@ -3122,7 +3819,7 @@ Address these findings within your assigned scope, preserve the complete task re
3122
3819
  while (next < agents2.length && !signal?.aborted && !cancelled) {
3123
3820
  const index = next;
3124
3821
  next += 1;
3125
- const result = await this.runner.run(agents2[index], signal);
3822
+ const result = await this.runner.run(agents2[index], signal, this.recovery || void 0);
3126
3823
  results.set(index, result);
3127
3824
  if (result.status === "cancelled") cancelled = true;
3128
3825
  }
@@ -3154,7 +3851,7 @@ Address these findings within your assigned scope, preserve the complete task re
3154
3851
  * it deliberately left out.
3155
3852
  */
3156
3853
  readTaskPlan() {
3157
- const taskPlanPath = path5.join(this.workDir, "task-plan.json");
3854
+ const taskPlanPath = path6.join(this.workDir, "task-plan.json");
3158
3855
  if (!fs4.existsSync(taskPlanPath)) return void 0;
3159
3856
  let taskPlan;
3160
3857
  try {
@@ -3210,8 +3907,8 @@ Address these findings within your assigned scope, preserve the complete task re
3210
3907
  }
3211
3908
  if (executedIds.has(agent.id)) continue;
3212
3909
  agent.taskTitle = task.title;
3213
- agent.dependsOn = dependsOn;
3214
- agent.agentScope = task.scope;
3910
+ agent.dependsOn = dependsOn ?? agent.dependsOn;
3911
+ agent.agentScope = this.assignments.get(agent.id) ?? task.scope ?? agent.agentScope;
3215
3912
  if (task.plan?.length) {
3216
3913
  agent.agentPlan = task.plan.map((s, i) => `${i + 1}. ${s}`).join("\n");
3217
3914
  }
@@ -3233,7 +3930,7 @@ Address these findings within your assigned scope, preserve the complete task re
3233
3930
  const task = taskMap.get(agent.id);
3234
3931
  if (!task) continue;
3235
3932
  agent.taskTitle = task.title;
3236
- agent.agentScope = task.scope;
3933
+ agent.agentScope = this.assignments.get(agent.id) ?? task.scope ?? agent.agentScope;
3237
3934
  if (task.plan?.length) {
3238
3935
  agent.agentPlan = task.plan.map((s, i) => `${i + 1}. ${s}`).join("\n");
3239
3936
  }
@@ -3278,11 +3975,6 @@ Address these findings within your assigned scope, preserve the complete task re
3278
3975
  reply: { mode, message },
3279
3976
  ...this.requirementField()
3280
3977
  };
3281
- this.eventBus.emit({
3282
- type: "orchestration:complete",
3283
- timestamp: endTime.toISOString(),
3284
- payload: result
3285
- });
3286
3978
  return result;
3287
3979
  }
3288
3980
  warn(message) {
@@ -3304,11 +3996,6 @@ Address these findings within your assigned scope, preserve the complete task re
3304
3996
  endTime: endTime.toISOString(),
3305
3997
  ...this.requirementField()
3306
3998
  };
3307
- this.eventBus.emit({
3308
- type: "orchestration:complete",
3309
- timestamp: endTime.toISOString(),
3310
- payload: result
3311
- });
3312
3999
  return result;
3313
4000
  }
3314
4001
  /**
@@ -3335,11 +4022,6 @@ Address these findings within your assigned scope, preserve the complete task re
3335
4022
  endTime: endTime.toISOString(),
3336
4023
  ...this.requirementField()
3337
4024
  };
3338
- this.eventBus.emit({
3339
- type: "orchestration:complete",
3340
- timestamp: endTime.toISOString(),
3341
- payload: result
3342
- });
3343
4025
  return result;
3344
4026
  }
3345
4027
  };
@@ -3394,24 +4076,24 @@ function parseVersion(raw) {
3394
4076
  }
3395
4077
  var SGR_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
3396
4078
  async function versionFor(command) {
3397
- return new Promise((resolve4) => {
4079
+ return new Promise((resolve5) => {
3398
4080
  let out = "";
3399
4081
  const child = spawn2(command, ["--version"], { env: process.env });
3400
4082
  child.stdout.on("data", (d) => {
3401
4083
  out += d.toString();
3402
4084
  });
3403
4085
  child.on("close", () => {
3404
- resolve4(out.trim() || void 0);
4086
+ resolve5(out.trim() || void 0);
3405
4087
  });
3406
4088
  child.on("error", () => {
3407
- resolve4(void 0);
4089
+ resolve5(void 0);
3408
4090
  });
3409
4091
  setTimeout(() => {
3410
4092
  try {
3411
4093
  child.kill("SIGKILL");
3412
4094
  } catch {
3413
4095
  }
3414
- resolve4(void 0);
4096
+ resolve5(void 0);
3415
4097
  }, 3e3);
3416
4098
  });
3417
4099
  }
@@ -3480,7 +4162,7 @@ function describeResumePlan({ agents: agents2, sessions = {}, team }) {
3480
4162
  const teamById = new Map(team.map((member) => [member.id, member]));
3481
4163
  const running = new Set(agents2);
3482
4164
  const clauses = agents2.map(
3483
- (id) => sessions[id] ? `${id} continues its session` : `${id} starts fresh`
4165
+ (id) => sessions[id]?.sessionId ? `${id} will attempt to resume its session` : `${id} starts fresh`
3484
4166
  );
3485
4167
  for (const id of agents2) {
3486
4168
  const dropped = (teamById.get(id)?.dependsOn ?? []).filter(
@@ -3504,8 +4186,12 @@ export {
3504
4186
  buildStages,
3505
4187
  defineConfig,
3506
4188
  describeResumePlan,
4189
+ describeSession,
3507
4190
  detectProviders,
3508
4191
  isAbortError,
4192
+ isSessionEvent,
4193
+ isSessionRecord,
4194
+ isWorkContext,
3509
4195
  presentProviders,
3510
4196
  providerCommands,
3511
4197
  providerIds,
@@ -3519,6 +4205,7 @@ export {
3519
4205
  resolveProvider,
3520
4206
  resolvesOnPath,
3521
4207
  runtimeRegistry,
4208
+ sessionCompatibilityKey,
3522
4209
  specForCommand,
3523
4210
  specForProvider,
3524
4211
  tierModel,