@wrongstack/core 0.298.3 → 0.300.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 (70) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/director.d.ts +8 -0
  4. package/dist/coordination/fleet-manager.d.ts +48 -3
  5. package/dist/coordination/ifleet-manager.d.ts +2 -0
  6. package/dist/coordination/index.js +127 -24
  7. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  8. package/dist/core/fallback-model.d.ts +48 -0
  9. package/dist/core/index.d.ts +3 -2
  10. package/dist/core/index.js +288 -34
  11. package/dist/core/instruction-template.d.ts +80 -0
  12. package/dist/core/system-prompt-blocks.d.ts +10 -1
  13. package/dist/core/system-prompt-builder.d.ts +35 -1
  14. package/dist/defaults/index.js +358 -117
  15. package/dist/design/index.js +4 -1
  16. package/dist/execution/autonomy-brain.d.ts +7 -0
  17. package/dist/execution/council-brain.d.ts +17 -2
  18. package/dist/execution/council-orchestrator.d.ts +23 -4
  19. package/dist/execution/council-personas.d.ts +10 -0
  20. package/dist/execution/council-prompts.d.ts +12 -1
  21. package/dist/execution/index.d.ts +1 -1
  22. package/dist/execution/index.js +412 -145
  23. package/dist/fleet-notifier.d.ts +9 -2
  24. package/dist/goal/index.js +4 -1
  25. package/dist/hooks/index.js +140 -10
  26. package/dist/hq/exposure.d.ts +0 -11
  27. package/dist/hq/index.js +34 -8
  28. package/dist/hq/protocol/client.d.ts +14 -1
  29. package/dist/hq/protocol/fleet.d.ts +22 -0
  30. package/dist/hq/protocol.js +12 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +1718 -753
  33. package/dist/infrastructure/index.js +50 -2
  34. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  35. package/dist/kernel/events/brain-events.d.ts +9 -0
  36. package/dist/kernel/events/provider-events.d.ts +49 -2
  37. package/dist/kernel/events/sdd-events.d.ts +2 -0
  38. package/dist/models/index.js +1 -1
  39. package/dist/plugin/api.d.ts +6 -0
  40. package/dist/plugin/config.d.ts +55 -0
  41. package/dist/plugin/index.d.ts +1 -1
  42. package/dist/plugin/index.js +138 -22
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +157 -42
  45. package/dist/security/permission-helpers.d.ts +23 -6
  46. package/dist/security/permission-policy.d.ts +16 -0
  47. package/dist/security/totp.d.ts +14 -0
  48. package/dist/storage/director-state.d.ts +7 -0
  49. package/dist/storage/index.js +46 -9
  50. package/dist/tools/council-tool.d.ts +1 -1
  51. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  52. package/dist/tools/index.js +449 -112
  53. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  54. package/dist/types/config/tools.d.ts +99 -0
  55. package/dist/types/council.d.ts +11 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/index.js +3 -3
  58. package/dist/types/multi-agent.d.ts +10 -0
  59. package/dist/types/one-shot-llm.d.ts +31 -3
  60. package/dist/types/plugin.d.ts +28 -0
  61. package/dist/types/session.d.ts +5 -1
  62. package/dist/utils/index.js +4 -1
  63. package/dist/utils/wstack-paths.d.ts +2 -0
  64. package/dist/worktree/index.js +47 -25
  65. package/dist/worktree/worktree-manager.d.ts +16 -10
  66. package/instructions/coordination/subagent-baseline.md +8 -0
  67. package/instructions/system-lite.md +83 -3
  68. package/instructions/system-pro.md +286 -97
  69. package/instructions/system.md +236 -85
  70. package/package.json +3 -3
@@ -5,8 +5,13 @@ export interface FleetNotifierOptions {
5
5
  projectRoot: string;
6
6
  /** This process's pid, so a WebUI never pings itself. */
7
7
  selfPid?: number | undefined;
8
- /** Injectable POST for tests. Defaults to a timed `fetch`. */
9
- post?: ((url: string) => Promise<void>) | undefined;
8
+ /**
9
+ * Injectable POST for tests. Defaults to a timed `fetch`.
10
+ *
11
+ * The second parameter carries the target's API token (H3). It is optional
12
+ * so an existing `(url) => …` stub stays assignable.
13
+ */
14
+ post?: ((url: string, token?: string | undefined) => Promise<void>) | undefined;
10
15
  }
11
16
  export declare class FleetNotifier {
12
17
  private readonly baseDir;
@@ -21,6 +26,8 @@ export declare class FleetNotifier {
21
26
  notify(): void;
22
27
  /** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */
23
28
  endpoints(): Promise<string[]>;
29
+ /** Ping targets with their tokens (cached briefly). */
30
+ private targets;
24
31
  dispose(): void;
25
32
  /** Re-scope notifications after an in-process project switch. */
26
33
  setProjectRoot(projectRoot: string): void;
@@ -2790,7 +2790,9 @@ function safeProfileName(name) {
2790
2790
  function activeProfileName(globalRoot) {
2791
2791
  try {
2792
2792
  const parsed = JSON.parse(fs.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
2793
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2793
+ return safeProfileName(
2794
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2795
+ );
2794
2796
  } catch {
2795
2797
  return "default";
2796
2798
  }
@@ -2870,6 +2872,7 @@ function resolveWstackPaths(opts) {
2870
2872
  projectPlan: path4.join(projectDir, "plan.json"),
2871
2873
  projectAutophase: path4.join(projectDir, "autophase"),
2872
2874
  projectSddBoards: path4.join(projectDir, "sdd-boards"),
2875
+ projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
2873
2876
  syncConfig: path4.join(profileDir, "sync.json"),
2874
2877
  configHistoryDir: path4.join(globalRoot, "config-history"),
2875
2878
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
@@ -1,3 +1,121 @@
1
+ // src/utils/ip-guard.ts
2
+ import * as dns from "node:dns/promises";
3
+ import * as net from "node:net";
4
+ function isPrivateIPv4(addr) {
5
+ const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
6
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
7
+ return true;
8
+ }
9
+ const [a, b, c] = parts;
10
+ if (a === 0) return true;
11
+ if (a === 10) return true;
12
+ if (a === 127) return true;
13
+ if (a === 169 && b === 254) return true;
14
+ if (a === 172 && b >= 16 && b <= 31) return true;
15
+ if (a === 192 && b === 168) return true;
16
+ if (a === 192 && b === 0 && c === 0) return true;
17
+ if (a === 100 && b >= 64 && b <= 127) return true;
18
+ if (a >= 224) return true;
19
+ return false;
20
+ }
21
+ function isPrivateIPv6(raw) {
22
+ const lower = raw.toLowerCase();
23
+ if (lower === "::" || lower === "::1") return true;
24
+ const groups = expandIPv6(lower);
25
+ if (!groups) return true;
26
+ if (groups[0] === 100 && groups[1] === 65435 && groups[2] === 1) return true;
27
+ if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
28
+ const a = (groups[6] ?? 0) >> 8;
29
+ const b = (groups[6] ?? 0) & 255;
30
+ const c = (groups[7] ?? 0) >> 8;
31
+ const d = (groups[7] ?? 0) & 255;
32
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
33
+ }
34
+ const embedded = embeddedIPv4(groups);
35
+ if (embedded) return isPrivateIPv4(embedded);
36
+ const high = groups[0] ?? 0;
37
+ if ((high & 65024) === 64512) return true;
38
+ if ((high & 65472) === 65152) return true;
39
+ if ((high & 65280) === 65280) return true;
40
+ return false;
41
+ }
42
+ function quad(hi, lo) {
43
+ return `${(hi ?? 0) >> 8}.${(hi ?? 0) & 255}.${(lo ?? 0) >> 8}.${(lo ?? 0) & 255}`;
44
+ }
45
+ function embeddedIPv4(groups) {
46
+ const g = (i) => groups[i] ?? 0;
47
+ if (g(0) === 100 && g(1) === 65435) {
48
+ if (g(2) === 0 && g(3) === 0 && g(4) === 0 && g(5) === 0) {
49
+ return quad(g(6), g(7));
50
+ }
51
+ }
52
+ if (g(0) === 8194) return quad(g(1), g(2));
53
+ if (g(0) === 8193 && g(1) === 0) {
54
+ return quad(~g(6) & 65535, ~g(7) & 65535);
55
+ }
56
+ if ((g(4) === 0 || g(4) === 512) && g(5) === 24318) {
57
+ return quad(g(6), g(7));
58
+ }
59
+ const highZero = g(0) === 0 && g(1) === 0 && g(2) === 0 && g(3) === 0;
60
+ if (highZero && g(4) === 65535 && g(5) === 0) return quad(g(6), g(7));
61
+ if (highZero && g(4) === 0 && g(5) === 0) return quad(g(6), g(7));
62
+ return void 0;
63
+ }
64
+ function expandIPv6(addr) {
65
+ const parts = addr.split("::");
66
+ if (parts.length > 2) return null;
67
+ const parseGroups = (s) => {
68
+ if (s === "") return [];
69
+ const out = [];
70
+ for (const g of s.split(":")) {
71
+ if (g.length === 0 || g.length > 4) return null;
72
+ const n = Number.parseInt(g, 16);
73
+ if (Number.isNaN(n) || n < 0 || n > 65535) return null;
74
+ out.push(n);
75
+ }
76
+ return out;
77
+ };
78
+ if (parts.length === 1) {
79
+ const groups = parseGroups(parts[0] ?? "");
80
+ if (groups?.length !== 8) return null;
81
+ return groups;
82
+ }
83
+ const head = parseGroups(parts[0] ?? "");
84
+ const tail = parseGroups(parts[1] ?? "");
85
+ if (!head || !tail) return null;
86
+ const fill = 8 - head.length - tail.length;
87
+ if (fill < 0) return null;
88
+ return [...head, ...new Array(fill).fill(0), ...tail];
89
+ }
90
+ async function assertNotPrivateHost(hostname) {
91
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
92
+ if (host === "localhost" || host.endsWith(".localhost")) {
93
+ throw new Error("fetch: blocked localhost target");
94
+ }
95
+ const ipVersion = net.isIP(host);
96
+ if (ipVersion === 4) {
97
+ if (isPrivateIPv4(host)) {
98
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
99
+ }
100
+ } else if (ipVersion === 6) {
101
+ if (isPrivateIPv6(host)) {
102
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
103
+ }
104
+ } else {
105
+ try {
106
+ const records = await dns.lookup(host, { all: true });
107
+ for (const r of records) {
108
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
109
+ if (bad) {
110
+ throw new Error(`fetch: resolved to private address ${r.address}`);
111
+ }
112
+ }
113
+ } catch (err) {
114
+ if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
115
+ }
116
+ }
117
+ }
118
+
1
119
  // src/hooks/shell-executor.ts
2
120
  import { spawn } from "node:child_process";
3
121
 
@@ -424,18 +542,26 @@ function parseHookOutcome(stdout) {
424
542
  // src/hooks/http-executor.ts
425
543
  var DEFAULT_TIMEOUT_MS2 = 5e3;
426
544
  var MAX_OUTPUT_BYTES2 = 64 * 1024;
427
- function isAllowedUrl(raw) {
545
+ async function isAllowedUrl(raw) {
546
+ let url;
428
547
  try {
429
- const url = new URL(raw);
430
- if (url.protocol === "https:") return true;
431
- if (url.protocol !== "http:") return false;
432
- return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
548
+ url = new URL(raw);
433
549
  } catch {
434
550
  return false;
435
551
  }
552
+ if (url.protocol === "https:") {
553
+ try {
554
+ await assertNotPrivateHost(url.hostname);
555
+ return true;
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
560
+ if (url.protocol !== "http:") return false;
561
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
436
562
  }
437
563
  async function runHttpHookDetailed(spec, input, logger, options = {}) {
438
- if (!isAllowedUrl(spec.url)) {
564
+ if (!await isAllowedUrl(spec.url)) {
439
565
  return {
440
566
  outcome: null,
441
567
  failure: {
@@ -650,7 +776,11 @@ function hookMatcherMatches(matcher, toolName) {
650
776
 
651
777
  // src/hooks/runner.ts
652
778
  var DEFAULT_TIMEOUT_MS3 = 5e3;
779
+ var DEFAULT_BACKGROUND_TIMEOUT_MS = 6e4;
653
780
  var MAX_TIMEOUT_MS = 10 * 6e4;
781
+ function hookTimeoutMs(entry) {
782
+ return entry.timeoutMs ?? (entry.background ? DEFAULT_BACKGROUND_TIMEOUT_MS : DEFAULT_TIMEOUT_MS3);
783
+ }
654
784
  function normalizePreToolOutcome(outcome) {
655
785
  if ("action" in outcome) return outcome;
656
786
  if (outcome.decision === "block") {
@@ -835,12 +965,12 @@ var HookRunner = class {
835
965
  result = await this.invokeInProcess(entry, payload, env);
836
966
  } else {
837
967
  result = entry.kind === "shell" ? await runShellHookDetailed(
838
- { command: entry.command, timeoutMs: entry.timeoutMs },
968
+ { command: entry.command, timeoutMs: hookTimeoutMs(entry) },
839
969
  payload,
840
970
  this.opts.logger,
841
971
  { signal: env.signal }
842
972
  ) : await runHttpHookDetailed(
843
- { url: entry.url, headers: entry.headers, timeoutMs: entry.timeoutMs },
973
+ { url: entry.url, headers: entry.headers, timeoutMs: hookTimeoutMs(entry) },
844
974
  payload,
845
975
  this.opts.logger,
846
976
  { signal: env.signal }
@@ -857,12 +987,12 @@ var HookRunner = class {
857
987
  }
858
988
  if (!result.failure) return result.outcome;
859
989
  this.opts.logger?.warn?.(
860
- `${payload.event} hook "${entry.name}" failed (${result.failure.kind}): ${result.failure.message}`
990
+ `${payload.event} ${entry.background ? "background " : ""}hook "${entry.name}" failed (${result.failure.kind}): ${result.failure.message}`
861
991
  );
862
992
  return this.failureOutcome(entry, payload, result.failure);
863
993
  }
864
994
  async invokeInProcess(entry, payload, env) {
865
- const timeoutMs = Math.max(1, Math.min(entry.timeoutMs ?? DEFAULT_TIMEOUT_MS3, MAX_TIMEOUT_MS));
995
+ const timeoutMs = Math.max(1, Math.min(hookTimeoutMs(entry), MAX_TIMEOUT_MS));
866
996
  const controller = new AbortController();
867
997
  let timedOut = false;
868
998
  const onParentAbort = () => controller.abort(env.signal?.reason);
@@ -16,17 +16,6 @@
16
16
  * ones. Nothing here changes the default bind — it guards the case where the
17
17
  * default silently turns a local-only setup into a public one.
18
18
  */
19
- /**
20
- * The bind the HQ *CLI* surfaces choose on purpose: HQ is the deliberate
21
- * cross-machine surface, so `wstack hq` and the launch menu reach every
22
- * interface without an extra flag.
23
- *
24
- * This deliberately does not live in `startHqServer`'s default. A caller
25
- * that never mentions a host — a test, an embedder — has not asked to be
26
- * published to its network, and that library default stays loopback. Keep
27
- * the two apart: collapsing them is what turns "HQ is reachable from my
28
- * phone" into "every embedder silently binds 0.0.0.0".
29
- */
30
19
  export declare const HQ_CLI_DEFAULT_HOST = "0.0.0.0";
31
20
  /**
32
21
  * Thrown when the bind host and the auth configuration combine into an
package/dist/hq/index.js CHANGED
@@ -167,7 +167,8 @@ var KNOWN_HQ_CLIENT_FRAME_TYPES = /* @__PURE__ */ new Set([
167
167
  "client.event",
168
168
  "client.command_poll",
169
169
  "client.command_ack",
170
- "client.resume"
170
+ "client.resume",
171
+ "client.event_poll"
171
172
  ]);
172
173
  var HQ_CLIENT_KINDS = /* @__PURE__ */ new Set([
173
174
  "tui",
@@ -491,6 +492,9 @@ function isHqSubagentSummary(x) {
491
492
  const v = x;
492
493
  return typeof v.subagentId === "string" && typeof v.status === "string" && HQ_FLEET_SUBAGENT_STATUS.has(v.status);
493
494
  }
495
+ function isOptionalFiniteNumber(x) {
496
+ return x === void 0 || typeof x === "number" && Number.isFinite(x);
497
+ }
494
498
  function isHqFleetSnapshotPayload(x) {
495
499
  if (typeof x !== "object" || x === null) return false;
496
500
  const v = x;
@@ -500,6 +504,13 @@ function isHqFleetSnapshotPayload(x) {
500
504
  for (const s of v.subagents) {
501
505
  if (!isHqSubagentSummary(s)) return false;
502
506
  }
507
+ if (!isOptionalFiniteNumber(v.maxConcurrent)) return false;
508
+ if (!isOptionalFiniteNumber(v.maxSpawns)) return false;
509
+ if (!isOptionalFiniteNumber(v.usedSpawns)) return false;
510
+ if (!isOptionalFiniteNumber(v.remainingSpawns)) return false;
511
+ if (!isOptionalFiniteNumber(v.checkpointMaxSpawns)) return false;
512
+ if (v.effectiveSource !== void 0 && typeof v.effectiveSource !== "string") return false;
513
+ if (v.ceilingMismatch !== void 0 && typeof v.ceilingMismatch !== "boolean") return false;
503
514
  return true;
504
515
  }
505
516
  function isHqFleetEventPayload(x) {
@@ -2102,7 +2113,9 @@ function safeProfileName(name) {
2102
2113
  function activeProfileName(globalRoot) {
2103
2114
  try {
2104
2115
  const parsed = JSON.parse(fs2.readFileSync(path2.join(globalRoot, "config.json"), "utf8"));
2105
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2116
+ return safeProfileName(
2117
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2118
+ );
2106
2119
  } catch {
2107
2120
  return "default";
2108
2121
  }
@@ -2182,6 +2195,7 @@ function resolveWstackPaths(opts) {
2182
2195
  projectPlan: path2.join(projectDir, "plan.json"),
2183
2196
  projectAutophase: path2.join(projectDir, "autophase"),
2184
2197
  projectSddBoards: path2.join(projectDir, "sdd-boards"),
2198
+ projectRequirementIntakes: path2.join(projectDir, "requirement-intakes"),
2185
2199
  syncConfig: path2.join(profileDir, "sync.json"),
2186
2200
  configHistoryDir: path2.join(globalRoot, "config-history"),
2187
2201
  projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
@@ -2570,9 +2584,6 @@ function watchHqAuthFile(dataDir, onChange, opts = {}) {
2570
2584
  }
2571
2585
  }
2572
2586
  });
2573
- watcher.on("error", (err) => {
2574
- opts.warn?.(`HQ auth watcher error: ${err.message}`);
2575
- });
2576
2587
  return {
2577
2588
  close: () => {
2578
2589
  closed = true;
@@ -3309,7 +3320,14 @@ function startFleetTelemetryBridge(opts) {
3309
3320
  completedTasks: stats.completed,
3310
3321
  failedTasks: stats.stopped,
3311
3322
  ...stats.totalCostUsd !== void 0 ? { totalCostUsd: stats.totalCostUsd } : {},
3312
- subagents
3323
+ subagents,
3324
+ ...stats.maxConcurrent !== void 0 ? { maxConcurrent: stats.maxConcurrent } : {},
3325
+ ...stats.maxSpawns !== void 0 ? { maxSpawns: stats.maxSpawns } : {},
3326
+ ...stats.usedSpawns !== void 0 ? { usedSpawns: stats.usedSpawns } : {},
3327
+ ...stats.remainingSpawns !== void 0 ? { remainingSpawns: stats.remainingSpawns } : {},
3328
+ ...stats.effectiveSource !== void 0 ? { effectiveSource: stats.effectiveSource } : {},
3329
+ ...stats.checkpointMaxSpawns !== void 0 ? { checkpointMaxSpawns: stats.checkpointMaxSpawns } : {},
3330
+ ...stats.ceilingMismatch ? { ceilingMismatch: true } : {}
3313
3331
  };
3314
3332
  }
3315
3333
  const off = events.on("coordinator.stats", (stats) => {
@@ -4970,6 +4988,7 @@ var HqBootstrapCodeStore = class {
4970
4988
  };
4971
4989
 
4972
4990
  // src/hq/exposure.ts
4991
+ import { isIP } from "node:net";
4973
4992
  var HQ_CLI_DEFAULT_HOST = "0.0.0.0";
4974
4993
  var HqInsecureExposureError = class extends Error {
4975
4994
  constructor(message) {
@@ -4979,8 +4998,15 @@ var HqInsecureExposureError = class extends Error {
4979
4998
  };
4980
4999
  function isLoopbackHost(host) {
4981
5000
  const normalized = host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
4982
- if (normalized === "localhost" || normalized === "::1") return true;
4983
- return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized);
5001
+ if (normalized === "localhost") return true;
5002
+ const family = isIP(normalized);
5003
+ if (family === 4) {
5004
+ return normalized.startsWith("127.");
5005
+ }
5006
+ if (family === 6) {
5007
+ return normalized === "::1" || /^::ffff:127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized);
5008
+ }
5009
+ return false;
4984
5010
  }
4985
5011
  function isOpenMode(input) {
4986
5012
  return !input.hasBrowserTokens && !input.hasPassword;
@@ -82,5 +82,18 @@ export interface HqClientResumeMessage {
82
82
  clientId?: string;
83
83
  projectId?: string;
84
84
  }
85
- export type HqClientMessage = HqClientHelloMessage | HqClientEventMessage | HqClientCommandPollMessage | HqClientCommandAckMessage | HqClientResumeMessage;
85
+ export type HqClientMessage = HqClientHelloMessage | HqClientEventMessage | HqClientCommandPollMessage | HqClientCommandAckMessage | HqClientResumeMessage | HqClientEventPollMessage;
86
+ /**
87
+ * Proactive gap-fill: a client can request missed envelopes by `clientId`
88
+ * and `afterSeq` without waiting for the push path. The server replies with
89
+ * `hq.resume_gap` (bounded list) or `hq.snapshot` when the gap is too large.
90
+ * See `docs/plans/hq-evolution-2026-08.md` §2.2.
91
+ */
92
+ export interface HqClientEventPollMessage {
93
+ type: 'client.event_poll';
94
+ clientId: string;
95
+ projectId: string;
96
+ afterSeq: number;
97
+ limit?: number;
98
+ }
86
99
  //# sourceMappingURL=client.d.ts.map
@@ -7,6 +7,19 @@ export interface HqFleetSnapshotPayload {
7
7
  failedTasks: number;
8
8
  totalCostUsd?: number;
9
9
  subagents: readonly HqSubagentSummary[];
10
+ /** Concurrent-subagent ceiling (maxConcurrent). */
11
+ maxConcurrent?: number | undefined;
12
+ /** Lifetime spawn ceiling (maxSpawns). */
13
+ maxSpawns?: number | undefined;
14
+ /** Cumulative spawns consumed this director run. */
15
+ usedSpawns?: number | undefined;
16
+ /** maxSpawns − usedSpawns (clamped ≥ 0). */
17
+ remainingSpawns?: number | undefined;
18
+ /** Winning config source labels, e.g. "maxConcurrent=profile, maxSpawns=env". */
19
+ effectiveSource?: string | undefined;
20
+ /** Historical checkpoint maxSpawns when it disagreed with the live ceiling. */
21
+ checkpointMaxSpawns?: number | undefined;
22
+ ceilingMismatch?: boolean | undefined;
10
23
  }
11
24
  export interface HqFleetEventPayload {
12
25
  runId: string;
@@ -62,9 +75,18 @@ export interface HqFleetSummary {
62
75
  failedTasks: number;
63
76
  totalCostUsd?: number;
64
77
  lastActivityAt: string;
78
+ maxConcurrent?: number | undefined;
79
+ maxSpawns?: number | undefined;
80
+ usedSpawns?: number | undefined;
81
+ remainingSpawns?: number | undefined;
82
+ effectiveSource?: string | undefined;
83
+ checkpointMaxSpawns?: number | undefined;
84
+ ceilingMismatch?: boolean | undefined;
65
85
  }
66
86
  export interface HqQueuedCommand {
67
87
  commandId: string;
88
+ /** Idempotency key deduplicates commands across leader handoffs (§2.2). */
89
+ idempotencyKey?: string;
68
90
  type: string;
69
91
  createdAt: string;
70
92
  payload: unknown;
@@ -167,7 +167,8 @@ var KNOWN_HQ_CLIENT_FRAME_TYPES = /* @__PURE__ */ new Set([
167
167
  "client.event",
168
168
  "client.command_poll",
169
169
  "client.command_ack",
170
- "client.resume"
170
+ "client.resume",
171
+ "client.event_poll"
171
172
  ]);
172
173
  var HQ_CLIENT_KINDS = /* @__PURE__ */ new Set([
173
174
  "tui",
@@ -491,6 +492,9 @@ function isHqSubagentSummary(x) {
491
492
  const v = x;
492
493
  return typeof v.subagentId === "string" && typeof v.status === "string" && HQ_FLEET_SUBAGENT_STATUS.has(v.status);
493
494
  }
495
+ function isOptionalFiniteNumber(x) {
496
+ return x === void 0 || typeof x === "number" && Number.isFinite(x);
497
+ }
494
498
  function isHqFleetSnapshotPayload(x) {
495
499
  if (typeof x !== "object" || x === null) return false;
496
500
  const v = x;
@@ -500,6 +504,13 @@ function isHqFleetSnapshotPayload(x) {
500
504
  for (const s of v.subagents) {
501
505
  if (!isHqSubagentSummary(s)) return false;
502
506
  }
507
+ if (!isOptionalFiniteNumber(v.maxConcurrent)) return false;
508
+ if (!isOptionalFiniteNumber(v.maxSpawns)) return false;
509
+ if (!isOptionalFiniteNumber(v.usedSpawns)) return false;
510
+ if (!isOptionalFiniteNumber(v.remainingSpawns)) return false;
511
+ if (!isOptionalFiniteNumber(v.checkpointMaxSpawns)) return false;
512
+ if (v.effectiveSource !== void 0 && typeof v.effectiveSource !== "string") return false;
513
+ if (v.ceilingMismatch !== void 0 && typeof v.ceilingMismatch !== "boolean") return false;
503
514
  return true;
504
515
  }
505
516
  function isHqFleetEventPayload(x) {
package/dist/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export type { FallbackChain, FallbackChainEntry, ProviderHealth, } from './core/
54
54
  export { FallbackProfileManager } from './core/fallback-profile-manager.js';
55
55
  export { InputBuilder, type InputBuilderEvent, type InputBuilderOptions, } from './core/input-builder.js';
56
56
  export { type InstructionBundle, type InstructionBundlePaths, loadInstructionBundle, mergeInstructionBundle, type SystemInstructionBundle, type SystemInstructionVariant, } from './core/instruction-bundle.js';
57
+ export { type InstructionTemplateContext, renderInstructionLayer, } from './core/instruction-template.js';
57
58
  export { buildMailboxBlock, buildMailboxBtwAwarenessBlock, createMailboxChecker, injectPendingMailboxMessages, type MailboxDeliveryMode, type MailboxLoopOptions, } from './core/mailbox-loop.js';
58
59
  export { evaluateModelCalendar, logicalCalendarTarget, type ModelBlackoutRule, type ModelCalendarDecision, } from './core/model-availability-calendar.js';
59
60
  export { runProviderWithRetry } from './core/provider-runner.js';
@@ -69,7 +70,7 @@ export { type BrainApplyResult, type BrainConfigPatch, type BrainConfigSnapshot,
69
70
  export { buildLosslessDigest, buildSmartDigest, type ContentScore, type EliseResult, eliseOldToolResults, estimateMessages, extractText, findPreserveStart, hasTextContent, scoreMessage, } from './execution/compaction-core.js';
70
71
  export { COUNCIL_REFUSE_OPTION_ID, type CouncilBrainOptions, type CouncilVoter, createCouncilBrainArbiter, } from './execution/council-brain.js';
71
72
  export { COUNCIL_REFUSAL_OPTION_ID, CouncilOrchestrator, type CouncilOrchestratorOptions, DEFAULT_COUNCIL_MAX_CONCURRENCY, MAX_COUNCIL_CONCURRENCY, } from './execution/council-orchestrator.js';
72
- export { BUILTIN_COUNCIL_PERSONAS, CouncilPersonaRegistry, createCouncilPersonaRegistry, DEFAULT_COUNCIL_PERSONA_REGISTRY, } from './execution/council-personas.js';
73
+ export { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS, CouncilPersonaRegistry, createCouncilPersonaRegistry, DEFAULT_COUNCIL_PERSONA_REGISTRY, } from './execution/council-personas.js';
73
74
  export { BUILTIN_COUNCIL_PROFILES, CouncilProfileRegistry, createCouncilProfileRegistry, DEFAULT_COUNCIL_APPROVAL_FRACTION, DEFAULT_COUNCIL_JUDGE_MAX_TOKENS, DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS, DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS, DEFAULT_COUNCIL_PROFILE_REGISTRY, DEFAULT_COUNCIL_QUORUM_FRACTION, DEFAULT_COUNCIL_VOTER_MAX_TOKENS, normalizeCouncilProfile, resolveCouncilProfile, } from './execution/council-profiles.js';
74
75
  export { buildCouncilJudgeSystemPrompt, buildCouncilJudgeUserPrompt, buildCouncilQuestionPrompt, buildCouncilVoterSystemPrompt, buildCouncilVoterUserPrompt, COUNCIL_JUDGE_PROMPT_PATH, COUNCIL_VOTER_PROMPT_PATH, } from './execution/council-prompts.js';
75
76
  export { type CouncilResolution, type CouncilResolutionInput, type CouncilResolutionSeat, type CouncilResolutionVote, resolveCouncilVotes, } from './execution/council-resolution.js';