@wrongstack/core 0.308.7 → 0.309.1

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 (47) hide show
  1. package/dist/coordination/director/director-toolset.d.ts +2 -2
  2. package/dist/coordination/director-mutation-test-tool.d.ts +29 -0
  3. package/dist/coordination/director-tools.d.ts +2 -0
  4. package/dist/coordination/director.d.ts +16 -0
  5. package/dist/coordination/explore-companion.d.ts +9 -6
  6. package/dist/coordination/fleet.d.ts +12 -0
  7. package/dist/coordination/index.d.ts +1 -1
  8. package/dist/coordination/index.js +990 -61
  9. package/dist/coordination/mail-tools.d.ts +10 -6
  10. package/dist/coordination/mailbox-codecs.d.ts +31 -0
  11. package/dist/coordination/multi-agent-coordinator.d.ts +14 -0
  12. package/dist/coordination/multi-agent-timeout.d.ts +11 -1
  13. package/dist/coordination/mutation-engine.d.ts +76 -0
  14. package/dist/coordination/subagent-budget.d.ts +54 -0
  15. package/dist/coordination/subagent-finish.d.ts +78 -0
  16. package/dist/core/index.js +58 -13
  17. package/dist/defaults/index.js +1132 -106
  18. package/dist/execution/index.js +260 -20
  19. package/dist/hq/index.js +45 -5
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1456 -263
  22. package/dist/infrastructure/index.js +22 -3
  23. package/dist/kernel/events/agent-events.d.ts +31 -2
  24. package/dist/models/index.js +11 -1
  25. package/dist/observability/index.js +1 -1
  26. package/dist/plugin/index.js +113 -12
  27. package/dist/prompts/index.js +360 -3
  28. package/dist/security/auto-approve-policy.d.ts +2 -2
  29. package/dist/security/index.js +177 -50
  30. package/dist/security/permission-helpers.d.ts +11 -0
  31. package/dist/security/permission-policy.d.ts +10 -1
  32. package/dist/security/yolo-risk.d.ts +17 -0
  33. package/dist/session-catalog/index.js +32 -2
  34. package/dist/session-catalog/project-server.js +32 -2
  35. package/dist/skills/index.js +39 -6
  36. package/dist/storage/index.js +44 -3
  37. package/dist/types/index.d.ts +1 -1
  38. package/dist/types/index.js +14 -0
  39. package/dist/types/multi-agent.d.ts +15 -0
  40. package/dist/types/provider.d.ts +29 -1
  41. package/dist/types/tool.d.ts +15 -0
  42. package/dist/utils/index.d.ts +1 -0
  43. package/dist/utils/index.js +54 -4
  44. package/dist/utils/terminal-sanitize.d.ts +41 -0
  45. package/dist/utils/tool-subject.d.ts +1 -1
  46. package/instructions/agents/chaos-monkey.md +61 -0
  47. package/package.json +4 -4
@@ -2378,7 +2378,7 @@ function walk(node, vault, transform) {
2378
2378
  }
2379
2379
  return out;
2380
2380
  }
2381
- var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
2381
+ var SECRET_KEY_PATTERN = /(?:api[-_]?key|auth[-_]?token|authorization|proxy-authorization|cookie|bearer|secret|password|passwd|pwd|refresh[-_]?token|session[-_]?key|access[_-]?token|private[_-]?key|token\b)/i;
2382
2382
  var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
2383
2383
  function isSecretField(name) {
2384
2384
  const lc = name.toLowerCase();
@@ -2554,6 +2554,14 @@ function keyFileNeedsHardening(keyFile, opts) {
2554
2554
  }
2555
2555
  return false;
2556
2556
  }
2557
+ function mkdirSecretDirSync(dir) {
2558
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
2559
+ if (process.platform === "win32") return;
2560
+ try {
2561
+ fs2.chmodSync(dir, 448);
2562
+ } catch {
2563
+ }
2564
+ }
2557
2565
  function writeKeyFileAtomicSync(keyFile, content) {
2558
2566
  const tmp = `${keyFile}.${randomBytes(4).toString("hex")}.tmp`;
2559
2567
  const fd = fs2.openSync(tmp, "w", 384);
@@ -2701,7 +2709,7 @@ var DefaultSecretVault = class {
2701
2709
  const oldVersion = this._keyVersion;
2702
2710
  const newKey = randomBytes(KEY_BYTES);
2703
2711
  const newVersion = oldVersion + 1;
2704
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
2712
+ mkdirSecretDirSync(path3.dirname(this.keyFile));
2705
2713
  const passphrase = getVaultPassphrase();
2706
2714
  if (passphrase) {
2707
2715
  writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
@@ -2785,7 +2793,7 @@ var DefaultSecretVault = class {
2785
2793
  } catch (err) {
2786
2794
  if (err.code !== "ENOENT") throw err;
2787
2795
  }
2788
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
2796
+ mkdirSecretDirSync(path3.dirname(this.keyFile));
2789
2797
  const key = randomBytes(KEY_BYTES);
2790
2798
  const passphrase = getVaultPassphrase();
2791
2799
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -3636,6 +3644,17 @@ var IN_PROJECT_DENIED_PATHS = [
3636
3644
  // See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
3637
3645
  path: "features.mailboxBridge",
3638
3646
  reason: "Enables the mailbox bridge, whose CLI-entry resolution walks up from the project root \u2014 a repo-supplied packages/cli/dist/index.js would be spawned on WebUI boot."
3647
+ },
3648
+ {
3649
+ // `plugins` is already denied above, so a repo cannot ADD a plugin. This
3650
+ // closes the other half: a repo could previously ship
3651
+ // `{"features":{"pluginsTrust":false}}` and switch off the integrity gate
3652
+ // for plugins the user had ALREADY installed globally — disarming the
3653
+ // trust-on-first-use pin that exists to catch a supply-chain update
3654
+ // rewriting a plugin's entry file. Same operator-owned class as the
3655
+ // switches above.
3656
+ path: "features.pluginsTrust",
3657
+ reason: "Disables the plugin trust-on-first-use integrity gate, re-trusting changed code in already-installed global plugins."
3639
3658
  }
3640
3659
  ];
3641
3660
  function deleteNestedPath(target, path13) {
@@ -144,8 +144,8 @@ export interface AgentEventMap {
144
144
  /**
145
145
  * A spawn resolved fewer skills than it selected. Emitted so a skill that
146
146
  * silently failed to load — missing from the loader, gated by a capability
147
- * the subagent lacks, or cut by the prompt budget — is observable instead of
148
- * leaving the agent believing it received guidance it never got.
147
+ * the subagent lacks, or cut by the prompt budget — is observable instead
148
+ * of leaving the agent believing it received guidance it never got.
149
149
  */
150
150
  'subagent.skills.dropped': {
151
151
  sessionId?: string | undefined;
@@ -155,6 +155,35 @@ export interface AgentEventMap {
155
155
  /** skill → reason it was dropped. */
156
156
  dropped: Record<string, string>;
157
157
  };
158
+ /**
159
+ * In-band graceful-finish request for a background subagent (see
160
+ * coordination/subagent-finish.ts). Emitted on the subagent's OWN EventBus
161
+ * when its wall-clock deadline is crossed — or the leader explicitly asked
162
+ * it to finish (session shutdown) — and the subagent's config opted into
163
+ * `gracefulFinish`. The runner folds the notice into the conversation as a
164
+ * `/btw` note at the next iteration boundary; the model then completes its
165
+ * task in its own turn within the granted grace window. This event is a
166
+ * notification, never an interrupt: nothing aborts when it fires.
167
+ */
168
+ 'subagent.finish_requested': {
169
+ /** Parent/host session id. */
170
+ sessionId?: string | undefined;
171
+ /**
172
+ * Owning subagent id when the budget knows it; omitted when the budget
173
+ * was constructed without one. An empty string would look like an
174
+ * address that matches nothing — the runner treats an omitted id as
175
+ * deliverable (`if (e.subagentId && …)`), so omission is safe.
176
+ */
177
+ subagentId?: string | undefined;
178
+ /** Why the finish was requested (deadline crossed / leader finished). */
179
+ reason: string;
180
+ /** Epoch ms by which the subagent should have produced its final output. */
181
+ deadlineMs: number;
182
+ /** Granted working-time window in ms. */
183
+ graceMs: number;
184
+ /** Ready-to-read notice text; the loop folds it in as a `/btw` note. */
185
+ notice: string;
186
+ };
158
187
  /**
159
188
  * A background learning-distillation pass finished for a roster role.
160
189
  * Emitted by the fleet host's auto-optimize scheduler so surfaces can show
@@ -877,7 +877,17 @@ function normalizeModelsDevModel(model) {
877
877
  const reasoningConfig = {
878
878
  default: disableSupported ? "enabled" : "always_on",
879
879
  disableSupported,
880
- effortSupported: effortLevels.length > 0,
880
+ // Tri-state (see ReasoningConfig.effortSupported):
881
+ // options present → documented answer (true when effort values exist;
882
+ // an explicitly EMPTY array is a documented "no
883
+ // effort control", not an absent field).
884
+ // field ABSENT → the model is known to reason but its vocabulary is
885
+ // undocumented → `undefined`, so the resolver forwards
886
+ // the request and each wire adapter applies its own
887
+ // transport gating. Sending `false` here would make
888
+ // the resolver claim "does not support effort" — an
889
+ // assertion the catalog never made.
890
+ ...raw === void 0 ? {} : { effortSupported: effortLevels.length > 0 },
881
891
  effortLevels,
882
892
  preserveThinking: model.interleaved ? "always_on" : "unsupported"
883
893
  };
@@ -388,7 +388,7 @@ var SENSITIVE_FLAG_PATTERNS = [
388
388
  // redaction function (false positive = cosmetic noise; false negative = leak).
389
389
  /(?<![-\w])-(?:password|p|a)(?:[=\s]+)?[^\s,-]+/gi,
390
390
  // env var–style secrets: TOKEN=x, API_KEY=y, etc.
391
- /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
391
+ /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD|PASSPHRASE)\s*[=:]\s*[^\s,]+/gi,
392
392
  // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
393
393
  // when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
394
394
  // every such flag in the command line is redacted, not just the first.
@@ -1945,18 +1945,88 @@ var DefaultPluginAPI = class {
1945
1945
  list: () => tr.list()
1946
1946
  };
1947
1947
  const pr = init.providerRegistry;
1948
+ const providerTypesIOwn = /* @__PURE__ */ new Set();
1949
+ const assertCanMutateProvider = (type, op) => {
1950
+ if (isOfficial) return;
1951
+ if (providerTypesIOwn.has(type)) return;
1952
+ if (!pr.has(type)) return;
1953
+ throw new Error(
1954
+ `Plugin "${owner}" may not ${op} provider "${type}" \u2014 it was not registered by this plugin. Replacing an existing provider would route prompts and credentials through plugin code.`
1955
+ );
1956
+ };
1948
1957
  this.providers = {
1949
- register: (f) => pr.register(f),
1950
- unregister: (type) => pr.unregister(type),
1958
+ register: (f) => {
1959
+ assertCanMutateProvider(f.type, "replace");
1960
+ pr.register(f);
1961
+ providerTypesIOwn.add(f.type);
1962
+ },
1963
+ unregister: (type) => {
1964
+ assertCanMutateProvider(type, "unregister");
1965
+ providerTypesIOwn.delete(type);
1966
+ return pr.unregister(type);
1967
+ },
1951
1968
  create: (cfg) => pr.create(cfg),
1952
1969
  list: () => pr.list()
1953
1970
  };
1954
- this.mcp = init.mcpRegistry ?? noopMcp;
1971
+ const mcpRegistry = init.mcpRegistry;
1972
+ if (!mcpRegistry) {
1973
+ this.mcp = noopMcp;
1974
+ } else {
1975
+ const mcpServersIStarted = /* @__PURE__ */ new Set();
1976
+ const assertOwnsMcpServer = (name, op) => {
1977
+ if (isOfficial) return;
1978
+ if (mcpServersIStarted.has(name)) return;
1979
+ if (!mcpRegistry.list().some((s) => s.name === name)) return;
1980
+ throw new Error(
1981
+ `Plugin "${owner}" may not ${op} MCP server "${name}" \u2014 it was not started by this plugin.`
1982
+ );
1983
+ };
1984
+ this.mcp = {
1985
+ start: async (cfg) => {
1986
+ const name = cfg?.name;
1987
+ if (typeof name === "string" && mcpRegistry.list().some((s) => s.name === name)) {
1988
+ assertOwnsMcpServer(name, "start");
1989
+ }
1990
+ await mcpRegistry.start(cfg);
1991
+ if (typeof name === "string") mcpServersIStarted.add(name);
1992
+ },
1993
+ stop: async (name) => {
1994
+ assertOwnsMcpServer(name, "stop");
1995
+ await mcpRegistry.stop(name);
1996
+ },
1997
+ restart: async (name) => {
1998
+ assertOwnsMcpServer(name, "restart");
1999
+ await mcpRegistry.restart(name);
2000
+ },
2001
+ list: () => mcpRegistry.list()
2002
+ };
2003
+ }
1955
2004
  const scr = init.slashCommandRegistry;
1956
2005
  const official = init.official === true;
2006
+ const commandsIOwn = /* @__PURE__ */ new Set();
1957
2007
  this.slashCommands = scr ? {
1958
- register: (cmd) => scr.register(cmd, owner, { official }),
1959
- unregister: (name) => scr.unregister(name),
2008
+ register: (cmd) => {
2009
+ scr.register(cmd, owner, { official });
2010
+ for (const key of [cmd.name, ...cmd.aliases ?? []]) {
2011
+ commandsIOwn.add(key);
2012
+ commandsIOwn.add(`${owner}:${key}`);
2013
+ }
2014
+ },
2015
+ unregister: (name) => {
2016
+ if (!official && !commandsIOwn.has(name) && scr.get(name) !== void 0) {
2017
+ throw new Error(
2018
+ `Plugin "${owner}" may not unregister slash command "${name}" \u2014 it was not registered by this plugin.`
2019
+ );
2020
+ }
2021
+ for (const key of [
2022
+ name,
2023
+ `${owner}:${name}`,
2024
+ name.startsWith(`${owner}:`) ? name.slice(owner.length + 1) : name
2025
+ ]) {
2026
+ commandsIOwn.delete(key);
2027
+ }
2028
+ return scr.unregister(name);
2029
+ },
1960
2030
  get: (name) => scr.get(name),
1961
2031
  list: () => scr.list()
1962
2032
  } : noopSlashCommands;
@@ -5829,6 +5899,30 @@ function withSqliteExperimentalWarningSuppressed(run) {
5829
5899
  }
5830
5900
  }
5831
5901
 
5902
+ // src/utils/win32-cmd.ts
5903
+ var WIN32_CMD_META = /[&|<>"%\r\n\0]/;
5904
+ function buildWin32CmdShimInvocation(command, args = []) {
5905
+ assertSafeWin32CmdArgs([command, ...args]);
5906
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
5907
+ return {
5908
+ command: process.env["COMSPEC"] ?? "cmd.exe",
5909
+ args: ["/d", "/c", line],
5910
+ windowsVerbatimArguments: true
5911
+ };
5912
+ }
5913
+ function assertSafeWin32CmdArgs(args) {
5914
+ for (const arg of args) {
5915
+ if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
5916
+ throw new Error(
5917
+ 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
5918
+ );
5919
+ }
5920
+ }
5921
+ }
5922
+ function quoteWin32CmdArg(arg) {
5923
+ return `"${arg}"`;
5924
+ }
5925
+
5832
5926
  // src/chronicle/sqlite-journal-schema.ts
5833
5927
  var CHRONICLE_SQLITE_FILE = "chronicle.sqlite";
5834
5928
  var LEGACY_JSONL_MIGRATION_KEY = "legacy-jsonl-v1";
@@ -11600,14 +11694,21 @@ async function openInEditor(filePath, env = process.env) {
11600
11694
  context: { filePath }
11601
11695
  });
11602
11696
  }
11603
- const child = spawn5(parts[0], [...parts.slice(1), filePath], {
11697
+ const editorArgs = [...parts.slice(1), filePath];
11698
+ const child = shell ? (() => {
11699
+ const inv = buildWin32CmdShimInvocation(parts[0], editorArgs);
11700
+ return spawn5(inv.command, inv.args, {
11701
+ stdio: "ignore",
11702
+ detached: true,
11703
+ windowsVerbatimArguments: inv.windowsVerbatimArguments,
11704
+ // Suppresses the console flash the cmd.exe wrapper would otherwise
11705
+ // show before the editor appears. Repo convention — see
11706
+ // `core/tests/architecture/spawn-convention.test.ts`.
11707
+ windowsHide: true
11708
+ });
11709
+ })() : spawn5(parts[0], editorArgs, {
11604
11710
  stdio: "ignore",
11605
11711
  detached: true,
11606
- shell,
11607
- // `shell` routes through cmd.exe on win32, which flashes a console window
11608
- // before the editor appears. A GUI editor is unaffected by the flag; the
11609
- // shell wrapper is what it suppresses. Repo convention — see
11610
- // `core/tests/architecture/spawn-convention.test.ts`.
11611
11712
  windowsHide: true
11612
11713
  });
11613
11714
  child.unref();
@@ -13789,7 +13890,7 @@ function walk2(node, vault, transform) {
13789
13890
  }
13790
13891
  return out;
13791
13892
  }
13792
- var SECRET_KEY_PATTERN = /(?:apikey|api_key|authtoken|auth_token|bearer|secret|password|passwd|pwd|refreshtoken|refresh_token|sessionkey|session_key|access[_-]?token|private[_-]?key|token\b)/i;
13893
+ var SECRET_KEY_PATTERN = /(?:api[-_]?key|auth[-_]?token|authorization|proxy-authorization|cookie|bearer|secret|password|passwd|pwd|refresh[-_]?token|session[-_]?key|access[_-]?token|private[_-]?key|token\b)/i;
13793
13894
  var NON_SECRET_OVERRIDES = /* @__PURE__ */ new Set(["publickey", "public_key"]);
13794
13895
  function isSecretField(name) {
13795
13896
  const lc = name.toLowerCase();