@wrongstack/core 0.299.0 → 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 (50) hide show
  1. package/dist/coordination/director.d.ts +8 -0
  2. package/dist/coordination/fleet-manager.d.ts +48 -3
  3. package/dist/coordination/ifleet-manager.d.ts +2 -0
  4. package/dist/coordination/index.js +120 -20
  5. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  6. package/dist/core/fallback-model.d.ts +48 -0
  7. package/dist/core/index.d.ts +3 -2
  8. package/dist/core/index.js +226 -26
  9. package/dist/core/instruction-template.d.ts +80 -0
  10. package/dist/core/system-prompt-blocks.d.ts +10 -1
  11. package/dist/core/system-prompt-builder.d.ts +35 -1
  12. package/dist/defaults/index.js +238 -99
  13. package/dist/execution/autonomy-brain.d.ts +7 -0
  14. package/dist/execution/council-brain.d.ts +11 -0
  15. package/dist/execution/council-orchestrator.d.ts +23 -4
  16. package/dist/execution/council-prompts.d.ts +12 -1
  17. package/dist/execution/index.js +355 -138
  18. package/dist/fleet-notifier.d.ts +9 -2
  19. package/dist/hooks/index.js +8 -4
  20. package/dist/hq/index.js +18 -4
  21. package/dist/hq/protocol/fleet.d.ts +20 -0
  22. package/dist/hq/protocol.js +10 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1512 -707
  25. package/dist/kernel/events/brain-events.d.ts +9 -0
  26. package/dist/kernel/events/provider-events.d.ts +42 -1
  27. package/dist/models/index.js +1 -1
  28. package/dist/plugin/api.d.ts +6 -0
  29. package/dist/plugin/config.d.ts +55 -0
  30. package/dist/plugin/index.d.ts +1 -1
  31. package/dist/plugin/index.js +134 -21
  32. package/dist/security/index.d.ts +1 -1
  33. package/dist/security/index.js +157 -42
  34. package/dist/security/permission-helpers.d.ts +23 -6
  35. package/dist/security/permission-policy.d.ts +16 -0
  36. package/dist/security/totp.d.ts +14 -0
  37. package/dist/storage/director-state.d.ts +7 -0
  38. package/dist/storage/index.js +33 -8
  39. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  40. package/dist/tools/index.js +388 -102
  41. package/dist/types/council.d.ts +11 -0
  42. package/dist/types/index.d.ts +1 -1
  43. package/dist/types/multi-agent.d.ts +10 -0
  44. package/dist/types/one-shot-llm.d.ts +9 -0
  45. package/dist/types/plugin.d.ts +28 -0
  46. package/dist/worktree/index.js +4 -4
  47. package/instructions/system-lite.md +81 -3
  48. package/instructions/system-pro.md +275 -90
  49. package/instructions/system.md +228 -81
  50. package/package.json +3 -3
@@ -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;
@@ -160,6 +160,8 @@ export interface ProviderEventMap {
160
160
  };
161
161
  status: number;
162
162
  providerSwitched: boolean;
163
+ /** Gate correlation id — set when a fallback gate mediated the switch. */
164
+ requestId?: string | undefined;
163
165
  contextWindowWarning?: {
164
166
  fromMaxContext: number;
165
167
  toMaxContext: number;
@@ -167,7 +169,33 @@ export interface ProviderEventMap {
167
169
  } | undefined;
168
170
  };
169
171
  /**
170
- * 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
171
199
  * healthy/degraded/blocked states. The tracker emits this so the
172
200
  * CLI/TUI/WebUI can render a live status indicator.
173
201
  */
@@ -180,6 +208,19 @@ export interface ProviderEventMap {
180
208
  timestamp: number;
181
209
  stateExpiresAt?: number | undefined;
182
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
+ };
183
224
  /**
184
225
  * Fired when the agent's actively selected (primary, not fallback)
185
226
  * provider/model is blocked and will be skipped. The CLI/TUI should
@@ -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}.`;
@@ -8958,7 +9060,7 @@ function createPromptsPlugin(opts) {
8958
9060
  version: "1.0.0",
8959
9061
  description: "Prompt library with 100+ builtin prompts, search, and AI authoring",
8960
9062
  apiVersion: "^0.1",
8961
- capabilities: { slashCommands: true },
9063
+ capabilities: { slashCommands: true, llm: true },
8962
9064
  defaultConfig: {},
8963
9065
  setup(api) {
8964
9066
  const rawConfig = api.config;
@@ -8969,7 +9071,7 @@ function createPromptsPlugin(opts) {
8969
9071
  bundledDir: rawConfig["bundledPromptsDir"]
8970
9072
  }) : null);
8971
9073
  usage = opts?.usage ?? (paths ? new PromptUsageStore(paths.promptUsage) : null);
8972
- api.slashCommands.register(buildPromptsCommand(() => store, () => loader));
9074
+ api.slashCommands.register(buildPromptsCommand(() => store, () => loader, () => api.llm));
8973
9075
  api.slashCommands.register(buildPromptSearchCommand(() => loader, () => usage));
8974
9076
  api.slashCommands.register(buildPromptGenCommand(() => loader));
8975
9077
  api.log.info("[prompts] loaded \u2014 /prompts, /prompt, /prompt-gen available");
@@ -8985,7 +9087,7 @@ function createPromptsPlugin(opts) {
8985
9087
  }
8986
9088
  };
8987
9089
  }
8988
- function buildPromptsCommand(getStore, getLoader) {
9090
+ function buildPromptsCommand(getStore, getLoader, getLlm) {
8989
9091
  return {
8990
9092
  name: "prompts",
8991
9093
  description: "Manage your prompt library: /prompts [list|view|add|edit|delete|favorite|extend]",
@@ -9080,16 +9182,25 @@ ${lines.join("\n")}
9080
9182
  const matches2 = await store.find(parsed.title);
9081
9183
  if (matches2.length === 0) return { message: `No prompt matching "${parsed.title}".` };
9082
9184
  const exact = matches2.find((m) => m.title.toLowerCase() === parsed.title?.toLowerCase()) ?? expectDefined(matches2[0]);
9083
- const prov = ctx.provider;
9084
- if (!prov?.complete) return { message: "LLM not available. Configure a provider first." };
9085
- const enhanced = await prov.complete(
9086
- ctx.model,
9087
- renderInstructionTemplate(readBundledInstructionText("llm/prompt-extend.md"), {
9088
- existingPrompt: exact.content,
9089
- additionalInstructions: parsed.content
9090
- })
9091
- );
9092
- 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();
9093
9204
  exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9094
9205
  await store.save(exact);
9095
9206
  getLoader()?.invalidateCache();
@@ -11973,10 +12084,12 @@ export {
11973
12084
  parseChimeraReviewReport,
11974
12085
  parseReviewSeverity,
11975
12086
  persistReviewReport,
12087
+ pluginEntryMatchesName,
11976
12088
  recordCompletedReview,
11977
12089
  recordStartedReview,
11978
12090
  redactPluginConfig,
11979
12091
  resolvePluginConfig,
12092
+ resolvePluginEnablement,
11980
12093
  resolvePluginManifestConfig,
11981
12094
  selectRoundRobinReviewerAssignment,
11982
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';