@wrongstack/core 0.298.2 → 0.299.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 (42) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/index.js +7 -4
  4. package/dist/core/index.js +62 -8
  5. package/dist/defaults/index.js +120 -18
  6. package/dist/design/index.js +4 -1
  7. package/dist/execution/council-brain.d.ts +6 -2
  8. package/dist/execution/council-personas.d.ts +10 -0
  9. package/dist/execution/index.d.ts +1 -1
  10. package/dist/execution/index.js +57 -7
  11. package/dist/goal/index.js +4 -1
  12. package/dist/hooks/index.js +132 -6
  13. package/dist/hq/exposure.d.ts +0 -11
  14. package/dist/hq/index.js +16 -4
  15. package/dist/hq/protocol/client.d.ts +14 -1
  16. package/dist/hq/protocol/fleet.d.ts +2 -0
  17. package/dist/hq/protocol.js +2 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +207 -47
  20. package/dist/infrastructure/index.js +50 -2
  21. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  22. package/dist/kernel/events/provider-events.d.ts +7 -1
  23. package/dist/kernel/events/sdd-events.d.ts +2 -0
  24. package/dist/plugin/index.js +4 -1
  25. package/dist/storage/index.js +13 -1
  26. package/dist/tools/council-tool.d.ts +1 -1
  27. package/dist/tools/index.js +61 -10
  28. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  29. package/dist/types/config/tools.d.ts +99 -0
  30. package/dist/types/index.d.ts +2 -1
  31. package/dist/types/index.js +3 -3
  32. package/dist/types/one-shot-llm.d.ts +22 -3
  33. package/dist/types/session.d.ts +5 -1
  34. package/dist/utils/index.js +4 -1
  35. package/dist/utils/wstack-paths.d.ts +2 -0
  36. package/dist/worktree/index.js +43 -21
  37. package/dist/worktree/worktree-manager.d.ts +16 -10
  38. package/instructions/coordination/subagent-baseline.md +8 -0
  39. package/instructions/system-lite.md +2 -0
  40. package/instructions/system-pro.md +14 -10
  41. package/instructions/system.md +11 -7
  42. package/package.json +3 -3
@@ -4193,7 +4193,7 @@ function truncate(s, max) {
4193
4193
  var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)(?:\s+(?:has|have))?(?:\s+been)?[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
4194
4194
 
4195
4195
  // src/types/provider.ts
4196
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
4196
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
4197
4197
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
4198
4198
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
4199
4199
  function classifyProviderError(status, body, message) {
@@ -4203,7 +4203,7 @@ function classifyProviderError(status, body, message) {
4203
4203
  if (status === 408) return "timeout";
4204
4204
  if (status === 599) return "stream_hang";
4205
4205
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
4206
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
4206
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
4207
4207
  return "quota_exhausted";
4208
4208
  }
4209
4209
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -4280,7 +4280,7 @@ var ProviderError = class extends WrongStackError {
4280
4280
  const e = err;
4281
4281
  const name = e.name;
4282
4282
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
4283
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
4283
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
4284
4284
  }
4285
4285
  constructor(message, status, retryable, providerId, opts = {}) {
4286
4286
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -4785,6 +4785,9 @@ var BUILTIN_COUNCIL_PERSONAS = Object.freeze([
4785
4785
  tags: ["users", "usability", "accessibility"]
4786
4786
  })
4787
4787
  ]);
4788
+ var BUILTIN_COUNCIL_PERSONA_IDS = Object.freeze(
4789
+ BUILTIN_COUNCIL_PERSONAS.map((persona) => persona.id)
4790
+ );
4788
4791
  var CouncilPersonaRegistry = class _CouncilPersonaRegistry {
4789
4792
  byId;
4790
4793
  constructor(personas = []) {
@@ -5906,6 +5909,44 @@ function errorMessage(error) {
5906
5909
  }
5907
5910
 
5908
5911
  // src/execution/council-brain.ts
5912
+ var MAX_CUSTOM_PERSONA_ID_CHARS = 48;
5913
+ function customPersonaId(raw, index) {
5914
+ const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_CUSTOM_PERSONA_ID_CHARS).replace(/-+$/g, "");
5915
+ return slug ? `custom-${slug}` : `custom-${index + 1}`;
5916
+ }
5917
+ function resolvePersonaRegistry(voters) {
5918
+ let registry = DEFAULT_COUNCIL_PERSONA_REGISTRY;
5919
+ const personaIds = [];
5920
+ const byRawText = /* @__PURE__ */ new Map();
5921
+ voters.forEach((voter, index) => {
5922
+ const raw = voter.persona?.trim();
5923
+ if (!raw) {
5924
+ personaIds.push("executor");
5925
+ return;
5926
+ }
5927
+ if (registry.has(raw)) {
5928
+ personaIds.push(raw);
5929
+ return;
5930
+ }
5931
+ const existing = byRawText.get(raw);
5932
+ if (existing) {
5933
+ personaIds.push(existing);
5934
+ return;
5935
+ }
5936
+ let id = customPersonaId(raw, index);
5937
+ let suffix = 2;
5938
+ while (registry.has(id)) id = `${customPersonaId(raw, index)}-${suffix++}`;
5939
+ registry = registry.with({
5940
+ id,
5941
+ name: raw.length <= 40 ? raw : `${raw.slice(0, 39)}\u2026`,
5942
+ description: "Custom decision lens supplied by the host configuration.",
5943
+ instruction: raw
5944
+ });
5945
+ byRawText.set(raw, id);
5946
+ personaIds.push(id);
5947
+ });
5948
+ return { registry, personaIds };
5949
+ }
5909
5950
  function createCouncilBrainArbiter(opts) {
5910
5951
  if (opts.voters.length === 0) {
5911
5952
  throw new Error("createCouncilBrainArbiter: at least one voter is required.");
@@ -5971,14 +6012,15 @@ function createCouncilBrainArbiter(opts) {
5971
6012
  }
5972
6013
  return makeSeatCallerForVoter(voter);
5973
6014
  };
6015
+ const { registry: personas, personaIds } = resolvePersonaRegistry(opts.voters);
5974
6016
  const seats = opts.voters.map((voter, i) => ({
5975
6017
  id: `voter-${i}`,
5976
6018
  label: voter.label ?? voter.model,
5977
- persona: voter.persona ?? "executor",
6019
+ persona: personaIds[i] ?? "executor",
5978
6020
  target: {
5979
6021
  providerId: voter.provider.id,
5980
6022
  model: voter.model,
5981
- role: voter.persona ?? "executor"
6023
+ role: personaIds[i] ?? "executor"
5982
6024
  },
5983
6025
  weight: voter.weight,
5984
6026
  veto: voter.veto
@@ -6000,6 +6042,9 @@ function createCouncilBrainArbiter(opts) {
6000
6042
  };
6001
6043
  const orchestrator = new CouncilOrchestrator({
6002
6044
  defaultProfile: "brain-council-adapter",
6045
+ // Carries the ad-hoc lenses registered above; without it the orchestrator
6046
+ // falls back to the built-in registry and rejects every custom persona.
6047
+ personas,
6003
6048
  // Previously never passed, so the panel was pinned at the orchestrator's
6004
6049
  // default of 3 concurrent seats no matter how many voters were configured.
6005
6050
  ...opts.maxConcurrency !== void 0 ? { maxConcurrency: opts.maxConcurrency } : {},
@@ -7820,7 +7865,9 @@ function safeProfileName(name) {
7820
7865
  function activeProfileName(globalRoot) {
7821
7866
  try {
7822
7867
  const parsed = JSON.parse(fs.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
7823
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
7868
+ return safeProfileName(
7869
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
7870
+ );
7824
7871
  } catch {
7825
7872
  return "default";
7826
7873
  }
@@ -7900,6 +7947,7 @@ function resolveWstackPaths(opts) {
7900
7947
  projectPlan: path4.join(projectDir, "plan.json"),
7901
7948
  projectAutophase: path4.join(projectDir, "autophase"),
7902
7949
  projectSddBoards: path4.join(projectDir, "sdd-boards"),
7950
+ projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
7903
7951
  syncConfig: path4.join(profileDir, "sync.json"),
7904
7952
  configHistoryDir: path4.join(globalRoot, "config-history"),
7905
7953
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
@@ -20469,7 +20517,8 @@ var OneShotOrchestrator = class {
20469
20517
  resolveTarget(input, config) {
20470
20518
  if (input.role && this.opts.modelRouter) {
20471
20519
  const pick = this.opts.modelRouter.pickForTask(input.role, "");
20472
- if (pick) {
20520
+ const hasExplicitTarget = Boolean(input.providerId || input.model);
20521
+ if (pick && (pick.fromMatrix === true || !hasExplicitTarget)) {
20473
20522
  return { providerId: pick.provider, model: pick.model };
20474
20523
  }
20475
20524
  }
@@ -21142,6 +21191,7 @@ export {
21142
21191
  AutonomousRunner,
21143
21192
  BRAIN_EVALUATION_CASE_VERSION,
21144
21193
  BUILTIN_COUNCIL_PERSONAS,
21194
+ BUILTIN_COUNCIL_PERSONA_IDS,
21145
21195
  BUILTIN_COUNCIL_PROFILES,
21146
21196
  COUNCIL_JUDGE_PROMPT_PATH,
21147
21197
  COUNCIL_REFUSAL_OPTION_ID,
@@ -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: {
@@ -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",
@@ -2102,7 +2103,9 @@ function safeProfileName(name) {
2102
2103
  function activeProfileName(globalRoot) {
2103
2104
  try {
2104
2105
  const parsed = JSON.parse(fs2.readFileSync(path2.join(globalRoot, "config.json"), "utf8"));
2105
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2106
+ return safeProfileName(
2107
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2108
+ );
2106
2109
  } catch {
2107
2110
  return "default";
2108
2111
  }
@@ -2182,6 +2185,7 @@ function resolveWstackPaths(opts) {
2182
2185
  projectPlan: path2.join(projectDir, "plan.json"),
2183
2186
  projectAutophase: path2.join(projectDir, "autophase"),
2184
2187
  projectSddBoards: path2.join(projectDir, "sdd-boards"),
2188
+ projectRequirementIntakes: path2.join(projectDir, "requirement-intakes"),
2185
2189
  syncConfig: path2.join(profileDir, "sync.json"),
2186
2190
  configHistoryDir: path2.join(globalRoot, "config-history"),
2187
2191
  projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
@@ -4970,6 +4974,7 @@ var HqBootstrapCodeStore = class {
4970
4974
  };
4971
4975
 
4972
4976
  // src/hq/exposure.ts
4977
+ import { isIP } from "node:net";
4973
4978
  var HQ_CLI_DEFAULT_HOST = "0.0.0.0";
4974
4979
  var HqInsecureExposureError = class extends Error {
4975
4980
  constructor(message) {
@@ -4979,8 +4984,15 @@ var HqInsecureExposureError = class extends Error {
4979
4984
  };
4980
4985
  function isLoopbackHost(host) {
4981
4986
  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);
4987
+ if (normalized === "localhost") return true;
4988
+ const family = isIP(normalized);
4989
+ if (family === 4) {
4990
+ return normalized.startsWith("127.");
4991
+ }
4992
+ if (family === 6) {
4993
+ return normalized === "::1" || /^::ffff:127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized);
4994
+ }
4995
+ return false;
4984
4996
  }
4985
4997
  function isOpenMode(input) {
4986
4998
  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
@@ -65,6 +65,8 @@ export interface HqFleetSummary {
65
65
  }
66
66
  export interface HqQueuedCommand {
67
67
  commandId: string;
68
+ /** Idempotency key deduplicates commands across leader handoffs (§2.2). */
69
+ idempotencyKey?: string;
68
70
  type: string;
69
71
  createdAt: string;
70
72
  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",
package/dist/index.d.ts CHANGED
@@ -69,7 +69,7 @@ export { type BrainApplyResult, type BrainConfigPatch, type BrainConfigSnapshot,
69
69
  export { buildLosslessDigest, buildSmartDigest, type ContentScore, type EliseResult, eliseOldToolResults, estimateMessages, extractText, findPreserveStart, hasTextContent, scoreMessage, } from './execution/compaction-core.js';
70
70
  export { COUNCIL_REFUSE_OPTION_ID, type CouncilBrainOptions, type CouncilVoter, createCouncilBrainArbiter, } from './execution/council-brain.js';
71
71
  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';
72
+ export { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS, CouncilPersonaRegistry, createCouncilPersonaRegistry, DEFAULT_COUNCIL_PERSONA_REGISTRY, } from './execution/council-personas.js';
73
73
  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
74
  export { buildCouncilJudgeSystemPrompt, buildCouncilJudgeUserPrompt, buildCouncilQuestionPrompt, buildCouncilVoterSystemPrompt, buildCouncilVoterUserPrompt, COUNCIL_JUDGE_PROMPT_PATH, COUNCIL_VOTER_PROMPT_PATH, } from './execution/council-prompts.js';
75
75
  export { type CouncilResolution, type CouncilResolutionInput, type CouncilResolutionSeat, type CouncilResolutionVote, resolveCouncilVotes, } from './execution/council-resolution.js';