@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
@@ -1397,6 +1397,38 @@ var sshManagerServer = () => ({
1397
1397
  permission: "confirm",
1398
1398
  requestTimeoutMs: 18e4
1399
1399
  });
1400
+ var requirementIntakeServer = () => ({
1401
+ name: "requirement-intake",
1402
+ description: "WrongStack Requirements Intake \u2014 list intake records and file new ones (project-scoped, --writable)",
1403
+ transport: "stdio",
1404
+ command: "wstack-requirement-intake-mcp",
1405
+ args: ["--project-root", ".", "--writable"],
1406
+ permission: "auto"
1407
+ });
1408
+ var kanbanServer = () => ({
1409
+ name: "kanban",
1410
+ description: "WrongStack Kanban \u2014 inspect and manage project work boards (project-scoped, manage tier, no destructive ops)",
1411
+ transport: "stdio",
1412
+ command: "wstack-kanban-mcp",
1413
+ args: ["--project-root", ".", "--writable"],
1414
+ permission: "confirm"
1415
+ });
1416
+ var mailboxServer = () => ({
1417
+ name: "mailbox",
1418
+ description: "WrongStack Mailbox \u2014 read and send project agent mail (project-scoped, no admin/credentials)",
1419
+ transport: "stdio",
1420
+ command: "wstack-mailbox-mcp",
1421
+ args: ["--project-root", ".", "--actor", "external-agent", "--writable"],
1422
+ permission: "auto"
1423
+ });
1424
+ var codebaseIndexServer = () => ({
1425
+ name: "codebase-index",
1426
+ description: "WrongStack Codebase Index \u2014 symbol search and dependency graphs (project-scoped, --writable)",
1427
+ transport: "stdio",
1428
+ command: "wstack-codebase-index-mcp",
1429
+ args: ["--project-root", ".", "--writable"],
1430
+ permission: "auto"
1431
+ });
1400
1432
  var allServers = () => ({
1401
1433
  filesystem: { ...filesystemServer(), enabled: false },
1402
1434
  github: { ...githubServer(), enabled: false },
@@ -1411,7 +1443,11 @@ var allServers = () => ({
1411
1443
  "zai-vision": { ...zaiVisionServer(), enabled: false },
1412
1444
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false },
1413
1445
  playwright: { ...playwrightServer(), enabled: false },
1414
- ssh: { ...sshManagerServer(), enabled: false }
1446
+ ssh: { ...sshManagerServer(), enabled: false },
1447
+ kanban: { ...kanbanServer(), enabled: false },
1448
+ mailbox: { ...mailboxServer(), enabled: false },
1449
+ "codebase-index": { ...codebaseIndexServer(), enabled: false },
1450
+ "requirement-intake": { ...requirementIntakeServer(), enabled: false }
1415
1451
  });
1416
1452
 
1417
1453
  // src/utils/expect-defined.ts
@@ -2900,7 +2936,9 @@ function safeProfileName(name) {
2900
2936
  function activeProfileName(globalRoot) {
2901
2937
  try {
2902
2938
  const parsed = JSON.parse(fs4.readFileSync(path5.join(globalRoot, "config.json"), "utf8"));
2903
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2939
+ return safeProfileName(
2940
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2941
+ );
2904
2942
  } catch {
2905
2943
  return "default";
2906
2944
  }
@@ -2980,6 +3018,7 @@ function resolveWstackPaths(opts) {
2980
3018
  projectPlan: path5.join(projectDir, "plan.json"),
2981
3019
  projectAutophase: path5.join(projectDir, "autophase"),
2982
3020
  projectSddBoards: path5.join(projectDir, "sdd-boards"),
3021
+ projectRequirementIntakes: path5.join(projectDir, "requirement-intakes"),
2983
3022
  syncConfig: path5.join(profileDir, "sync.json"),
2984
3023
  configHistoryDir: path5.join(globalRoot, "config-history"),
2985
3024
  projectStatus: (projectHash2) => path5.join(globalRoot, "projects", projectHash2, "status.json")
@@ -3413,6 +3452,15 @@ var IN_PROJECT_DENIED_PATHS = [
3413
3452
  reason: "Extends the exec allow-list; a repo could authorise its own binaries."
3414
3453
  },
3415
3454
  { path: "tools.exec.danger", reason: "Weakens the destructive-command banner." },
3455
+ {
3456
+ // The whole subtree, not just the dangerous leaves: a persona's
3457
+ // `instruction` is rendered into the voter SYSTEM prompt, a profile seat
3458
+ // may pin providerId/model, and `defaultProfile` selects which of those
3459
+ // runs when the agent names no profile. Denying the parent leaves no leaf
3460
+ // to reclassify wrongly later.
3461
+ path: "tools.council",
3462
+ reason: "Council tool panel definitions: persona instructions are injected into the voter SYSTEM prompt and profile seats can pin an attacker-chosen providerId/model."
3463
+ },
3416
3464
  { path: "skills.extraDirs", reason: "Loads skill definitions from repo-chosen directories." },
3417
3465
  { path: "skills.registryUrl", reason: "Redirects skill installs to a repo-chosen host." },
3418
3466
  // Deliberately NOT denied: skills.mode and skills.eagerMaxChars. The audit
@@ -78,6 +78,41 @@ export declare const miniMaxVisionServer: () => MCPServerConfig;
78
78
  * env/TOML config (for example SSH_SERVER_<NAME>_HOST, USER, KEYPATH/PASSWORD) or ssh-agent.
79
79
  */
80
80
  export declare const sshManagerServer: () => MCPServerConfig;
81
+ /**
82
+ * WrongStack Requirements Intake — list intake records and file + submit new
83
+ * ones against the project's intake store (`wstack-requirement-intake-mcp`).
84
+ * Project-scoped: resolves the project from the spawn cwd (`--project-root .`);
85
+ * customize the arg for absolute paths. Tools are structured domain tools
86
+ * (list/submit) with no file, shell, or network surface, so the permission
87
+ * defaults to `auto`.
88
+ */
89
+ export declare const requirementIntakeServer: () => MCPServerConfig;
90
+ /**
91
+ * WrongStack Kanban — inspect and manage durable project work through the
92
+ * project-scoped Kanban IPC owner (`wstack-kanban-mcp`). The preset exposes
93
+ * the writable manage tier (create/update/transition tasks, splitting,
94
+ * assignment) but never `--destructive` (delete/merge/transfer). Mutations
95
+ * touch durable project board state, so the permission defaults to `confirm`.
96
+ */
97
+ export declare const kanbanServer: () => MCPServerConfig;
98
+ /**
99
+ * WrongStack Mailbox — coordinate with project agents through the existing
100
+ * project-scoped mailbox IPC owner (`wstack-mailbox-mcp`). The preset exposes
101
+ * the writable tier (send, receipts, self-presence) but never `--admin`
102
+ * (maintenance + credential operations).
103
+ *
104
+ * `--actor` is mandatory: the child CLI exits with code 2 without it. The
105
+ * default `external-agent` identity should be customized per external agent
106
+ * (it is the authoritative identity for sends, receipts, and registration).
107
+ */
108
+ export declare const mailboxServer: () => MCPServerConfig;
109
+ /**
110
+ * WrongStack Codebase Index — symbol search and dependency graphs over the
111
+ * project's codebase index (`wstack-codebase-index-mcp`). The preset exposes
112
+ * `--writable` so `codebase_index` (incremental/full rebuild) is available;
113
+ * index rebuilds are CPU/disk work with no semantic risk.
114
+ */
115
+ export declare const codebaseIndexServer: () => MCPServerConfig;
81
116
  /** Everything bundled — full set of built-in servers. Useful for `wstack mcp add --all`. */
82
117
  export declare const allServers: () => Record<string, MCPServerConfig>;
83
118
  //# sourceMappingURL=mcp-servers.d.ts.map
@@ -264,6 +264,15 @@ export interface BrainEventMap {
264
264
  completed: number;
265
265
  /** Optional fleet-wide cost total (USD) — set by the host bridge when known. */
266
266
  totalCostUsd?: number | undefined;
267
+ /** Concurrent-subagent ceiling (issue #323). */
268
+ maxConcurrent?: number | undefined;
269
+ /** Lifetime spawn budget snapshot (issue #323). */
270
+ maxSpawns?: number | undefined;
271
+ usedSpawns?: number | undefined;
272
+ remainingSpawns?: number | undefined;
273
+ effectiveSource?: string | undefined;
274
+ checkpointMaxSpawns?: number | undefined;
275
+ ceilingMismatch?: boolean | undefined;
267
276
  subagentStatuses: {
268
277
  subagentId: string;
269
278
  taskId: string;
@@ -1,7 +1,7 @@
1
1
  import type { ChroniclePromptManifest } from '../../chronicle/prompt-manifest.js';
2
2
  import type { Context } from '../../core/context.js';
3
3
  import type { ContentBlock } from '../../types/blocks.js';
4
- import type { Usage } from '../../types/provider.js';
4
+ import type { ProviderErrorBody, Usage } from '../../types/provider.js';
5
5
  export interface ProviderEventMap {
6
6
  /** Fired before every physical provider/model attempt, including attempt zero. */
7
7
  'provider.attempt.started': {
@@ -63,6 +63,8 @@ export interface ProviderEventMap {
63
63
  retryScheduled: boolean;
64
64
  retryDelayMs?: number | undefined;
65
65
  providerRequestId?: string | undefined;
66
+ /** Scrubbed provider failure envelope/body retained for diagnostics. */
67
+ errorBody?: ProviderErrorBody | undefined;
66
68
  /** Kanban task this provider call belongs to, when known. */
67
69
  taskId?: string | undefined;
68
70
  /** Kanban board this provider call belongs to, when known. */
@@ -123,6 +125,8 @@ export interface ProviderEventMap {
123
125
  delayMs: number;
124
126
  status: number;
125
127
  description: string;
128
+ /** Scrubbed provider failure envelope/body retained for diagnostics. */
129
+ errorBody?: ProviderErrorBody | undefined;
126
130
  };
127
131
  /**
128
132
  * Fired once when a provider call ultimately fails (retries exhausted, or
@@ -134,6 +138,8 @@ export interface ProviderEventMap {
134
138
  status: number;
135
139
  description: string;
136
140
  retryable: boolean;
141
+ /** Scrubbed provider failure envelope/body retained for diagnostics. */
142
+ errorBody?: ProviderErrorBody | undefined;
137
143
  };
138
144
  /**
139
145
  * Fired by the fallback-model extension when the primary model is overloaded
@@ -154,6 +160,8 @@ export interface ProviderEventMap {
154
160
  };
155
161
  status: number;
156
162
  providerSwitched: boolean;
163
+ /** Gate correlation id — set when a fallback gate mediated the switch. */
164
+ requestId?: string | undefined;
157
165
  contextWindowWarning?: {
158
166
  fromMaxContext: number;
159
167
  toMaxContext: number;
@@ -161,7 +169,33 @@ export interface ProviderEventMap {
161
169
  } | undefined;
162
170
  };
163
171
  /**
164
- * Fired whenever a (providerId, model) pair transitions between
172
+ * Fired by the fallback gate function (supplied to the fallback-model
173
+ * extension via `FallbackModelDeps.fallbackGate`) when the chain is about
174
+ * to engage, BEFORE attempting any fallback entry. Carries the full
175
+ * candidate list so the UI can show a modal with a countdown and manual
176
+ * pick. The gate waits for a `provider.fallback_choice` event bus emission
177
+ * (or the countdown timer) before proceeding with the chosen model or
178
+ * auto-switching to the next candidate.
179
+ */
180
+ 'provider.fallback_pending': {
181
+ sessionId?: string | undefined;
182
+ from: {
183
+ providerId: string;
184
+ model: string;
185
+ };
186
+ status: number;
187
+ candidates: Array<{
188
+ providerId: string;
189
+ model: string;
190
+ }>;
191
+ /** Seconds the UI should count down before auto-switching to the next model. */
192
+ autoSwitchSeconds: number;
193
+ /** Unique request id — the UI echoes this back in the choice message. */
194
+ requestId: string;
195
+ timestamp: number;
196
+ };
197
+ /**
198
+ * Fired when a (providerId, model) pair transitions between
165
199
  * healthy/degraded/blocked states. The tracker emits this so the
166
200
  * CLI/TUI/WebUI can render a live status indicator.
167
201
  */
@@ -174,6 +208,19 @@ export interface ProviderEventMap {
174
208
  timestamp: number;
175
209
  stateExpiresAt?: number | undefined;
176
210
  };
211
+ /**
212
+ * Fired by the UI when the user manually picks a model from the
213
+ * fallback modal. The fallback gate listens for this event (matched
214
+ * by `requestId`) to resolve with the chosen model instead of waiting
215
+ * for the countdown.
216
+ */
217
+ 'provider.fallback_choice': {
218
+ requestId: string;
219
+ providerId?: string | undefined;
220
+ model?: string | undefined;
221
+ /** When true, auto-switch to the next candidate (countdown expired or Esc). */
222
+ autoSwitch?: boolean | undefined;
223
+ };
177
224
  /**
178
225
  * Fired when the agent's actively selected (primary, not fallback)
179
226
  * provider/model is blocked and will be skipped. The CLI/TUI should
@@ -17,6 +17,8 @@ export interface SddEventMap {
17
17
  completed: number;
18
18
  failed: number;
19
19
  stopped: boolean;
20
+ /** Fatal, non-recoverable error that hard-stopped the run (implies `stopped: true`). */
21
+ fatalError?: string | undefined;
20
22
  };
21
23
  /** A task began executing on a worker (carries who + which worktree). */
22
24
  'sdd.task.started': {
@@ -584,7 +584,7 @@ var DefaultModelsRegistry = class {
584
584
  async load(opts = {}) {
585
585
  if (this.payload && !opts.force) return this.payload;
586
586
  if (this.seed) {
587
- this.payload = this.seed;
587
+ this.payload = this.withExtraOverlay(this.seed);
588
588
  this.fetchedAt = /* @__PURE__ */ new Date();
589
589
  return this.payload;
590
590
  }
@@ -7,9 +7,11 @@ import type { ProviderRegistry } from '../registry/provider-registry.js';
7
7
  import type { SlashCommandRegistry } from '../registry/slash-command-registry.js';
8
8
  import type { ToolRegistry } from '../registry/tool-registry.js';
9
9
  import type { Config } from '../types/config.js';
10
+ import type { CouncilQuestion, CouncilResult } from '../types/council.js';
10
11
  import type { HookEvent, HookMatcher, InProcessHook } from '../types/hooks.js';
11
12
  import type { Logger } from '../types/logger.js';
12
13
  import type { ModelsRegistry } from '../types/models-registry.js';
14
+ import type { OneShotLLMInput, OneShotLLMResult } from '../types/one-shot-llm.js';
13
15
  import type { MCPRegistryView, MetricsSinkView, Notifier, PluginAPI, PluginCapabilities, PluginDependency, PluginLLM, PluginPipelines, ProviderRegistryView, SessionWriterView, SlashCommandRegistryView, ToolRegistryView } from '../types/plugin.js';
14
16
  import type { Provider } from '../types/provider.js';
15
17
  import type { SystemPromptContributor } from '../types/system-prompt-contributor.js';
@@ -91,6 +93,10 @@ export interface PluginAPIInit {
91
93
  getProvider?: (() => Provider) | undefined;
92
94
  getModel?: (() => string) | undefined;
93
95
  createProvider?: ((name: string, model?: string) => Provider) | undefined;
96
+ /** Preferred production path: shared One Shot runtime with fallbacks. */
97
+ oneShot?: ((input: OneShotLLMInput) => Promise<OneShotLLMResult>) | undefined;
98
+ /** Optional shared multi-model Council runtime. */
99
+ council?: ((question: CouncilQuestion) => Promise<CouncilResult>) | undefined;
94
100
  } | undefined;
95
101
  config: Config;
96
102
  /**
@@ -28,6 +28,61 @@ export interface PluginConfigChange {
28
28
  * beat aliases, making migrations deterministic regardless of object order.
29
29
  */
30
30
  export declare function resolvePluginConfig(input: ResolvePluginConfigInput): ResolvedPluginConfig;
31
+ /**
32
+ * Default `config.plugins` name matcher: the canonical name, a declared
33
+ * alias, or the `@wrongstack/plugins/<name>` subpath spelling of either.
34
+ *
35
+ * Surfaces that know more pass their own `matches` — the loader has an alias
36
+ * table (`lsp` → `@wrongstack/plug-lsp`), the CLI folds `telegram` and
37
+ * `@wrongstack/telegram` into one row. This is the floor, not the ceiling.
38
+ */
39
+ export declare function pluginEntryMatchesName(configuredName: string, name: string, aliases?: readonly string[]): boolean;
40
+ export type PluginEnablementSource = 'feature-flag' | 'plugin-entry' | 'extension' | 'default';
41
+ export interface ResolvedPluginEnablement {
42
+ enabled: boolean;
43
+ source: PluginEnablementSource;
44
+ }
45
+ export interface ResolvePluginEnablementInput {
46
+ name: string;
47
+ aliases?: readonly string[] | undefined;
48
+ /**
49
+ * `'active'` — runs unless something turns it off.
50
+ * `'inactive'` — runs only when something explicitly turns it on.
51
+ * Omitted behaves like `'inactive'`.
52
+ */
53
+ defaultState?: 'active' | 'inactive' | undefined;
54
+ config?: (Partial<Pick<Config, 'plugins' | 'extensions'>> & {
55
+ features?: Partial<Config['features']> | undefined;
56
+ }) | undefined;
57
+ /**
58
+ * Overrides how a `config.plugins` entry name is matched against this
59
+ * plugin. Surfaces normalize specs differently (the loader maps
60
+ * `@wrongstack/plugins/foo` → `foo`, the CLI treats `telegram` and
61
+ * `@wrongstack/telegram` as one row), so matching is per-surface —
62
+ * only the PRECEDENCE below is shared. Defaults to name/alias equality.
63
+ */
64
+ matches?: ((configuredName: string) => boolean) | undefined;
65
+ }
66
+ /**
67
+ * Resolve whether a plugin boots, under ONE precedence shared by every
68
+ * surface that reports or acts on plugin state:
69
+ *
70
+ * 1. `features.plugins === false` — kills every plugin.
71
+ * 2. a matching `config.plugins` entry — `{ enabled: false }` is off,
72
+ * anything else is on. This is what `wstack plugin enable|disable`
73
+ * writes, so it stays the highest per-plugin authority.
74
+ * 3. `config.extensions[name].enabled`, when it is a boolean — the
75
+ * plugin's own master switch.
76
+ * 4. the catalog `defaultState`.
77
+ *
78
+ * Rule 3 used to be read only by the loader, and only in its `=== true`
79
+ * direction: a plugin switched on via `extensions` ran while every
80
+ * reporting surface — which looked at `config.plugins` alone — called it
81
+ * disabled, and `extensions[name].enabled = false` turned nothing off.
82
+ * Both directions now resolve here, so "what runs" and "what the report
83
+ * says" cannot drift apart again.
84
+ */
85
+ export declare function resolvePluginEnablement(input: ResolvePluginEnablementInput): ResolvedPluginEnablement;
31
86
  export declare function resolvePluginManifestConfig(plugin: Pick<Plugin, 'name' | 'configAliases' | 'defaultConfig'>, config?: ResolvePluginConfigInput['config'], explicitOptions?: Readonly<Record<string, unknown>>): ResolvedPluginConfig;
32
87
  /** Unknown fields are immutable by default so a new option cannot silently hot-reload. */
33
88
  export declare function diffPluginConfig(previous: Readonly<Record<string, unknown>>, next: Readonly<Record<string, unknown>>, fields: Readonly<Record<string, PluginConfigFieldMetadata>>): PluginConfigChange[];
@@ -1,5 +1,5 @@
1
1
  export { DefaultPluginAPI, definePlugin, type PluginAPIInit } from './api.js';
2
- export { diffPluginConfig, type PluginConfigChange, type PluginConfigSource, type ResolvePluginConfigInput, type ResolvedPluginConfig, redactPluginConfig, resolvePluginConfig, resolvePluginManifestConfig, validatePluginConfigMetadata, } from './config.js';
2
+ export { diffPluginConfig, type PluginConfigChange, type PluginConfigSource, type PluginEnablementSource, pluginEntryMatchesName, type ResolvePluginConfigInput, type ResolvePluginEnablementInput, type ResolvedPluginConfig, type ResolvedPluginEnablement, redactPluginConfig, resolvePluginConfig, resolvePluginEnablement, resolvePluginManifestConfig, validatePluginConfigMetadata, } from './config.js';
3
3
  export { KERNEL_API_VERSION, loadPlugins, type LoadPluginsOptions, type PluginHostHandle, type PluginLoadFailure, unloadPlugins, } from './loader.js';
4
4
  export type { PluginAPI } from '../types/plugin.js';
5
5
  export { buildReviewerModelPool, createAutoReviewPlugin, parseReviewSeverity, type ReviewerModelAssignment, selectRoundRobinReviewerAssignment, } from '../plugins/auto-review-plugin.js';
@@ -1275,6 +1275,36 @@ function resolvePluginConfig(input) {
1275
1275
  merge(input.explicitOptions, "explicit-options");
1276
1276
  return { options, configured, sources };
1277
1277
  }
1278
+ function pluginEntryMatchesName(configuredName, name, aliases = []) {
1279
+ for (const candidate of [name, ...aliases]) {
1280
+ if (configuredName === candidate) return true;
1281
+ if (configuredName === `@wrongstack/plugins/${candidate}`) return true;
1282
+ }
1283
+ return false;
1284
+ }
1285
+ function resolvePluginEnablement(input) {
1286
+ if (input.config?.features?.plugins === false) {
1287
+ return { enabled: false, source: "feature-flag" };
1288
+ }
1289
+ const names = [.../* @__PURE__ */ new Set([input.name, ...input.aliases ?? []])];
1290
+ const matches2 = input.matches ?? ((configuredName) => pluginEntryMatchesName(configuredName, input.name, input.aliases ?? []));
1291
+ const plugins = input.config?.plugins;
1292
+ if (Array.isArray(plugins)) {
1293
+ for (const candidate of plugins) {
1294
+ if (typeof candidate === "string") {
1295
+ if (matches2(candidate)) return { enabled: true, source: "plugin-entry" };
1296
+ continue;
1297
+ }
1298
+ if (!isPluginEntry(candidate) || !matches2(candidate.name)) continue;
1299
+ return { enabled: candidate.enabled !== false, source: "plugin-entry" };
1300
+ }
1301
+ }
1302
+ for (const name of names) {
1303
+ const enabled = input.config?.extensions?.[name]?.["enabled"];
1304
+ if (typeof enabled === "boolean") return { enabled, source: "extension" };
1305
+ }
1306
+ return { enabled: input.defaultState === "active", source: "default" };
1307
+ }
1278
1308
  function resolvePluginManifestConfig(plugin, config, explicitOptions) {
1279
1309
  return resolvePluginConfig({
1280
1310
  name: plugin.name,
@@ -1740,11 +1770,12 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
1740
1770
  });
1741
1771
  const wrappedLlm = caps.llm !== false || !api.llm ? api.llm : new Proxy(api.llm, {
1742
1772
  get(target, prop, receiver) {
1743
- if (prop === "complete") {
1773
+ if (prop === "complete" || prop === "council") {
1744
1774
  return (prompt, options) => {
1745
- violate("llm", `complete(${prompt.length} chars)`);
1746
- const complete = target.complete;
1747
- return options === void 0 ? complete(prompt) : complete(prompt, options);
1775
+ violate("llm", `${String(prop)}(${prompt.length} chars)`);
1776
+ const method = target[prop];
1777
+ if (!method) return void 0;
1778
+ return options === void 0 ? method.call(target, prompt) : method.call(target, prompt, options);
1748
1779
  };
1749
1780
  }
1750
1781
  return Reflect.get(target, prop, receiver);
@@ -1973,6 +2004,8 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
1973
2004
  const currentModel = () => hostLLM.getModel?.() ?? hostLLM.model;
1974
2005
  const DEFAULT_MAX_TOKENS = 2048;
1975
2006
  const HARD_MAX_TOKENS = 32768;
2007
+ const DEFAULT_TIMEOUT_MS = 3e4;
2008
+ const HARD_TIMEOUT_MS = 12e4;
1976
2009
  const pluginDefaults = () => {
1977
2010
  const extensions = currentConfig().extensions;
1978
2011
  const raw = extensions?.[owner]?.["llm"];
@@ -1982,7 +2015,11 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
1982
2015
  ...typeof r["provider"] === "string" && r["provider"] ? { provider: r["provider"] } : {},
1983
2016
  ...typeof r["model"] === "string" && r["model"] ? { model: r["model"] } : {},
1984
2017
  ...typeof r["maxTokens"] === "number" ? { maxTokens: r["maxTokens"] } : {},
1985
- ...typeof r["temperature"] === "number" ? { temperature: r["temperature"] } : {}
2018
+ ...typeof r["temperature"] === "number" ? { temperature: r["temperature"] } : {},
2019
+ ...typeof r["role"] === "string" && r["role"] ? { role: r["role"] } : {},
2020
+ ...Array.isArray(r["fallbackModels"]) && r["fallbackModels"].every((v) => typeof v === "string") ? { fallbackModels: [...r["fallbackModels"]] } : {},
2021
+ ...typeof r["timeoutMs"] === "number" ? { timeoutMs: r["timeoutMs"] } : {},
2022
+ ...typeof r["councilProfile"] === "string" && r["councilProfile"] ? { councilProfile: r["councilProfile"] } : {}
1986
2023
  };
1987
2024
  };
1988
2025
  const resolveProvider = (name, model) => {
@@ -2019,7 +2056,7 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
2019
2056
  providerCache.set(cacheKey, created);
2020
2057
  return { provider: created, providerName };
2021
2058
  };
2022
- return {
2059
+ const pluginLlm = {
2023
2060
  defaults() {
2024
2061
  const d = pluginDefaults();
2025
2062
  return {
@@ -2030,12 +2067,49 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
2030
2067
  async complete(prompt, opts) {
2031
2068
  const defaults = pluginDefaults();
2032
2069
  const model = opts?.model ?? defaults.model ?? currentModel();
2033
- const { provider, providerName } = resolveProvider(opts?.provider, model);
2070
+ const providerName = opts?.provider ?? defaults.provider ?? currentProvider().id;
2034
2071
  const maxTokens = Math.min(
2035
2072
  HARD_MAX_TOKENS,
2036
2073
  opts?.maxTokens ?? defaults.maxTokens ?? DEFAULT_MAX_TOKENS
2037
2074
  );
2038
2075
  const temperature = opts?.temperature ?? defaults.temperature;
2076
+ const timeoutMs = Math.min(
2077
+ HARD_TIMEOUT_MS,
2078
+ Math.max(1, opts?.timeoutMs ?? defaults.timeoutMs ?? DEFAULT_TIMEOUT_MS)
2079
+ );
2080
+ if (hostLLM.oneShot) {
2081
+ metrics.counter("llm.calls", 1, { provider: providerName, model, engine: "one-shot" });
2082
+ const result = await hostLLM.oneShot({
2083
+ userPrompt: prompt,
2084
+ providerId: providerName,
2085
+ model,
2086
+ maxTokens,
2087
+ timeoutMs,
2088
+ ...temperature !== void 0 ? { temperature } : {},
2089
+ ...opts?.system ? { system: opts.system } : {},
2090
+ ...opts?.responseFormat === "json" ? { responseFormat: { type: "json_object" } } : {},
2091
+ ...opts?.signal ? { signal: opts.signal } : {},
2092
+ ...opts?.role ?? defaults.role ? { role: opts?.role ?? defaults.role } : {},
2093
+ ...opts?.fallbackModels ?? defaults.fallbackModels ? { fallbackModels: [...opts?.fallbackModels ?? defaults.fallbackModels ?? []] } : {}
2094
+ });
2095
+ if (result.error) {
2096
+ metrics.counter("llm.errors", 1, { provider: result.provider, model: result.model });
2097
+ throw new Error(result.error);
2098
+ }
2099
+ metrics.counter("llm.tokens_in", result.tokens.input);
2100
+ metrics.counter("llm.tokens_out", result.tokens.output);
2101
+ return {
2102
+ text: result.text,
2103
+ model: result.model,
2104
+ provider: result.provider,
2105
+ usage: { input: result.tokens.input, output: result.tokens.output },
2106
+ stopReason: result.stopReason ?? "end_turn",
2107
+ fromFallback: result.fromFallback,
2108
+ attempts: result.attempts,
2109
+ durationMs: result.durationMs
2110
+ };
2111
+ }
2112
+ const { provider } = resolveProvider(providerName, model);
2039
2113
  const request = {
2040
2114
  model,
2041
2115
  messages: [{ role: "user", content: [{ type: "text", text: prompt }] }],
@@ -2044,7 +2118,8 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
2044
2118
  ...opts?.system ? { system: [{ type: "text", text: opts.system }] } : {},
2045
2119
  ...opts?.responseFormat === "json" ? { responseFormat: { type: "json_object" } } : {}
2046
2120
  };
2047
- const signal = opts?.signal ?? new AbortController().signal;
2121
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
2122
+ const signal = opts?.signal ? AbortSignal.any([opts.signal, timeoutSignal]) : timeoutSignal;
2048
2123
  metrics.counter("llm.calls", 1, { provider: providerName, model });
2049
2124
  try {
2050
2125
  const response = await provider.complete(request, { signal });
@@ -2069,6 +2144,33 @@ function makePluginLLM(owner, hostLLM, providerRegistry, config, getLiveConfig,
2069
2144
  }
2070
2145
  }
2071
2146
  };
2147
+ const council = hostLLM.council;
2148
+ if (council) {
2149
+ pluginLlm.council = async (question, opts) => {
2150
+ const trimmed = question.trim();
2151
+ if (!trimmed) throw new Error("Plugin Council question must not be empty.");
2152
+ if (trimmed.length > 2e4) {
2153
+ throw new Error("Plugin Council question must not exceed 20000 characters.");
2154
+ }
2155
+ if ((opts?.context?.length ?? 0) > 8e4) {
2156
+ throw new Error("Plugin Council context must not exceed 80000 characters.");
2157
+ }
2158
+ const defaults = pluginDefaults();
2159
+ metrics.counter("llm.council.calls", 1, { plugin: owner });
2160
+ const result = await council({
2161
+ question: trimmed,
2162
+ ...opts?.context ? { context: opts.context } : {},
2163
+ ...opts?.options ? { options: opts.options } : {},
2164
+ ...opts?.profile ?? defaults.councilProfile ? { profile: opts?.profile ?? defaults.councilProfile } : {},
2165
+ ...opts?.signal ? { signal: opts.signal } : {}
2166
+ });
2167
+ metrics.counter("llm.tokens_in", result.usage.inputTokens);
2168
+ metrics.counter("llm.tokens_out", result.usage.outputTokens);
2169
+ if (result.status === "failed") metrics.counter("llm.errors", 1, { provider: "council" });
2170
+ return result;
2171
+ };
2172
+ }
2173
+ return pluginLlm;
2072
2174
  }
2073
2175
  function scopedMetrics(sink, pluginName) {
2074
2176
  const prefix = `plugin.${pluginName}.`;
@@ -2603,7 +2705,9 @@ function safeProfileName(name) {
2603
2705
  function activeProfileName(globalRoot) {
2604
2706
  try {
2605
2707
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
2606
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
2708
+ return safeProfileName(
2709
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
2710
+ );
2607
2711
  } catch {
2608
2712
  return "default";
2609
2713
  }
@@ -2683,6 +2787,7 @@ function resolveWstackPaths(opts) {
2683
2787
  projectPlan: path.join(projectDir, "plan.json"),
2684
2788
  projectAutophase: path.join(projectDir, "autophase"),
2685
2789
  projectSddBoards: path.join(projectDir, "sdd-boards"),
2790
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
2686
2791
  syncConfig: path.join(profileDir, "sync.json"),
2687
2792
  configHistoryDir: path.join(globalRoot, "config-history"),
2688
2793
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -8955,7 +9060,7 @@ function createPromptsPlugin(opts) {
8955
9060
  version: "1.0.0",
8956
9061
  description: "Prompt library with 100+ builtin prompts, search, and AI authoring",
8957
9062
  apiVersion: "^0.1",
8958
- capabilities: { slashCommands: true },
9063
+ capabilities: { slashCommands: true, llm: true },
8959
9064
  defaultConfig: {},
8960
9065
  setup(api) {
8961
9066
  const rawConfig = api.config;
@@ -8966,7 +9071,7 @@ function createPromptsPlugin(opts) {
8966
9071
  bundledDir: rawConfig["bundledPromptsDir"]
8967
9072
  }) : null);
8968
9073
  usage = opts?.usage ?? (paths ? new PromptUsageStore(paths.promptUsage) : null);
8969
- api.slashCommands.register(buildPromptsCommand(() => store, () => loader));
9074
+ api.slashCommands.register(buildPromptsCommand(() => store, () => loader, () => api.llm));
8970
9075
  api.slashCommands.register(buildPromptSearchCommand(() => loader, () => usage));
8971
9076
  api.slashCommands.register(buildPromptGenCommand(() => loader));
8972
9077
  api.log.info("[prompts] loaded \u2014 /prompts, /prompt, /prompt-gen available");
@@ -8982,7 +9087,7 @@ function createPromptsPlugin(opts) {
8982
9087
  }
8983
9088
  };
8984
9089
  }
8985
- function buildPromptsCommand(getStore, getLoader) {
9090
+ function buildPromptsCommand(getStore, getLoader, getLlm) {
8986
9091
  return {
8987
9092
  name: "prompts",
8988
9093
  description: "Manage your prompt library: /prompts [list|view|add|edit|delete|favorite|extend]",
@@ -9077,16 +9182,25 @@ ${lines.join("\n")}
9077
9182
  const matches2 = await store.find(parsed.title);
9078
9183
  if (matches2.length === 0) return { message: `No prompt matching "${parsed.title}".` };
9079
9184
  const exact = matches2.find((m) => m.title.toLowerCase() === parsed.title?.toLowerCase()) ?? expectDefined(matches2[0]);
9080
- const prov = ctx.provider;
9081
- if (!prov?.complete) return { message: "LLM not available. Configure a provider first." };
9082
- const enhanced = await prov.complete(
9083
- ctx.model,
9084
- renderInstructionTemplate(readBundledInstructionText("llm/prompt-extend.md"), {
9085
- existingPrompt: exact.content,
9086
- additionalInstructions: parsed.content
9087
- })
9088
- );
9089
- exact.content = enhanced.trim();
9185
+ const llm = getLlm();
9186
+ if (!llm) return { message: "LLM not available. Configure a provider first." };
9187
+ let enhanced;
9188
+ try {
9189
+ enhanced = await llm.complete(
9190
+ renderInstructionTemplate(readBundledInstructionText("llm/prompt-extend.md"), {
9191
+ existingPrompt: exact.content,
9192
+ additionalInstructions: parsed.content
9193
+ }),
9194
+ {
9195
+ system: "You improve reusable prompts while preserving their intent and variables.",
9196
+ role: "prompt-refiner",
9197
+ maxTokens: 2048
9198
+ }
9199
+ );
9200
+ } catch {
9201
+ return { message: "LLM enhancement failed. The saved prompt was not changed." };
9202
+ }
9203
+ exact.content = enhanced.text.trim();
9090
9204
  exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9091
9205
  await store.save(exact);
9092
9206
  getLoader()?.invalidateCache();
@@ -11970,10 +12084,12 @@ export {
11970
12084
  parseChimeraReviewReport,
11971
12085
  parseReviewSeverity,
11972
12086
  persistReviewReport,
12087
+ pluginEntryMatchesName,
11973
12088
  recordCompletedReview,
11974
12089
  recordStartedReview,
11975
12090
  redactPluginConfig,
11976
12091
  resolvePluginConfig,
12092
+ resolvePluginEnablement,
11977
12093
  resolvePluginManifestConfig,
11978
12094
  selectRoundRobinReviewerAssignment,
11979
12095
  unloadPlugins,
@@ -10,7 +10,7 @@ export { AutoApprovePermissionPolicy, alwaysAllowUnavailableReason, DefaultPermi
10
10
  export { TRUST_POLICY_JSON_SCHEMA, TRUST_POLICY_LIMITS, TRUST_POLICY_SCHEMA_VERSION, type TrustPolicyDiagnostic, type TrustPolicyDiagnosticCode, type TrustPolicyValidationResult, validateTrustPolicy, } from './permission-policy-schema.js';
11
11
  export { ReadOnlyPermissionPolicy } from './readonly-permission-policy.js';
12
12
  export { DefaultSecretScrubber } from './secret-scrubber.js';
13
- export { base32Decode, base32Encode, buildOtpAuthUri, generateRecoveryCodes, generateTotpSecret, generateTotp, hashRecoveryCode, verifyRecoveryCode, verifyTotp, } from './totp.js';
13
+ export { base32Decode, base32Encode, buildOtpAuthUri, generateRecoveryCodes, generateTotpSecret, generateTotp, hashRecoveryCode, verifyRecoveryCode, verifyTotp, verifyTotpCounter, } from './totp.js';
14
14
  export { DefaultSecretVault, migratePlaintextSecrets, rewriteConfigEncrypted, rotateConfigKeys, type SecretVaultOptions, } from './secret-vault.js';
15
15
  export type { CompatibilityTrustBoundaryOptions, TrustActor, TrustActorKind, TrustAllowDecision, TrustAttribute, TrustAuthContext, TrustAuthMethod, TrustBoundary, TrustBoundaryAuditEntry, TrustBoundaryDecision, TrustBoundaryRequest, TrustConfirmDecision, TrustDecodeIssue, TrustDecodeResult, TrustDenyDecision, TrustRisk, TrustScope, TrustScopedTokenDecision, TrustSubject, TrustSurface, } from './trust-boundary.js';
16
16
  export { createCompatibilityTrustBoundary, decodeTrustBoundaryDecision, decodeTrustBoundaryRequest, isTrustDecisionAllowed, TRUST_BOUNDARY_VERSION, } from './trust-boundary.js';