ai-runtime-engine 1.3.0 → 2.8.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 (153) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/dist/agents/admit.d.ts +69 -0
  3. package/dist/agents/admit.js +129 -0
  4. package/dist/agents/definition.d.ts +36 -0
  5. package/dist/agents/definition.js +9 -0
  6. package/dist/agents/envelope.d.ts +53 -0
  7. package/dist/agents/envelope.js +68 -0
  8. package/dist/agents/finding.d.ts +79 -0
  9. package/dist/agents/finding.js +80 -0
  10. package/dist/agents/roles.d.ts +36 -0
  11. package/dist/agents/roles.js +44 -0
  12. package/dist/agents/synthesize.d.ts +44 -0
  13. package/dist/agents/synthesize.js +60 -0
  14. package/dist/agents/task.d.ts +112 -0
  15. package/dist/agents/task.js +48 -0
  16. package/dist/agents/worker.d.ts +91 -0
  17. package/dist/agents/worker.js +377 -0
  18. package/dist/capabilities/capability.d.ts +117 -0
  19. package/dist/capabilities/capability.js +66 -0
  20. package/dist/capabilities/registry.d.ts +139 -0
  21. package/dist/capabilities/registry.js +413 -0
  22. package/dist/capabilities/vocabulary.d.ts +32 -0
  23. package/dist/capabilities/vocabulary.js +34 -0
  24. package/dist/cli/cli.js +55 -4
  25. package/dist/cli/commands/cleanup.js +29 -27
  26. package/dist/cli/commands/doctor.d.ts +14 -0
  27. package/dist/cli/commands/doctor.js +38 -8
  28. package/dist/cli/commands/executions.js +34 -25
  29. package/dist/cli/commands/info.d.ts +1 -0
  30. package/dist/cli/commands/info.js +11 -9
  31. package/dist/cli/commands/init.js +19 -0
  32. package/dist/cli/commands/inspect.d.ts +40 -1
  33. package/dist/cli/commands/inspect.js +157 -2
  34. package/dist/cli/commands/mcp.d.ts +45 -0
  35. package/dist/cli/commands/mcp.js +148 -0
  36. package/dist/cli/commands/route.js +21 -0
  37. package/dist/cli/commands/run.d.ts +1 -0
  38. package/dist/cli/commands/run.js +21 -2
  39. package/dist/cli/commands/skills.d.ts +2 -0
  40. package/dist/cli/commands/skills.js +29 -7
  41. package/dist/cli/interactive/ansi.d.ts +41 -0
  42. package/dist/cli/interactive/ansi.js +43 -0
  43. package/dist/cli/interactive/complete.d.ts +10 -0
  44. package/dist/cli/interactive/complete.js +19 -0
  45. package/dist/cli/interactive/lanes.d.ts +69 -0
  46. package/dist/cli/interactive/lanes.js +181 -0
  47. package/dist/cli/interactive/repl.d.ts +3 -0
  48. package/dist/cli/interactive/repl.js +91 -13
  49. package/dist/cli/interactive/session.d.ts +8 -0
  50. package/dist/cli/interactive/session.js +73 -2
  51. package/dist/cli/render.d.ts +7 -0
  52. package/dist/cli/render.js +10 -0
  53. package/dist/cli/runtimeSession.d.ts +11 -0
  54. package/dist/cli/runtimeSession.js +17 -0
  55. package/dist/config/defaults.d.ts +3 -1
  56. package/dist/config/defaults.js +2 -0
  57. package/dist/config/schema.d.ts +1 -0
  58. package/dist/config/schema.js +2 -2
  59. package/dist/context/lossVerifier.d.ts +24 -0
  60. package/dist/context/lossVerifier.js +45 -0
  61. package/dist/context/summarize.d.ts +19 -0
  62. package/dist/context/summarize.js +53 -0
  63. package/dist/core/fallback/fallback.d.ts +5 -0
  64. package/dist/core/fallback/fallback.js +3 -1
  65. package/dist/core/router/router.d.ts +3 -0
  66. package/dist/core/router/router.js +1 -0
  67. package/dist/executions/agentTasks.d.ts +627 -0
  68. package/dist/executions/agentTasks.js +149 -0
  69. package/dist/executions/checkpoint.d.ts +5 -1
  70. package/dist/executions/checkpoint.js +13 -1
  71. package/dist/executions/execution.d.ts +36 -2
  72. package/dist/executions/store.d.ts +37 -0
  73. package/dist/executions/store.js +33 -0
  74. package/dist/generation/generateAdapter.d.ts +14 -0
  75. package/dist/generation/generateAdapter.js +38 -0
  76. package/dist/generation/generateSkill.d.ts +26 -0
  77. package/dist/generation/generateSkill.js +51 -0
  78. package/dist/index.d.ts +47 -4
  79. package/dist/index.js +33 -2
  80. package/dist/mcp/client.d.ts +70 -0
  81. package/dist/mcp/client.js +221 -0
  82. package/dist/mcp/manager.d.ts +151 -0
  83. package/dist/mcp/manager.js +493 -0
  84. package/dist/mcp/protocol.d.ts +216 -0
  85. package/dist/mcp/protocol.js +149 -0
  86. package/dist/mcp/toolAdapter.d.ts +44 -0
  87. package/dist/mcp/toolAdapter.js +94 -0
  88. package/dist/mcp/transport.d.ts +109 -0
  89. package/dist/mcp/transport.js +383 -0
  90. package/dist/memory/embedders/hash.d.ts +12 -0
  91. package/dist/memory/embedders/hash.js +31 -0
  92. package/dist/memory/embedders/http.d.ts +25 -0
  93. package/dist/memory/embedders/http.js +48 -0
  94. package/dist/memory/memory.d.ts +19 -2
  95. package/dist/memory/memory.js +75 -11
  96. package/dist/memory/semantic.d.ts +17 -0
  97. package/dist/memory/semantic.js +29 -0
  98. package/dist/orchestration/budget.d.ts +30 -0
  99. package/dist/orchestration/budget.js +40 -0
  100. package/dist/orchestration/executor.d.ts +64 -1
  101. package/dist/orchestration/executor.js +104 -7
  102. package/dist/orchestration/orchestrator.d.ts +35 -1
  103. package/dist/orchestration/orchestrator.js +106 -8
  104. package/dist/orchestration/plan.d.ts +15 -1
  105. package/dist/orchestration/plan.js +23 -4
  106. package/dist/orchestration/planner.d.ts +19 -1
  107. package/dist/orchestration/planner.js +25 -5
  108. package/dist/plugin/ai.d.ts +4 -0
  109. package/dist/plugin/ai.js +9 -0
  110. package/dist/runtime/config.js +50 -6
  111. package/dist/runtime/intent/aiClassifier.d.ts +19 -0
  112. package/dist/runtime/intent/aiClassifier.js +74 -0
  113. package/dist/runtime/models/modelProfile.d.ts +61 -0
  114. package/dist/runtime/models/modelProfile.js +139 -0
  115. package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
  116. package/dist/runtime/planning/deriveCapabilities.js +146 -0
  117. package/dist/runtime/policy.d.ts +10 -0
  118. package/dist/runtime/policy.js +9 -2
  119. package/dist/runtime/runtime.d.ts +233 -0
  120. package/dist/runtime/runtime.js +1042 -60
  121. package/dist/runtime/types.d.ts +88 -2
  122. package/dist/security/redact.js +22 -10
  123. package/dist/skills/manifest.d.ts +3 -0
  124. package/dist/skills/manifest.js +24 -0
  125. package/dist/skills/registry.d.ts +16 -1
  126. package/dist/skills/registry.js +21 -1
  127. package/dist/skills/skill.d.ts +6 -1
  128. package/dist/store/area.d.ts +15 -1
  129. package/dist/store/area.js +19 -8
  130. package/dist/store/crypto.d.ts +21 -0
  131. package/dist/store/crypto.js +49 -0
  132. package/dist/store/paths.d.ts +5 -1
  133. package/dist/store/paths.js +6 -0
  134. package/dist/store/store.d.ts +15 -3
  135. package/dist/store/store.js +28 -7
  136. package/dist/telemetry/sinks/otlp.d.ts +31 -0
  137. package/dist/telemetry/sinks/otlp.js +76 -0
  138. package/dist/tools/builtins/filesystem.js +1 -0
  139. package/dist/tools/builtins/git.js +1 -0
  140. package/dist/tools/builtins/shell.js +1 -0
  141. package/dist/tools/permissions.d.ts +28 -0
  142. package/dist/tools/permissions.js +72 -0
  143. package/dist/tools/registry.d.ts +18 -2
  144. package/dist/tools/registry.js +22 -2
  145. package/dist/tools/tool.d.ts +4 -0
  146. package/dist/types.d.ts +5 -1
  147. package/dist/util/flatten.d.ts +11 -0
  148. package/dist/util/flatten.js +18 -0
  149. package/dist/util/hash.d.ts +19 -0
  150. package/dist/util/hash.js +39 -0
  151. package/dist/util/semaphore.d.ts +19 -0
  152. package/dist/util/semaphore.js +60 -0
  153. package/package.json +24 -9
package/dist/cli/cli.js CHANGED
@@ -17,10 +17,11 @@ import { setupCommand } from './commands/setup.js';
17
17
  import { infoCommand } from './commands/info.js';
18
18
  import { cleanupCommand } from './commands/cleanup.js';
19
19
  import { skillsCommand } from './commands/skills.js';
20
+ import { mcpCommand, mcpAddCommand, mcpRemoveCommand, mcpEnableCommand, mcpTestCommand } from './commands/mcp.js';
20
21
  import { startRepl } from './interactive/repl.js';
21
22
  import { printError } from './render.js';
22
23
  const program = new Command();
23
- program.name('ai-runtime').description('Universal, provider-agnostic AI Runtime & Orchestration Platform').version('1.3.0');
24
+ program.name('ai-runtime').description('Universal, provider-agnostic AI Runtime & Orchestration Platform').version('2.8.0');
24
25
  const configOpt = ['-c, --config <path>', 'path to an ai-runtime config file'];
25
26
  // Bare `ai-runtime` (no subcommand) opens the interactive terminal. `allowExcessArguments(false)` keeps
26
27
  // a mistyped subcommand (e.g. `ai-runtime porviders`) failing fast instead of silently opening the REPL.
@@ -36,6 +37,7 @@ program
36
37
  .option('--json', 'print the full RuntimeResult as JSON')
37
38
  .option('--dry-run', 'plan and report what would happen without making any changes')
38
39
  .option('--stream', 'stream the answer token-by-token (text output only)')
40
+ .option('--partial', 'if over the call budget, run the phases that fit and pause (vs. notify-and-wait)')
39
41
  .action((input, o) => runCommand(input, o));
40
42
  program
41
43
  .command('executions')
@@ -60,11 +62,57 @@ program
60
62
  .action((o) => setupCommand(o));
61
63
  program
62
64
  .command('skills')
63
- .description('List available skills; --discover scans the workspace for more (reported, not loaded)')
65
+ .description('List available skills; --discover scans the workspace for more; --scaffold drafts a new one')
64
66
  .option(...configOpt)
65
67
  .option('--discover', 'scan the workspace for skill files elsewhere and report them')
68
+ .option('--scaffold <goal>', 'draft a *.skill.yaml manifest for a goal (asks before saving)')
69
+ .option('-y, --yes', 'with --scaffold, save the drafted skill without prompting')
66
70
  .option('--json', 'print as JSON')
67
71
  .action((o) => skillsCommand(o));
72
+ // `mcp` — MCP servers. A subcommand-bearing group whose bare form lists, and `mcp <id>` details one.
73
+ // Every path releases stdio children on completion (Runtime.close), so a one-shot command never orphans.
74
+ const mcp = program.command('mcp').description('Inspect and manage MCP servers (tools from external servers)');
75
+ mcp
76
+ .argument('[id]', 'a server id to show in detail')
77
+ .option(...configOpt)
78
+ .option('--json', 'print as JSON')
79
+ .action((id, o) => mcpCommand(id, o));
80
+ mcp
81
+ .command('add')
82
+ .argument('<id>', 'a short id for the server (lowercase, kebab/snake)')
83
+ .description('Add a server (stored locally — your config file is never modified)')
84
+ .option('--command <argv>', 'stdio server command line, e.g. "npx -y some-mcp-server"')
85
+ .option('--url <url>', 'streamable-HTTP endpoint')
86
+ .option('--token-env <NAME>', 'env-var NAME holding the credential (never the value itself)')
87
+ .option('--timeout <ms>', 'per-call timeout override')
88
+ .option(...configOpt)
89
+ .option('--json', 'print as JSON')
90
+ .action((id, o) => mcpAddCommand(id, o));
91
+ mcp
92
+ .command('remove')
93
+ .argument('<id>', 'server id')
94
+ .description('Remove a server that was added with `mcp add`')
95
+ .option(...configOpt)
96
+ .action((id, o) => mcpRemoveCommand(id, o));
97
+ mcp
98
+ .command('enable')
99
+ .argument('<id>', 'server id')
100
+ .description('Enable a disabled server')
101
+ .option(...configOpt)
102
+ .action((id, o) => mcpEnableCommand(id, true, o));
103
+ mcp
104
+ .command('disable')
105
+ .argument('<id>', 'server id')
106
+ .description('Disable a server without removing it')
107
+ .option(...configOpt)
108
+ .action((id, o) => mcpEnableCommand(id, false, o));
109
+ mcp
110
+ .command('test')
111
+ .argument('<id>', 'server id')
112
+ .description('Connect, handshake, list tools, and ping — reporting exactly what happened')
113
+ .option(...configOpt)
114
+ .option('--json', 'print as JSON')
115
+ .action((id, o) => mcpTestCommand(id, o));
68
116
  program
69
117
  .command('info')
70
118
  .description('Summarize the runtime: workspace, providers, skills, tools, memory, learning, storage')
@@ -90,13 +138,16 @@ program
90
138
  .description('List known models across providers')
91
139
  .option(...configOpt)
92
140
  .option('--json', 'print as JSON')
141
+ .option('--profile', 'show the models.md per-mode/per-task routing profile instead')
93
142
  .action((o) => modelsCommand(o));
94
143
  program
95
144
  .command('capabilities')
96
- .description('Show the capability matrix by model')
145
+ .argument('[goal]', 'with --actions: which action capabilities this goal needs (offline — no model call)')
146
+ .description('Show the model capability matrix; --actions shows action capabilities (read_file, run_tests, …)')
97
147
  .option(...configOpt)
148
+ .option('--actions', 'show ACTION capabilities (what the runtime can do) instead of the model matrix')
98
149
  .option('--json', 'print as JSON')
99
- .action((o) => capabilitiesCommand(o));
150
+ .action((goal, o) => capabilitiesCommand(goal, o));
100
151
  program
101
152
  .command('doctor')
102
153
  .description('Diagnose the runtime: config, .env, providers, models, store, skills, tools, permissions, git')
@@ -4,9 +4,9 @@
4
4
  * Nothing is deleted without confirmation unless `--yes`; `--dry-run` only reports the plan. Integrity
5
5
  * issues are reported, never auto-"repaired" destructively.
6
6
  */
7
- import { Runtime } from '../../runtime/runtime.js';
8
7
  import { TERMINAL } from '../../executions/execution.js';
9
8
  import { confirm } from '../prompt.js';
9
+ import { withRuntime } from '../runtimeSession.js';
10
10
  import { print } from '../render.js';
11
11
  const DAY_MS = 86_400_000;
12
12
  function numEnv(v) {
@@ -67,30 +67,32 @@ function renderPlan(plan) {
67
67
  return lines;
68
68
  }
69
69
  export async function cleanupCommand(opts) {
70
- const rt = await Runtime.load({ ...(opts.config ? { config: opts.config } : {}) });
71
- if (!rt.store.enabled) {
72
- print('storage is disabled (stateless mode) — nothing to clean.');
73
- return;
74
- }
75
- const plan = planCleanup(rt, Date.now());
76
- if (opts.json)
77
- return print(JSON.stringify(plan, null, 2));
78
- for (const line of renderPlan(plan))
79
- print(line);
80
- const removable = toRemove(plan);
81
- if (removable === 0) {
82
- print('\nNothing to remove.');
83
- return;
84
- }
85
- if (opts.dryRun) {
86
- print('\n(dry run — nothing was removed)');
87
- return;
88
- }
89
- const ok = opts.yes || (await confirm(`\nRemove ${removable} item(s)?`));
90
- if (!ok) {
91
- print('cancelled — nothing was removed.');
92
- return;
93
- }
94
- const done = applyCleanup(rt, plan);
95
- print(`\nremoved: ${done.conversations} conversation(s), ${done.executions} execution(s), ${done.artifacts} artifact(s), ${done.cache} cache entr${done.cache === 1 ? 'y' : 'ies'}.`);
70
+ // Every Runtime-based command releases its Runtime (MCP children included) on every path.
71
+ return withRuntime(opts, async (rt) => {
72
+ if (!rt.store.enabled) {
73
+ print('storage is disabled (stateless mode) — nothing to clean.');
74
+ return;
75
+ }
76
+ const plan = planCleanup(rt, Date.now());
77
+ if (opts.json)
78
+ return print(JSON.stringify(plan, null, 2));
79
+ for (const line of renderPlan(plan))
80
+ print(line);
81
+ const removable = toRemove(plan);
82
+ if (removable === 0) {
83
+ print('\nNothing to remove.');
84
+ return;
85
+ }
86
+ if (opts.dryRun) {
87
+ print('\n(dry run — nothing was removed)');
88
+ return;
89
+ }
90
+ const ok = opts.yes || (await confirm(`\nRemove ${removable} item(s)?`));
91
+ if (!ok) {
92
+ print('cancelled — nothing was removed.');
93
+ return;
94
+ }
95
+ const done = applyCleanup(rt, plan);
96
+ print(`\nremoved: ${done.conversations} conversation(s), ${done.executions} execution(s), ${done.artifacts} artifact(s), ${done.cache} cache entr${done.cache === 1 ? 'y' : 'ies'}.`);
97
+ });
96
98
  }
@@ -4,6 +4,7 @@
4
4
  * Never prints a secret VALUE; all output is redacted. `--json` emits the structured report.
5
5
  */
6
6
  import { Runtime } from '../../runtime/runtime.js';
7
+ import type { McpServerStatus } from '../../mcp/manager.js';
7
8
  import type { IntegrityIssue } from '../../store/area.js';
8
9
  export interface DoctorReport {
9
10
  configFile?: string;
@@ -36,6 +37,19 @@ export interface DoctorReport {
36
37
  };
37
38
  skills: string[];
38
39
  tools: string[];
40
+ /**
41
+ * MCP servers (Phase 3.3). ABSENT when none are configured, so a no-MCP `doctor --json` is unchanged.
42
+ * `connected: false` always: `doctor` is read-only and never connects (it is also the REPL `/doctor`
43
+ * path, and `gatherDoctor` is side-effect-free) — so states are as last recorded. `servers` reuses
44
+ * `McpServerStatus` VERBATIM, which is name-only for credentials (`tokenEnv` + `tokenSet`, never a
45
+ * value); adding any field beyond it would risk the secret-leak scan that stringifies this report.
46
+ */
47
+ mcp?: {
48
+ connected: boolean;
49
+ servers: McpServerStatus[];
50
+ grants: Record<string, string>;
51
+ warnings: string[];
52
+ };
39
53
  permissions: {
40
54
  fsRead: boolean;
41
55
  fsWrite: boolean;
@@ -3,8 +3,9 @@
3
3
  * health, accessible models, the local store (integrity + counts), skills, tools, permissions, and git.
4
4
  * Never prints a secret VALUE; all output is redacted. `--json` emits the structured report.
5
5
  */
6
- import { Runtime } from '../../runtime/runtime.js';
6
+ import { mcpRow } from './mcp.js';
7
7
  import { loadRuntimeConfig } from '../../runtime/config.js';
8
+ import { withRuntime } from '../runtimeSession.js';
8
9
  import { print } from '../render.js';
9
10
  /** The credential env-var NAMES ai-runtime knows about (§16). Presence is reported, never the value. */
10
11
  const KNOWN_KEY_ENVS = [
@@ -45,6 +46,21 @@ export async function gatherDoctor(rt, configFile, env) {
45
46
  },
46
47
  skills: rt.skills().map((s) => `${s.id}@${s.version}`),
47
48
  tools: rt.tools().map((t) => t.id),
49
+ // No connectMcp(): doctor stays read-only. `list()` is id-sorted, so rendering is deterministic, and
50
+ // the spread makes the null-prototype grant map plain JSON.
51
+ ...(rt.hasMcpServers()
52
+ ? {
53
+ mcp: {
54
+ // Truthful rather than hardcoded: `doctor` never CONNECTS, but the REPL's `/doctor` runs
55
+ // against a session whose servers are already live, and reporting `false` there would be a
56
+ // lie. This says whether any server is connected right now, not whether doctor connected it.
57
+ connected: rt.mcp().list().some((sv) => sv.state === 'connected' || sv.state === 'degraded'),
58
+ servers: rt.mcp().list(),
59
+ grants: { ...rt.permissions().mcp.servers },
60
+ warnings: rt.mcpWarningsList(),
61
+ },
62
+ }
63
+ : {}),
48
64
  permissions: { fsRead: perms.filesystem.read, fsWrite: perms.filesystem.write, shell: perms.shell.enabled, gitCommit: perms.git.commit, gitPush: perms.git.push, network: perms.network.enabled },
49
65
  git: { repo: !!ws?.git.isRepo, ...(ws?.git.branch ? { branch: ws.git.branch } : {}) },
50
66
  };
@@ -69,6 +85,18 @@ export function renderDoctor(r) {
69
85
  lines.push(` ${r.store.conversations} conversation(s), ${r.store.executions} execution(s), ${r.store.memory} memory record(s), ${r.store.artifacts} artifact(s)`);
70
86
  lines.push(` integrity: ${r.store.integrity.length ? `${r.store.integrity.length} issue(s) — ${r.store.integrity.map((i) => `${i.file} (${i.problem})`).join(', ')}` : 'ok'}`);
71
87
  }
88
+ if (r.mcp) {
89
+ lines.push('', `MCP servers (${r.mcp.connected ? 'live in this session' : 'as last recorded'} — \`doctor\` does not connect; run \`ai-runtime mcp\` for a live check):`);
90
+ if (!r.mcp.servers.length)
91
+ lines.push(' (none)');
92
+ for (const sv of r.mcp.servers)
93
+ lines.push(mcpRow(r.mcp.grants, sv));
94
+ const ungranted = r.mcp.servers.filter((sv) => !Object.prototype.hasOwnProperty.call(r.mcp.grants, sv.id) || r.mcp.grants[sv.id] === 'off').map((sv) => sv.id);
95
+ if (ungranted.length)
96
+ lines.push(` not permitted to execute: ${ungranted.join(', ')} — grant permissions.mcp.servers.<id>: read (or full)`);
97
+ for (const w of r.mcp.warnings)
98
+ lines.push(` ! ${w}`);
99
+ }
72
100
  lines.push('', `Skills: ${r.skills.length ? r.skills.join(', ') : '(none)'}`);
73
101
  lines.push(`Tools: ${r.tools.join(', ')}`);
74
102
  lines.push(`Perms: fsRead=${r.permissions.fsRead} fsWrite=${r.permissions.fsWrite} shell=${r.permissions.shell} gitCommit=${r.permissions.gitCommit} gitPush=${r.permissions.gitPush} network=${r.permissions.network}`);
@@ -76,11 +104,13 @@ export function renderDoctor(r) {
76
104
  return lines;
77
105
  }
78
106
  export async function doctorCommand(opts) {
79
- const loaded = loadRuntimeConfig({ workspaceRoot: process.cwd(), ...(opts.config ? { explicitPath: opts.config } : {}) });
80
- const rt = await Runtime.load({ ...(opts.config ? { config: opts.config } : {}) });
81
- const report = await gatherDoctor(rt, loaded.configFile, process.env);
82
- if (opts.json)
83
- return print(JSON.stringify(report, null, 2));
84
- for (const line of renderDoctor(report))
85
- print(line);
107
+ // Every Runtime-based command releases its Runtime (MCP children included) on every path.
108
+ return withRuntime(opts, async (rt) => {
109
+ const loaded = loadRuntimeConfig({ workspaceRoot: process.cwd(), ...(opts.config ? { explicitPath: opts.config } : {}) });
110
+ const report = await gatherDoctor(rt, loaded.configFile, process.env);
111
+ if (opts.json)
112
+ return print(JSON.stringify(report, null, 2));
113
+ for (const line of renderDoctor(report))
114
+ print(line);
115
+ });
86
116
  }
@@ -1,32 +1,41 @@
1
1
  /** `ai-runtime executions` (list) and `resume-execution <id>` — inspect and resume persisted work. */
2
- import { Runtime } from '../../runtime/runtime.js';
3
2
  import { print } from '../render.js';
3
+ import { withRuntime } from '../runtimeSession.js';
4
4
  export async function executionsCommand(options) {
5
- const rt = await Runtime.load({ ...(options.config ? { config: options.config } : {}) });
6
- const list = rt.executions();
7
- if (options.json)
8
- return print(JSON.stringify(list, null, 2));
9
- if (!list.length)
10
- return print('No executions.');
11
- print('Executions:');
12
- for (const e of list)
13
- print(` ${e.id} [${e.status}] ${e.mode}: ${e.goal.slice(0, 60)}`);
5
+ return withRuntime(options, async (rt) => {
6
+ const list = rt.executions();
7
+ if (options.json)
8
+ return print(JSON.stringify(list, null, 2));
9
+ if (!list.length)
10
+ return print('No executions.');
11
+ print('Executions:');
12
+ for (const e of list)
13
+ print(` ${e.id} [${e.status}] ${e.mode}: ${e.goal.slice(0, 60)}`);
14
+ });
14
15
  }
15
16
  export async function resumeExecutionCommand(id, options) {
16
- const rt = await Runtime.load({ ...(options.config ? { config: options.config } : {}) });
17
- // --approve proceeds past a waiting-for-approval gate; --deny cancels it. Neither leave the decision.
18
- const approve = options.approve ? true : options.deny ? false : undefined;
19
- const r = await rt.resumeExecution(id, {
20
- ...(options.answer ? { clarificationAnswer: options.answer } : {}),
21
- ...(approve !== undefined ? { approve } : {}),
17
+ return withRuntime(options, async (rt) => {
18
+ // A resumed plan can execute MCP tool steps, so its servers must be connected — and released on the
19
+ // way out (withRuntime), or a live stdio child keeps the CLI from ever exiting.
20
+ if (rt.hasMcpServers()) {
21
+ await rt.connectMcp();
22
+ for (const w of rt.mcpWarningsList())
23
+ print(`! ${w}`);
24
+ }
25
+ // --approve proceeds past a waiting-for-approval gate; --deny cancels it. Neither → leave the decision.
26
+ const approve = options.approve ? true : options.deny ? false : undefined;
27
+ const r = await rt.resumeExecution(id, {
28
+ ...(options.answer ? { clarificationAnswer: options.answer } : {}),
29
+ ...(approve !== undefined ? { approve } : {}),
30
+ });
31
+ if (r.response?.text)
32
+ print(r.response.text);
33
+ print(`status: ${r.status}`);
34
+ if (r.status === 'waiting_for_approval')
35
+ print('(waiting for approval — re-run with --approve to proceed or --deny to cancel)');
36
+ if (r.clarification)
37
+ print(`? ${r.clarification.question}`);
38
+ if (!r.ok)
39
+ process.exitCode = 1;
22
40
  });
23
- if (r.response?.text)
24
- print(r.response.text);
25
- print(`status: ${r.status}`);
26
- if (r.status === 'waiting_for_approval')
27
- print('(waiting for approval — re-run with --approve to proceed or --deny to cancel)');
28
- if (r.clarification)
29
- print(`? ${r.clarification.question}`);
30
- if (!r.ok)
31
- process.exitCode = 1;
32
41
  }
@@ -32,6 +32,7 @@ export interface InfoReport {
32
32
  home: string;
33
33
  project: string;
34
34
  enabled: boolean;
35
+ organization?: string;
35
36
  };
36
37
  }
37
38
  /** Gather the read-only info report from a loaded runtime. Pure aside from reading the local store. */
@@ -3,9 +3,9 @@
3
3
  * providers/models, skills, tools, memory + learning stats, and storage paths. Makes NO live calls and
4
4
  * mutates nothing. `--json` prints the structured report.
5
5
  */
6
- import { Runtime } from '../../runtime/runtime.js';
7
6
  import { resolveContextBudget } from '../../context/budget.js';
8
7
  import { summarizeWorkspace } from '../../runtime/workspace/workspace.js';
8
+ import { withRuntime } from '../runtimeSession.js';
9
9
  import { print } from '../render.js';
10
10
  /** Gather the read-only info report from a loaded runtime. Pure aside from reading the local store. */
11
11
  export async function gatherInfo(rt) {
@@ -31,7 +31,7 @@ export async function gatherInfo(rt) {
31
31
  tools: rt.tools().map((t) => t.id),
32
32
  memory: { enabled: rt.memory.enabled, facts },
33
33
  learning: { enabled: rt.learning.enabled, entries: learningSnap.length, preferredProviders: rt.learning.enabled ? rt.learning.preferredProviders() : [] },
34
- storage: { home: paths.home, project: paths.project, enabled: rt.store.enabled },
34
+ storage: { home: paths.home, project: paths.project, enabled: rt.store.enabled, ...(paths.organization ? { organization: paths.organization } : {}) },
35
35
  };
36
36
  }
37
37
  export function renderInfo(r) {
@@ -40,14 +40,16 @@ export function renderInfo(r) {
40
40
  lines.push(`workspace: ${r.workspace.name}${r.workspace.branch ? ` @ ${r.workspace.branch}` : ''}`, ` ${r.workspace.summary}`);
41
41
  else
42
42
  lines.push('workspace: (none detected)');
43
- lines.push(`mode: ${r.defaultMode}`, `context: ${r.contextBudgetTokens} token budget`, `providers: ${r.providers.total} configured (${r.providers.enabled} enabled), ${r.models} models`, `skills: ${r.skills.length ? r.skills.join(', ') : '(none — register tools to enable)'}`, `tools: ${r.tools.join(', ')}`, `memory: ${r.memory.enabled ? `${r.memory.facts} fact(s)` : 'disabled'}`, `learning: ${r.learning.enabled ? `${r.learning.entries} record(s)${r.learning.preferredProviders.length ? `, prefers ${r.learning.preferredProviders.join(', ')}` : ''}` : 'disabled'}`, `storage: ${r.storage.enabled ? r.storage.project : 'disabled (stateless)'}`);
43
+ lines.push(`mode: ${r.defaultMode}`, `context: ${r.contextBudgetTokens} token budget`, `providers: ${r.providers.total} configured (${r.providers.enabled} enabled), ${r.models} models`, `skills: ${r.skills.length ? r.skills.join(', ') : '(none — register tools to enable)'}`, `tools: ${r.tools.join(', ')}`, `memory: ${r.memory.enabled ? `${r.memory.facts} fact(s)` : 'disabled'}`, `learning: ${r.learning.enabled ? `${r.learning.entries} record(s)${r.learning.preferredProviders.length ? `, prefers ${r.learning.preferredProviders.join(', ')}` : ''}` : 'disabled'}`, `storage: ${r.storage.enabled ? r.storage.project : 'disabled (stateless)'}`, ...(r.storage.organization ? [`organization: ${r.storage.organization}`] : []));
44
44
  return lines;
45
45
  }
46
46
  export async function infoCommand(opts) {
47
- const rt = await Runtime.load({ ...(opts.config ? { config: opts.config } : {}) });
48
- const report = await gatherInfo(rt);
49
- if (opts.json)
50
- return print(JSON.stringify(report, null, 2));
51
- for (const line of renderInfo(report))
52
- print(line);
47
+ // Every Runtime-based command releases its Runtime (MCP children included) on every path.
48
+ return withRuntime(opts, async (rt) => {
49
+ const report = await gatherInfo(rt);
50
+ if (opts.json)
51
+ return print(JSON.stringify(report, null, 2));
52
+ for (const line of renderInfo(report))
53
+ print(line);
54
+ });
53
55
  }
@@ -41,6 +41,24 @@ GEMINI_API_KEY=
41
41
  GROQ_API_KEY=
42
42
  ANTHROPIC_API_KEY=
43
43
  `;
44
+ const EXAMPLE_MODELS = `# models.md — optional per-mode / per-task model routing (Phase 23). Uncomment and edit.
45
+ # Each entry is a soft prefer (default), a hard pin (=provider:model), or a strategy keyword
46
+ # (best|fastest|cheapest|highest-quality|local-only|cloud-only|privacy-first|provider-specific|
47
+ # fallback-only, or 'free' → cheapest). Explicit per-run choices always win over this file.
48
+ #
49
+ # What applies where:
50
+ # - chat mode + '## By task' (router tasks via 'ai-runtime route <task>'): prefer, pin, AND strategy.
51
+ # - orchestration modes (plan/execute/orchestrate/agent/debug): soft PREFER only — a hard pin or a
52
+ # strategy is NOT applied there (the planner and skills pick their own models). Use prefer for those.
53
+ #
54
+ # ## By mode
55
+ # - chat: free # chat: cheapest/local models (strategy works here)
56
+ # - plan: anthropic:claude-sonnet-5 # orchestration: soft prefer a strong planning model
57
+ # - execute: openai:gpt-5 # orchestration: soft prefer
58
+ #
59
+ # ## By task
60
+ # - generate-playwright-tests: =anthropic:claude-opus-5 # via 'ai-runtime route generate-playwright-tests'
61
+ `;
44
62
  const GITIGNORE_ENTRIES = ['.env', '.env.*', '!.env.example', '.ai-runtime/'];
45
63
  function writeIfAbsent(file, contents) {
46
64
  const full = resolve(process.cwd(), file);
@@ -70,6 +88,7 @@ export function initCommand() {
70
88
  // and `setup` scaffold the same file. A root `ai-runtime.yaml` still works as a fallback if present.
71
89
  writeIfAbsent('.ai-runtime/config.yaml', EXAMPLE_YAML);
72
90
  writeIfAbsent('.env.example', EXAMPLE_ENV);
91
+ writeIfAbsent('models.md', EXAMPLE_MODELS);
73
92
  ensureGitignore();
74
93
  print('\nNext steps:');
75
94
  print(' 1. Copy .env.example to .env and fill in the keys you have');
@@ -2,6 +2,8 @@
2
2
  * `ai-runtime providers` / `models` / `capabilities` — read-only inspection of the configured fleet.
3
3
  * All go through the key-free facade views and redacted output.
4
4
  */
5
+ import { Runtime } from '../../runtime/runtime.js';
6
+ import type { CapabilityGapReason, GapSuggestion, MissingCapabilityReport } from '../../capabilities/capability.js';
5
7
  export declare function providersCommand(opts: {
6
8
  config?: string;
7
9
  json?: boolean;
@@ -9,8 +11,45 @@ export declare function providersCommand(opts: {
9
11
  export declare function modelsCommand(opts: {
10
12
  config?: string;
11
13
  json?: boolean;
14
+ profile?: boolean;
12
15
  }): Promise<void>;
13
- export declare function capabilitiesCommand(opts: {
16
+ /**
17
+ * Gather the ACTION-capability table (Phase 3.1). Uses a full Runtime (like `doctor`) so autoloaded
18
+ * skills appear — a listing that omitted them would misreport availability.
19
+ */
20
+ export declare function actionCapabilityRows(rt: Runtime): Array<{
21
+ id: string;
22
+ provider: string;
23
+ source: string;
24
+ effects: string;
25
+ availability: string;
26
+ evidence: string;
27
+ description: string;
28
+ }>;
29
+ export declare function gatherActionCapabilities(config?: string): Promise<Array<{
30
+ id: string;
31
+ provider: string;
32
+ source: string;
33
+ effects: string;
34
+ availability: string;
35
+ evidence: string;
36
+ description: string;
37
+ }>>;
38
+ /** Render the action-capability table as aligned text (shared by the CLI and the REPL). */
39
+ export declare function renderActionCapabilities(rows: Awaited<ReturnType<typeof gatherActionCapabilities>>): string[];
40
+ /** One-line gloss per gap reason (exhaustive over CapabilityGapReason). */
41
+ export declare const GAP_REASON_GLOSS: Record<CapabilityGapReason, string>;
42
+ /** One actionable line per GapSuggestion (exhaustive over all four arms). */
43
+ export declare function renderGapSuggestion(s: GapSuggestion): string;
44
+ /**
45
+ * Render a MissingCapabilityReport. THE one gap renderer: `run`, the REPL result tail,
46
+ * `capabilities --actions <goal>` and `/capabilities <goal>` all call this, so they cannot drift.
47
+ * Every id, hint, and policyKey passes `displaySafe` — a gap's capabilityId originates in the model's
48
+ * plan JSON, and `sanitizeSegment` restricts its charset but sets no length bound.
49
+ */
50
+ export declare function renderCapabilityGaps(report: MissingCapabilityReport): string[];
51
+ export declare function capabilitiesCommand(goal: string | undefined, opts: {
14
52
  config?: string;
15
53
  json?: boolean;
54
+ actions?: boolean;
16
55
  }): Promise<void>;