@wrongstack/core 0.63.4 → 0.68.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.
Files changed (57) hide show
  1. package/dist/{agent-bridge-B5rxWrg3.d.ts → agent-bridge-D-j6OOBT.d.ts} +1 -1
  2. package/dist/agent-subagent-runner-DRZ9-NnR.d.ts +1042 -0
  3. package/dist/{compactor-0vjZ8KTk.d.ts → compactor-D_ExJajC.d.ts} +1 -1
  4. package/dist/{config-BdDuaZmB.d.ts → config--86aHSln.d.ts} +1 -1
  5. package/dist/{context-iFMEO2rN.d.ts → context-y87Jc5ei.d.ts} +3 -3
  6. package/dist/coordination/index.d.ts +12 -12
  7. package/dist/coordination/index.js +265 -275
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +22 -22
  10. package/dist/defaults/index.js +181 -180
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-k8CHjcrN.d.ts → events-CIplI98R.d.ts} +1 -1
  13. package/dist/execution/index.d.ts +16 -385
  14. package/dist/execution/index.js +124 -146
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/goal-store-C7jcumEh.d.ts +96 -0
  18. package/dist/{index-Bc6BiP5q.d.ts → index-DKUvyTvV.d.ts} +28 -442
  19. package/dist/{index-CWdW_CJt.d.ts → index-b5uhfTSl.d.ts} +8 -8
  20. package/dist/index.d.ts +34 -32
  21. package/dist/index.js +692 -750
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/kernel/index.d.ts +9 -9
  25. package/dist/{mcp-servers-CwqQDMYy.d.ts → mcp-servers-DwoNBf6r.d.ts} +3 -3
  26. package/dist/models/index.d.ts +2 -2
  27. package/dist/{multi-agent-coordinator-CNUJYq7U.d.ts → multi-agent-coordinator-CWnH-CiX.d.ts} +10 -2
  28. package/dist/{null-fleet-bus-DRoJ0uOY.d.ts → null-fleet-bus-CuN0ObJr.d.ts} +24 -31
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/parallel-eternal-engine-0UwotoSx.d.ts +483 -0
  31. package/dist/{path-resolver-C5sPVne8.d.ts → path-resolver-DVkEcIw8.d.ts} +2 -2
  32. package/dist/{permission-Ld-i5ugf.d.ts → permission-C1A5whY5.d.ts} +5 -1
  33. package/dist/{permission-policy-CL-mPufp.d.ts → permission-policy-B2dK-T5N.d.ts} +19 -5
  34. package/dist/{plan-templates-ThBHOjaM.d.ts → plan-templates-Bprrzhbu.d.ts} +4 -4
  35. package/dist/{provider-runner-DJQa211J.d.ts → provider-runner-mXvXGSIw.d.ts} +3 -3
  36. package/dist/{retry-policy-BfBScewS.d.ts → retry-policy-CG3qvH_e.d.ts} +1 -1
  37. package/dist/sdd/index.d.ts +8 -8
  38. package/dist/sdd/index.js +123 -146
  39. package/dist/sdd/index.js.map +1 -1
  40. package/dist/security/index.d.ts +3 -3
  41. package/dist/security/index.js +31 -22
  42. package/dist/security/index.js.map +1 -1
  43. package/dist/{selector-DxhW7ML3.d.ts → selector-RvBR_YRW.d.ts} +1 -1
  44. package/dist/session-event-bridge-CDHxcmQU.d.ts +93 -0
  45. package/dist/{session-reader-q2ThszgG.d.ts → session-reader-BIpwM60D.d.ts} +1 -1
  46. package/dist/storage/index.d.ts +7 -6
  47. package/dist/{system-prompt-7LHyBbIf.d.ts → system-prompt-b61lOd49.d.ts} +2 -2
  48. package/dist/types/index.d.ts +23 -14
  49. package/dist/types/index.js.map +1 -1
  50. package/dist/utils/index.d.ts +2 -2
  51. package/dist/utils/index.js.map +1 -1
  52. package/package.json +1 -1
  53. package/skills/multi-agent/SKILL.md +0 -2
  54. package/dist/agent-subagent-runner-Zc3f37Sg.d.ts +0 -182
  55. package/dist/goal-store-iHltMi5n.d.ts +0 -188
  56. package/dist/multi-agent-SASYOrWA.d.ts +0 -554
  57. package/dist/tool-executor-CIjpGaRA.d.ts +0 -111
package/dist/index.js CHANGED
@@ -3395,6 +3395,29 @@ function createToolOutputSerializer(opts = {}) {
3395
3395
  }
3396
3396
  return { serialize, enforceCap, capBytes };
3397
3397
  }
3398
+ function truncateForEvent(content, max = 400) {
3399
+ if (!content) return "";
3400
+ return content.length <= max ? content : `${content.slice(0, max - 1)}\u2026`;
3401
+ }
3402
+ function sizeSignals(toolName, content) {
3403
+ if (!content || content.length === 0) {
3404
+ return { outputBytes: 0, outputTokens: 0, outputLines: void 0 };
3405
+ }
3406
+ const outputBytes = Buffer.byteLength(content, "utf8");
3407
+ const outputTokens = Math.max(1, Math.round(outputBytes / 3.5));
3408
+ let outputLines;
3409
+ if (toolName === "read") {
3410
+ const lineRe = /^\s*\d+→/gm;
3411
+ let count = 0;
3412
+ while (lineRe.exec(content) !== null) count++;
3413
+ if (count > 0) outputLines = count;
3414
+ } else if (toolName === "bash" || toolName === "shell" || toolName === "grep" || toolName === "logs") {
3415
+ let nl = 0;
3416
+ for (let i = 0; i < content.length; i++) if (content.charCodeAt(i) === 10) nl++;
3417
+ outputLines = nl + (content.endsWith("\n") ? 0 : 1);
3418
+ }
3419
+ return { outputBytes, outputTokens, outputLines };
3420
+ }
3398
3421
 
3399
3422
  // src/execution/tool-executor.ts
3400
3423
  var ToolExecutor = class {
@@ -7080,6 +7103,8 @@ var DefaultPermissionPolicy = class {
7080
7103
  trustFile;
7081
7104
  yolo;
7082
7105
  yoloDestructive;
7106
+ /** When true, destructive ops still require confirmation even in YOLO mode. */
7107
+ confirmDestructive;
7083
7108
  /**
7084
7109
  * Session-scoped "soft deny" map. When the user presses 'n' (block once),
7085
7110
  * the tool+pattern is added here. If the LLM retries in the same session,
@@ -7113,6 +7138,7 @@ var DefaultPermissionPolicy = class {
7113
7138
  this.trustFile = opts.trustFile;
7114
7139
  this.yolo = opts.yolo ?? false;
7115
7140
  this.yoloDestructive = opts.yoloDestructive ?? opts.forceAllYolo ?? false;
7141
+ this.confirmDestructive = opts.confirmDestructive ?? false;
7116
7142
  this.promptDelegate = opts.promptDelegate;
7117
7143
  }
7118
7144
  /**
@@ -7140,6 +7166,14 @@ var DefaultPermissionPolicy = class {
7140
7166
  getYoloDestructive() {
7141
7167
  return this.yoloDestructive;
7142
7168
  }
7169
+ /** Toggle destructive confirmation gate (only meaningful when yolo is active). */
7170
+ setConfirmDestructive(enabled) {
7171
+ this.confirmDestructive = enabled;
7172
+ }
7173
+ /** Check whether destructive confirmation gate is active. */
7174
+ getConfirmDestructive() {
7175
+ return this.confirmDestructive;
7176
+ }
7143
7177
  /** @deprecated Use `setYoloDestructive`. */
7144
7178
  setForceAllYolo(enabled) {
7145
7179
  this.setYoloDestructive(enabled);
@@ -7193,30 +7227,28 @@ var DefaultPermissionPolicy = class {
7193
7227
  return { permission: "auto", source: "trust" };
7194
7228
  }
7195
7229
  if (this.yolo) {
7196
- const destructive = this.isDestructiveYoloCall(tool, input, ctx);
7197
- if (destructive && !this.yoloDestructive) {
7198
- if (this.promptDelegate) {
7199
- const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
7200
- if (decision === "always") {
7201
- await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
7202
- return {
7203
- permission: "auto",
7204
- source: "user",
7205
- reason: "destructive yolo always-allowed"
7206
- };
7207
- }
7208
- if (decision === "deny") {
7209
- await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
7210
- return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
7230
+ if (this.confirmDestructive) {
7231
+ const destructive = this.isDestructiveYoloCall(tool, input, ctx);
7232
+ if (destructive) {
7233
+ if (this.promptDelegate) {
7234
+ const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
7235
+ if (decision === "always") {
7236
+ await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
7237
+ return { permission: "auto", source: "user", reason: "destructive yolo always-allowed" };
7238
+ }
7239
+ if (decision === "deny") {
7240
+ await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
7241
+ return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
7242
+ }
7243
+ return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
7211
7244
  }
7212
- return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
7245
+ return {
7246
+ permission: "confirm",
7247
+ source: "yolo_destructive",
7248
+ riskTier: "destructive",
7249
+ reason: "destructive tool needs explicit approval (confirmDestructive is on)"
7250
+ };
7213
7251
  }
7214
- return {
7215
- permission: "confirm",
7216
- source: "yolo_destructive",
7217
- riskTier: "destructive",
7218
- reason: "destructive tool needs explicit approval even in yolo mode"
7219
- };
7220
7252
  }
7221
7253
  return { permission: "auto", source: "yolo" };
7222
7254
  }
@@ -9775,15 +9807,22 @@ var SubagentBudget = class _SubagentBudget {
9775
9807
  void this.checkLimits();
9776
9808
  }
9777
9809
  /**
9778
- * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
9779
- * wall-clock time cannot be "extended" by the coordinator, so it throws
9780
- * synchronously rather than entering the negotiation flow.
9810
+ * Wall-clock / idle budget check. Delegates to `checkLimits(elapsed)`, so
9811
+ * `timeout` and `idle_timeout` follow the SAME negotiation path as the other
9812
+ * kinds they are NOT a special-cased hard stop. This is deliberate: a
9813
+ * heartbeat-aware policy (see `attachAutoExtend` and `CollabSession`) grants
9814
+ * a timeout extension only while the agent is making progress and denies it
9815
+ * once the agent is genuinely stuck, which is safer than an unconditional
9816
+ * hard kill of a long-but-working agent. The runner translates the resulting
9817
+ * `BudgetThresholdSignal` decision (`extend` → patch limits in place,
9818
+ * `stop` → abort) just like every other kind.
9781
9819
  *
9782
- * Decision table:
9783
- * - no `onThreshold` handler → throw `BudgetExceededError`
9784
- * - `mode === 'sync'` → throw `BudgetExceededError`
9785
- * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
9786
- * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
9820
+ * Decision table (same as `checkLimits`):
9821
+ * - no `onThreshold` handler → throw `BudgetExceededError` (hard stop)
9822
+ * - `mode === 'sync'` → throw `BudgetExceededError` (hard stop)
9823
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (no one to ask)
9824
+ * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` (negotiated;
9825
+ * a heartbeat-aware policy may extend the timeout)
9787
9826
  */
9788
9827
  checkTimeout() {
9789
9828
  if (this.startTime === null) return;
@@ -12285,6 +12324,71 @@ Do not add prose, markdown, or code fences.`;
12285
12324
  };
12286
12325
  }
12287
12326
 
12327
+ // src/coordination/coordinator/error-classifier.ts
12328
+ function classifySubagentError(err, hints = {}) {
12329
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
12330
+ if (err instanceof ProviderError) {
12331
+ const baseMessage2 = err.describe();
12332
+ return providerErrorToSubagentError(err, baseMessage2, cause);
12333
+ }
12334
+ const baseMessage = err instanceof Error ? err.message : String(err);
12335
+ if (err instanceof BudgetExceededError) {
12336
+ const map = {
12337
+ iterations: "budget_iterations",
12338
+ tool_calls: "budget_tool_calls",
12339
+ tokens: "budget_tokens",
12340
+ cost: "budget_cost",
12341
+ timeout: "budget_timeout",
12342
+ idle_timeout: "budget_timeout"
12343
+ };
12344
+ return {
12345
+ kind: map[err.kind],
12346
+ message: baseMessage,
12347
+ retryable: false,
12348
+ cause
12349
+ };
12350
+ }
12351
+ if (hints.parentAborted) {
12352
+ return { kind: "aborted_by_parent", message: baseMessage, retryable: false, cause };
12353
+ }
12354
+ const lower = baseMessage.toLowerCase();
12355
+ if (/agent aborted$/i.test(baseMessage)) {
12356
+ return { kind: "aborted_by_parent", message: baseMessage, retryable: false, cause };
12357
+ }
12358
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
12359
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
12360
+ }
12361
+ if (/empty response$/i.test(baseMessage)) {
12362
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
12363
+ }
12364
+ if (/^tool failed: /i.test(baseMessage)) {
12365
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
12366
+ }
12367
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
12368
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
12369
+ }
12370
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
12371
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
12372
+ }
12373
+ return { kind: "unknown", message: baseMessage, retryable: false, cause };
12374
+ }
12375
+ function providerErrorToSubagentError(err, message, cause) {
12376
+ const status = err.status;
12377
+ if (status === 429 || err.body?.type === "rate_limit_error") {
12378
+ return { kind: "provider_rate_limit", message, retryable: true, backoffMs: 5e3, cause };
12379
+ }
12380
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
12381
+ return { kind: "provider_auth", message, retryable: false, cause };
12382
+ }
12383
+ if (status === 408 || status === 0) {
12384
+ return { kind: "provider_timeout", message, retryable: true, cause };
12385
+ }
12386
+ if (status >= 500 && status < 600) {
12387
+ return { kind: "provider_5xx", message, retryable: true, backoffMs: 3e3, cause };
12388
+ }
12389
+ return { kind: "unknown", message, retryable: err.retryable, cause };
12390
+ }
12391
+
12288
12392
  // src/coordination/fleet.ts
12289
12393
  var AUDIT_LOG_AGENT = {
12290
12394
  id: "audit-log",
@@ -12649,7 +12753,10 @@ var NICKNAME_POOL = {
12649
12753
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
12650
12754
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
12651
12755
  };
12652
- var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
12756
+ var ALL_NICKNAMES = Object.entries(NICKNAME_POOL);
12757
+ var NAME_TO_KEY = Object.fromEntries(
12758
+ ALL_NICKNAMES.map(([key, entry]) => [entry.name, key])
12759
+ );
12653
12760
  var DOMAIN_PREFERENCES = {
12654
12761
  "security": ["shannon", "turing", "lamarr", "stallman"],
12655
12762
  "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
@@ -12682,17 +12789,23 @@ function assignNickname(role, used) {
12682
12789
  for (const key of preferences) {
12683
12790
  const entry = NICKNAME_POOL[key];
12684
12791
  if (entry && !used.has(key)) {
12685
- return `${entry.name} (${formatRole(role)})`;
12792
+ return { key, display: `${entry.name} (${formatRole(role)})` };
12686
12793
  }
12687
12794
  }
12688
- for (const entry of ALL_NICKNAMES) {
12689
- const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
12690
- if (key && !used.has(key)) {
12691
- return `${entry.name} (${formatRole(role)})`;
12795
+ for (const [key, entry] of ALL_NICKNAMES) {
12796
+ if (!used.has(key)) {
12797
+ return { key, display: `${entry.name} (${formatRole(role)})` };
12692
12798
  }
12693
12799
  }
12694
12800
  const counter = used.size + 1;
12695
- return `Scientist #${counter} (${formatRole(role)})`;
12801
+ return { key: `scientist-${counter}`, display: `Scientist #${counter} (${formatRole(role)})` };
12802
+ }
12803
+ function nicknameKeyFromDisplay(display) {
12804
+ const base = display.replace(/\s*\([^)]*\)\s*$/, "").trim();
12805
+ const key = NAME_TO_KEY[base];
12806
+ if (key) return key;
12807
+ const synthesized = base.match(/^Scientist #(\d+)$/);
12808
+ return synthesized ? `scientist-${synthesized[1]}` : void 0;
12696
12809
  }
12697
12810
  function formatRole(role) {
12698
12811
  return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
@@ -12779,11 +12892,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12779
12892
  const name = subagent.name?.trim() ?? "";
12780
12893
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
12781
12894
  if (!isPlaceholder) return subagent;
12782
- const nickname = assignNickname(role, this.usedNicknames);
12783
- const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
12784
- this.usedNicknames.add(baseKey);
12785
- this.subagentNicknames.set(subagentId, baseKey);
12786
- return { ...subagent, name: nickname };
12895
+ const { key, display } = assignNickname(role, this.usedNicknames);
12896
+ this.usedNicknames.add(key);
12897
+ this.subagentNicknames.set(subagentId, key);
12898
+ return { ...subagent, name: display };
12787
12899
  }
12788
12900
  async spawn(subagent) {
12789
12901
  const id = subagent.id || randomUUID();
@@ -13035,23 +13147,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
13035
13147
  */
13036
13148
  drainPendingAsAborted(message) {
13037
13149
  const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
13038
- for (const t2 of dropped) {
13039
- const synthetic = {
13040
- subagentId: t2.subagentId ?? "unassigned",
13041
- taskId: t2.id,
13042
- status: "stopped",
13043
- error: {
13044
- kind: "aborted_by_parent",
13045
- message,
13046
- retryable: false
13047
- },
13048
- iterations: 0,
13049
- toolCalls: 0,
13050
- durationMs: 0
13051
- };
13052
- this.completedResults.push(synthetic);
13053
- this.emit("task.completed", { task: t2, result: synthetic });
13054
- }
13150
+ for (const t2 of dropped) this.emitPendingAborted(t2, message);
13151
+ }
13152
+ /**
13153
+ * Emit a synthetic `stopped`/`aborted_by_parent` completion for a single
13154
+ * PENDING task — one that was never counted in `inFlight`. This MUST bypass
13155
+ * `recordCompletion`: that path does `inFlight--`, which for a pending task
13156
+ * steals a decrement from a genuinely in-flight task and trips the underflow
13157
+ * guard — suppressing that real task's `task.completed` and hanging its
13158
+ * `awaitTasks()` caller. Pushes the result and fires the event directly.
13159
+ */
13160
+ emitPendingAborted(task, message) {
13161
+ const synthetic = {
13162
+ subagentId: task.subagentId ?? "unassigned",
13163
+ taskId: task.id,
13164
+ status: "stopped",
13165
+ error: {
13166
+ kind: "aborted_by_parent",
13167
+ message,
13168
+ retryable: false
13169
+ },
13170
+ iterations: 0,
13171
+ toolCalls: 0,
13172
+ durationMs: 0
13173
+ };
13174
+ this.completedResults.push(synthetic);
13175
+ this.emit("task.completed", { task, result: synthetic });
13055
13176
  }
13056
13177
  async runDispatched(subagentId, task) {
13057
13178
  const subagent = this.subagents.get(subagentId);
@@ -13312,20 +13433,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
13312
13433
  const orphaned = this.pendingTasks.filter((t2) => t2.subagentId === subagentId);
13313
13434
  this.pendingTasks = this.pendingTasks.filter((t2) => t2.subagentId !== subagentId);
13314
13435
  for (const t2 of orphaned) {
13315
- const synthetic = {
13316
- subagentId,
13317
- taskId: t2.id,
13318
- status: "stopped",
13319
- error: {
13320
- kind: "aborted_by_parent",
13321
- message: `Subagent "${subagentId}" was removed while task "${t2.id}" was pending`,
13322
- retryable: false
13323
- },
13324
- iterations: 0,
13325
- toolCalls: 0,
13326
- durationMs: 0
13327
- };
13328
- this.recordCompletion(synthetic);
13436
+ this.emitPendingAborted(
13437
+ t2,
13438
+ `Subagent "${subagentId}" was removed while task "${t2.id}" was pending`
13439
+ );
13329
13440
  }
13330
13441
  this.fleetBus?.emit({
13331
13442
  subagentId,
@@ -13345,101 +13456,6 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
13345
13456
  return false;
13346
13457
  }
13347
13458
  };
13348
- function classifySubagentError(err, hints = {}) {
13349
- const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
13350
- if (err instanceof ProviderError) {
13351
- const baseMessage2 = err.describe();
13352
- return providerErrorToSubagentError(err, baseMessage2, cause);
13353
- }
13354
- const baseMessage = err instanceof Error ? err.message : String(err);
13355
- if (err instanceof BudgetExceededError) {
13356
- const map = {
13357
- iterations: "budget_iterations",
13358
- tool_calls: "budget_tool_calls",
13359
- tokens: "budget_tokens",
13360
- cost: "budget_cost",
13361
- timeout: "budget_timeout",
13362
- idle_timeout: "budget_timeout"
13363
- };
13364
- return {
13365
- kind: map[err.kind],
13366
- message: baseMessage,
13367
- // Budgets are user-configured ceilings, not transient failures —
13368
- // retrying with the same budget will hit the same ceiling. The
13369
- // orchestrator must raise the budget or narrow the task first.
13370
- retryable: false,
13371
- cause
13372
- };
13373
- }
13374
- if (hints.parentAborted) {
13375
- return {
13376
- kind: "aborted_by_parent",
13377
- message: baseMessage,
13378
- retryable: false,
13379
- cause
13380
- };
13381
- }
13382
- const lower = baseMessage.toLowerCase();
13383
- if (/agent aborted$/i.test(baseMessage)) {
13384
- return {
13385
- kind: "aborted_by_parent",
13386
- message: baseMessage,
13387
- retryable: false,
13388
- cause
13389
- };
13390
- }
13391
- if (/agent exhausted iteration limit$/i.test(baseMessage)) {
13392
- return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
13393
- }
13394
- if (/empty response$/i.test(baseMessage)) {
13395
- return { kind: "empty_response", message: baseMessage, retryable: false, cause };
13396
- }
13397
- if (/^tool failed: /i.test(baseMessage)) {
13398
- return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
13399
- }
13400
- if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
13401
- return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
13402
- }
13403
- if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
13404
- return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
13405
- }
13406
- return {
13407
- kind: "unknown",
13408
- message: baseMessage,
13409
- retryable: false,
13410
- cause
13411
- };
13412
- }
13413
- function providerErrorToSubagentError(err, message, cause) {
13414
- const status = err.status;
13415
- if (status === 429 || err.body?.type === "rate_limit_error") {
13416
- return {
13417
- kind: "provider_rate_limit",
13418
- message,
13419
- retryable: true,
13420
- // Conservative default: 5s. Provider-specific code can override
13421
- // by emitting an error whose body carries an explicit hint.
13422
- backoffMs: 5e3,
13423
- cause
13424
- };
13425
- }
13426
- if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
13427
- return { kind: "provider_auth", message, retryable: false, cause };
13428
- }
13429
- if (status === 408 || status === 0) {
13430
- return { kind: "provider_timeout", message, retryable: true, cause };
13431
- }
13432
- if (status >= 500 && status < 600) {
13433
- return {
13434
- kind: "provider_5xx",
13435
- message,
13436
- retryable: true,
13437
- backoffMs: 3e3,
13438
- cause
13439
- };
13440
- }
13441
- return { kind: "unknown", message, retryable: err.retryable, cause };
13442
- }
13443
13459
 
13444
13460
  // src/execution/parallel-eternal-engine.ts
13445
13461
  function sleep2(ms) {
@@ -13703,6 +13719,7 @@ ${personaLine}Task: ${task}
13703
13719
  } catch {
13704
13720
  results = coordinator.results().slice(-taskIds.length);
13705
13721
  }
13722
+ await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
13706
13723
  const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
13707
13724
  const goalComplete = results.some(
13708
13725
  (r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
@@ -14498,6 +14515,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
14498
14515
  return lines.join("\n");
14499
14516
  }
14500
14517
  cleanup() {
14518
+ if (this._timeoutTimer) {
14519
+ clearTimeout(this._timeoutTimer);
14520
+ this._timeoutTimer = void 0;
14521
+ }
14501
14522
  for (const dispose of this.disposers) dispose();
14502
14523
  this.disposers.length = 0;
14503
14524
  }
@@ -15317,7 +15338,7 @@ function isValidMatrixKey(key) {
15317
15338
  return matrixKeyKind(key) !== "unknown";
15318
15339
  }
15319
15340
 
15320
- // src/coordination/director.ts
15341
+ // src/coordination/director/director-errors.ts
15321
15342
  var FleetSpawnBudgetError = class extends Error {
15322
15343
  kind;
15323
15344
  limit;
@@ -15359,6 +15380,8 @@ var FleetContextOverflowError = class extends Error {
15359
15380
  this.observed = observed;
15360
15381
  }
15361
15382
  };
15383
+
15384
+ // src/coordination/director.ts
15362
15385
  var Director = class _Director {
15363
15386
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
15364
15387
  get coordinatorId() {
@@ -15627,18 +15650,20 @@ var Director = class _Director {
15627
15650
  if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
15628
15651
  return;
15629
15652
  }
15630
- if (payload.kind === "timeout") {
15653
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
15654
+ const heartbeatKey = `${e.subagentId}:${payload.kind}`;
15631
15655
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
15632
- const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
15656
+ const lastProgress = lastTimeoutProgress.get(heartbeatKey) ?? -1;
15633
15657
  if (progress <= lastProgress) {
15634
15658
  payload.deny();
15635
15659
  return;
15636
15660
  }
15637
- lastTimeoutProgress.set(e.subagentId, progress);
15661
+ lastTimeoutProgress.set(heartbeatKey, progress);
15662
+ const field = payload.kind === "timeout" ? "timeoutMs" : "idleTimeoutMs";
15638
15663
  setImmediate(() => {
15639
15664
  const newLimit = Math.min(Math.ceil(payload.limit * 2), 24 * 60 * 6e4);
15640
- this.recordExtension(e.subagentId, e.taskId, "timeout", newLimit);
15641
- payload.extend({ timeoutMs: newLimit });
15665
+ this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
15666
+ payload.extend({ [field]: newLimit });
15642
15667
  });
15643
15668
  return;
15644
15669
  }
@@ -15954,10 +15979,9 @@ var Director = class _Director {
15954
15979
  if (this.fleetManager) {
15955
15980
  this.fleetManager.assignNicknameAndRecord(config);
15956
15981
  } else {
15957
- config.name = assignNickname(role, this._usedNicknames);
15958
- this._usedNicknames.add(
15959
- config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
15960
- );
15982
+ const { key, display } = assignNickname(role, this._usedNicknames);
15983
+ config.name = display;
15984
+ this._usedNicknames.add(key);
15961
15985
  }
15962
15986
  }
15963
15987
  result = await this.coordinator.spawn(config);
@@ -16279,8 +16303,8 @@ var Director = class _Director {
16279
16303
  } else {
16280
16304
  const entry = this.manifestEntries.get(subagentId);
16281
16305
  if (entry?.name) {
16282
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
16283
- this._usedNicknames.delete(nicknameKey);
16306
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
16307
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
16284
16308
  }
16285
16309
  }
16286
16310
  this.manifestEntries.delete(subagentId);
@@ -24414,11 +24438,10 @@ var FleetManager = class {
24414
24438
  */
24415
24439
  assignNicknameAndRecord(config) {
24416
24440
  const role = config.role ?? "subagent";
24417
- const nickname = assignNickname(role, this._usedNicknames);
24418
- const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
24419
- this._usedNicknames.add(baseKey);
24420
- config.name = nickname;
24421
- return nickname;
24441
+ const { key, display } = assignNickname(role, this._usedNicknames);
24442
+ this._usedNicknames.add(key);
24443
+ config.name = display;
24444
+ return display;
24422
24445
  }
24423
24446
  /**
24424
24447
  * Returns the set of already-assigned nickname keys — useful for debugging
@@ -24603,8 +24626,8 @@ var FleetManager = class {
24603
24626
  removeSubagent(subagentId) {
24604
24627
  const entry = this.manifestEntries.get(subagentId);
24605
24628
  if (entry?.name) {
24606
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
24607
- this._usedNicknames.delete(nicknameKey);
24629
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
24630
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
24608
24631
  }
24609
24632
  for (const [taskId, task] of this.pendingTasks) {
24610
24633
  if (task.subagentId === subagentId) {
@@ -25051,61 +25074,170 @@ var ExtensionRegistry = class {
25051
25074
  }
25052
25075
  };
25053
25076
 
25054
- // src/core/btw.ts
25055
- var META_KEY = "_btwNotes";
25056
- function readQueue(ctx) {
25057
- const raw = ctx.meta[META_KEY];
25058
- return Array.isArray(raw) ? raw : [];
25059
- }
25060
- function setBtwNote(ctx, text) {
25061
- const trimmed = text.trim();
25062
- if (!trimmed) return readQueue(ctx).length;
25063
- const next = [...readQueue(ctx), trimmed].slice(-20);
25064
- ctx.meta[META_KEY] = next;
25065
- return next.length;
25066
- }
25067
- function pendingBtwCount(ctx) {
25068
- return readQueue(ctx).length;
25069
- }
25070
- function consumeBtwNotes(ctx) {
25071
- const notes = readQueue(ctx);
25072
- if (notes.length > 0) delete ctx.meta[META_KEY];
25073
- return notes;
25074
- }
25075
- function buildBtwBlock(notes) {
25076
- const body = notes.map((n) => `- ${n}`).join("\n");
25077
- return [
25078
- "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
25079
- "your current task; do not restart from scratch unless it contradicts the",
25080
- "goal:",
25081
- "",
25082
- body,
25083
- "]"
25084
- ].join("\n");
25085
- }
25086
-
25087
- // src/core/continue-to-next-iteration.ts
25088
- function parseContinueDirective(text) {
25089
- const LINE_MARKERS = /^\s*\[(continue|next step|proceed|done)\]\s*$/gim;
25090
- let match;
25091
- let lastDirective = "none";
25092
- while ((match = LINE_MARKERS.exec(text)) !== null) {
25093
- const value = (match[1] ?? "").toLowerCase();
25094
- if (value === "continue" || value === "next step" || value === "proceed") {
25095
- lastDirective = "continue";
25096
- } else if (value === "done") {
25097
- lastDirective = "stop";
25098
- }
25099
- }
25077
+ // src/core/agent-tools.ts
25078
+ function createAgentToolHandler(a) {
25079
+ async function executeSingleWithDecision(tool, use) {
25080
+ const start = Date.now();
25081
+ try {
25082
+ const result = await a.toolExecutor.executeTool(
25083
+ tool,
25084
+ use,
25085
+ a.ctx,
25086
+ a.perIterationOutputCapBytes
25087
+ );
25088
+ return { result, durationMs: Date.now() - start };
25089
+ } catch (err) {
25090
+ const msg = err instanceof Error ? err.message : String(err);
25091
+ return {
25092
+ result: {
25093
+ type: "tool_result",
25094
+ tool_use_id: use.id,
25095
+ content: `Tool "${tool.name}" threw: ${msg}`,
25096
+ is_error: true
25097
+ },
25098
+ durationMs: Date.now() - start
25099
+ };
25100
+ }
25101
+ }
25102
+ function waitForConfirm(info) {
25103
+ return new Promise((resolve13) => {
25104
+ a.events.emit("tool.confirm_needed", {
25105
+ tool: info.tool,
25106
+ input: info.input,
25107
+ toolUseId: info.toolUseId,
25108
+ suggestedPattern: info.suggestedPattern,
25109
+ resolve: resolve13
25110
+ });
25111
+ });
25112
+ }
25113
+ function emitToolExecuted(toolUseId, toolName, durationMs, ok, input, content) {
25114
+ const sig = sizeSignals(toolName, content);
25115
+ a.events.emit("tool.executed", {
25116
+ id: toolUseId,
25117
+ name: toolName,
25118
+ durationMs,
25119
+ ok,
25120
+ input,
25121
+ output: truncateForEvent(content),
25122
+ outputBytes: sig.outputBytes,
25123
+ outputTokens: sig.outputTokens,
25124
+ outputLines: sig.outputLines
25125
+ });
25126
+ }
25127
+ async function executeTools(toolUses) {
25128
+ const selectedToolUses = await a.extensions.runBeforeToolExecution(a.ctx, toolUses);
25129
+ const { outputs } = await a.toolExecutor.executeBatch(
25130
+ selectedToolUses,
25131
+ a.ctx,
25132
+ a.executionStrategy
25133
+ );
25134
+ const useById = new Map(selectedToolUses.map((u) => [u.id, u]));
25135
+ const resultsForMessage = [];
25136
+ for (const { result, tool, durationMs } of outputs) {
25137
+ if (result.type === "tool_confirm_pending") {
25138
+ const decision = await waitForConfirm({
25139
+ tool,
25140
+ input: result.input,
25141
+ toolUseId: result.toolUseId,
25142
+ suggestedPattern: result.suggestedPattern
25143
+ });
25144
+ if (decision === "always") {
25145
+ try {
25146
+ await a.permission.trust({ tool: tool.name, pattern: result.suggestedPattern });
25147
+ a.events.emit("trust.persisted", { tool: tool.name, pattern: result.suggestedPattern, decision });
25148
+ } catch {
25149
+ }
25150
+ } else if (decision === "deny") {
25151
+ try {
25152
+ await a.permission.deny({ tool: tool.name, pattern: result.suggestedPattern });
25153
+ a.events.emit("trust.persisted", { tool: tool.name, pattern: result.suggestedPattern, decision });
25154
+ } catch {
25155
+ }
25156
+ }
25157
+ if (decision === "yes") {
25158
+ const p = a.permission;
25159
+ p.allowOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25160
+ } else if (decision === "no") {
25161
+ const p = a.permission;
25162
+ p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25163
+ }
25164
+ const reRunResult = decision === "yes" || decision === "always" ? await executeSingleWithDecision(tool, {
25165
+ id: result.toolUseId,
25166
+ name: tool.name,
25167
+ input: result.input
25168
+ }) : {
25169
+ result: {
25170
+ type: "tool_result",
25171
+ tool_use_id: result.toolUseId,
25172
+ content: decision === "deny" ? `Tool "${tool.name}" denied and blocked for this pattern.` : `Tool "${tool.name}" denied by user.`,
25173
+ is_error: true
25174
+ },
25175
+ durationMs: 0
25176
+ };
25177
+ const use2 = useById.get(reRunResult.result.tool_use_id);
25178
+ if (use2) {
25179
+ await a.pipelines.toolCall.run({ toolUse: use2, result: reRunResult.result, ctx: a.ctx, tool });
25180
+ await a.ctx.session.append({
25181
+ type: "tool_result",
25182
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25183
+ id: reRunResult.result.tool_use_id,
25184
+ content: reRunResult.result.content,
25185
+ isError: !!reRunResult.result.is_error
25186
+ });
25187
+ emitToolExecuted(
25188
+ reRunResult.result.tool_use_id,
25189
+ tool.name,
25190
+ reRunResult.durationMs,
25191
+ !reRunResult.result.is_error,
25192
+ result.input,
25193
+ reRunResult.result.content
25194
+ );
25195
+ }
25196
+ resultsForMessage.push(reRunResult.result);
25197
+ continue;
25198
+ }
25199
+ resultsForMessage.push(result);
25200
+ const use = useById.get(result.tool_use_id);
25201
+ if (!use) continue;
25202
+ await a.pipelines.toolCall.run({ toolUse: use, result, ctx: a.ctx, tool: tool ?? void 0 });
25203
+ await a.ctx.session.append({
25204
+ type: "tool_result",
25205
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25206
+ id: result.tool_use_id,
25207
+ content: result.content,
25208
+ isError: !!result.is_error
25209
+ });
25210
+ emitToolExecuted(result.tool_use_id, use.name, durationMs, !result.is_error, use.input, result.content);
25211
+ }
25212
+ a.ctx.state.appendMessage({ role: "user", content: resultsForMessage });
25213
+ await a.extensions.runAfterToolExecution(a.ctx, outputs);
25214
+ return resultsForMessage;
25215
+ }
25216
+ return { executeTools, executeSingleWithDecision };
25217
+ }
25218
+
25219
+ // src/core/continue-to-next-iteration.ts
25220
+ function parseContinueDirective(text) {
25221
+ const LINE_MARKERS = /^\s*\[(continue|next step|proceed|done)\]\s*$/gim;
25222
+ let match;
25223
+ let lastDirective = "none";
25224
+ while ((match = LINE_MARKERS.exec(text)) !== null) {
25225
+ const value = (match[1] ?? "").toLowerCase();
25226
+ if (value === "continue" || value === "next step" || value === "proceed") {
25227
+ lastDirective = "continue";
25228
+ } else if (value === "done") {
25229
+ lastDirective = "stop";
25230
+ }
25231
+ }
25100
25232
  return lastDirective;
25101
25233
  }
25102
- var META_KEY2 = "_autonomousContinue";
25234
+ var META_KEY = "_autonomousContinue";
25103
25235
  function setAutonomousContinue(ctx) {
25104
- ctx.meta[META_KEY2] = true;
25236
+ ctx.meta[META_KEY] = true;
25105
25237
  }
25106
25238
  function consumeAutonomousContinue(ctx) {
25107
- const val = ctx.meta[META_KEY2] === true;
25108
- delete ctx.meta[META_KEY2];
25239
+ const val = ctx.meta[META_KEY] === true;
25240
+ delete ctx.meta[META_KEY];
25109
25241
  return val;
25110
25242
  }
25111
25243
  function makeContinueToNextIterationTool() {
@@ -25128,6 +25260,104 @@ function makeContinueToNextIterationTool() {
25128
25260
  };
25129
25261
  }
25130
25262
 
25263
+ // src/core/agent-response.ts
25264
+ function createAgentResponseHandler(a) {
25265
+ async function buildAndRunRequestPipeline(opts) {
25266
+ const repaired = repairToolUseAdjacency(a.ctx.messages);
25267
+ if (repaired.report.changed) {
25268
+ a.ctx.state.replaceMessages(repaired.messages);
25269
+ a.events.emit("context.repaired", {
25270
+ ctx: a.ctx,
25271
+ ...repaired.report
25272
+ });
25273
+ a.logger.warn(
25274
+ `Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
25275
+ );
25276
+ }
25277
+ const baseReq = {
25278
+ model: opts.model ?? a.ctx.model,
25279
+ system: a.ctx.systemPrompt,
25280
+ messages: a.ctx.messages,
25281
+ tools: a.tools.list(),
25282
+ maxTokens: 8192
25283
+ };
25284
+ return a.pipelines.request.run(baseReq);
25285
+ }
25286
+ async function processResponse(raw, req) {
25287
+ let res = raw;
25288
+ res = await a.pipelines.response.run(res);
25289
+ a.events.emit("provider.response", {
25290
+ ctx: a.ctx,
25291
+ usage: res.usage,
25292
+ stopReason: res.stopReason
25293
+ });
25294
+ a.ctx.tokenCounter.account(res.usage, req.model);
25295
+ a.ctx.state.appendMessage({ role: "assistant", content: res.content });
25296
+ await a.ctx.session.append({
25297
+ type: "llm_response",
25298
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25299
+ content: res.content,
25300
+ stopReason: res.stopReason,
25301
+ usage: res.usage
25302
+ });
25303
+ if (a.ctx.signal.aborted) {
25304
+ let finalText2 = "";
25305
+ for (const block of res.content) {
25306
+ if (isTextBlock(block)) finalText2 += block.text;
25307
+ }
25308
+ return { finalText: finalText2, aborted: true, done: false };
25309
+ }
25310
+ let finalText = "";
25311
+ const streamed = a.ctx.provider.capabilities.streaming;
25312
+ for (const block of res.content) {
25313
+ if (isTextBlock(block)) {
25314
+ const rendered = await a.pipelines.assistantOutput.run(block);
25315
+ finalText += rendered.text;
25316
+ if (!streamed) a.renderer?.write(rendered);
25317
+ }
25318
+ }
25319
+ let directive = "none";
25320
+ if (finalText) {
25321
+ directive = parseContinueDirective(finalText);
25322
+ }
25323
+ return { finalText, aborted: false, done: false, directive };
25324
+ }
25325
+ return { buildAndRunRequestPipeline, processResponse };
25326
+ }
25327
+
25328
+ // src/core/btw.ts
25329
+ var META_KEY2 = "_btwNotes";
25330
+ function readQueue(ctx) {
25331
+ const raw = ctx.meta[META_KEY2];
25332
+ return Array.isArray(raw) ? raw : [];
25333
+ }
25334
+ function setBtwNote(ctx, text) {
25335
+ const trimmed = text.trim();
25336
+ if (!trimmed) return readQueue(ctx).length;
25337
+ const next = [...readQueue(ctx), trimmed].slice(-20);
25338
+ ctx.meta[META_KEY2] = next;
25339
+ return next.length;
25340
+ }
25341
+ function pendingBtwCount(ctx) {
25342
+ return readQueue(ctx).length;
25343
+ }
25344
+ function consumeBtwNotes(ctx) {
25345
+ const notes = readQueue(ctx);
25346
+ if (notes.length > 0) delete ctx.meta[META_KEY2];
25347
+ return notes;
25348
+ }
25349
+ function buildBtwBlock(notes) {
25350
+ const body = notes.map((n) => `- ${n}`).join("\n");
25351
+ return [
25352
+ "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
25353
+ "your current task; do not restart from scratch unless it contradicts the",
25354
+ "goal:",
25355
+ "",
25356
+ body,
25357
+ "]"
25358
+ ].join("\n");
25359
+ }
25360
+
25131
25361
  // src/core/iteration-limit.ts
25132
25362
  function requestLimitExtension(opts) {
25133
25363
  const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
@@ -25173,209 +25403,106 @@ function requestLimitExtension(opts) {
25173
25403
  });
25174
25404
  }
25175
25405
 
25176
- // src/core/agent.ts
25177
- var DEFAULT_MAX_ITERATIONS = 100;
25178
- function normalizeInput(input) {
25179
- if (typeof input === "string") {
25180
- return { blocks: [{ type: "text", text: input }], text: input };
25181
- }
25182
- const text = input.filter(isTextBlock).map((b) => b.text).join("");
25183
- return { blocks: input, text };
25184
- }
25185
- function createDefaultPipelines() {
25186
- return {
25187
- request: new Pipeline(),
25188
- response: new Pipeline(),
25189
- toolCall: new Pipeline(),
25190
- userInput: new Pipeline(),
25191
- assistantOutput: new Pipeline(),
25192
- contextWindow: new Pipeline()
25193
- };
25406
+ // src/core/agent-loop.ts
25407
+ function toError(err) {
25408
+ return err instanceof Error ? err : new Error(String(err));
25194
25409
  }
25195
- var Agent = class {
25196
- container;
25197
- tools;
25198
- providers;
25199
- events;
25200
- pipelines;
25201
- ctx;
25202
- maxIterations;
25203
- executionStrategy;
25204
- perIterationOutputCapBytes;
25205
- plugins = [];
25206
- toolExecutor;
25207
- autoExtendLimit;
25208
- /** Enables autonomous continue: model can signal `[continue]` or call continue_to_next_iteration() to re-run. */
25209
- autonomousContinue;
25210
- tracer;
25211
- extensions;
25212
- constructor(init) {
25213
- this.container = init.container;
25214
- this.tools = init.tools;
25215
- this.providers = init.providers;
25216
- this.events = init.events;
25217
- this.pipelines = init.pipelines;
25218
- this.ctx = init.context;
25219
- this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
25220
- this.executionStrategy = init.executionStrategy ?? "smart";
25221
- this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
25222
- this.autoExtendLimit = init.autoExtendLimit ?? true;
25223
- this.autonomousContinue = init.autonomousContinue ?? false;
25224
- this.tracer = init.tracer;
25225
- this.extensions = init.extensions ?? new ExtensionRegistry();
25226
- this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
25227
- this.toolExecutor = init.toolExecutor;
25410
+ function createAgentLoopHandler(a, handlers) {
25411
+ async function compactContextIfNeeded() {
25412
+ await a.pipelines.contextWindow.run(a.ctx);
25228
25413
  }
25229
- get logger() {
25230
- return this.container.resolve(TOKENS.Logger);
25231
- }
25232
- get retry() {
25233
- return this.container.resolve(TOKENS.RetryPolicy);
25234
- }
25235
- get errorHandler() {
25236
- return this.container.resolve(TOKENS.ErrorHandler);
25414
+ function emitContextPct() {
25415
+ const maxContext = a.ctx.provider.capabilities.maxContext ?? 2e5;
25416
+ const { total } = estimateRequestTokens(a.ctx.messages, a.ctx.systemPrompt, a.ctx.tools ?? []);
25417
+ a.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
25237
25418
  }
25238
- get permission() {
25239
- return this.container.resolve(TOKENS.PermissionPolicy);
25240
- }
25241
- get renderer() {
25242
- return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
25243
- }
25244
- /**
25245
- * Switch from inline CLI prompts to event-driven confirmation.
25246
- * Clears both the ToolExecutor's confirmAwaiter (so it returns
25247
- * `ToolConfirmPendingResult`) and the permission policy's
25248
- * promptDelegate (so `evaluate()` returns `confirm`).
25249
- *
25250
- * Call this before entering TUI or any mode where stdin is owned
25251
- * by a UI framework (Ink, WebUI WS bridge). Without this, the
25252
- * inline prompt writes to stdout and blocks on stdin — both of
25253
- * which are owned by the framework — making the prompt invisible
25254
- * and the input deadlocked.
25255
- */
25256
- disableInteractiveConfirmation() {
25257
- this.toolExecutor.clearConfirmAwaiter();
25258
- const policy = this.permission;
25259
- if (typeof policy.setPromptDelegate === "function") {
25260
- policy.setPromptDelegate(void 0);
25419
+ function injectPendingBtwNotes() {
25420
+ const notes = consumeBtwNotes(a.ctx);
25421
+ if (notes.length === 0) return;
25422
+ const block = { type: "text", text: buildBtwBlock(notes) };
25423
+ const messages = a.ctx.messages;
25424
+ const last = messages[messages.length - 1];
25425
+ if (last && last.role === "user") {
25426
+ const content = typeof last.content === "string" ? [{ type: "text", text: last.content }, block] : [...last.content, block];
25427
+ a.ctx.state.replaceMessages([...messages.slice(0, -1), { ...last, content }]);
25428
+ } else {
25429
+ a.ctx.state.appendMessage({ role: "user", content: [block] });
25261
25430
  }
25262
25431
  }
25263
- register(tool) {
25264
- this.tools.register(tool);
25265
- }
25266
- async use(plugin, api) {
25267
- await plugin.setup(api);
25268
- this.plugins.push({ plugin, api });
25269
- }
25270
- /** Tear down all plugins in reverse order, calling their teardown hooks. */
25271
- async teardown() {
25272
- const errors = [];
25273
- for (const { plugin, api } of this.plugins.toReversed()) {
25274
- if (typeof plugin.teardown !== "function") continue;
25275
- try {
25276
- await plugin.teardown(api);
25277
- } catch (err) {
25278
- errors.push(err);
25432
+ async function checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations, delegateSummaries) {
25433
+ if (hasHardLimit && iterationIndex >= limit) {
25434
+ const extendBy = await requestLimitExtension({
25435
+ events: a.events,
25436
+ currentIterations,
25437
+ currentLimit: limit,
25438
+ autoExtend: a.autoExtendLimit
25439
+ });
25440
+ if (extendBy > 0) {
25441
+ const newLimit = limit + extendBy;
25442
+ a.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
25443
+ return { limit: newLimit };
25279
25444
  }
25445
+ return { limit, exit: { status: "max_iterations", iterations: currentIterations, delegateSummaries } };
25280
25446
  }
25281
- this.plugins.length = 0;
25282
- if (errors.length > 0) {
25283
- throw new Error(`Agent teardown failed: ${errors.map(String).join("; ")}`);
25284
- }
25285
- }
25286
- async run(userInput, opts = {}) {
25287
- const controller = new RunController({ parentSignal: opts.signal });
25288
- const signal = controller.signal;
25289
- this.ctx.signal = signal;
25290
- controller.onAbort(() => this.ctx.drainAbortHooks());
25291
- const span = this.tracer?.startSpan("agent.run", {
25292
- "agent.model": opts.model ?? this.ctx.model,
25293
- "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
25294
- });
25295
- const { blocks, text } = normalizeInput(userInput);
25296
- const inputPayload = { content: blocks, text, ctx: this.ctx };
25297
- await this.extensions.runBeforeRun(this.ctx, inputPayload);
25298
- try {
25299
- const result = await this.runInner(inputPayload, opts, controller);
25300
- span?.setAttribute("agent.status", result.status);
25301
- span?.setAttribute("agent.iterations", result.iterations);
25302
- await this.extensions.runAfterRun(this.ctx, result);
25303
- return result;
25304
- } catch (err) {
25305
- const wse = err instanceof AgentError ? err : toWrongStackError(err);
25306
- this.events.emit("error", { err: toError(err), phase: "agent" });
25307
- if (err instanceof Error) span?.recordError(err);
25308
- span?.setAttribute("agent.status", "failed");
25309
- const result = {
25310
- status: signal.aborted ? "aborted" : "failed",
25311
- iterations: 0,
25312
- error: wse
25313
- };
25314
- await this.extensions.runAfterRun(this.ctx, result);
25315
- return result;
25316
- } finally {
25317
- span?.end();
25318
- await controller.dispose();
25319
- }
25447
+ return { limit };
25320
25448
  }
25321
- async runInner(inputPayload, opts, controller) {
25322
- await this.pipelines.userInput.run(inputPayload);
25323
- this.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
25324
- await this.ctx.session.append({
25449
+ async function runInner(inputPayload, opts, controller, autonomousContinue) {
25450
+ await a.pipelines.userInput.run(inputPayload);
25451
+ a.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
25452
+ await a.ctx.session.append({
25325
25453
  type: "user_input",
25326
25454
  ts: (/* @__PURE__ */ new Date()).toISOString(),
25327
25455
  content: inputPayload.content
25328
25456
  });
25329
- const promptIndex = this.ctx.messages.filter((m) => m.role === "user").length - 1;
25457
+ const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
25330
25458
  const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
25331
- await this.ctx.session.writeCheckpoint(promptIndex, preview);
25459
+ await a.ctx.session.writeCheckpoint(promptIndex, preview);
25332
25460
  let finalText = "";
25333
25461
  let iterations = 0;
25334
25462
  const delegateSummaries = [];
25335
- let effectiveLimit = opts.maxIterations ?? this.maxIterations;
25463
+ let effectiveLimit = opts.maxIterations ?? a.maxIterations;
25336
25464
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
25337
25465
  let recoveryRetries = 0;
25338
- const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
25339
25466
  const onSubagentDone = ({ summary, ok }) => {
25340
25467
  delegateSummaries.push({ summary, ok });
25341
25468
  };
25342
- const offSubagentDone = this.events.on("subagent.done", onSubagentDone);
25343
- const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
25469
+ const offSubagentDone = a.events.on("subagent.done", onSubagentDone);
25470
+ const diRunner = a.container.has(TOKENS.ProviderRunner) ? a.container.resolve(TOKENS.ProviderRunner) : null;
25344
25471
  const baseRunner = diRunner ? (ctx, req) => diRunner.run({
25345
25472
  provider: ctx.provider,
25346
25473
  request: req,
25347
25474
  signal: controller.signal,
25348
25475
  ctx,
25349
- events: this.events,
25350
- retry: this.retry,
25351
- logger: this.logger,
25352
- tracer: this.tracer
25476
+ events: a.events,
25477
+ retry: a.retry,
25478
+ logger: a.logger,
25479
+ tracer: a.tracer
25353
25480
  }) : async (ctx, req) => runProviderWithRetry({
25354
25481
  provider: ctx.provider,
25355
25482
  request: req,
25356
25483
  signal: controller.signal,
25357
25484
  ctx,
25358
- events: this.events,
25359
- retry: this.retry,
25360
- logger: this.logger,
25361
- tracer: this.tracer
25485
+ events: a.events,
25486
+ retry: a.retry,
25487
+ logger: a.logger,
25488
+ tracer: a.tracer
25362
25489
  });
25363
- const customRunner = this.extensions.wrapProviderRunner(baseRunner);
25490
+ const customRunner = a.extensions.wrapProviderRunner(baseRunner);
25364
25491
  try {
25365
25492
  for (let i = 0; ; i++) {
25366
25493
  iterations = i + 1;
25367
25494
  if (controller.signal.aborted) {
25368
25495
  return { status: "aborted", iterations };
25369
25496
  }
25370
- await this.ctx.session.writeInFlightMarker(`iteration ${i} / max ${this.maxIterations}`).catch((err) => {
25371
- this.logger.debug?.(
25497
+ await a.ctx.session.writeInFlightMarker(`iteration ${i} / max ${a.maxIterations}`).catch((err) => {
25498
+ a.logger.debug?.(
25372
25499
  `in-flight marker write failed: ${err instanceof Error ? err.message : String(err)}`
25373
25500
  );
25374
25501
  });
25375
25502
  if (autonomousContinue) {
25376
- consumeAutonomousContinue(this.ctx);
25503
+ consumeAutonomousContinue(a.ctx);
25377
25504
  }
25378
- const limitCheck = await this.checkIterationLimit(
25505
+ const limitCheck = await checkIterationLimit(
25379
25506
  i,
25380
25507
  effectiveLimit,
25381
25508
  hasHardLimit,
@@ -25386,11 +25513,11 @@ var Agent = class {
25386
25513
  if (limitCheck.exit) {
25387
25514
  return { ...limitCheck.exit, finalText };
25388
25515
  }
25389
- await this.extensions.runBeforeIteration(this.ctx, i);
25390
- this.events.emit("iteration.started", { ctx: this.ctx, index: i });
25391
- this.injectPendingBtwNotes();
25392
- const req = await this.buildAndRunRequestPipeline(opts);
25393
- await this.ctx.session.append({
25516
+ await a.extensions.runBeforeIteration(a.ctx, i);
25517
+ a.events.emit("iteration.started", { ctx: a.ctx, index: i });
25518
+ injectPendingBtwNotes();
25519
+ const req = await handlers.response.buildAndRunRequestPipeline(opts);
25520
+ await a.ctx.session.append({
25394
25521
  type: "llm_request",
25395
25522
  ts: (/* @__PURE__ */ new Date()).toISOString(),
25396
25523
  model: req.model,
@@ -25401,39 +25528,39 @@ var Agent = class {
25401
25528
  });
25402
25529
  let res;
25403
25530
  try {
25404
- res = await customRunner(this.ctx, req);
25531
+ res = await customRunner(a.ctx, req);
25405
25532
  const calibratedEstimate = estimateRequestTokensCalibrated(req.messages, req.system, req.tools ?? []).total;
25406
25533
  recordActualUsage(res.usage.input, calibratedEstimate);
25407
25534
  recoveryRetries = 0;
25408
25535
  } catch (err) {
25409
25536
  if (controller.signal.aborted) {
25410
- this.events.emit("error", { err: toError(err), phase: "provider" });
25537
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25411
25538
  return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
25412
25539
  }
25413
- const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
25540
+ const extDecision = await a.extensions.runOnError(a.ctx, err, "provider", i);
25414
25541
  if (extDecision) {
25415
25542
  if (extDecision.action === "fail") {
25416
- this.events.emit("error", { err: toError(err), phase: "provider" });
25543
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25417
25544
  return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
25418
25545
  }
25419
25546
  if (extDecision.action === "continue") {
25420
- await this.extensions.runAfterIteration(this.ctx, i);
25547
+ await a.extensions.runAfterIteration(a.ctx, i);
25421
25548
  continue;
25422
25549
  }
25423
25550
  if (extDecision.action === "retry") {
25424
25551
  recoveryRetries++;
25425
25552
  if (recoveryRetries > 2) {
25426
- this.events.emit("error", { err: toError(err), phase: "provider" });
25553
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25427
25554
  return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
25428
25555
  }
25429
- if (extDecision.model) this.ctx.model = extDecision.model;
25430
- this.logger.info("Extension requested retry; retrying turn");
25556
+ if (extDecision.model) a.ctx.model = extDecision.model;
25557
+ a.logger.info("Extension requested retry; retrying turn");
25431
25558
  continue;
25432
25559
  }
25433
25560
  }
25434
- const recovered = await this.errorHandler.recover(err, this.ctx);
25561
+ const recovered = await a.errorHandler.recover(err, a.ctx);
25435
25562
  if (!recovered || recovered.action === "fail") {
25436
- this.events.emit("error", { err: toError(err), phase: "provider" });
25563
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25437
25564
  return {
25438
25565
  status: "failed",
25439
25566
  iterations,
@@ -25444,17 +25571,17 @@ var Agent = class {
25444
25571
  if (recovered.action === "retry") {
25445
25572
  recoveryRetries++;
25446
25573
  if (recoveryRetries > 2) {
25447
- this.events.emit("error", { err: toError(err), phase: "provider" });
25574
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25448
25575
  return { status: "failed", iterations, error: toWrongStackError(err) };
25449
25576
  }
25450
- if (recovered.model) this.ctx.model = recovered.model;
25451
- this.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
25577
+ if (recovered.model) a.ctx.model = recovered.model;
25578
+ a.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
25452
25579
  continue;
25453
25580
  }
25454
25581
  recoveryRetries = 0;
25455
25582
  res = recovered.response;
25456
25583
  }
25457
- const responseResult = await this.processResponse(res, req);
25584
+ const responseResult = await handlers.response.processResponse(res, req);
25458
25585
  if (responseResult.aborted) {
25459
25586
  return { status: "aborted", iterations, finalText: responseResult.finalText, delegateSummaries };
25460
25587
  }
@@ -25464,11 +25591,11 @@ var Agent = class {
25464
25591
  finalText = responseResult.finalText;
25465
25592
  const toolUses = res.content.filter(isToolUseBlock);
25466
25593
  if (toolUses.length === 0) {
25467
- this.emitContextPct();
25468
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
25594
+ emitContextPct();
25595
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25469
25596
  if (autonomousContinue && responseResult.directive === "continue") {
25470
- await this.compactContextIfNeeded();
25471
- await this.extensions.runAfterIteration(this.ctx, i);
25597
+ await compactContextIfNeeded();
25598
+ await a.extensions.runAfterIteration(a.ctx, i);
25472
25599
  continue;
25473
25600
  }
25474
25601
  if (autonomousContinue && responseResult.directive === "stop") {
@@ -25476,18 +25603,18 @@ var Agent = class {
25476
25603
  }
25477
25604
  return { status: "done", iterations, finalText, delegateSummaries };
25478
25605
  }
25479
- await this.executeTools(toolUses);
25480
- if (autonomousContinue && consumeAutonomousContinue(this.ctx)) {
25481
- this.emitContextPct();
25482
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
25483
- await this.compactContextIfNeeded();
25484
- await this.extensions.runAfterIteration(this.ctx, i);
25606
+ await handlers.tools.executeTools(toolUses);
25607
+ if (autonomousContinue && consumeAutonomousContinue(a.ctx)) {
25608
+ emitContextPct();
25609
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25610
+ await compactContextIfNeeded();
25611
+ await a.extensions.runAfterIteration(a.ctx, i);
25485
25612
  continue;
25486
25613
  }
25487
- this.emitContextPct();
25488
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
25489
- await this.compactContextIfNeeded();
25490
- await this.extensions.runAfterIteration(this.ctx, i);
25614
+ emitContextPct();
25615
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25616
+ await compactContextIfNeeded();
25617
+ await a.extensions.runAfterIteration(a.ctx, i);
25491
25618
  if (autonomousContinue && responseResult.directive === "continue") {
25492
25619
  continue;
25493
25620
  }
@@ -25498,346 +25625,161 @@ var Agent = class {
25498
25625
  } finally {
25499
25626
  offSubagentDone();
25500
25627
  const reason = controller.signal.aborted ? "aborted" : "clean";
25501
- await this.ctx.session.clearInFlightMarker(reason).catch((err) => {
25502
- this.logger.debug?.(
25628
+ await a.ctx.session.clearInFlightMarker(reason).catch((err) => {
25629
+ a.logger.debug?.(
25503
25630
  `in-flight marker clear failed: ${err instanceof Error ? err.message : String(err)}`
25504
25631
  );
25505
25632
  });
25506
25633
  }
25507
25634
  }
25508
- /**
25509
- * Check if iteration limit has been reached and request extension if needed.
25510
- * Returns the new effective limit (possibly extended) and a RunResult if
25511
- * the loop should exit. Returns `{ limit }` with no result when the
25512
- * iteration may proceed.
25513
- */
25514
- async checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations, delegateSummaries) {
25515
- if (hasHardLimit && iterationIndex >= limit) {
25516
- const extendBy = await requestLimitExtension({
25517
- events: this.events,
25518
- currentIterations,
25519
- currentLimit: limit,
25520
- autoExtend: this.autoExtendLimit
25521
- });
25522
- if (extendBy > 0) {
25523
- const newLimit = limit + extendBy;
25524
- this.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
25525
- return { limit: newLimit };
25526
- }
25527
- return { limit, exit: { status: "max_iterations", iterations: currentIterations, delegateSummaries } };
25528
- }
25529
- return { limit };
25530
- }
25531
- /**
25532
- * Build request and run through request pipeline.
25533
- */
25534
- async buildAndRunRequestPipeline(opts) {
25535
- const repaired = repairToolUseAdjacency(this.ctx.messages);
25536
- if (repaired.report.changed) {
25537
- this.ctx.state.replaceMessages(repaired.messages);
25538
- this.events.emit("context.repaired", {
25539
- ctx: this.ctx,
25540
- ...repaired.report
25541
- });
25542
- this.logger.warn(
25543
- `Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
25544
- );
25545
- }
25546
- const baseReq = {
25547
- model: opts.model ?? this.ctx.model,
25548
- system: this.ctx.systemPrompt,
25549
- messages: this.ctx.messages,
25550
- tools: this.tools.list(),
25551
- maxTokens: 8192
25552
- };
25553
- return this.pipelines.request.run(baseReq);
25635
+ return { runInner };
25636
+ }
25637
+
25638
+ // src/core/agent-types.ts
25639
+ var DEFAULT_MAX_ITERATIONS = 100;
25640
+ function normalizeInput(input) {
25641
+ if (typeof input === "string") {
25642
+ return { blocks: [{ type: "text", text: input }], text: input };
25554
25643
  }
25555
- /**
25556
- * Process the provider response: run response pipeline, emit events,
25557
- * update session, render text, handle abort.
25558
- */
25559
- async processResponse(raw, req) {
25560
- let res = raw;
25561
- res = await this.pipelines.response.run(res);
25562
- this.events.emit("provider.response", {
25563
- ctx: this.ctx,
25564
- usage: res.usage,
25565
- stopReason: res.stopReason
25566
- });
25567
- this.ctx.tokenCounter.account(res.usage, req.model);
25568
- this.ctx.state.appendMessage({ role: "assistant", content: res.content });
25569
- await this.ctx.session.append({
25570
- type: "llm_response",
25571
- ts: (/* @__PURE__ */ new Date()).toISOString(),
25572
- content: res.content,
25573
- stopReason: res.stopReason,
25574
- usage: res.usage
25644
+ const text = input.filter(isTextBlock).map((b) => b.text).join("");
25645
+ return { blocks: input, text };
25646
+ }
25647
+ function createDefaultPipelines() {
25648
+ return {
25649
+ request: new Pipeline(),
25650
+ response: new Pipeline(),
25651
+ toolCall: new Pipeline(),
25652
+ userInput: new Pipeline(),
25653
+ assistantOutput: new Pipeline(),
25654
+ contextWindow: new Pipeline()
25655
+ };
25656
+ }
25657
+
25658
+ // src/core/agent.ts
25659
+ var Agent = class {
25660
+ container;
25661
+ tools;
25662
+ providers;
25663
+ events;
25664
+ pipelines;
25665
+ ctx;
25666
+ maxIterations;
25667
+ executionStrategy;
25668
+ perIterationOutputCapBytes;
25669
+ plugins = [];
25670
+ toolExecutor;
25671
+ autoExtendLimit;
25672
+ autonomousContinue;
25673
+ tracer;
25674
+ extensions;
25675
+ _toolHandler;
25676
+ _responseHandler;
25677
+ _loopHandler;
25678
+ constructor(init) {
25679
+ this.container = init.container;
25680
+ this.tools = init.tools;
25681
+ this.providers = init.providers;
25682
+ this.events = init.events;
25683
+ this.pipelines = init.pipelines;
25684
+ this.ctx = init.context;
25685
+ this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
25686
+ this.executionStrategy = init.executionStrategy ?? "smart";
25687
+ this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
25688
+ this.autoExtendLimit = init.autoExtendLimit ?? true;
25689
+ this.autonomousContinue = init.autonomousContinue ?? false;
25690
+ this.tracer = init.tracer;
25691
+ this.extensions = init.extensions ?? new ExtensionRegistry();
25692
+ this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
25693
+ this.toolExecutor = init.toolExecutor;
25694
+ this._toolHandler = createAgentToolHandler(this);
25695
+ this._responseHandler = createAgentResponseHandler(this);
25696
+ this._loopHandler = createAgentLoopHandler(this, {
25697
+ tools: this._toolHandler,
25698
+ response: this._responseHandler
25575
25699
  });
25576
- if (this.ctx.signal.aborted) {
25577
- let finalText2 = "";
25578
- for (const block of res.content) {
25579
- if (isTextBlock(block)) finalText2 += block.text;
25580
- }
25581
- return { finalText: finalText2, aborted: true, done: false };
25582
- }
25583
- let finalText = "";
25584
- const streamed = this.ctx.provider.capabilities.streaming;
25585
- for (const block of res.content) {
25586
- if (isTextBlock(block)) {
25587
- const rendered = await this.pipelines.assistantOutput.run(block);
25588
- finalText += rendered.text;
25589
- if (!streamed) this.renderer?.write(rendered);
25590
- }
25591
- }
25592
- let directive = "none";
25593
- if (finalText) {
25594
- directive = parseContinueDirective(finalText);
25700
+ }
25701
+ get logger() {
25702
+ return this.container.resolve(TOKENS.Logger);
25703
+ }
25704
+ get retry() {
25705
+ return this.container.resolve(TOKENS.RetryPolicy);
25706
+ }
25707
+ get errorHandler() {
25708
+ return this.container.resolve(TOKENS.ErrorHandler);
25709
+ }
25710
+ get permission() {
25711
+ return this.container.resolve(TOKENS.PermissionPolicy);
25712
+ }
25713
+ get renderer() {
25714
+ return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
25715
+ }
25716
+ disableInteractiveConfirmation() {
25717
+ this.toolExecutor.clearConfirmAwaiter();
25718
+ const policy = this.permission;
25719
+ if (typeof policy.setPromptDelegate === "function") {
25720
+ policy.setPromptDelegate(void 0);
25595
25721
  }
25596
- return { finalText, aborted: false, done: false, directive };
25597
25722
  }
25598
- /**
25599
- * Execute tools and append tool results to context.
25600
- * When a tool returns `tool_confirm_pending` (no confirmAwaiter set),
25601
- * we pause and emit `tool.confirm_needed`. The run is blocked until
25602
- * the event listener resolves the confirmation, then we re-run the
25603
- * single tool.
25604
- */
25605
- async executeTools(toolUses) {
25606
- const selectedToolUses = await this.extensions.runBeforeToolExecution(this.ctx, toolUses);
25607
- const { outputs } = await this.toolExecutor.executeBatch(
25608
- selectedToolUses,
25609
- this.ctx,
25610
- this.executionStrategy
25611
- );
25612
- const useById = new Map(selectedToolUses.map((u) => [u.id, u]));
25613
- const resultsForMessage = [];
25614
- for (const { result, tool, durationMs } of outputs) {
25615
- if (result.type === "tool_confirm_pending") {
25616
- const decision = await this.waitForConfirm({
25617
- tool,
25618
- input: result.input,
25619
- toolUseId: result.toolUseId,
25620
- suggestedPattern: result.suggestedPattern
25621
- });
25622
- if (decision === "always") {
25623
- try {
25624
- await this.permission.trust({
25625
- tool: tool.name,
25626
- pattern: result.suggestedPattern
25627
- });
25628
- this.events.emit("trust.persisted", {
25629
- tool: tool.name,
25630
- pattern: result.suggestedPattern,
25631
- decision
25632
- });
25633
- } catch {
25634
- }
25635
- } else if (decision === "deny") {
25636
- try {
25637
- await this.permission.deny({
25638
- tool: tool.name,
25639
- pattern: result.suggestedPattern
25640
- });
25641
- this.events.emit("trust.persisted", {
25642
- tool: tool.name,
25643
- pattern: result.suggestedPattern,
25644
- decision
25645
- });
25646
- } catch {
25647
- }
25648
- }
25649
- if (decision === "yes") {
25650
- const p = this.permission;
25651
- p.allowOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25652
- } else if (decision === "no") {
25653
- const p = this.permission;
25654
- p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25655
- }
25656
- const reRunResult = decision === "yes" || decision === "always" ? await this.executeSingleWithDecision(
25657
- tool,
25658
- { id: result.toolUseId, name: tool.name, input: result.input }
25659
- ) : {
25660
- result: {
25661
- type: "tool_result",
25662
- tool_use_id: result.toolUseId,
25663
- content: decision === "deny" ? `Tool "${tool.name}" denied and blocked for this pattern.` : `Tool "${tool.name}" denied by user.`,
25664
- is_error: true
25665
- },
25666
- durationMs: 0
25667
- };
25668
- const use2 = useById.get(reRunResult.result.tool_use_id);
25669
- if (use2) {
25670
- await this.pipelines.toolCall.run({
25671
- toolUse: use2,
25672
- result: reRunResult.result,
25673
- ctx: this.ctx,
25674
- tool
25675
- });
25676
- await this.ctx.session.append({
25677
- type: "tool_result",
25678
- ts: (/* @__PURE__ */ new Date()).toISOString(),
25679
- id: reRunResult.result.tool_use_id,
25680
- content: reRunResult.result.content,
25681
- isError: !!reRunResult.result.is_error
25682
- });
25683
- {
25684
- const sig = sizeSignals(tool?.name, reRunResult.result.content);
25685
- this.events.emit("tool.executed", {
25686
- id: reRunResult.result.tool_use_id,
25687
- name: tool.name,
25688
- durationMs: reRunResult.durationMs,
25689
- ok: !reRunResult.result.is_error,
25690
- input: result.input,
25691
- output: truncateForEvent(reRunResult.result.content),
25692
- outputBytes: sig.outputBytes,
25693
- outputTokens: sig.outputTokens,
25694
- outputLines: sig.outputLines
25695
- });
25696
- }
25697
- }
25698
- resultsForMessage.push(reRunResult.result);
25699
- continue;
25700
- }
25701
- resultsForMessage.push(result);
25702
- const use = useById.get(result.tool_use_id);
25703
- if (!use) continue;
25704
- await this.pipelines.toolCall.run({
25705
- toolUse: use,
25706
- result,
25707
- ctx: this.ctx,
25708
- tool: tool ?? void 0
25709
- });
25710
- await this.ctx.session.append({
25711
- type: "tool_result",
25712
- ts: (/* @__PURE__ */ new Date()).toISOString(),
25713
- id: result.tool_use_id,
25714
- content: result.content,
25715
- isError: !!result.is_error
25716
- });
25717
- {
25718
- const sig = sizeSignals(use.name, result.content);
25719
- this.events.emit("tool.executed", {
25720
- id: result.tool_use_id,
25721
- name: use.name,
25722
- durationMs,
25723
- ok: !result.is_error,
25724
- input: use.input,
25725
- output: truncateForEvent(result.content),
25726
- outputBytes: sig.outputBytes,
25727
- outputTokens: sig.outputTokens,
25728
- outputLines: sig.outputLines
25729
- });
25723
+ register(tool) {
25724
+ this.tools.register(tool);
25725
+ }
25726
+ async use(plugin, api) {
25727
+ await plugin.setup(api);
25728
+ this.plugins.push({ plugin, api });
25729
+ }
25730
+ async teardown() {
25731
+ const errors = [];
25732
+ for (const { plugin, api } of this.plugins.toReversed()) {
25733
+ if (typeof plugin.teardown !== "function") continue;
25734
+ try {
25735
+ await plugin.teardown(api);
25736
+ } catch (err) {
25737
+ errors.push(err);
25730
25738
  }
25731
25739
  }
25732
- this.ctx.state.appendMessage({
25733
- role: "user",
25734
- content: resultsForMessage
25735
- });
25736
- await this.extensions.runAfterToolExecution(this.ctx, outputs);
25737
- }
25738
- /**
25739
- * Fold any pending `/btw` notes into the conversation. Called at the top of
25740
- * each iteration so a note set mid-run reaches the model on its next turn.
25741
- *
25742
- * To stay valid across all provider wire families we never create two
25743
- * consecutive user messages: if the last message is a user turn (e.g. the
25744
- * tool_result block from the previous iteration) we append the note as an
25745
- * extra text block on that turn; otherwise we add a fresh user message.
25746
- */
25747
- injectPendingBtwNotes() {
25748
- const notes = consumeBtwNotes(this.ctx);
25749
- if (notes.length === 0) return;
25750
- const block = { type: "text", text: buildBtwBlock(notes) };
25751
- const messages = this.ctx.messages;
25752
- const last = messages[messages.length - 1];
25753
- if (last && last.role === "user") {
25754
- const content = typeof last.content === "string" ? [{ type: "text", text: last.content }, block] : [...last.content, block];
25755
- this.ctx.state.replaceMessages([...messages.slice(0, -1), { ...last, content }]);
25756
- } else {
25757
- this.ctx.state.appendMessage({ role: "user", content: [block] });
25740
+ this.plugins.length = 0;
25741
+ if (errors.length > 0) {
25742
+ throw new Error(`Agent teardown failed: ${errors.map(String).join("; ")}`);
25758
25743
  }
25759
25744
  }
25760
- waitForConfirm(info) {
25761
- return new Promise((resolve13) => {
25762
- this.events.emit("tool.confirm_needed", {
25763
- tool: info.tool,
25764
- input: info.input,
25765
- toolUseId: info.toolUseId,
25766
- suggestedPattern: info.suggestedPattern,
25767
- resolve: resolve13
25768
- });
25745
+ async run(userInput, opts = {}) {
25746
+ const controller = new RunController({ parentSignal: opts.signal });
25747
+ const signal = controller.signal;
25748
+ this.ctx.signal = signal;
25749
+ controller.onAbort(() => this.ctx.drainAbortHooks());
25750
+ const span = this.tracer?.startSpan("agent.run", {
25751
+ "agent.model": opts.model ?? this.ctx.model,
25752
+ "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
25769
25753
  });
25770
- }
25771
- async executeSingleWithDecision(tool, use) {
25772
- const start = Date.now();
25754
+ const { blocks, text } = normalizeInput(userInput);
25755
+ const inputPayload = { content: blocks, text, ctx: this.ctx };
25756
+ await this.extensions.runBeforeRun(this.ctx, inputPayload);
25773
25757
  try {
25774
- const result = await this.toolExecutor.executeTool(
25775
- tool,
25776
- use,
25777
- this.ctx,
25778
- this.perIterationOutputCapBytes
25779
- );
25780
- return { result, durationMs: Date.now() - start };
25758
+ const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
25759
+ const result = await this._loopHandler.runInner(inputPayload, opts, controller, autonomousContinue);
25760
+ span?.setAttribute("agent.status", result.status);
25761
+ span?.setAttribute("agent.iterations", result.iterations);
25762
+ await this.extensions.runAfterRun(this.ctx, result);
25763
+ return result;
25781
25764
  } catch (err) {
25782
- const msg = err instanceof Error ? err.message : String(err);
25783
- return {
25784
- result: {
25785
- type: "tool_result",
25786
- tool_use_id: use.id,
25787
- content: `Tool "${tool.name}" threw: ${msg}`,
25788
- is_error: true
25789
- },
25790
- durationMs: Date.now() - start
25765
+ const wse = err instanceof AgentError ? err : toWrongStackError(err);
25766
+ this.events.emit("error", { err: err instanceof Error ? err : new Error(String(err)), phase: "agent" });
25767
+ if (err instanceof Error) span?.recordError(err);
25768
+ span?.setAttribute("agent.status", "failed");
25769
+ const result = {
25770
+ status: signal.aborted ? "aborted" : "failed",
25771
+ iterations: 0,
25772
+ error: wse
25791
25773
  };
25774
+ await this.extensions.runAfterRun(this.ctx, result);
25775
+ return result;
25776
+ } finally {
25777
+ span?.end();
25778
+ await controller.dispose();
25792
25779
  }
25793
25780
  }
25794
- /**
25795
- * Run context window pipeline. The pipeline may be empty, or it may contain
25796
- * middleware with its own injected dependencies.
25797
- */
25798
- async compactContextIfNeeded() {
25799
- await this.pipelines.contextWindow.run(this.ctx);
25800
- }
25801
- /**
25802
- * Emit the current context window load as a `ctx.pct` event so subscribers
25803
- * (FleetBus → TUI) can render a live fill bar per agent.
25804
- */
25805
- emitContextPct() {
25806
- const maxContext = this.ctx.provider.capabilities.maxContext ?? 2e5;
25807
- const { total } = estimateRequestTokens(
25808
- this.ctx.messages,
25809
- this.ctx.systemPrompt,
25810
- this.ctx.tools ?? []
25811
- );
25812
- this.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
25813
- }
25781
+ // ── Tool + response execution handled by AgentToolHandler / AgentResponseHandler ──
25814
25782
  };
25815
- function toError(err) {
25816
- return err instanceof Error ? err : new Error(String(err));
25817
- }
25818
- function truncateForEvent(content, max = 400) {
25819
- if (!content) return "";
25820
- return content.length <= max ? content : `${content.slice(0, max - 1)}\u2026`;
25821
- }
25822
- function sizeSignals(toolName, content) {
25823
- if (typeof content !== "string" || content.length === 0) {
25824
- return { outputBytes: 0, outputTokens: 0, outputLines: void 0 };
25825
- }
25826
- const outputBytes = Buffer.byteLength(content, "utf8");
25827
- const outputTokens = Math.max(1, Math.round(outputBytes / 3.5));
25828
- let outputLines;
25829
- if (toolName === "read") {
25830
- const lineRe = /^\s*\d+→/gm;
25831
- let count = 0;
25832
- while (lineRe.exec(content) !== null) count++;
25833
- if (count > 0) outputLines = count;
25834
- } else if (toolName === "bash" || toolName === "shell" || toolName === "grep" || toolName === "logs") {
25835
- let nl = 0;
25836
- for (let i = 0; i < content.length; i++) if (content.charCodeAt(i) === 10) nl++;
25837
- outputLines = nl + (content.endsWith("\n") ? 0 : 1);
25838
- }
25839
- return { outputBytes, outputTokens, outputLines };
25840
- }
25841
25783
 
25842
25784
  // src/hooks/registry.ts
25843
25785
  var HookRegistry = class {