@wrongstack/core 0.63.4 → 0.66.13

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 +87 -69
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +22 -22
  10. package/dist/defaults/index.js +113 -84
  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 +59 -51
  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 +647 -677
  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-VApKRxcp.d.ts} +6 -7
  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 +58 -51
  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;
@@ -12649,7 +12688,10 @@ var NICKNAME_POOL = {
12649
12688
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
12650
12689
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
12651
12690
  };
12652
- var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
12691
+ var ALL_NICKNAMES = Object.entries(NICKNAME_POOL);
12692
+ var NAME_TO_KEY = Object.fromEntries(
12693
+ ALL_NICKNAMES.map(([key, entry]) => [entry.name, key])
12694
+ );
12653
12695
  var DOMAIN_PREFERENCES = {
12654
12696
  "security": ["shannon", "turing", "lamarr", "stallman"],
12655
12697
  "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
@@ -12682,17 +12724,23 @@ function assignNickname(role, used) {
12682
12724
  for (const key of preferences) {
12683
12725
  const entry = NICKNAME_POOL[key];
12684
12726
  if (entry && !used.has(key)) {
12685
- return `${entry.name} (${formatRole(role)})`;
12727
+ return { key, display: `${entry.name} (${formatRole(role)})` };
12686
12728
  }
12687
12729
  }
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)})`;
12730
+ for (const [key, entry] of ALL_NICKNAMES) {
12731
+ if (!used.has(key)) {
12732
+ return { key, display: `${entry.name} (${formatRole(role)})` };
12692
12733
  }
12693
12734
  }
12694
12735
  const counter = used.size + 1;
12695
- return `Scientist #${counter} (${formatRole(role)})`;
12736
+ return { key: `scientist-${counter}`, display: `Scientist #${counter} (${formatRole(role)})` };
12737
+ }
12738
+ function nicknameKeyFromDisplay(display) {
12739
+ const base = display.replace(/\s*\([^)]*\)\s*$/, "").trim();
12740
+ const key = NAME_TO_KEY[base];
12741
+ if (key) return key;
12742
+ const synthesized = base.match(/^Scientist #(\d+)$/);
12743
+ return synthesized ? `scientist-${synthesized[1]}` : void 0;
12696
12744
  }
12697
12745
  function formatRole(role) {
12698
12746
  return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
@@ -12779,11 +12827,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12779
12827
  const name = subagent.name?.trim() ?? "";
12780
12828
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
12781
12829
  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 };
12830
+ const { key, display } = assignNickname(role, this.usedNicknames);
12831
+ this.usedNicknames.add(key);
12832
+ this.subagentNicknames.set(subagentId, key);
12833
+ return { ...subagent, name: display };
12787
12834
  }
12788
12835
  async spawn(subagent) {
12789
12836
  const id = subagent.id || randomUUID();
@@ -13035,23 +13082,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
13035
13082
  */
13036
13083
  drainPendingAsAborted(message) {
13037
13084
  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
- }
13085
+ for (const t2 of dropped) this.emitPendingAborted(t2, message);
13086
+ }
13087
+ /**
13088
+ * Emit a synthetic `stopped`/`aborted_by_parent` completion for a single
13089
+ * PENDING task — one that was never counted in `inFlight`. This MUST bypass
13090
+ * `recordCompletion`: that path does `inFlight--`, which for a pending task
13091
+ * steals a decrement from a genuinely in-flight task and trips the underflow
13092
+ * guard — suppressing that real task's `task.completed` and hanging its
13093
+ * `awaitTasks()` caller. Pushes the result and fires the event directly.
13094
+ */
13095
+ emitPendingAborted(task, message) {
13096
+ const synthetic = {
13097
+ subagentId: task.subagentId ?? "unassigned",
13098
+ taskId: task.id,
13099
+ status: "stopped",
13100
+ error: {
13101
+ kind: "aborted_by_parent",
13102
+ message,
13103
+ retryable: false
13104
+ },
13105
+ iterations: 0,
13106
+ toolCalls: 0,
13107
+ durationMs: 0
13108
+ };
13109
+ this.completedResults.push(synthetic);
13110
+ this.emit("task.completed", { task, result: synthetic });
13055
13111
  }
13056
13112
  async runDispatched(subagentId, task) {
13057
13113
  const subagent = this.subagents.get(subagentId);
@@ -13312,20 +13368,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
13312
13368
  const orphaned = this.pendingTasks.filter((t2) => t2.subagentId === subagentId);
13313
13369
  this.pendingTasks = this.pendingTasks.filter((t2) => t2.subagentId !== subagentId);
13314
13370
  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);
13371
+ this.emitPendingAborted(
13372
+ t2,
13373
+ `Subagent "${subagentId}" was removed while task "${t2.id}" was pending`
13374
+ );
13329
13375
  }
13330
13376
  this.fleetBus?.emit({
13331
13377
  subagentId,
@@ -13703,6 +13749,7 @@ ${personaLine}Task: ${task}
13703
13749
  } catch {
13704
13750
  results = coordinator.results().slice(-taskIds.length);
13705
13751
  }
13752
+ await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
13706
13753
  const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
13707
13754
  const goalComplete = results.some(
13708
13755
  (r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
@@ -14498,6 +14545,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
14498
14545
  return lines.join("\n");
14499
14546
  }
14500
14547
  cleanup() {
14548
+ if (this._timeoutTimer) {
14549
+ clearTimeout(this._timeoutTimer);
14550
+ this._timeoutTimer = void 0;
14551
+ }
14501
14552
  for (const dispose of this.disposers) dispose();
14502
14553
  this.disposers.length = 0;
14503
14554
  }
@@ -15627,18 +15678,20 @@ var Director = class _Director {
15627
15678
  if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
15628
15679
  return;
15629
15680
  }
15630
- if (payload.kind === "timeout") {
15681
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
15682
+ const heartbeatKey = `${e.subagentId}:${payload.kind}`;
15631
15683
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
15632
- const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
15684
+ const lastProgress = lastTimeoutProgress.get(heartbeatKey) ?? -1;
15633
15685
  if (progress <= lastProgress) {
15634
15686
  payload.deny();
15635
15687
  return;
15636
15688
  }
15637
- lastTimeoutProgress.set(e.subagentId, progress);
15689
+ lastTimeoutProgress.set(heartbeatKey, progress);
15690
+ const field = payload.kind === "timeout" ? "timeoutMs" : "idleTimeoutMs";
15638
15691
  setImmediate(() => {
15639
15692
  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 });
15693
+ this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
15694
+ payload.extend({ [field]: newLimit });
15642
15695
  });
15643
15696
  return;
15644
15697
  }
@@ -15954,10 +16007,9 @@ var Director = class _Director {
15954
16007
  if (this.fleetManager) {
15955
16008
  this.fleetManager.assignNicknameAndRecord(config);
15956
16009
  } else {
15957
- config.name = assignNickname(role, this._usedNicknames);
15958
- this._usedNicknames.add(
15959
- config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
15960
- );
16010
+ const { key, display } = assignNickname(role, this._usedNicknames);
16011
+ config.name = display;
16012
+ this._usedNicknames.add(key);
15961
16013
  }
15962
16014
  }
15963
16015
  result = await this.coordinator.spawn(config);
@@ -16279,8 +16331,8 @@ var Director = class _Director {
16279
16331
  } else {
16280
16332
  const entry = this.manifestEntries.get(subagentId);
16281
16333
  if (entry?.name) {
16282
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
16283
- this._usedNicknames.delete(nicknameKey);
16334
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
16335
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
16284
16336
  }
16285
16337
  }
16286
16338
  this.manifestEntries.delete(subagentId);
@@ -24414,11 +24466,10 @@ var FleetManager = class {
24414
24466
  */
24415
24467
  assignNicknameAndRecord(config) {
24416
24468
  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;
24469
+ const { key, display } = assignNickname(role, this._usedNicknames);
24470
+ this._usedNicknames.add(key);
24471
+ config.name = display;
24472
+ return display;
24422
24473
  }
24423
24474
  /**
24424
24475
  * Returns the set of already-assigned nickname keys — useful for debugging
@@ -24603,8 +24654,8 @@ var FleetManager = class {
24603
24654
  removeSubagent(subagentId) {
24604
24655
  const entry = this.manifestEntries.get(subagentId);
24605
24656
  if (entry?.name) {
24606
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
24607
- this._usedNicknames.delete(nicknameKey);
24657
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
24658
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
24608
24659
  }
24609
24660
  for (const [taskId, task] of this.pendingTasks) {
24610
24661
  if (task.subagentId === subagentId) {
@@ -25051,37 +25102,146 @@ var ExtensionRegistry = class {
25051
25102
  }
25052
25103
  };
25053
25104
 
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");
25105
+ // src/core/agent-tools.ts
25106
+ function createAgentToolHandler(a) {
25107
+ async function executeSingleWithDecision(tool, use) {
25108
+ const start = Date.now();
25109
+ try {
25110
+ const result = await a.toolExecutor.executeTool(
25111
+ tool,
25112
+ use,
25113
+ a.ctx,
25114
+ a.perIterationOutputCapBytes
25115
+ );
25116
+ return { result, durationMs: Date.now() - start };
25117
+ } catch (err) {
25118
+ const msg = err instanceof Error ? err.message : String(err);
25119
+ return {
25120
+ result: {
25121
+ type: "tool_result",
25122
+ tool_use_id: use.id,
25123
+ content: `Tool "${tool.name}" threw: ${msg}`,
25124
+ is_error: true
25125
+ },
25126
+ durationMs: Date.now() - start
25127
+ };
25128
+ }
25129
+ }
25130
+ function waitForConfirm(info) {
25131
+ return new Promise((resolve13) => {
25132
+ a.events.emit("tool.confirm_needed", {
25133
+ tool: info.tool,
25134
+ input: info.input,
25135
+ toolUseId: info.toolUseId,
25136
+ suggestedPattern: info.suggestedPattern,
25137
+ resolve: resolve13
25138
+ });
25139
+ });
25140
+ }
25141
+ function emitToolExecuted(toolUseId, toolName, durationMs, ok, input, content) {
25142
+ const sig = sizeSignals(toolName, content);
25143
+ a.events.emit("tool.executed", {
25144
+ id: toolUseId,
25145
+ name: toolName,
25146
+ durationMs,
25147
+ ok,
25148
+ input,
25149
+ output: truncateForEvent(content),
25150
+ outputBytes: sig.outputBytes,
25151
+ outputTokens: sig.outputTokens,
25152
+ outputLines: sig.outputLines
25153
+ });
25154
+ }
25155
+ async function executeTools(toolUses) {
25156
+ const selectedToolUses = await a.extensions.runBeforeToolExecution(a.ctx, toolUses);
25157
+ const { outputs } = await a.toolExecutor.executeBatch(
25158
+ selectedToolUses,
25159
+ a.ctx,
25160
+ a.executionStrategy
25161
+ );
25162
+ const useById = new Map(selectedToolUses.map((u) => [u.id, u]));
25163
+ const resultsForMessage = [];
25164
+ for (const { result, tool, durationMs } of outputs) {
25165
+ if (result.type === "tool_confirm_pending") {
25166
+ const decision = await waitForConfirm({
25167
+ tool,
25168
+ input: result.input,
25169
+ toolUseId: result.toolUseId,
25170
+ suggestedPattern: result.suggestedPattern
25171
+ });
25172
+ if (decision === "always") {
25173
+ try {
25174
+ await a.permission.trust({ tool: tool.name, pattern: result.suggestedPattern });
25175
+ a.events.emit("trust.persisted", { tool: tool.name, pattern: result.suggestedPattern, decision });
25176
+ } catch {
25177
+ }
25178
+ } else if (decision === "deny") {
25179
+ try {
25180
+ await a.permission.deny({ tool: tool.name, pattern: result.suggestedPattern });
25181
+ a.events.emit("trust.persisted", { tool: tool.name, pattern: result.suggestedPattern, decision });
25182
+ } catch {
25183
+ }
25184
+ }
25185
+ if (decision === "yes") {
25186
+ const p = a.permission;
25187
+ p.allowOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25188
+ } else if (decision === "no") {
25189
+ const p = a.permission;
25190
+ p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
25191
+ }
25192
+ const reRunResult = decision === "yes" || decision === "always" ? await executeSingleWithDecision(tool, {
25193
+ id: result.toolUseId,
25194
+ name: tool.name,
25195
+ input: result.input
25196
+ }) : {
25197
+ result: {
25198
+ type: "tool_result",
25199
+ tool_use_id: result.toolUseId,
25200
+ content: decision === "deny" ? `Tool "${tool.name}" denied and blocked for this pattern.` : `Tool "${tool.name}" denied by user.`,
25201
+ is_error: true
25202
+ },
25203
+ durationMs: 0
25204
+ };
25205
+ const use2 = useById.get(reRunResult.result.tool_use_id);
25206
+ if (use2) {
25207
+ await a.pipelines.toolCall.run({ toolUse: use2, result: reRunResult.result, ctx: a.ctx, tool });
25208
+ await a.ctx.session.append({
25209
+ type: "tool_result",
25210
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25211
+ id: reRunResult.result.tool_use_id,
25212
+ content: reRunResult.result.content,
25213
+ isError: !!reRunResult.result.is_error
25214
+ });
25215
+ emitToolExecuted(
25216
+ reRunResult.result.tool_use_id,
25217
+ tool.name,
25218
+ reRunResult.durationMs,
25219
+ !reRunResult.result.is_error,
25220
+ result.input,
25221
+ reRunResult.result.content
25222
+ );
25223
+ }
25224
+ resultsForMessage.push(reRunResult.result);
25225
+ continue;
25226
+ }
25227
+ resultsForMessage.push(result);
25228
+ const use = useById.get(result.tool_use_id);
25229
+ if (!use) continue;
25230
+ await a.pipelines.toolCall.run({ toolUse: use, result, ctx: a.ctx, tool: tool ?? void 0 });
25231
+ await a.ctx.session.append({
25232
+ type: "tool_result",
25233
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25234
+ id: result.tool_use_id,
25235
+ content: result.content,
25236
+ isError: !!result.is_error
25237
+ });
25238
+ emitToolExecuted(result.tool_use_id, use.name, durationMs, !result.is_error, use.input, result.content);
25239
+ }
25240
+ a.ctx.state.appendMessage({ role: "user", content: resultsForMessage });
25241
+ await a.extensions.runAfterToolExecution(a.ctx, outputs);
25242
+ return resultsForMessage;
25243
+ }
25244
+ return { executeTools, executeSingleWithDecision };
25085
25245
  }
25086
25246
 
25087
25247
  // src/core/continue-to-next-iteration.ts
@@ -25099,13 +25259,13 @@ function parseContinueDirective(text) {
25099
25259
  }
25100
25260
  return lastDirective;
25101
25261
  }
25102
- var META_KEY2 = "_autonomousContinue";
25262
+ var META_KEY = "_autonomousContinue";
25103
25263
  function setAutonomousContinue(ctx) {
25104
- ctx.meta[META_KEY2] = true;
25264
+ ctx.meta[META_KEY] = true;
25105
25265
  }
25106
25266
  function consumeAutonomousContinue(ctx) {
25107
- const val = ctx.meta[META_KEY2] === true;
25108
- delete ctx.meta[META_KEY2];
25267
+ const val = ctx.meta[META_KEY] === true;
25268
+ delete ctx.meta[META_KEY];
25109
25269
  return val;
25110
25270
  }
25111
25271
  function makeContinueToNextIterationTool() {
@@ -25128,254 +25288,249 @@ function makeContinueToNextIterationTool() {
25128
25288
  };
25129
25289
  }
25130
25290
 
25131
- // src/core/iteration-limit.ts
25132
- function requestLimitExtension(opts) {
25133
- const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
25134
- return new Promise((resolve13) => {
25135
- let resolved = false;
25136
- const timerFired = () => {
25137
- if (!resolved) {
25138
- resolved = true;
25139
- resolve13(0);
25140
- }
25291
+ // src/core/agent-response.ts
25292
+ function createAgentResponseHandler(a) {
25293
+ async function buildAndRunRequestPipeline(opts) {
25294
+ const repaired = repairToolUseAdjacency(a.ctx.messages);
25295
+ if (repaired.report.changed) {
25296
+ a.ctx.state.replaceMessages(repaired.messages);
25297
+ a.events.emit("context.repaired", {
25298
+ ctx: a.ctx,
25299
+ ...repaired.report
25300
+ });
25301
+ a.logger.warn(
25302
+ `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)`
25303
+ );
25304
+ }
25305
+ const baseReq = {
25306
+ model: opts.model ?? a.ctx.model,
25307
+ system: a.ctx.systemPrompt,
25308
+ messages: a.ctx.messages,
25309
+ tools: a.tools.list(),
25310
+ maxTokens: 8192
25141
25311
  };
25142
- const timer = setTimeout(timerFired, timeoutMs);
25143
- timer.unref();
25144
- const deny = () => {
25145
- if (!resolved) {
25146
- resolved = true;
25147
- clearTimeout(timer);
25148
- resolve13(0);
25312
+ return a.pipelines.request.run(baseReq);
25313
+ }
25314
+ async function processResponse(raw, req) {
25315
+ let res = raw;
25316
+ res = await a.pipelines.response.run(res);
25317
+ a.events.emit("provider.response", {
25318
+ ctx: a.ctx,
25319
+ usage: res.usage,
25320
+ stopReason: res.stopReason
25321
+ });
25322
+ a.ctx.tokenCounter.account(res.usage, req.model);
25323
+ a.ctx.state.appendMessage({ role: "assistant", content: res.content });
25324
+ await a.ctx.session.append({
25325
+ type: "llm_response",
25326
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
25327
+ content: res.content,
25328
+ stopReason: res.stopReason,
25329
+ usage: res.usage
25330
+ });
25331
+ if (a.ctx.signal.aborted) {
25332
+ let finalText2 = "";
25333
+ for (const block of res.content) {
25334
+ if (isTextBlock(block)) finalText2 += block.text;
25149
25335
  }
25150
- };
25151
- const grant = (extra) => {
25152
- if (!resolved) {
25153
- resolved = true;
25154
- clearTimeout(timer);
25155
- resolve13(Math.max(0, extra));
25336
+ return { finalText: finalText2, aborted: true, done: false };
25337
+ }
25338
+ let finalText = "";
25339
+ const streamed = a.ctx.provider.capabilities.streaming;
25340
+ for (const block of res.content) {
25341
+ if (isTextBlock(block)) {
25342
+ const rendered = await a.pipelines.assistantOutput.run(block);
25343
+ finalText += rendered.text;
25344
+ if (!streamed) a.renderer?.write(rendered);
25156
25345
  }
25157
- };
25158
- events.emit("iteration.limit_reached", {
25159
- currentIterations,
25160
- currentLimit,
25161
- grant,
25162
- deny
25163
- });
25164
- if (autoExtend) {
25165
- setImmediate(() => {
25166
- if (!resolved) {
25167
- resolved = true;
25168
- clearTimeout(timer);
25169
- resolve13(100);
25170
- }
25171
- });
25172
25346
  }
25173
- });
25347
+ let directive = "none";
25348
+ if (finalText) {
25349
+ directive = parseContinueDirective(finalText);
25350
+ }
25351
+ return { finalText, aborted: false, done: false, directive };
25352
+ }
25353
+ return { buildAndRunRequestPipeline, processResponse };
25174
25354
  }
25175
25355
 
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 };
25356
+ // src/core/btw.ts
25357
+ var META_KEY2 = "_btwNotes";
25358
+ function readQueue(ctx) {
25359
+ const raw = ctx.meta[META_KEY2];
25360
+ return Array.isArray(raw) ? raw : [];
25184
25361
  }
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
- };
25362
+ function setBtwNote(ctx, text) {
25363
+ const trimmed = text.trim();
25364
+ if (!trimmed) return readQueue(ctx).length;
25365
+ const next = [...readQueue(ctx), trimmed].slice(-20);
25366
+ ctx.meta[META_KEY2] = next;
25367
+ return next.length;
25194
25368
  }
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;
25228
- }
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);
25237
- }
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);
25369
+ function pendingBtwCount(ctx) {
25370
+ return readQueue(ctx).length;
25371
+ }
25372
+ function consumeBtwNotes(ctx) {
25373
+ const notes = readQueue(ctx);
25374
+ if (notes.length > 0) delete ctx.meta[META_KEY2];
25375
+ return notes;
25376
+ }
25377
+ function buildBtwBlock(notes) {
25378
+ const body = notes.map((n) => `- ${n}`).join("\n");
25379
+ return [
25380
+ "[BY THE WAY \u2014 the user added this while you were working. Fold it into",
25381
+ "your current task; do not restart from scratch unless it contradicts the",
25382
+ "goal:",
25383
+ "",
25384
+ body,
25385
+ "]"
25386
+ ].join("\n");
25387
+ }
25388
+
25389
+ // src/core/iteration-limit.ts
25390
+ function requestLimitExtension(opts) {
25391
+ const { events, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
25392
+ return new Promise((resolve13) => {
25393
+ let resolved = false;
25394
+ const timerFired = () => {
25395
+ if (!resolved) {
25396
+ resolved = true;
25397
+ resolve13(0);
25398
+ }
25399
+ };
25400
+ const timer = setTimeout(timerFired, timeoutMs);
25401
+ timer.unref();
25402
+ const deny = () => {
25403
+ if (!resolved) {
25404
+ resolved = true;
25405
+ clearTimeout(timer);
25406
+ resolve13(0);
25407
+ }
25408
+ };
25409
+ const grant = (extra) => {
25410
+ if (!resolved) {
25411
+ resolved = true;
25412
+ clearTimeout(timer);
25413
+ resolve13(Math.max(0, extra));
25414
+ }
25415
+ };
25416
+ events.emit("iteration.limit_reached", {
25417
+ currentIterations,
25418
+ currentLimit,
25419
+ grant,
25420
+ deny
25421
+ });
25422
+ if (autoExtend) {
25423
+ setImmediate(() => {
25424
+ if (!resolved) {
25425
+ resolved = true;
25426
+ clearTimeout(timer);
25427
+ resolve13(100);
25428
+ }
25429
+ });
25261
25430
  }
25431
+ });
25432
+ }
25433
+
25434
+ // src/core/agent-loop.ts
25435
+ function toError(err) {
25436
+ return err instanceof Error ? err : new Error(String(err));
25437
+ }
25438
+ function createAgentLoopHandler(a, handlers) {
25439
+ async function compactContextIfNeeded() {
25440
+ await a.pipelines.contextWindow.run(a.ctx);
25262
25441
  }
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 });
25442
+ function emitContextPct() {
25443
+ const maxContext = a.ctx.provider.capabilities.maxContext ?? 2e5;
25444
+ const { total } = estimateRequestTokens(a.ctx.messages, a.ctx.systemPrompt, a.ctx.tools ?? []);
25445
+ a.events.emit("ctx.pct", { load: total / maxContext, tokens: total, maxContext });
25269
25446
  }
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);
25279
- }
25280
- }
25281
- this.plugins.length = 0;
25282
- if (errors.length > 0) {
25283
- throw new Error(`Agent teardown failed: ${errors.map(String).join("; ")}`);
25447
+ function injectPendingBtwNotes() {
25448
+ const notes = consumeBtwNotes(a.ctx);
25449
+ if (notes.length === 0) return;
25450
+ const block = { type: "text", text: buildBtwBlock(notes) };
25451
+ const messages = a.ctx.messages;
25452
+ const last = messages[messages.length - 1];
25453
+ if (last && last.role === "user") {
25454
+ const content = typeof last.content === "string" ? [{ type: "text", text: last.content }, block] : [...last.content, block];
25455
+ a.ctx.state.replaceMessages([...messages.slice(0, -1), { ...last, content }]);
25456
+ } else {
25457
+ a.ctx.state.appendMessage({ role: "user", content: [block] });
25284
25458
  }
25285
25459
  }
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();
25460
+ async function checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations, delegateSummaries) {
25461
+ if (hasHardLimit && iterationIndex >= limit) {
25462
+ const extendBy = await requestLimitExtension({
25463
+ events: a.events,
25464
+ currentIterations,
25465
+ currentLimit: limit,
25466
+ autoExtend: a.autoExtendLimit
25467
+ });
25468
+ if (extendBy > 0) {
25469
+ const newLimit = limit + extendBy;
25470
+ a.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
25471
+ return { limit: newLimit };
25472
+ }
25473
+ return { limit, exit: { status: "max_iterations", iterations: currentIterations, delegateSummaries } };
25319
25474
  }
25475
+ return { limit };
25320
25476
  }
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({
25477
+ async function runInner(inputPayload, opts, controller, autonomousContinue) {
25478
+ await a.pipelines.userInput.run(inputPayload);
25479
+ a.ctx.state.appendMessage({ role: "user", content: inputPayload.content });
25480
+ await a.ctx.session.append({
25325
25481
  type: "user_input",
25326
25482
  ts: (/* @__PURE__ */ new Date()).toISOString(),
25327
25483
  content: inputPayload.content
25328
25484
  });
25329
- const promptIndex = this.ctx.messages.filter((m) => m.role === "user").length - 1;
25485
+ const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
25330
25486
  const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
25331
- await this.ctx.session.writeCheckpoint(promptIndex, preview);
25487
+ await a.ctx.session.writeCheckpoint(promptIndex, preview);
25332
25488
  let finalText = "";
25333
25489
  let iterations = 0;
25334
25490
  const delegateSummaries = [];
25335
- let effectiveLimit = opts.maxIterations ?? this.maxIterations;
25491
+ let effectiveLimit = opts.maxIterations ?? a.maxIterations;
25336
25492
  const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
25337
25493
  let recoveryRetries = 0;
25338
- const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
25339
25494
  const onSubagentDone = ({ summary, ok }) => {
25340
25495
  delegateSummaries.push({ summary, ok });
25341
25496
  };
25342
- const offSubagentDone = this.events.on("subagent.done", onSubagentDone);
25343
- const diRunner = this.container.has(TOKENS.ProviderRunner) ? this.container.resolve(TOKENS.ProviderRunner) : null;
25497
+ const offSubagentDone = a.events.on("subagent.done", onSubagentDone);
25498
+ const diRunner = a.container.has(TOKENS.ProviderRunner) ? a.container.resolve(TOKENS.ProviderRunner) : null;
25344
25499
  const baseRunner = diRunner ? (ctx, req) => diRunner.run({
25345
25500
  provider: ctx.provider,
25346
25501
  request: req,
25347
25502
  signal: controller.signal,
25348
25503
  ctx,
25349
- events: this.events,
25350
- retry: this.retry,
25351
- logger: this.logger,
25352
- tracer: this.tracer
25504
+ events: a.events,
25505
+ retry: a.retry,
25506
+ logger: a.logger,
25507
+ tracer: a.tracer
25353
25508
  }) : async (ctx, req) => runProviderWithRetry({
25354
25509
  provider: ctx.provider,
25355
25510
  request: req,
25356
25511
  signal: controller.signal,
25357
25512
  ctx,
25358
- events: this.events,
25359
- retry: this.retry,
25360
- logger: this.logger,
25361
- tracer: this.tracer
25513
+ events: a.events,
25514
+ retry: a.retry,
25515
+ logger: a.logger,
25516
+ tracer: a.tracer
25362
25517
  });
25363
- const customRunner = this.extensions.wrapProviderRunner(baseRunner);
25518
+ const customRunner = a.extensions.wrapProviderRunner(baseRunner);
25364
25519
  try {
25365
25520
  for (let i = 0; ; i++) {
25366
25521
  iterations = i + 1;
25367
25522
  if (controller.signal.aborted) {
25368
25523
  return { status: "aborted", iterations };
25369
25524
  }
25370
- await this.ctx.session.writeInFlightMarker(`iteration ${i} / max ${this.maxIterations}`).catch((err) => {
25371
- this.logger.debug?.(
25525
+ await a.ctx.session.writeInFlightMarker(`iteration ${i} / max ${a.maxIterations}`).catch((err) => {
25526
+ a.logger.debug?.(
25372
25527
  `in-flight marker write failed: ${err instanceof Error ? err.message : String(err)}`
25373
25528
  );
25374
25529
  });
25375
25530
  if (autonomousContinue) {
25376
- consumeAutonomousContinue(this.ctx);
25531
+ consumeAutonomousContinue(a.ctx);
25377
25532
  }
25378
- const limitCheck = await this.checkIterationLimit(
25533
+ const limitCheck = await checkIterationLimit(
25379
25534
  i,
25380
25535
  effectiveLimit,
25381
25536
  hasHardLimit,
@@ -25386,11 +25541,11 @@ var Agent = class {
25386
25541
  if (limitCheck.exit) {
25387
25542
  return { ...limitCheck.exit, finalText };
25388
25543
  }
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({
25544
+ await a.extensions.runBeforeIteration(a.ctx, i);
25545
+ a.events.emit("iteration.started", { ctx: a.ctx, index: i });
25546
+ injectPendingBtwNotes();
25547
+ const req = await handlers.response.buildAndRunRequestPipeline(opts);
25548
+ await a.ctx.session.append({
25394
25549
  type: "llm_request",
25395
25550
  ts: (/* @__PURE__ */ new Date()).toISOString(),
25396
25551
  model: req.model,
@@ -25401,39 +25556,39 @@ var Agent = class {
25401
25556
  });
25402
25557
  let res;
25403
25558
  try {
25404
- res = await customRunner(this.ctx, req);
25559
+ res = await customRunner(a.ctx, req);
25405
25560
  const calibratedEstimate = estimateRequestTokensCalibrated(req.messages, req.system, req.tools ?? []).total;
25406
25561
  recordActualUsage(res.usage.input, calibratedEstimate);
25407
25562
  recoveryRetries = 0;
25408
25563
  } catch (err) {
25409
25564
  if (controller.signal.aborted) {
25410
- this.events.emit("error", { err: toError(err), phase: "provider" });
25565
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25411
25566
  return { status: "aborted", iterations, error: toWrongStackError(err, "AGENT_ABORTED") };
25412
25567
  }
25413
- const extDecision = await this.extensions.runOnError(this.ctx, err, "provider", i);
25568
+ const extDecision = await a.extensions.runOnError(a.ctx, err, "provider", i);
25414
25569
  if (extDecision) {
25415
25570
  if (extDecision.action === "fail") {
25416
- this.events.emit("error", { err: toError(err), phase: "provider" });
25571
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25417
25572
  return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
25418
25573
  }
25419
25574
  if (extDecision.action === "continue") {
25420
- await this.extensions.runAfterIteration(this.ctx, i);
25575
+ await a.extensions.runAfterIteration(a.ctx, i);
25421
25576
  continue;
25422
25577
  }
25423
25578
  if (extDecision.action === "retry") {
25424
25579
  recoveryRetries++;
25425
25580
  if (recoveryRetries > 2) {
25426
- this.events.emit("error", { err: toError(err), phase: "provider" });
25581
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25427
25582
  return { status: "failed", iterations, error: toWrongStackError(err), delegateSummaries };
25428
25583
  }
25429
- if (extDecision.model) this.ctx.model = extDecision.model;
25430
- this.logger.info("Extension requested retry; retrying turn");
25584
+ if (extDecision.model) a.ctx.model = extDecision.model;
25585
+ a.logger.info("Extension requested retry; retrying turn");
25431
25586
  continue;
25432
25587
  }
25433
25588
  }
25434
- const recovered = await this.errorHandler.recover(err, this.ctx);
25589
+ const recovered = await a.errorHandler.recover(err, a.ctx);
25435
25590
  if (!recovered || recovered.action === "fail") {
25436
- this.events.emit("error", { err: toError(err), phase: "provider" });
25591
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25437
25592
  return {
25438
25593
  status: "failed",
25439
25594
  iterations,
@@ -25444,17 +25599,17 @@ var Agent = class {
25444
25599
  if (recovered.action === "retry") {
25445
25600
  recoveryRetries++;
25446
25601
  if (recoveryRetries > 2) {
25447
- this.events.emit("error", { err: toError(err), phase: "provider" });
25602
+ a.events.emit("error", { err: toError(err), phase: "provider" });
25448
25603
  return { status: "failed", iterations, error: toWrongStackError(err) };
25449
25604
  }
25450
- if (recovered.model) this.ctx.model = recovered.model;
25451
- this.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
25605
+ if (recovered.model) a.ctx.model = recovered.model;
25606
+ a.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
25452
25607
  continue;
25453
25608
  }
25454
25609
  recoveryRetries = 0;
25455
25610
  res = recovered.response;
25456
25611
  }
25457
- const responseResult = await this.processResponse(res, req);
25612
+ const responseResult = await handlers.response.processResponse(res, req);
25458
25613
  if (responseResult.aborted) {
25459
25614
  return { status: "aborted", iterations, finalText: responseResult.finalText, delegateSummaries };
25460
25615
  }
@@ -25464,11 +25619,11 @@ var Agent = class {
25464
25619
  finalText = responseResult.finalText;
25465
25620
  const toolUses = res.content.filter(isToolUseBlock);
25466
25621
  if (toolUses.length === 0) {
25467
- this.emitContextPct();
25468
- this.events.emit("iteration.completed", { ctx: this.ctx, index: i });
25622
+ emitContextPct();
25623
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25469
25624
  if (autonomousContinue && responseResult.directive === "continue") {
25470
- await this.compactContextIfNeeded();
25471
- await this.extensions.runAfterIteration(this.ctx, i);
25625
+ await compactContextIfNeeded();
25626
+ await a.extensions.runAfterIteration(a.ctx, i);
25472
25627
  continue;
25473
25628
  }
25474
25629
  if (autonomousContinue && responseResult.directive === "stop") {
@@ -25476,18 +25631,18 @@ var Agent = class {
25476
25631
  }
25477
25632
  return { status: "done", iterations, finalText, delegateSummaries };
25478
25633
  }
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);
25634
+ await handlers.tools.executeTools(toolUses);
25635
+ if (autonomousContinue && consumeAutonomousContinue(a.ctx)) {
25636
+ emitContextPct();
25637
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25638
+ await compactContextIfNeeded();
25639
+ await a.extensions.runAfterIteration(a.ctx, i);
25485
25640
  continue;
25486
25641
  }
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);
25642
+ emitContextPct();
25643
+ a.events.emit("iteration.completed", { ctx: a.ctx, index: i });
25644
+ await compactContextIfNeeded();
25645
+ await a.extensions.runAfterIteration(a.ctx, i);
25491
25646
  if (autonomousContinue && responseResult.directive === "continue") {
25492
25647
  continue;
25493
25648
  }
@@ -25498,346 +25653,161 @@ var Agent = class {
25498
25653
  } finally {
25499
25654
  offSubagentDone();
25500
25655
  const reason = controller.signal.aborted ? "aborted" : "clean";
25501
- await this.ctx.session.clearInFlightMarker(reason).catch((err) => {
25502
- this.logger.debug?.(
25656
+ await a.ctx.session.clearInFlightMarker(reason).catch((err) => {
25657
+ a.logger.debug?.(
25503
25658
  `in-flight marker clear failed: ${err instanceof Error ? err.message : String(err)}`
25504
25659
  );
25505
25660
  });
25506
25661
  }
25507
25662
  }
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);
25663
+ return { runInner };
25664
+ }
25665
+
25666
+ // src/core/agent-types.ts
25667
+ var DEFAULT_MAX_ITERATIONS = 100;
25668
+ function normalizeInput(input) {
25669
+ if (typeof input === "string") {
25670
+ return { blocks: [{ type: "text", text: input }], text: input };
25554
25671
  }
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
25672
+ const text = input.filter(isTextBlock).map((b) => b.text).join("");
25673
+ return { blocks: input, text };
25674
+ }
25675
+ function createDefaultPipelines() {
25676
+ return {
25677
+ request: new Pipeline(),
25678
+ response: new Pipeline(),
25679
+ toolCall: new Pipeline(),
25680
+ userInput: new Pipeline(),
25681
+ assistantOutput: new Pipeline(),
25682
+ contextWindow: new Pipeline()
25683
+ };
25684
+ }
25685
+
25686
+ // src/core/agent.ts
25687
+ var Agent = class {
25688
+ container;
25689
+ tools;
25690
+ providers;
25691
+ events;
25692
+ pipelines;
25693
+ ctx;
25694
+ maxIterations;
25695
+ executionStrategy;
25696
+ perIterationOutputCapBytes;
25697
+ plugins = [];
25698
+ toolExecutor;
25699
+ autoExtendLimit;
25700
+ autonomousContinue;
25701
+ tracer;
25702
+ extensions;
25703
+ _toolHandler;
25704
+ _responseHandler;
25705
+ _loopHandler;
25706
+ constructor(init) {
25707
+ this.container = init.container;
25708
+ this.tools = init.tools;
25709
+ this.providers = init.providers;
25710
+ this.events = init.events;
25711
+ this.pipelines = init.pipelines;
25712
+ this.ctx = init.context;
25713
+ this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
25714
+ this.executionStrategy = init.executionStrategy ?? "smart";
25715
+ this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
25716
+ this.autoExtendLimit = init.autoExtendLimit ?? true;
25717
+ this.autonomousContinue = init.autonomousContinue ?? false;
25718
+ this.tracer = init.tracer;
25719
+ this.extensions = init.extensions ?? new ExtensionRegistry();
25720
+ this.extensions.setLogger(this.container.resolve(TOKENS.Logger));
25721
+ this.toolExecutor = init.toolExecutor;
25722
+ this._toolHandler = createAgentToolHandler(this);
25723
+ this._responseHandler = createAgentResponseHandler(this);
25724
+ this._loopHandler = createAgentLoopHandler(this, {
25725
+ tools: this._toolHandler,
25726
+ response: this._responseHandler
25575
25727
  });
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);
25728
+ }
25729
+ get logger() {
25730
+ return this.container.resolve(TOKENS.Logger);
25731
+ }
25732
+ get retry() {
25733
+ return this.container.resolve(TOKENS.RetryPolicy);
25734
+ }
25735
+ get errorHandler() {
25736
+ return this.container.resolve(TOKENS.ErrorHandler);
25737
+ }
25738
+ get permission() {
25739
+ return this.container.resolve(TOKENS.PermissionPolicy);
25740
+ }
25741
+ get renderer() {
25742
+ return this.container.has(TOKENS.Renderer) ? this.container.resolve(TOKENS.Renderer) : void 0;
25743
+ }
25744
+ disableInteractiveConfirmation() {
25745
+ this.toolExecutor.clearConfirmAwaiter();
25746
+ const policy = this.permission;
25747
+ if (typeof policy.setPromptDelegate === "function") {
25748
+ policy.setPromptDelegate(void 0);
25595
25749
  }
25596
- return { finalText, aborted: false, done: false, directive };
25597
25750
  }
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
- });
25751
+ register(tool) {
25752
+ this.tools.register(tool);
25753
+ }
25754
+ async use(plugin, api) {
25755
+ await plugin.setup(api);
25756
+ this.plugins.push({ plugin, api });
25757
+ }
25758
+ async teardown() {
25759
+ const errors = [];
25760
+ for (const { plugin, api } of this.plugins.toReversed()) {
25761
+ if (typeof plugin.teardown !== "function") continue;
25762
+ try {
25763
+ await plugin.teardown(api);
25764
+ } catch (err) {
25765
+ errors.push(err);
25730
25766
  }
25731
25767
  }
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] });
25768
+ this.plugins.length = 0;
25769
+ if (errors.length > 0) {
25770
+ throw new Error(`Agent teardown failed: ${errors.map(String).join("; ")}`);
25758
25771
  }
25759
25772
  }
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
- });
25773
+ async run(userInput, opts = {}) {
25774
+ const controller = new RunController({ parentSignal: opts.signal });
25775
+ const signal = controller.signal;
25776
+ this.ctx.signal = signal;
25777
+ controller.onAbort(() => this.ctx.drainAbortHooks());
25778
+ const span = this.tracer?.startSpan("agent.run", {
25779
+ "agent.model": opts.model ?? this.ctx.model,
25780
+ "agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
25769
25781
  });
25770
- }
25771
- async executeSingleWithDecision(tool, use) {
25772
- const start = Date.now();
25782
+ const { blocks, text } = normalizeInput(userInput);
25783
+ const inputPayload = { content: blocks, text, ctx: this.ctx };
25784
+ await this.extensions.runBeforeRun(this.ctx, inputPayload);
25773
25785
  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 };
25786
+ const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
25787
+ const result = await this._loopHandler.runInner(inputPayload, opts, controller, autonomousContinue);
25788
+ span?.setAttribute("agent.status", result.status);
25789
+ span?.setAttribute("agent.iterations", result.iterations);
25790
+ await this.extensions.runAfterRun(this.ctx, result);
25791
+ return result;
25781
25792
  } 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
25793
+ const wse = err instanceof AgentError ? err : toWrongStackError(err);
25794
+ this.events.emit("error", { err: err instanceof Error ? err : new Error(String(err)), phase: "agent" });
25795
+ if (err instanceof Error) span?.recordError(err);
25796
+ span?.setAttribute("agent.status", "failed");
25797
+ const result = {
25798
+ status: signal.aborted ? "aborted" : "failed",
25799
+ iterations: 0,
25800
+ error: wse
25791
25801
  };
25802
+ await this.extensions.runAfterRun(this.ctx, result);
25803
+ return result;
25804
+ } finally {
25805
+ span?.end();
25806
+ await controller.dispose();
25792
25807
  }
25793
25808
  }
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
- }
25809
+ // ── Tool + response execution handled by AgentToolHandler / AgentResponseHandler ──
25814
25810
  };
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
25811
 
25842
25812
  // src/hooks/registry.ts
25843
25813
  var HookRegistry = class {