ai-runtime-engine 1.1.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 (269) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +15 -0
  3. package/README.md +489 -0
  4. package/dist/artifacts/artifacts.d.ts +28 -0
  5. package/dist/artifacts/artifacts.js +46 -0
  6. package/dist/benchmark/benchmark.d.ts +23 -0
  7. package/dist/benchmark/benchmark.js +40 -0
  8. package/dist/cli/cli.d.ts +6 -0
  9. package/dist/cli/cli.js +161 -0
  10. package/dist/cli/commands/cleanup.d.ts +47 -0
  11. package/dist/cli/commands/cleanup.js +96 -0
  12. package/dist/cli/commands/config.d.ts +8 -0
  13. package/dist/cli/commands/config.js +28 -0
  14. package/dist/cli/commands/doctor.d.ts +57 -0
  15. package/dist/cli/commands/doctor.js +86 -0
  16. package/dist/cli/commands/executions.d.ts +9 -0
  17. package/dist/cli/commands/executions.js +25 -0
  18. package/dist/cli/commands/info.d.ts +43 -0
  19. package/dist/cli/commands/info.js +53 -0
  20. package/dist/cli/commands/init.d.ts +5 -0
  21. package/dist/cli/commands/init.js +75 -0
  22. package/dist/cli/commands/inspect.d.ts +16 -0
  23. package/dist/cli/commands/inspect.js +60 -0
  24. package/dist/cli/commands/phase2.d.ts +22 -0
  25. package/dist/cli/commands/phase2.js +83 -0
  26. package/dist/cli/commands/route.d.ts +14 -0
  27. package/dist/cli/commands/route.js +49 -0
  28. package/dist/cli/commands/run.d.ts +11 -0
  29. package/dist/cli/commands/run.js +37 -0
  30. package/dist/cli/commands/setup.d.ts +34 -0
  31. package/dist/cli/commands/setup.js +104 -0
  32. package/dist/cli/commands/skills.d.ts +28 -0
  33. package/dist/cli/commands/skills.js +48 -0
  34. package/dist/cli/commands/test.d.ts +7 -0
  35. package/dist/cli/commands/test.js +29 -0
  36. package/dist/cli/context.d.ts +12 -0
  37. package/dist/cli/context.js +16 -0
  38. package/dist/cli/interactive/repl.d.ts +6 -0
  39. package/dist/cli/interactive/repl.js +45 -0
  40. package/dist/cli/interactive/session.d.ts +36 -0
  41. package/dist/cli/interactive/session.js +356 -0
  42. package/dist/cli/prompt.d.ts +6 -0
  43. package/dist/cli/prompt.js +18 -0
  44. package/dist/cli/render.d.ts +7 -0
  45. package/dist/cli/render.js +14 -0
  46. package/dist/comparison/analysis.d.ts +46 -0
  47. package/dist/comparison/analysis.js +177 -0
  48. package/dist/comparison/comparator.d.ts +46 -0
  49. package/dist/comparison/comparator.js +270 -0
  50. package/dist/comparison/comparison.d.ts +140 -0
  51. package/dist/comparison/comparison.js +9 -0
  52. package/dist/comparison/render.d.ts +7 -0
  53. package/dist/comparison/render.js +66 -0
  54. package/dist/config/defaults.d.ts +52 -0
  55. package/dist/config/defaults.js +56 -0
  56. package/dist/config/load.d.ts +17 -0
  57. package/dist/config/load.js +50 -0
  58. package/dist/config/providerDefaults.d.ts +17 -0
  59. package/dist/config/providerDefaults.js +61 -0
  60. package/dist/config/schema.d.ts +9 -0
  61. package/dist/config/schema.js +78 -0
  62. package/dist/context/budget.d.ts +13 -0
  63. package/dist/context/budget.js +17 -0
  64. package/dist/context/compiler.d.ts +61 -0
  65. package/dist/context/compiler.js +125 -0
  66. package/dist/context/tokens.d.ts +19 -0
  67. package/dist/context/tokens.js +38 -0
  68. package/dist/conversations/conversations.d.ts +38 -0
  69. package/dist/conversations/conversations.js +64 -0
  70. package/dist/core/capabilities/evidence.d.ts +40 -0
  71. package/dist/core/capabilities/evidence.js +102 -0
  72. package/dist/core/capabilities/overlay.d.ts +15 -0
  73. package/dist/core/capabilities/overlay.js +0 -0
  74. package/dist/core/capabilities/taxonomy.d.ts +19 -0
  75. package/dist/core/capabilities/taxonomy.js +25 -0
  76. package/dist/core/fallback/errors.d.ts +30 -0
  77. package/dist/core/fallback/errors.js +80 -0
  78. package/dist/core/fallback/fallback.d.ts +40 -0
  79. package/dist/core/fallback/fallback.js +82 -0
  80. package/dist/core/fallback/retryPolicy.d.ts +11 -0
  81. package/dist/core/fallback/retryPolicy.js +14 -0
  82. package/dist/core/health/health.d.ts +3 -0
  83. package/dist/core/health/health.js +5 -0
  84. package/dist/core/health/monitor.d.ts +23 -0
  85. package/dist/core/health/monitor.js +82 -0
  86. package/dist/core/policies/budget.d.ts +19 -0
  87. package/dist/core/policies/budget.js +37 -0
  88. package/dist/core/registry/builtinTasks.d.ts +8 -0
  89. package/dist/core/registry/builtinTasks.js +54 -0
  90. package/dist/core/registry/registry.d.ts +18 -0
  91. package/dist/core/registry/registry.js +33 -0
  92. package/dist/core/registry/taskRegistry.d.ts +15 -0
  93. package/dist/core/registry/taskRegistry.js +30 -0
  94. package/dist/core/router/confidence.d.ts +7 -0
  95. package/dist/core/router/confidence.js +20 -0
  96. package/dist/core/router/dimensions.d.ts +16 -0
  97. package/dist/core/router/dimensions.js +60 -0
  98. package/dist/core/router/executor.d.ts +16 -0
  99. package/dist/core/router/executor.js +25 -0
  100. package/dist/core/router/filter.d.ts +34 -0
  101. package/dist/core/router/filter.js +113 -0
  102. package/dist/core/router/normalize.d.ts +30 -0
  103. package/dist/core/router/normalize.js +119 -0
  104. package/dist/core/router/request.d.ts +4 -0
  105. package/dist/core/router/request.js +21 -0
  106. package/dist/core/router/router.d.ts +32 -0
  107. package/dist/core/router/router.js +195 -0
  108. package/dist/core/router/routingPrefs.d.ts +11 -0
  109. package/dist/core/router/routingPrefs.js +30 -0
  110. package/dist/core/router/scorer.d.ts +19 -0
  111. package/dist/core/router/scorer.js +50 -0
  112. package/dist/core/router/weights.d.ts +9 -0
  113. package/dist/core/router/weights.js +31 -0
  114. package/dist/core/validation/validator.d.ts +16 -0
  115. package/dist/core/validation/validator.js +33 -0
  116. package/dist/discovery/modelCatalog.d.ts +28 -0
  117. package/dist/discovery/modelCatalog.js +105 -0
  118. package/dist/discovery/openapi.d.ts +25 -0
  119. package/dist/discovery/openapi.js +76 -0
  120. package/dist/executions/checkpoint.d.ts +26 -0
  121. package/dist/executions/checkpoint.js +114 -0
  122. package/dist/executions/execution.d.ts +51 -0
  123. package/dist/executions/execution.js +8 -0
  124. package/dist/executions/store.d.ts +52 -0
  125. package/dist/executions/store.js +124 -0
  126. package/dist/generation/generateAdapter.d.ts +17 -0
  127. package/dist/generation/generateAdapter.js +30 -0
  128. package/dist/index.d.ts +147 -0
  129. package/dist/index.js +107 -0
  130. package/dist/learning/feedback.d.ts +9 -0
  131. package/dist/learning/feedback.js +18 -0
  132. package/dist/learning/learningStore.d.ts +68 -0
  133. package/dist/learning/learningStore.js +138 -0
  134. package/dist/learning/performanceStore.d.ts +27 -0
  135. package/dist/learning/performanceStore.js +0 -0
  136. package/dist/marketplace/presets.d.ts +24 -0
  137. package/dist/marketplace/presets.js +52 -0
  138. package/dist/mcp/mcp.d.ts +31 -0
  139. package/dist/mcp/mcp.js +54 -0
  140. package/dist/memory/bm25.d.ts +16 -0
  141. package/dist/memory/bm25.js +56 -0
  142. package/dist/memory/classifier.d.ts +14 -0
  143. package/dist/memory/classifier.js +17 -0
  144. package/dist/memory/memory.d.ts +80 -0
  145. package/dist/memory/memory.js +191 -0
  146. package/dist/orchestration/executor.d.ts +35 -0
  147. package/dist/orchestration/executor.js +65 -0
  148. package/dist/orchestration/orchestrator.d.ts +42 -0
  149. package/dist/orchestration/orchestrator.js +63 -0
  150. package/dist/orchestration/plan.d.ts +37 -0
  151. package/dist/orchestration/plan.js +70 -0
  152. package/dist/orchestration/planner.d.ts +29 -0
  153. package/dist/orchestration/planner.js +69 -0
  154. package/dist/plugin/ai.d.ts +82 -0
  155. package/dist/plugin/ai.js +167 -0
  156. package/dist/probing/probe.d.ts +25 -0
  157. package/dist/probing/probe.js +63 -0
  158. package/dist/providers/factory.d.ts +18 -0
  159. package/dist/providers/factory.js +54 -0
  160. package/dist/providers/httpClient.d.ts +34 -0
  161. package/dist/providers/httpClient.js +80 -0
  162. package/dist/providers/httpProvider.d.ts +49 -0
  163. package/dist/providers/httpProvider.js +135 -0
  164. package/dist/providers/mock/demo.d.ts +13 -0
  165. package/dist/providers/mock/demo.js +58 -0
  166. package/dist/providers/mock/mockProvider.d.ts +35 -0
  167. package/dist/providers/mock/mockProvider.js +121 -0
  168. package/dist/providers/mock/scenarios.d.ts +44 -0
  169. package/dist/providers/mock/scenarios.js +30 -0
  170. package/dist/providers/provider.d.ts +26 -0
  171. package/dist/providers/provider.js +11 -0
  172. package/dist/providers/wire/anthropicWire.d.ts +6 -0
  173. package/dist/providers/wire/anthropicWire.js +83 -0
  174. package/dist/providers/wire/openaiWire.d.ts +7 -0
  175. package/dist/providers/wire/openaiWire.js +81 -0
  176. package/dist/providers/wire/registry.d.ts +8 -0
  177. package/dist/providers/wire/registry.js +20 -0
  178. package/dist/providers/wire/types.d.ts +39 -0
  179. package/dist/providers/wire/types.js +24 -0
  180. package/dist/runtime/config.d.ts +31 -0
  181. package/dist/runtime/config.js +121 -0
  182. package/dist/runtime/context.d.ts +34 -0
  183. package/dist/runtime/context.js +11 -0
  184. package/dist/runtime/events.d.ts +99 -0
  185. package/dist/runtime/events.js +82 -0
  186. package/dist/runtime/host.d.ts +27 -0
  187. package/dist/runtime/host.js +7 -0
  188. package/dist/runtime/intent/classifier.d.ts +30 -0
  189. package/dist/runtime/intent/classifier.js +60 -0
  190. package/dist/runtime/intent/signals.d.ts +19 -0
  191. package/dist/runtime/intent/signals.js +46 -0
  192. package/dist/runtime/modes/availability.d.ts +11 -0
  193. package/dist/runtime/modes/availability.js +17 -0
  194. package/dist/runtime/modes/chat.d.ts +18 -0
  195. package/dist/runtime/modes/chat.js +67 -0
  196. package/dist/runtime/modes/modeResolver.d.ts +43 -0
  197. package/dist/runtime/modes/modeResolver.js +78 -0
  198. package/dist/runtime/policy.d.ts +72 -0
  199. package/dist/runtime/policy.js +59 -0
  200. package/dist/runtime/providerView.d.ts +62 -0
  201. package/dist/runtime/providerView.js +105 -0
  202. package/dist/runtime/routing.d.ts +26 -0
  203. package/dist/runtime/routing.js +65 -0
  204. package/dist/runtime/runtime.d.ts +191 -0
  205. package/dist/runtime/runtime.js +718 -0
  206. package/dist/runtime/types.d.ts +153 -0
  207. package/dist/runtime/types.js +9 -0
  208. package/dist/runtime/workspace/detectors.d.ts +15 -0
  209. package/dist/runtime/workspace/detectors.js +57 -0
  210. package/dist/runtime/workspace/workspace.d.ts +29 -0
  211. package/dist/runtime/workspace/workspace.js +116 -0
  212. package/dist/security/credentials.d.ts +26 -0
  213. package/dist/security/credentials.js +34 -0
  214. package/dist/security/redact.d.ts +16 -0
  215. package/dist/security/redact.js +57 -0
  216. package/dist/skills/builtins/fileAnalyzer.d.ts +7 -0
  217. package/dist/skills/builtins/fileAnalyzer.js +47 -0
  218. package/dist/skills/builtins/repositoryAnalyzer.d.ts +6 -0
  219. package/dist/skills/builtins/repositoryAnalyzer.js +47 -0
  220. package/dist/skills/discovery.d.ts +61 -0
  221. package/dist/skills/discovery.js +211 -0
  222. package/dist/skills/manifest.d.ts +30 -0
  223. package/dist/skills/manifest.js +75 -0
  224. package/dist/skills/registry.d.ts +15 -0
  225. package/dist/skills/registry.js +22 -0
  226. package/dist/skills/skill.d.ts +64 -0
  227. package/dist/skills/skill.js +8 -0
  228. package/dist/store/area.d.ts +54 -0
  229. package/dist/store/area.js +164 -0
  230. package/dist/store/paths.d.ts +15 -0
  231. package/dist/store/paths.js +48 -0
  232. package/dist/store/store.d.ts +59 -0
  233. package/dist/store/store.js +140 -0
  234. package/dist/telemetry/sinks/file.d.ts +12 -0
  235. package/dist/telemetry/sinks/file.js +28 -0
  236. package/dist/telemetry/telemetry.d.ts +36 -0
  237. package/dist/telemetry/telemetry.js +63 -0
  238. package/dist/tools/builtins/filesystem.d.ts +7 -0
  239. package/dist/tools/builtins/filesystem.js +53 -0
  240. package/dist/tools/builtins/git.d.ts +10 -0
  241. package/dist/tools/builtins/git.js +66 -0
  242. package/dist/tools/builtins/shell.d.ts +17 -0
  243. package/dist/tools/builtins/shell.js +91 -0
  244. package/dist/tools/jail.d.ts +12 -0
  245. package/dist/tools/jail.js +98 -0
  246. package/dist/tools/permissions.d.ts +25 -0
  247. package/dist/tools/permissions.js +24 -0
  248. package/dist/tools/registry.d.ts +10 -0
  249. package/dist/tools/registry.js +20 -0
  250. package/dist/tools/runner.d.ts +23 -0
  251. package/dist/tools/runner.js +64 -0
  252. package/dist/tools/tool.d.ts +53 -0
  253. package/dist/tools/tool.js +24 -0
  254. package/dist/tools/untrusted.d.ts +13 -0
  255. package/dist/tools/untrusted.js +30 -0
  256. package/dist/types.d.ts +460 -0
  257. package/dist/types.js +12 -0
  258. package/dist/util/clock.d.ts +6 -0
  259. package/dist/util/clock.js +4 -0
  260. package/dist/util/extractJson.d.ts +8 -0
  261. package/dist/util/extractJson.js +54 -0
  262. package/dist/verification/verify.d.ts +26 -0
  263. package/dist/verification/verify.js +67 -0
  264. package/docs/GUIDE.md +358 -0
  265. package/docs/README.md +21 -0
  266. package/docs/architecture.md +78 -0
  267. package/docs/router.md +376 -0
  268. package/docs/security.md +55 -0
  269. package/package.json +67 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `ai-runtime test <provider>` — one minimal LIVE call to confirm a provider works. Skipped with a
3
+ * clear message when the provider has no configured key (so it is safe to run without secrets).
4
+ */
5
+ export declare function testCommand(providerId: string, opts: {
6
+ config?: string;
7
+ }): Promise<void>;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * `ai-runtime test <provider>` — one minimal LIVE call to confirm a provider works. Skipped with a
3
+ * clear message when the provider has no configured key (so it is safe to run without secrets).
4
+ */
5
+ import { loadAI } from '../context.js';
6
+ import { print, printError } from '../render.js';
7
+ export async function testCommand(providerId, opts) {
8
+ const { ai, hasProviders } = loadAI(opts.config);
9
+ if (!hasProviders)
10
+ return print('No providers configured.');
11
+ const provider = ai.providers().find((p) => p.id === providerId);
12
+ if (!provider)
13
+ return printError(`unknown provider '${providerId}'`);
14
+ const health = await ai.checkHealth();
15
+ const h = health.find((x) => x.providerId === providerId);
16
+ if (h?.state === 'NOT_CONFIGURED') {
17
+ return print(`⚠ ${providerId}: not configured${h.detail ? ` — ${h.detail}` : ''} (skipped)`);
18
+ }
19
+ print(`Testing ${providerId} with a live call...`);
20
+ const result = await ai.run({ task: 'smoke-test', input: 'Reply with the single word: ok', provider: providerId });
21
+ if (result.ok) {
22
+ const last = result.routing.attempts[result.routing.attempts.length - 1];
23
+ print(`✓ ${providerId}: ${result.routing.selected?.model} responded in ${last?.latencyMs ?? '?'}ms`);
24
+ }
25
+ else {
26
+ printError(`✗ ${providerId} [${result.error?.category}]: ${result.error?.message}`);
27
+ process.exitCode = 1;
28
+ }
29
+ }
@@ -0,0 +1,12 @@
1
+ /** Shared CLI helper: build an AI instance from the runtime config (or an empty config). */
2
+ import { AI } from '../plugin/ai.js';
3
+ import type { AIOptions } from '../plugin/ai.js';
4
+ /**
5
+ * Load an AI for the CLI. Now sits on the runtime config layer, so `.ai-runtime/config.yaml` is honored
6
+ * (root `ai-runtime.yaml` still works as a fallback). The return shape is unchanged for callers.
7
+ */
8
+ export declare function loadAI(configPath?: string, options?: AIOptions): {
9
+ ai: AI;
10
+ hasProviders: boolean;
11
+ configFile?: string;
12
+ };
@@ -0,0 +1,16 @@
1
+ /** Shared CLI helper: build an AI instance from the runtime config (or an empty config). */
2
+ import { AI } from '../plugin/ai.js';
3
+ import { loadRuntimeConfig } from '../runtime/config.js';
4
+ /**
5
+ * Load an AI for the CLI. Now sits on the runtime config layer, so `.ai-runtime/config.yaml` is honored
6
+ * (root `ai-runtime.yaml` still works as a fallback). The return shape is unchanged for callers.
7
+ */
8
+ export function loadAI(configPath, options) {
9
+ const loaded = loadRuntimeConfig({ workspaceRoot: process.cwd(), ...(configPath ? { explicitPath: configPath } : {}) });
10
+ const ai = new AI(loaded.config.router, options);
11
+ return {
12
+ ai,
13
+ hasProviders: loaded.config.router.providers.length > 0,
14
+ ...(loaded.configFile ? { configFile: loaded.configFile } : {}),
15
+ };
16
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The interactive REPL — a thin node:readline wrapper around ReplSession. All output goes through the
3
+ * redacted `print`. Progress is shown by subscribing to the runtime's lifecycle events. Zero new deps.
4
+ */
5
+ /** Start the interactive session. Resolves when the user exits (or stdin closes). */
6
+ export declare function startRepl(configPath?: string): Promise<void>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The interactive REPL — a thin node:readline wrapper around ReplSession. All output goes through the
3
+ * redacted `print`. Progress is shown by subscribing to the runtime's lifecycle events. Zero new deps.
4
+ */
5
+ import { createInterface } from 'node:readline';
6
+ import { Runtime } from '../../runtime/runtime.js';
7
+ import { print, printError } from '../render.js';
8
+ import { summarizeWorkspace } from '../../runtime/workspace/workspace.js';
9
+ import { ReplSession } from './session.js';
10
+ function banner(rt) {
11
+ const ws = rt.workspaceInfo();
12
+ print('ai-runtime');
13
+ if (ws)
14
+ print(summarizeWorkspace(ws));
15
+ print(`mode: auto · ${rt.ai.providers().length} providers · type /help or a request, /exit to quit`);
16
+ print('');
17
+ }
18
+ /** Start the interactive session. Resolves when the user exits (or stdin closes). */
19
+ export async function startRepl(configPath) {
20
+ const rt = await Runtime.load({ ...(configPath ? { config: configPath } : {}) });
21
+ const session = new ReplSession(rt);
22
+ banner(rt);
23
+ const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: '> ' });
24
+ rl.prompt();
25
+ for await (const line of rl) {
26
+ let result;
27
+ try {
28
+ result = await session.handle(line);
29
+ }
30
+ catch (err) {
31
+ printError(`error: ${err instanceof Error ? err.message : String(err)}`);
32
+ rl.prompt();
33
+ continue;
34
+ }
35
+ if (result.clear)
36
+ process.stdout.write('\x1b[2J\x1b[H');
37
+ for (const l of result.lines)
38
+ print(l);
39
+ if (result.exit) {
40
+ rl.close();
41
+ return;
42
+ }
43
+ rl.prompt();
44
+ }
45
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ReplSession — the interactive command router, decoupled from readline so it is unit-testable without
3
+ * a TTY. `handle(line)` returns the lines to print (and control flags). Natural language goes to
4
+ * Runtime.run(); slash commands inspect providers/models/config/status/mode.
5
+ */
6
+ import { Runtime } from '../../runtime/runtime.js';
7
+ import type { RuntimeMode } from '../../runtime/types.js';
8
+ export interface HandleResult {
9
+ lines: string[];
10
+ exit?: boolean;
11
+ clear?: boolean;
12
+ }
13
+ export declare class ReplSession {
14
+ private readonly runtime;
15
+ private mode;
16
+ private viewCache?;
17
+ private conversationId?;
18
+ private dryRunMode;
19
+ constructor(runtime: Runtime);
20
+ currentMode(): RuntimeMode;
21
+ private views;
22
+ handle(raw: string): Promise<HandleResult>;
23
+ private runInput;
24
+ private memory;
25
+ private learning;
26
+ private permissions;
27
+ private conversationsList;
28
+ private executionsList;
29
+ private resumeExecution;
30
+ private resume;
31
+ private status;
32
+ private setOrShowMode;
33
+ private config;
34
+ private models;
35
+ private providers;
36
+ }
@@ -0,0 +1,356 @@
1
+ /**
2
+ * ReplSession — the interactive command router, decoupled from readline so it is unit-testable without
3
+ * a TTY. `handle(line)` returns the lines to print (and control flags). Natural language goes to
4
+ * Runtime.run(); slash commands inspect providers/models/config/status/mode.
5
+ */
6
+ import { RUNTIME_MODES } from '../../runtime/types.js';
7
+ import { ProviderViewCache } from '../../runtime/providerView.js';
8
+ import { SETUP_CANDIDATES } from '../commands/setup.js';
9
+ import { gatherInfo, renderInfo } from '../commands/info.js';
10
+ import { gatherDoctor, renderDoctor } from '../commands/doctor.js';
11
+ import { planCleanup } from '../commands/cleanup.js';
12
+ const HELP = [
13
+ 'Commands:',
14
+ ' /help show this help',
15
+ ' /status workspace, mode, provider count',
16
+ ' /info full runtime summary',
17
+ ' /providers/setup how to add a provider (keys / Ollama)',
18
+ ' /doctor diagnose config, providers, store, permissions',
19
+ ' /cleanup preview retention/cache cleanup (apply via CLI)',
20
+ ' /mode [name] show or set the mode (auto|chat|plan|compare|…)',
21
+ ' /compare <request> compare the request across all enabled providers',
22
+ ' /providers list providers + access state',
23
+ ' /providers/all models list every provider’s models',
24
+ ' /providers/<id> [models|capabilities|health] inspect one provider',
25
+ ' /models list all known models',
26
+ ' /tools list registered tools',
27
+ ' /skills [discover] list available skills; discover scans the workspace',
28
+ ' /permissions show the current tool permissions',
29
+ ' /feedback <text> record feedback on the last result (e.g. "that worked")',
30
+ ' /learning show what the learning store has observed',
31
+ ' /memory [list] list stored facts',
32
+ ' /memory search <query> search memory',
33
+ ' /memory delete <id> delete a fact (with cascade)',
34
+ ' /memory clear clear project-scope facts',
35
+ ' /conversations list recent conversations',
36
+ ' /resume <id> resume a conversation',
37
+ ' /executions list persisted executions',
38
+ ' /resume-execution <id> resume an execution',
39
+ ' /pause <id> pause an execution',
40
+ ' /cancel <id> cancel an execution',
41
+ ' /config show the resolved configuration',
42
+ ' /dry-run toggle dry-run (plan only, no changes)',
43
+ ' /clear clear the screen',
44
+ ' /exit leave the session',
45
+ '',
46
+ 'Anything else is sent to the runtime as a request.',
47
+ ];
48
+ export class ReplSession {
49
+ runtime;
50
+ mode = 'auto';
51
+ viewCache;
52
+ conversationId;
53
+ dryRunMode = false;
54
+ constructor(runtime) {
55
+ this.runtime = runtime;
56
+ }
57
+ currentMode() {
58
+ return this.mode;
59
+ }
60
+ views() {
61
+ if (!this.viewCache)
62
+ this.viewCache = new ProviderViewCache(this.runtime.ai);
63
+ return this.viewCache;
64
+ }
65
+ async handle(raw) {
66
+ const line = raw.trim();
67
+ if (!line)
68
+ return { lines: [] };
69
+ if (!line.startsWith('/'))
70
+ return this.runInput(line);
71
+ const [token, ...args] = line.split(/\s+/);
72
+ const parts = token.slice(1).split('/'); // "/providers/all" → ["providers","all"]
73
+ const cmd = parts[0].toLowerCase();
74
+ switch (cmd) {
75
+ case 'help':
76
+ return { lines: HELP };
77
+ case 'exit':
78
+ case 'quit':
79
+ return { lines: ['bye.'], exit: true };
80
+ case 'clear':
81
+ return { lines: [], clear: true };
82
+ case 'status':
83
+ return this.status();
84
+ case 'info':
85
+ return { lines: renderInfo(await gatherInfo(this.runtime)) };
86
+ case 'doctor':
87
+ return { lines: renderDoctor(await gatherDoctor(this.runtime, this.runtime.configFile, process.env)) };
88
+ case 'cleanup': {
89
+ if (!this.runtime.store.enabled)
90
+ return { lines: ['storage is disabled (stateless mode).'] };
91
+ const plan = planCleanup(this.runtime, Date.now());
92
+ const n = plan.conversations.expired.length + plan.executions.expired.length + plan.artifacts.expired.length + plan.cache.entries;
93
+ return { lines: [`cleanup preview: ${plan.conversations.expired.length} conversation(s), ${plan.executions.expired.length} execution(s), ${plan.artifacts.expired.length} artifact(s), ${plan.cache.entries} cache entr${plan.cache.entries === 1 ? 'y' : 'ies'}`, `integrity: ${plan.integrity.length ? plan.integrity.length + ' issue(s)' : 'ok'}`, n ? 'run `ai-runtime cleanup` to apply.' : 'nothing to remove.'] };
94
+ }
95
+ case 'mode':
96
+ return this.setOrShowMode(args[0]);
97
+ case 'compare': {
98
+ const request = args.join(' ');
99
+ return request ? this.runInput(request, 'compare') : { lines: ['usage: /compare <request>'] };
100
+ }
101
+ case 'models':
102
+ return this.models();
103
+ case 'config':
104
+ return this.config();
105
+ case 'providers':
106
+ return this.providers(parts[1], args[0]);
107
+ case 'tools':
108
+ return { lines: ['Tools:', ...this.runtime.tools().map((t) => ` ${t.id.padEnd(14)} ${t.description}`)] };
109
+ case 'skills': {
110
+ if (args[0] === 'discover') {
111
+ const candidates = this.runtime.discoverSkillCandidates();
112
+ if (!candidates.length)
113
+ return { lines: ['no other skill files found in the workspace.'] };
114
+ return { lines: [`Discovered ${candidates.length} candidate(s) (not loaded):`, ...candidates.map((c) => ` • ${c.relative} [${c.kind}]`), 'Enable one: move it into .ai-runtime/skills/ or add it to `skills.paths` in config.'] };
115
+ }
116
+ const skills = this.runtime.skills();
117
+ return { lines: skills.length ? ['Skills:', ...skills.map((s) => ` ${s.id.padEnd(20)} v${s.version} ${s.description}`)] : ['no skills available (a skill needs its tools registered).'] };
118
+ }
119
+ case 'permissions':
120
+ return this.permissions();
121
+ case 'feedback': {
122
+ const text = args.join(' ');
123
+ if (!text)
124
+ return { lines: ['usage: /feedback <text> (e.g. "that worked" / "wrong root cause")'] };
125
+ const sig = this.runtime.feedback(text);
126
+ return { lines: [sig === 'neutral' ? 'noted — no clear positive/negative signal, so nothing was recorded.' : `recorded ${sig} feedback on the last result.`] };
127
+ }
128
+ case 'learning':
129
+ return this.learning();
130
+ case 'dry-run':
131
+ case 'dryrun':
132
+ this.dryRunMode = !this.dryRunMode;
133
+ return { lines: [`dry-run ${this.dryRunMode ? 'ON — plans will be shown, nothing executed' : 'OFF'}`] };
134
+ case 'memory':
135
+ return this.memory(args);
136
+ case 'conversations':
137
+ return this.conversationsList();
138
+ case 'resume':
139
+ return this.resume(args[0]);
140
+ case 'executions':
141
+ return this.executionsList();
142
+ case 'resume-execution':
143
+ return this.resumeExecution(args[0]);
144
+ case 'pause':
145
+ return args[0] ? { lines: [this.runtime.pauseExecution(args[0]) ? `paused ${args[0]}` : `cannot pause '${args[0]}'`] } : { lines: ['usage: /pause <execution-id>'] };
146
+ case 'cancel':
147
+ return args[0] ? { lines: [this.runtime.cancelExecution(args[0]) ? `cancelled ${args[0]}` : `cannot cancel '${args[0]}'`] } : { lines: ['usage: /cancel <execution-id>'] };
148
+ default:
149
+ return { lines: [`unknown command: ${token}. Try /help.`] };
150
+ }
151
+ }
152
+ async runInput(input, forceMode) {
153
+ if (this.runtime.conversations.enabled) {
154
+ if (!this.conversationId)
155
+ this.conversationId = this.runtime.conversations.start();
156
+ this.runtime.conversations.append(this.conversationId, 'user', input);
157
+ }
158
+ const result = await this.runtime.run({ input, mode: forceMode ?? this.mode, ...(this.dryRunMode ? { dryRun: true } : {}) });
159
+ const lines = [];
160
+ if (result.response?.text)
161
+ lines.push(result.response.text);
162
+ else if (result.response?.json !== undefined)
163
+ lines.push(JSON.stringify(result.response.json, null, 2));
164
+ else if (result.error)
165
+ lines.push(`[${result.error.category}] ${result.error.message}`);
166
+ if (result.mode.executed !== result.mode.selected)
167
+ lines.push(`(mode: ${result.mode.selected} → ${result.mode.executed})`);
168
+ if (result.status === 'waiting_for_approval')
169
+ lines.push('(approval required before execution — resume the execution to approve)');
170
+ if (result.status === 'failed')
171
+ lines.push('(did not complete)');
172
+ if (result.clarification)
173
+ lines.push(`? ${result.clarification.question}`);
174
+ if (result.memory?.captured)
175
+ lines.push('(remembered)');
176
+ if (this.conversationId && result.response?.text)
177
+ this.runtime.conversations.append(this.conversationId, 'assistant', result.response.text, result.runId);
178
+ return { lines };
179
+ }
180
+ memory(args) {
181
+ if (!this.runtime.memory.enabled)
182
+ return { lines: ['memory is disabled (stateless mode).'] };
183
+ const sub = args[0];
184
+ if (sub === 'search') {
185
+ const q = args.slice(1).join(' ');
186
+ if (!q)
187
+ return { lines: ['usage: /memory search <query>'] };
188
+ const hits = this.runtime.memory.search(q, { limit: 10 });
189
+ return { lines: hits.length ? hits.map((r) => ` ${r.id} [${r.scope}] ${r.text}`) : ['no matches.'] };
190
+ }
191
+ if (sub === 'delete') {
192
+ if (!args[1])
193
+ return { lines: ['usage: /memory delete <id>'] };
194
+ const audit = this.runtime.memory.delete(args[1]);
195
+ if (!audit.primary)
196
+ return { lines: [`no memory '${args[1]}'.`] };
197
+ return { lines: [`Deleted ${audit.id}`, ' ✓ primary record', ' ✓ indexes', ` ✓ relationships (${audit.relationships} updated)`, ' ✓ cache'] };
198
+ }
199
+ if (sub === 'clear') {
200
+ const removed = this.runtime.memory.all().filter((r) => r.scope === 'project');
201
+ for (const r of removed)
202
+ this.runtime.memory.delete(r.id);
203
+ return { lines: [`cleared ${removed.length} project-scope facts.`] };
204
+ }
205
+ // default: list
206
+ const all = this.runtime.memory.all().filter((r) => !r.supersededBy);
207
+ return { lines: all.length ? all.map((r) => ` ${r.id} [${r.scope}] ${r.text}`) : ['no memories stored.'] };
208
+ }
209
+ learning() {
210
+ if (!this.runtime.learning.enabled)
211
+ return { lines: ['learning is disabled (stateless mode).'] };
212
+ const snap = this.runtime.learning.snapshot();
213
+ if (!snap.length)
214
+ return { lines: ['no learning recorded yet.'] };
215
+ const lines = ['Learning (observed outcomes):'];
216
+ for (const s of snap.slice(0, 15))
217
+ lines.push(` ${s.key.padEnd(28)} ${s.successes}/${s.attempts} ok +${s.positive}/-${s.negative}`);
218
+ const preferred = this.runtime.learning.preferredProviders();
219
+ if (preferred.length)
220
+ lines.push(`soft-preferred providers: ${preferred.join(', ')}`);
221
+ return { lines };
222
+ }
223
+ permissions() {
224
+ const p = this.runtime.permissions();
225
+ return {
226
+ lines: [
227
+ 'Tool permissions:',
228
+ ` filesystem read=${p.filesystem.read} write=${p.filesystem.write}`,
229
+ ` shell enabled=${p.shell.enabled} allowed=[${p.shell.allowedCommands.join(', ')}]`,
230
+ ` git commit=${p.git.commit} push=${p.git.push}`,
231
+ ` network enabled=${p.network.enabled}`,
232
+ '(write/exec are off unless granted in .ai-runtime/config.yaml; destructive shell always needs approval)',
233
+ ],
234
+ };
235
+ }
236
+ conversationsList() {
237
+ if (!this.runtime.conversations.enabled)
238
+ return { lines: ['conversations are disabled (stateless mode).'] };
239
+ const list = this.runtime.conversations.list().slice(0, 10);
240
+ if (!list.length)
241
+ return { lines: ['no conversations yet.'] };
242
+ return { lines: ['Recent conversations:', ...list.map((c, i) => ` ${i + 1}. ${c.id} ${c.title} (${c.turns} turns)`)] };
243
+ }
244
+ executionsList() {
245
+ if (!this.runtime.executionStore.enabled)
246
+ return { lines: ['executions are disabled (stateless mode).'] };
247
+ const list = this.runtime.executions().slice(0, 10);
248
+ if (!list.length)
249
+ return { lines: ['no executions yet.'] };
250
+ return { lines: ['Executions:', ...list.map((e) => ` ${e.id} [${e.status}] ${e.mode}: ${e.goal.slice(0, 50)}`)] };
251
+ }
252
+ async resumeExecution(id) {
253
+ if (!id)
254
+ return { lines: ['usage: /resume-execution <execution-id>'] };
255
+ const r = await this.runtime.resumeExecution(id);
256
+ const lines = [];
257
+ if (r.response?.text)
258
+ lines.push(r.response.text);
259
+ lines.push(`status: ${r.status}`);
260
+ if (r.clarification)
261
+ lines.push(`? ${r.clarification.question}`);
262
+ return { lines };
263
+ }
264
+ resume(id) {
265
+ if (!id)
266
+ return { lines: ['usage: /resume <conversation-id>'] };
267
+ const meta = this.runtime.conversations.meta(id);
268
+ if (!meta)
269
+ return { lines: [`no conversation '${id}'.`] };
270
+ this.conversationId = id;
271
+ const turns = this.runtime.conversations.turns(id).slice(-6);
272
+ return { lines: [`resumed ${id} — ${meta.title}`, ...turns.map((t) => ` ${t.role}: ${t.text.length > 80 ? t.text.slice(0, 77) + '…' : t.text}`)] };
273
+ }
274
+ status() {
275
+ const ws = this.runtime.workspaceInfo();
276
+ const providers = this.runtime.ai.providers();
277
+ return {
278
+ lines: [
279
+ `workspace: ${ws?.name ?? '(none)'}${ws?.git.branch ? ` @ ${ws.git.branch}` : ''}`,
280
+ `mode: ${this.mode}`,
281
+ `providers: ${providers.length} configured (${providers.filter((p) => p.enabled).length} enabled)`,
282
+ ],
283
+ };
284
+ }
285
+ setOrShowMode(next) {
286
+ if (!next)
287
+ return { lines: [`mode: ${this.mode}`] };
288
+ if (!RUNTIME_MODES.includes(next))
289
+ return { lines: [`invalid mode '${next}'. valid: ${RUNTIME_MODES.join(', ')}`] };
290
+ this.mode = next;
291
+ return { lines: [`mode set to ${this.mode}`] };
292
+ }
293
+ config() {
294
+ const s = this.runtime.settings();
295
+ const providers = this.runtime.ai.providers();
296
+ const lines = [`default mode: ${s.defaultMode ?? 'auto'}`, `default strategy: ${s.defaultStrategy ?? '(router default)'}`, 'providers:'];
297
+ if (providers.length === 0)
298
+ lines.push(' (none configured)');
299
+ for (const p of providers)
300
+ lines.push(` ${p.enabled ? '●' : '○'} ${p.id} (${p.kind}, ${p.privacyClass})`);
301
+ return { lines };
302
+ }
303
+ async models() {
304
+ const views = await this.views().get();
305
+ const lines = ['Models:'];
306
+ for (const v of views)
307
+ for (const m of v.models)
308
+ lines.push(` ${v.id}/${m.id} ctx=${m.contextWindow ? Math.round(m.contextWindow / 1000) + 'k' : '?'} ${m.verified ? 'verified' : m.cataloged ? 'cataloged' : 'unknown'}`);
309
+ if (lines.length === 1)
310
+ lines.push(' (no providers configured)');
311
+ return { lines };
312
+ }
313
+ async providers(target, detail) {
314
+ if (target === 'setup') {
315
+ const lines = ['Add a provider by setting its API key env var (or install Ollama for a local, no-key option):'];
316
+ for (const c of SETUP_CANDIDATES)
317
+ lines.push(` • ${c.id.padEnd(11)} ${c.apiKeyEnv ? `${c.apiKeyEnv} → ` : 'local, no key → '}${c.link}`);
318
+ lines.push('Then run `ai-runtime setup` (or add them to .ai-runtime/config.yaml).');
319
+ return { lines };
320
+ }
321
+ const views = await this.views().get();
322
+ if (views.length === 0)
323
+ return { lines: ['No providers configured. Run `ai-runtime setup` or add a provider to .ai-runtime/config.yaml.'] };
324
+ if (!target || target === 'all') {
325
+ const lines = ['Providers:'];
326
+ for (const v of views) {
327
+ lines.push(` ${v.enabled ? '●' : '○'} ${v.id.padEnd(14)} ${v.accessState.padEnd(15)} ${v.models.length} models${v.stale ? ' (stale)' : ''}`);
328
+ if (detail === 'models')
329
+ for (const m of v.models)
330
+ lines.push(` ${m.id} ${m.verified ? 'verified' : m.cataloged ? 'cataloged' : 'unknown'}`);
331
+ }
332
+ return { lines };
333
+ }
334
+ const v = views.find((x) => x.id.toLowerCase() === target.toLowerCase());
335
+ if (!v)
336
+ return { lines: [`unknown provider '${target}'.`] };
337
+ return { lines: providerDetail(v, detail) };
338
+ }
339
+ }
340
+ function providerDetail(v, detail) {
341
+ if (detail === 'health')
342
+ return [`${v.id}: ${v.accessState} (health ${v.healthState}${v.stale ? ', stale' : ''}${v.ageMs !== undefined ? `, checked ${Math.round(v.ageMs / 1000)}s ago` : ''})`];
343
+ if (detail === 'capabilities') {
344
+ const lines = [`${v.id} model capabilities:`];
345
+ for (const m of v.models)
346
+ lines.push(` ${m.id}: ${m.verified ? 'verified' : m.cataloged ? 'cataloged' : 'unknown'} (source ${m.source})`);
347
+ return lines;
348
+ }
349
+ // default or "models"
350
+ const lines = [`${v.id} [${v.accessState}] — ${v.kind}, ${v.enabled ? 'enabled' : 'disabled'}`, 'models:'];
351
+ for (const m of v.models)
352
+ lines.push(` ${m.id} ctx=${m.contextWindow ? Math.round(m.contextWindow / 1000) + 'k' : '?'} ${m.verified ? 'verified' : m.cataloged ? 'cataloged' : 'unknown'}`);
353
+ if (v.models.length === 0)
354
+ lines.push(' (none discovered)');
355
+ return lines;
356
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Minimal yes/no terminal prompt (node:readline, zero deps). Used by mutating commands (setup, cleanup)
3
+ * so nothing on disk changes without an explicit confirmation. Defaults to NO on an empty answer or a
4
+ * non-interactive stdin — the safe default is never to mutate.
5
+ */
6
+ export declare function confirm(question: string): Promise<boolean>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Minimal yes/no terminal prompt (node:readline, zero deps). Used by mutating commands (setup, cleanup)
3
+ * so nothing on disk changes without an explicit confirmation. Defaults to NO on an empty answer or a
4
+ * non-interactive stdin — the safe default is never to mutate.
5
+ */
6
+ import { createInterface } from 'node:readline';
7
+ export async function confirm(question) {
8
+ if (!process.stdin.isTTY)
9
+ return false; // non-interactive (CI/pipe) → never mutate implicitly
10
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
11
+ try {
12
+ const answer = await new Promise((resolve) => rl.question(`${question} [y/N] `, resolve));
13
+ return /^y(es)?$/i.test(answer.trim());
14
+ }
15
+ finally {
16
+ rl.close();
17
+ }
18
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLI output helpers. EVERY line printed by the CLI goes through here so it is redacted — the terminal
3
+ * and CI logs are first-class egress sinks (see the security hardening notes). Never `console.log`
4
+ * directly from a command.
5
+ */
6
+ export declare function print(line: string): void;
7
+ export declare function printError(line: string): void;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * CLI output helpers. EVERY line printed by the CLI goes through here so it is redacted — the terminal
3
+ * and CI logs are first-class egress sinks (see the security hardening notes). Never `console.log`
4
+ * directly from a command.
5
+ */
6
+ import { redactString } from '../security/redact.js';
7
+ export function print(line) {
8
+ // eslint-disable-next-line no-console
9
+ console.log(redactString(line));
10
+ }
11
+ export function printError(line) {
12
+ // eslint-disable-next-line no-console
13
+ console.error(redactString(line));
14
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Deterministic comparison analysis — no model calls, no randomness. Outputs are compared by token-set
3
+ * similarity (reusing the memory tokenizer), clustered into agreement groups, diffed pairwise, checked
4
+ * for unique ("missing from others") sentences, and — for structured (JSON) outputs — checked for direct
5
+ * field-level contradictions. Everything here is a pure function of the candidates, so it is fully unit
6
+ * testable and never fabricates a contradiction it cannot ground (free-text contradictions are left to a
7
+ * judge; only structured fields are asserted as contradictions).
8
+ */
9
+ import type { AgreementCluster, ComparisonAnalysis, ComparisonCandidate, ComparisonDifference, Contradiction, MissingPoint } from './comparison.js';
10
+ /** Token-set Jaccard similarity of two token bags, 0..1. Two empty bags count as identical (1). */
11
+ export declare function jaccard(a: string[], b: string[]): number;
12
+ /**
13
+ * Similarity of two output texts, 0..1 (Jaccard over their tokenized bags). When either side tokenizes
14
+ * to nothing (e.g. all stopwords/punctuation), tokens carry no signal — fall back to exact normalized
15
+ * text equality so two DIFFERENT all-stopword outputs are not reported as identical.
16
+ */
17
+ export declare function similarity(a: string, b: string): number;
18
+ /** Deterministic key-sorted JSON stringification (so field order never affects comparison). */
19
+ export declare function stableStringify(value: unknown): string;
20
+ /** The text used to compare a candidate: its text, or a stable stringification of its JSON. */
21
+ export declare function comparableText(c: ComparisonCandidate): string;
22
+ /** Candidates that both ran successfully and produced comparable output. */
23
+ export declare function comparable(candidates: ComparisonCandidate[]): ComparisonCandidate[];
24
+ /**
25
+ * Greedy agreement clustering: each candidate joins the first cluster whose representative it is at
26
+ * least `threshold`-similar to, else starts its own. Deterministic in input order.
27
+ */
28
+ export declare function clusterByAgreement(candidates: ComparisonCandidate[], threshold: number): AgreementCluster[];
29
+ /** Every pair of candidates whose outputs are below the agreement threshold, most-different first. */
30
+ export declare function pairwiseDifferences(candidates: ComparisonCandidate[], threshold: number): ComparisonDifference[];
31
+ /** Split text into trimmed sentence-ish units on sentence terminators and newlines. */
32
+ export declare function splitSentences(text: string): string[];
33
+ /**
34
+ * For each candidate, sentences that no OTHER candidate expresses (similarity < 0.6 to every sentence of
35
+ * every other candidate). This surfaces information one model included and others omitted. Skipped when
36
+ * fewer than two candidates. Capped to keep output readable.
37
+ */
38
+ export declare function missingPoints(candidates: ComparisonCandidate[], perCandidateCap?: number): MissingPoint[];
39
+ /**
40
+ * Direct contradictions across STRUCTURED outputs only: for each top-level scalar field present in ≥2
41
+ * candidates' JSON, if the candidates disagree on its value it is a contradiction. Objects/arrays are not
42
+ * asserted as contradictions here (that needs semantic judgment, not a deterministic diff).
43
+ */
44
+ export declare function structuredContradictions(candidates: ComparisonCandidate[]): Contradiction[];
45
+ /** Full deterministic analysis of a set of candidates at the given agreement threshold. */
46
+ export declare function analyze(candidates: ComparisonCandidate[], threshold: number): ComparisonAnalysis;