@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
@@ -1174,6 +1174,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
1174
1174
  return lines.join("\n");
1175
1175
  }
1176
1176
  cleanup() {
1177
+ if (this._timeoutTimer) {
1178
+ clearTimeout(this._timeoutTimer);
1179
+ this._timeoutTimer = void 0;
1180
+ }
1177
1181
  for (const dispose of this.disposers) dispose();
1178
1182
  this.disposers.length = 0;
1179
1183
  }
@@ -4280,116 +4284,6 @@ function resolveModelMatrix(matrix, role) {
4280
4284
  return void 0;
4281
4285
  }
4282
4286
 
4283
- // src/types/errors.ts
4284
- var ERROR_CODES = {
4285
- // Provider
4286
- PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
4287
- PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
4288
- PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
4289
- PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
4290
- PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
4291
- PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR"};
4292
- var WrongStackError = class extends Error {
4293
- code;
4294
- subsystem;
4295
- severity;
4296
- recoverable;
4297
- context;
4298
- constructor(opts) {
4299
- super(opts.message, { cause: opts.cause });
4300
- this.name = "WrongStackError";
4301
- this.code = opts.code;
4302
- this.subsystem = opts.subsystem;
4303
- this.severity = opts.severity ?? "error";
4304
- this.recoverable = opts.recoverable ?? false;
4305
- this.context = opts.context;
4306
- }
4307
- /**
4308
- * Render a one-line user-facing description.
4309
- * Subclasses should override for domain-specific formatting.
4310
- */
4311
- describe() {
4312
- const ctx = this.context ? ` ${formatContext(this.context)}` : "";
4313
- return `${this.code}: ${this.message}${ctx}`;
4314
- }
4315
- };
4316
- function formatContext(ctx) {
4317
- const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
4318
- return parts.length > 0 ? `[${parts.join(" ")}]` : "";
4319
- }
4320
-
4321
- // src/types/provider.ts
4322
- var ProviderError = class extends WrongStackError {
4323
- status;
4324
- retryable;
4325
- providerId;
4326
- body;
4327
- constructor(message, status, retryable, providerId, opts = {}) {
4328
- super({
4329
- message,
4330
- code: providerStatusToCode(status, opts.body?.type),
4331
- subsystem: "provider",
4332
- severity: status >= 500 ? "error" : "warning",
4333
- recoverable: retryable,
4334
- context: { providerId, status },
4335
- cause: opts.cause
4336
- });
4337
- this.name = "ProviderError";
4338
- this.status = status;
4339
- this.retryable = retryable;
4340
- this.providerId = providerId;
4341
- this.body = opts.body;
4342
- }
4343
- /**
4344
- * Render a one-line, user-facing description. Designed for the CLI/TUI
4345
- * status line and the agent's retry warning. Avoids dumping raw JSON
4346
- * (which is what users see today when a 529 lands and the log message
4347
- * includes the full `{"type":"error",...}` body).
4348
- *
4349
- * Examples:
4350
- * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
4351
- * "openai rate limited (429): Retry after 12s"
4352
- * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
4353
- * "groq HTTP 500 (server error)"
4354
- */
4355
- describe() {
4356
- const kind = describeStatus(this.status, this.body?.type);
4357
- const head = `${this.providerId} ${kind}`;
4358
- const detail = this.body?.message?.trim();
4359
- const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
4360
- if (detail && detail.length > 0) {
4361
- return `${head}: ${truncate(detail, 240)}${reqId}`;
4362
- }
4363
- return `${head}${reqId}`;
4364
- }
4365
- };
4366
- function describeStatus(status, type) {
4367
- if (status === 0) return "network error";
4368
- if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
4369
- if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
4370
- if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
4371
- if (type === "permission_error" || status === 403) return `forbidden (${status})`;
4372
- if (type === "not_found_error" || status === 404) return `not found (${status})`;
4373
- if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
4374
- if (status === 408) return `timeout (${status})`;
4375
- if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
4376
- if (type) return `${type} (${status})`;
4377
- return `HTTP ${status}`;
4378
- }
4379
- function truncate(s, n) {
4380
- return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
4381
- }
4382
- function providerStatusToCode(status, type) {
4383
- if (status === 0) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
4384
- if (type === "rate_limit_error" || status === 429) return ERROR_CODES.PROVIDER_RATE_LIMITED;
4385
- if (type === "authentication_error" || status === 401) return ERROR_CODES.PROVIDER_AUTH_FAILED;
4386
- if (type === "overloaded_error" || status === 529) return ERROR_CODES.PROVIDER_OVERLOADED;
4387
- if (type === "invalid_request_error" || status === 400) return ERROR_CODES.PROVIDER_INVALID_REQUEST;
4388
- if (status === 408) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
4389
- if (status >= 500) return ERROR_CODES.PROVIDER_SERVER_ERROR;
4390
- return ERROR_CODES.PROVIDER_INVALID_REQUEST;
4391
- }
4392
-
4393
4287
  // src/coordination/subagent-budget.ts
4394
4288
  var BudgetExceededError = class extends Error {
4395
4289
  kind;
@@ -4679,15 +4573,22 @@ var SubagentBudget = class _SubagentBudget {
4679
4573
  void this.checkLimits();
4680
4574
  }
4681
4575
  /**
4682
- * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
4683
- * wall-clock time cannot be "extended" by the coordinator, so it throws
4684
- * synchronously rather than entering the negotiation flow.
4576
+ * Wall-clock / idle budget check. Delegates to `checkLimits(elapsed)`, so
4577
+ * `timeout` and `idle_timeout` follow the SAME negotiation path as the other
4578
+ * kinds they are NOT a special-cased hard stop. This is deliberate: a
4579
+ * heartbeat-aware policy (see `attachAutoExtend` and `CollabSession`) grants
4580
+ * a timeout extension only while the agent is making progress and denies it
4581
+ * once the agent is genuinely stuck, which is safer than an unconditional
4582
+ * hard kill of a long-but-working agent. The runner translates the resulting
4583
+ * `BudgetThresholdSignal` decision (`extend` → patch limits in place,
4584
+ * `stop` → abort) just like every other kind.
4685
4585
  *
4686
- * Decision table:
4687
- * - no `onThreshold` handler → throw `BudgetExceededError`
4688
- * - `mode === 'sync'` → throw `BudgetExceededError`
4689
- * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
4690
- * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
4586
+ * Decision table (same as `checkLimits`):
4587
+ * - no `onThreshold` handler → throw `BudgetExceededError` (hard stop)
4588
+ * - `mode === 'sync'` → throw `BudgetExceededError` (hard stop)
4589
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (no one to ask)
4590
+ * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` (negotiated;
4591
+ * a heartbeat-aware policy may extend the timeout)
4691
4592
  */
4692
4593
  checkTimeout() {
4693
4594
  if (this.startTime === null) return;
@@ -4722,6 +4623,181 @@ var SubagentBudget = class _SubagentBudget {
4722
4623
  }
4723
4624
  };
4724
4625
 
4626
+ // src/types/errors.ts
4627
+ var ERROR_CODES = {
4628
+ // Provider
4629
+ PROVIDER_RATE_LIMITED: "PROVIDER_RATE_LIMITED",
4630
+ PROVIDER_AUTH_FAILED: "PROVIDER_AUTH_FAILED",
4631
+ PROVIDER_OVERLOADED: "PROVIDER_OVERLOADED",
4632
+ PROVIDER_INVALID_REQUEST: "PROVIDER_INVALID_REQUEST",
4633
+ PROVIDER_SERVER_ERROR: "PROVIDER_SERVER_ERROR",
4634
+ PROVIDER_NETWORK_ERROR: "PROVIDER_NETWORK_ERROR"};
4635
+ var WrongStackError = class extends Error {
4636
+ code;
4637
+ subsystem;
4638
+ severity;
4639
+ recoverable;
4640
+ context;
4641
+ constructor(opts) {
4642
+ super(opts.message, { cause: opts.cause });
4643
+ this.name = "WrongStackError";
4644
+ this.code = opts.code;
4645
+ this.subsystem = opts.subsystem;
4646
+ this.severity = opts.severity ?? "error";
4647
+ this.recoverable = opts.recoverable ?? false;
4648
+ this.context = opts.context;
4649
+ }
4650
+ /**
4651
+ * Render a one-line user-facing description.
4652
+ * Subclasses should override for domain-specific formatting.
4653
+ */
4654
+ describe() {
4655
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
4656
+ return `${this.code}: ${this.message}${ctx}`;
4657
+ }
4658
+ };
4659
+ function formatContext(ctx) {
4660
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
4661
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
4662
+ }
4663
+
4664
+ // src/types/provider.ts
4665
+ var ProviderError = class extends WrongStackError {
4666
+ status;
4667
+ retryable;
4668
+ providerId;
4669
+ body;
4670
+ constructor(message, status, retryable, providerId, opts = {}) {
4671
+ super({
4672
+ message,
4673
+ code: providerStatusToCode(status, opts.body?.type),
4674
+ subsystem: "provider",
4675
+ severity: status >= 500 ? "error" : "warning",
4676
+ recoverable: retryable,
4677
+ context: { providerId, status },
4678
+ cause: opts.cause
4679
+ });
4680
+ this.name = "ProviderError";
4681
+ this.status = status;
4682
+ this.retryable = retryable;
4683
+ this.providerId = providerId;
4684
+ this.body = opts.body;
4685
+ }
4686
+ /**
4687
+ * Render a one-line, user-facing description. Designed for the CLI/TUI
4688
+ * status line and the agent's retry warning. Avoids dumping raw JSON
4689
+ * (which is what users see today when a 529 lands and the log message
4690
+ * includes the full `{"type":"error",...}` body).
4691
+ *
4692
+ * Examples:
4693
+ * "minimax-coding-plan overloaded (529): High traffic detected. Upgrade for highspeed model. [req 06534785201de9c0…]"
4694
+ * "openai rate limited (429): Retry after 12s"
4695
+ * "anthropic invalid request (400): messages.0.role must be one of 'user'|'assistant'"
4696
+ * "groq HTTP 500 (server error)"
4697
+ */
4698
+ describe() {
4699
+ const kind = describeStatus(this.status, this.body?.type);
4700
+ const head = `${this.providerId} ${kind}`;
4701
+ const detail = this.body?.message?.trim();
4702
+ const reqId = this.body?.requestId ? ` [req ${this.body.requestId.slice(0, 16)}${this.body.requestId.length > 16 ? "\u2026" : ""}]` : "";
4703
+ if (detail && detail.length > 0) {
4704
+ return `${head}: ${truncate(detail, 240)}${reqId}`;
4705
+ }
4706
+ return `${head}${reqId}`;
4707
+ }
4708
+ };
4709
+ function describeStatus(status, type) {
4710
+ if (status === 0) return "network error";
4711
+ if (type === "overloaded_error" || status === 529) return `overloaded (${status})`;
4712
+ if (type === "rate_limit_error" || status === 429) return `rate limited (${status})`;
4713
+ if (type === "authentication_error" || status === 401) return `auth failed (${status})`;
4714
+ if (type === "permission_error" || status === 403) return `forbidden (${status})`;
4715
+ if (type === "not_found_error" || status === 404) return `not found (${status})`;
4716
+ if (type === "invalid_request_error" || status === 400) return `invalid request (${status})`;
4717
+ if (status === 408) return `timeout (${status})`;
4718
+ if (status >= 500 && status < 600) return `HTTP ${status} (server error)`;
4719
+ if (type) return `${type} (${status})`;
4720
+ return `HTTP ${status}`;
4721
+ }
4722
+ function truncate(s, n) {
4723
+ return s.length <= n ? s : `${s.slice(0, n - 1)}\u2026`;
4724
+ }
4725
+ function providerStatusToCode(status, type) {
4726
+ if (status === 0) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
4727
+ if (type === "rate_limit_error" || status === 429) return ERROR_CODES.PROVIDER_RATE_LIMITED;
4728
+ if (type === "authentication_error" || status === 401) return ERROR_CODES.PROVIDER_AUTH_FAILED;
4729
+ if (type === "overloaded_error" || status === 529) return ERROR_CODES.PROVIDER_OVERLOADED;
4730
+ if (type === "invalid_request_error" || status === 400) return ERROR_CODES.PROVIDER_INVALID_REQUEST;
4731
+ if (status === 408) return ERROR_CODES.PROVIDER_NETWORK_ERROR;
4732
+ if (status >= 500) return ERROR_CODES.PROVIDER_SERVER_ERROR;
4733
+ return ERROR_CODES.PROVIDER_INVALID_REQUEST;
4734
+ }
4735
+
4736
+ // src/coordination/coordinator/error-classifier.ts
4737
+ function classifySubagentError(err, hints = {}) {
4738
+ const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
4739
+ if (err instanceof ProviderError) {
4740
+ const baseMessage2 = err.describe();
4741
+ return providerErrorToSubagentError(err, baseMessage2, cause);
4742
+ }
4743
+ const baseMessage = err instanceof Error ? err.message : String(err);
4744
+ if (err instanceof BudgetExceededError) {
4745
+ const map = {
4746
+ iterations: "budget_iterations",
4747
+ tool_calls: "budget_tool_calls",
4748
+ tokens: "budget_tokens",
4749
+ cost: "budget_cost",
4750
+ timeout: "budget_timeout",
4751
+ idle_timeout: "budget_timeout"
4752
+ };
4753
+ return {
4754
+ kind: map[err.kind],
4755
+ message: baseMessage,
4756
+ retryable: false,
4757
+ cause
4758
+ };
4759
+ }
4760
+ if (hints.parentAborted) {
4761
+ return { kind: "aborted_by_parent", message: baseMessage, retryable: false, cause };
4762
+ }
4763
+ const lower = baseMessage.toLowerCase();
4764
+ if (/agent aborted$/i.test(baseMessage)) {
4765
+ return { kind: "aborted_by_parent", message: baseMessage, retryable: false, cause };
4766
+ }
4767
+ if (/agent exhausted iteration limit$/i.test(baseMessage)) {
4768
+ return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
4769
+ }
4770
+ if (/empty response$/i.test(baseMessage)) {
4771
+ return { kind: "empty_response", message: baseMessage, retryable: false, cause };
4772
+ }
4773
+ if (/^tool failed: /i.test(baseMessage)) {
4774
+ return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
4775
+ }
4776
+ if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
4777
+ return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
4778
+ }
4779
+ if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
4780
+ return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
4781
+ }
4782
+ return { kind: "unknown", message: baseMessage, retryable: false, cause };
4783
+ }
4784
+ function providerErrorToSubagentError(err, message, cause) {
4785
+ const status = err.status;
4786
+ if (status === 429 || err.body?.type === "rate_limit_error") {
4787
+ return { kind: "provider_rate_limit", message, retryable: true, backoffMs: 5e3, cause };
4788
+ }
4789
+ if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
4790
+ return { kind: "provider_auth", message, retryable: false, cause };
4791
+ }
4792
+ if (status === 408 || status === 0) {
4793
+ return { kind: "provider_timeout", message, retryable: true, cause };
4794
+ }
4795
+ if (status >= 500 && status < 600) {
4796
+ return { kind: "provider_5xx", message, retryable: true, backoffMs: 3e3, cause };
4797
+ }
4798
+ return { kind: "unknown", message, retryable: err.retryable, cause };
4799
+ }
4800
+
4725
4801
  // src/coordination/fleet.ts
4726
4802
  var AUDIT_LOG_AGENT = {
4727
4803
  id: "audit-log",
@@ -5086,7 +5162,10 @@ var NICKNAME_POOL = {
5086
5162
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
5087
5163
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
5088
5164
  };
5089
- var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
5165
+ var ALL_NICKNAMES = Object.entries(NICKNAME_POOL);
5166
+ var NAME_TO_KEY = Object.fromEntries(
5167
+ ALL_NICKNAMES.map(([key, entry]) => [entry.name, key])
5168
+ );
5090
5169
  var DOMAIN_PREFERENCES = {
5091
5170
  "security": ["shannon", "turing", "lamarr", "stallman"],
5092
5171
  "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
@@ -5119,17 +5198,23 @@ function assignNickname(role, used) {
5119
5198
  for (const key of preferences) {
5120
5199
  const entry = NICKNAME_POOL[key];
5121
5200
  if (entry && !used.has(key)) {
5122
- return `${entry.name} (${formatRole(role)})`;
5201
+ return { key, display: `${entry.name} (${formatRole(role)})` };
5123
5202
  }
5124
5203
  }
5125
- for (const entry of ALL_NICKNAMES) {
5126
- const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
5127
- if (key && !used.has(key)) {
5128
- return `${entry.name} (${formatRole(role)})`;
5204
+ for (const [key, entry] of ALL_NICKNAMES) {
5205
+ if (!used.has(key)) {
5206
+ return { key, display: `${entry.name} (${formatRole(role)})` };
5129
5207
  }
5130
5208
  }
5131
5209
  const counter = used.size + 1;
5132
- return `Scientist #${counter} (${formatRole(role)})`;
5210
+ return { key: `scientist-${counter}`, display: `Scientist #${counter} (${formatRole(role)})` };
5211
+ }
5212
+ function nicknameKeyFromDisplay(display) {
5213
+ const base = display.replace(/\s*\([^)]*\)\s*$/, "").trim();
5214
+ const key = NAME_TO_KEY[base];
5215
+ if (key) return key;
5216
+ const synthesized = base.match(/^Scientist #(\d+)$/);
5217
+ return synthesized ? `scientist-${synthesized[1]}` : void 0;
5133
5218
  }
5134
5219
  function formatRole(role) {
5135
5220
  return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
@@ -5216,11 +5301,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5216
5301
  const name = subagent.name?.trim() ?? "";
5217
5302
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
5218
5303
  if (!isPlaceholder) return subagent;
5219
- const nickname = assignNickname(role, this.usedNicknames);
5220
- const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
5221
- this.usedNicknames.add(baseKey);
5222
- this.subagentNicknames.set(subagentId, baseKey);
5223
- return { ...subagent, name: nickname };
5304
+ const { key, display } = assignNickname(role, this.usedNicknames);
5305
+ this.usedNicknames.add(key);
5306
+ this.subagentNicknames.set(subagentId, key);
5307
+ return { ...subagent, name: display };
5224
5308
  }
5225
5309
  async spawn(subagent) {
5226
5310
  const id = subagent.id || randomUUID();
@@ -5472,23 +5556,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5472
5556
  */
5473
5557
  drainPendingAsAborted(message) {
5474
5558
  const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
5475
- for (const t of dropped) {
5476
- const synthetic = {
5477
- subagentId: t.subagentId ?? "unassigned",
5478
- taskId: t.id,
5479
- status: "stopped",
5480
- error: {
5481
- kind: "aborted_by_parent",
5482
- message,
5483
- retryable: false
5484
- },
5485
- iterations: 0,
5486
- toolCalls: 0,
5487
- durationMs: 0
5488
- };
5489
- this.completedResults.push(synthetic);
5490
- this.emit("task.completed", { task: t, result: synthetic });
5491
- }
5559
+ for (const t of dropped) this.emitPendingAborted(t, message);
5560
+ }
5561
+ /**
5562
+ * Emit a synthetic `stopped`/`aborted_by_parent` completion for a single
5563
+ * PENDING task — one that was never counted in `inFlight`. This MUST bypass
5564
+ * `recordCompletion`: that path does `inFlight--`, which for a pending task
5565
+ * steals a decrement from a genuinely in-flight task and trips the underflow
5566
+ * guard — suppressing that real task's `task.completed` and hanging its
5567
+ * `awaitTasks()` caller. Pushes the result and fires the event directly.
5568
+ */
5569
+ emitPendingAborted(task, message) {
5570
+ const synthetic = {
5571
+ subagentId: task.subagentId ?? "unassigned",
5572
+ taskId: task.id,
5573
+ status: "stopped",
5574
+ error: {
5575
+ kind: "aborted_by_parent",
5576
+ message,
5577
+ retryable: false
5578
+ },
5579
+ iterations: 0,
5580
+ toolCalls: 0,
5581
+ durationMs: 0
5582
+ };
5583
+ this.completedResults.push(synthetic);
5584
+ this.emit("task.completed", { task, result: synthetic });
5492
5585
  }
5493
5586
  async runDispatched(subagentId, task) {
5494
5587
  const subagent = this.subagents.get(subagentId);
@@ -5749,20 +5842,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5749
5842
  const orphaned = this.pendingTasks.filter((t) => t.subagentId === subagentId);
5750
5843
  this.pendingTasks = this.pendingTasks.filter((t) => t.subagentId !== subagentId);
5751
5844
  for (const t of orphaned) {
5752
- const synthetic = {
5753
- subagentId,
5754
- taskId: t.id,
5755
- status: "stopped",
5756
- error: {
5757
- kind: "aborted_by_parent",
5758
- message: `Subagent "${subagentId}" was removed while task "${t.id}" was pending`,
5759
- retryable: false
5760
- },
5761
- iterations: 0,
5762
- toolCalls: 0,
5763
- durationMs: 0
5764
- };
5765
- this.recordCompletion(synthetic);
5845
+ this.emitPendingAborted(
5846
+ t,
5847
+ `Subagent "${subagentId}" was removed while task "${t.id}" was pending`
5848
+ );
5766
5849
  }
5767
5850
  this.fleetBus?.emit({
5768
5851
  subagentId,
@@ -5782,103 +5865,8 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
5782
5865
  return false;
5783
5866
  }
5784
5867
  };
5785
- function classifySubagentError(err, hints = {}) {
5786
- const cause = err instanceof Error ? { name: err.name, message: err.message, stack: err.stack } : void 0;
5787
- if (err instanceof ProviderError) {
5788
- const baseMessage2 = err.describe();
5789
- return providerErrorToSubagentError(err, baseMessage2, cause);
5790
- }
5791
- const baseMessage = err instanceof Error ? err.message : String(err);
5792
- if (err instanceof BudgetExceededError) {
5793
- const map = {
5794
- iterations: "budget_iterations",
5795
- tool_calls: "budget_tool_calls",
5796
- tokens: "budget_tokens",
5797
- cost: "budget_cost",
5798
- timeout: "budget_timeout",
5799
- idle_timeout: "budget_timeout"
5800
- };
5801
- return {
5802
- kind: map[err.kind],
5803
- message: baseMessage,
5804
- // Budgets are user-configured ceilings, not transient failures —
5805
- // retrying with the same budget will hit the same ceiling. The
5806
- // orchestrator must raise the budget or narrow the task first.
5807
- retryable: false,
5808
- cause
5809
- };
5810
- }
5811
- if (hints.parentAborted) {
5812
- return {
5813
- kind: "aborted_by_parent",
5814
- message: baseMessage,
5815
- retryable: false,
5816
- cause
5817
- };
5818
- }
5819
- const lower = baseMessage.toLowerCase();
5820
- if (/agent aborted$/i.test(baseMessage)) {
5821
- return {
5822
- kind: "aborted_by_parent",
5823
- message: baseMessage,
5824
- retryable: false,
5825
- cause
5826
- };
5827
- }
5828
- if (/agent exhausted iteration limit$/i.test(baseMessage)) {
5829
- return { kind: "budget_iterations", message: baseMessage, retryable: false, cause };
5830
- }
5831
- if (/empty response$/i.test(baseMessage)) {
5832
- return { kind: "empty_response", message: baseMessage, retryable: false, cause };
5833
- }
5834
- if (/^tool failed: /i.test(baseMessage)) {
5835
- return { kind: "tool_failed", message: baseMessage, retryable: false, cause };
5836
- }
5837
- if (lower.includes("bridge transport") || /bridge.*(closed|disconnect)/i.test(baseMessage)) {
5838
- return { kind: "bridge_failed", message: baseMessage, retryable: false, cause };
5839
- }
5840
- if (/context length|max.*tokens?.*exceeded|prompt is too long/i.test(baseMessage)) {
5841
- return { kind: "context_overflow", message: baseMessage, retryable: false, cause };
5842
- }
5843
- return {
5844
- kind: "unknown",
5845
- message: baseMessage,
5846
- retryable: false,
5847
- cause
5848
- };
5849
- }
5850
- function providerErrorToSubagentError(err, message, cause) {
5851
- const status = err.status;
5852
- if (status === 429 || err.body?.type === "rate_limit_error") {
5853
- return {
5854
- kind: "provider_rate_limit",
5855
- message,
5856
- retryable: true,
5857
- // Conservative default: 5s. Provider-specific code can override
5858
- // by emitting an error whose body carries an explicit hint.
5859
- backoffMs: 5e3,
5860
- cause
5861
- };
5862
- }
5863
- if (status === 401 || status === 403 || err.body?.type === "authentication_error") {
5864
- return { kind: "provider_auth", message, retryable: false, cause };
5865
- }
5866
- if (status === 408 || status === 0) {
5867
- return { kind: "provider_timeout", message, retryable: true, cause };
5868
- }
5869
- if (status >= 500 && status < 600) {
5870
- return {
5871
- kind: "provider_5xx",
5872
- message,
5873
- retryable: true,
5874
- backoffMs: 3e3,
5875
- cause
5876
- };
5877
- }
5878
- return { kind: "unknown", message, retryable: err.retryable, cause };
5879
- }
5880
5868
 
5881
- // src/coordination/director.ts
5869
+ // src/coordination/director/director-errors.ts
5882
5870
  var FleetSpawnBudgetError = class extends Error {
5883
5871
  kind;
5884
5872
  limit;
@@ -5920,6 +5908,8 @@ var FleetContextOverflowError = class extends Error {
5920
5908
  this.observed = observed;
5921
5909
  }
5922
5910
  };
5911
+
5912
+ // src/coordination/director.ts
5923
5913
  var Director = class _Director {
5924
5914
  /** Alias for the ICoordinator contract. `id` is retained for backward compatibility. */
5925
5915
  get coordinatorId() {
@@ -6188,18 +6178,20 @@ var Director = class _Director {
6188
6178
  if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
6189
6179
  return;
6190
6180
  }
6191
- if (payload.kind === "timeout") {
6181
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
6182
+ const heartbeatKey = `${e.subagentId}:${payload.kind}`;
6192
6183
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
6193
- const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
6184
+ const lastProgress = lastTimeoutProgress.get(heartbeatKey) ?? -1;
6194
6185
  if (progress <= lastProgress) {
6195
6186
  payload.deny();
6196
6187
  return;
6197
6188
  }
6198
- lastTimeoutProgress.set(e.subagentId, progress);
6189
+ lastTimeoutProgress.set(heartbeatKey, progress);
6190
+ const field = payload.kind === "timeout" ? "timeoutMs" : "idleTimeoutMs";
6199
6191
  setImmediate(() => {
6200
6192
  const newLimit = Math.min(Math.ceil(payload.limit * 2), 24 * 60 * 6e4);
6201
- this.recordExtension(e.subagentId, e.taskId, "timeout", newLimit);
6202
- payload.extend({ timeoutMs: newLimit });
6193
+ this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
6194
+ payload.extend({ [field]: newLimit });
6203
6195
  });
6204
6196
  return;
6205
6197
  }
@@ -6515,10 +6507,9 @@ var Director = class _Director {
6515
6507
  if (this.fleetManager) {
6516
6508
  this.fleetManager.assignNicknameAndRecord(config);
6517
6509
  } else {
6518
- config.name = assignNickname(role, this._usedNicknames);
6519
- this._usedNicknames.add(
6520
- config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
6521
- );
6510
+ const { key, display } = assignNickname(role, this._usedNicknames);
6511
+ config.name = display;
6512
+ this._usedNicknames.add(key);
6522
6513
  }
6523
6514
  }
6524
6515
  result = await this.coordinator.spawn(config);
@@ -6840,8 +6831,8 @@ var Director = class _Director {
6840
6831
  } else {
6841
6832
  const entry = this.manifestEntries.get(subagentId);
6842
6833
  if (entry?.name) {
6843
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
6844
- this._usedNicknames.delete(nicknameKey);
6834
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
6835
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
6845
6836
  }
6846
6837
  }
6847
6838
  this.manifestEntries.delete(subagentId);
@@ -8453,11 +8444,10 @@ var FleetManager = class {
8453
8444
  */
8454
8445
  assignNicknameAndRecord(config) {
8455
8446
  const role = config.role ?? "subagent";
8456
- const nickname = assignNickname(role, this._usedNicknames);
8457
- const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
8458
- this._usedNicknames.add(baseKey);
8459
- config.name = nickname;
8460
- return nickname;
8447
+ const { key, display } = assignNickname(role, this._usedNicknames);
8448
+ this._usedNicknames.add(key);
8449
+ config.name = display;
8450
+ return display;
8461
8451
  }
8462
8452
  /**
8463
8453
  * Returns the set of already-assigned nickname keys — useful for debugging
@@ -8642,8 +8632,8 @@ var FleetManager = class {
8642
8632
  removeSubagent(subagentId) {
8643
8633
  const entry = this.manifestEntries.get(subagentId);
8644
8634
  if (entry?.name) {
8645
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
8646
- this._usedNicknames.delete(nicknameKey);
8635
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
8636
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
8647
8637
  }
8648
8638
  for (const [taskId, task] of this.pendingTasks) {
8649
8639
  if (task.subagentId === subagentId) {