ai-runtime-engine 1.2.0 → 2.7.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.
- package/CHANGELOG.md +602 -0
- package/dist/agents/admit.d.ts +69 -0
- package/dist/agents/admit.js +129 -0
- package/dist/agents/definition.d.ts +36 -0
- package/dist/agents/definition.js +9 -0
- package/dist/agents/envelope.d.ts +53 -0
- package/dist/agents/envelope.js +68 -0
- package/dist/agents/finding.d.ts +79 -0
- package/dist/agents/finding.js +80 -0
- package/dist/agents/task.d.ts +60 -0
- package/dist/agents/task.js +32 -0
- package/dist/agents/worker.d.ts +68 -0
- package/dist/agents/worker.js +256 -0
- package/dist/capabilities/capability.d.ts +117 -0
- package/dist/capabilities/capability.js +66 -0
- package/dist/capabilities/registry.d.ts +139 -0
- package/dist/capabilities/registry.js +413 -0
- package/dist/capabilities/vocabulary.d.ts +32 -0
- package/dist/capabilities/vocabulary.js +34 -0
- package/dist/cli/cli.js +56 -4
- package/dist/cli/commands/cleanup.js +29 -27
- package/dist/cli/commands/doctor.d.ts +14 -0
- package/dist/cli/commands/doctor.js +38 -8
- package/dist/cli/commands/executions.js +34 -25
- package/dist/cli/commands/info.d.ts +1 -0
- package/dist/cli/commands/info.js +11 -9
- package/dist/cli/commands/init.js +19 -0
- package/dist/cli/commands/inspect.d.ts +40 -1
- package/dist/cli/commands/inspect.js +157 -2
- package/dist/cli/commands/mcp.d.ts +45 -0
- package/dist/cli/commands/mcp.js +148 -0
- package/dist/cli/commands/route.js +21 -0
- package/dist/cli/commands/run.d.ts +2 -0
- package/dist/cli/commands/run.js +36 -4
- package/dist/cli/commands/skills.d.ts +2 -0
- package/dist/cli/commands/skills.js +29 -7
- package/dist/cli/interactive/ansi.d.ts +41 -0
- package/dist/cli/interactive/ansi.js +43 -0
- package/dist/cli/interactive/complete.d.ts +10 -0
- package/dist/cli/interactive/complete.js +19 -0
- package/dist/cli/interactive/repl.d.ts +3 -0
- package/dist/cli/interactive/repl.js +105 -16
- package/dist/cli/interactive/session.d.ts +12 -1
- package/dist/cli/interactive/session.js +83 -5
- package/dist/cli/render.d.ts +13 -0
- package/dist/cli/render.js +18 -0
- package/dist/cli/runtimeSession.d.ts +11 -0
- package/dist/cli/runtimeSession.js +17 -0
- package/dist/config/defaults.d.ts +3 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +2 -2
- package/dist/context/lossVerifier.d.ts +24 -0
- package/dist/context/lossVerifier.js +45 -0
- package/dist/context/summarize.d.ts +19 -0
- package/dist/context/summarize.js +53 -0
- package/dist/core/fallback/fallback.d.ts +8 -0
- package/dist/core/fallback/fallback.js +3 -1
- package/dist/core/router/executor.d.ts +6 -1
- package/dist/core/router/executor.js +9 -2
- package/dist/core/router/normalize.d.ts +2 -0
- package/dist/core/router/request.js +2 -0
- package/dist/core/router/router.d.ts +3 -0
- package/dist/core/router/router.js +7 -0
- package/dist/executions/execution.d.ts +13 -2
- package/dist/generation/generateAdapter.d.ts +14 -0
- package/dist/generation/generateAdapter.js +38 -0
- package/dist/generation/generateSkill.d.ts +26 -0
- package/dist/generation/generateSkill.js +51 -0
- package/dist/index.d.ts +44 -5
- package/dist/index.js +26 -2
- package/dist/mcp/client.d.ts +70 -0
- package/dist/mcp/client.js +221 -0
- package/dist/mcp/manager.d.ts +151 -0
- package/dist/mcp/manager.js +493 -0
- package/dist/mcp/protocol.d.ts +216 -0
- package/dist/mcp/protocol.js +149 -0
- package/dist/mcp/toolAdapter.d.ts +44 -0
- package/dist/mcp/toolAdapter.js +94 -0
- package/dist/mcp/transport.d.ts +109 -0
- package/dist/mcp/transport.js +383 -0
- package/dist/memory/embedders/hash.d.ts +12 -0
- package/dist/memory/embedders/hash.js +31 -0
- package/dist/memory/embedders/http.d.ts +25 -0
- package/dist/memory/embedders/http.js +48 -0
- package/dist/memory/memory.d.ts +19 -2
- package/dist/memory/memory.js +75 -11
- package/dist/memory/semantic.d.ts +17 -0
- package/dist/memory/semantic.js +29 -0
- package/dist/orchestration/budget.d.ts +30 -0
- package/dist/orchestration/budget.js +40 -0
- package/dist/orchestration/executor.d.ts +39 -1
- package/dist/orchestration/executor.js +64 -4
- package/dist/orchestration/orchestrator.d.ts +29 -1
- package/dist/orchestration/orchestrator.js +89 -8
- package/dist/orchestration/plan.d.ts +15 -1
- package/dist/orchestration/plan.js +23 -4
- package/dist/orchestration/planner.d.ts +19 -1
- package/dist/orchestration/planner.js +25 -5
- package/dist/plugin/ai.d.ts +4 -0
- package/dist/plugin/ai.js +9 -0
- package/dist/providers/httpClient.d.ts +25 -1
- package/dist/providers/httpClient.js +93 -0
- package/dist/providers/httpProvider.d.ts +1 -0
- package/dist/providers/httpProvider.js +67 -1
- package/dist/providers/mock/mockProvider.d.ts +3 -0
- package/dist/providers/mock/mockProvider.js +54 -0
- package/dist/providers/mock/scenarios.d.ts +7 -0
- package/dist/providers/provider.d.ts +6 -0
- package/dist/providers/wire/anthropicWire.js +34 -0
- package/dist/providers/wire/openaiWire.js +30 -0
- package/dist/providers/wire/types.d.ts +16 -0
- package/dist/runtime/config.js +50 -6
- package/dist/runtime/intent/aiClassifier.d.ts +19 -0
- package/dist/runtime/intent/aiClassifier.js +74 -0
- package/dist/runtime/models/modelProfile.d.ts +61 -0
- package/dist/runtime/models/modelProfile.js +139 -0
- package/dist/runtime/planning/deriveCapabilities.d.ts +95 -0
- package/dist/runtime/planning/deriveCapabilities.js +146 -0
- package/dist/runtime/policy.d.ts +10 -0
- package/dist/runtime/policy.js +9 -2
- package/dist/runtime/runtime.d.ts +173 -0
- package/dist/runtime/runtime.js +723 -50
- package/dist/runtime/types.d.ts +94 -2
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +24 -0
- package/dist/skills/registry.d.ts +16 -1
- package/dist/skills/registry.js +21 -1
- package/dist/skills/skill.d.ts +6 -1
- package/dist/store/area.d.ts +15 -1
- package/dist/store/area.js +19 -8
- package/dist/store/crypto.d.ts +21 -0
- package/dist/store/crypto.js +49 -0
- package/dist/store/paths.d.ts +5 -1
- package/dist/store/paths.js +6 -0
- package/dist/store/store.d.ts +15 -3
- package/dist/store/store.js +28 -7
- package/dist/telemetry/sinks/otlp.d.ts +31 -0
- package/dist/telemetry/sinks/otlp.js +76 -0
- package/dist/tools/builtins/filesystem.js +1 -0
- package/dist/tools/builtins/git.js +1 -0
- package/dist/tools/builtins/shell.js +1 -0
- package/dist/tools/permissions.d.ts +28 -0
- package/dist/tools/permissions.js +72 -0
- package/dist/tools/registry.d.ts +18 -2
- package/dist/tools/registry.js +22 -2
- package/dist/tools/tool.d.ts +4 -0
- package/dist/types.d.ts +11 -1
- package/dist/util/flatten.d.ts +11 -0
- package/dist/util/flatten.js +18 -0
- package/dist/util/semaphore.d.ts +19 -0
- package/dist/util/semaphore.js +60 -0
- package/package.json +24 -9
package/dist/runtime/types.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ import type { CompileMetrics, ContextValidation } from '../context/compiler.js';
|
|
|
8
8
|
import type { PermissionPolicy } from './policy.js';
|
|
9
9
|
import type { ExecutionPlan } from '../orchestration/plan.js';
|
|
10
10
|
import type { ComparisonPin, ComparisonResult } from '../comparison/comparison.js';
|
|
11
|
+
import type { MissingCapabilityReport } from '../capabilities/capability.js';
|
|
12
|
+
import type { AgentDefinition } from '../agents/definition.js';
|
|
11
13
|
export declare const RUNTIME_MODES: readonly ["auto", "chat", "plan", "execute", "orchestrate", "agent", "compare", "debug"];
|
|
12
14
|
export type RuntimeMode = (typeof RUNTIME_MODES)[number];
|
|
13
15
|
/** A mode that can actually run — never 'auto' (auto resolves TO one of these). */
|
|
@@ -39,7 +41,7 @@ export interface ArtifactRef {
|
|
|
39
41
|
metadata?: Record<string, unknown>;
|
|
40
42
|
}
|
|
41
43
|
/** Task-level status. `ok` (on RuntimeResult) is whether the Runtime OP succeeded; this is whether the TASK is done/waiting. */
|
|
42
|
-
export type RuntimeStatus = 'completed' | 'waiting_for_clarification' | 'waiting_for_approval' | 'failed';
|
|
44
|
+
export type RuntimeStatus = 'completed' | 'waiting_for_clarification' | 'waiting_for_approval' | 'waiting_for_budget' | 'failed';
|
|
43
45
|
export interface Clarification {
|
|
44
46
|
question: string;
|
|
45
47
|
options?: string[];
|
|
@@ -48,9 +50,13 @@ export interface RuntimeSettings {
|
|
|
48
50
|
/** Project default mode. 'auto' means "always detect". */
|
|
49
51
|
defaultMode?: RuntimeMode;
|
|
50
52
|
defaultStrategy?: Strategy;
|
|
51
|
-
/** Context-compilation settings
|
|
53
|
+
/** Context-compilation settings. `maxTokens` bounds the compiled context; `verifyLoss` (Phase 16) adds
|
|
54
|
+
* a model check for task-critical loss (appended to `validation.checks`, never blocks); `summarize`
|
|
55
|
+
* (Phase 16) abstractively shrinks over-budget non-critical blocks before compiling. Both default off. */
|
|
52
56
|
context?: {
|
|
53
57
|
maxTokens?: number;
|
|
58
|
+
verifyLoss?: boolean;
|
|
59
|
+
summarize?: boolean;
|
|
54
60
|
};
|
|
55
61
|
/** Project exclude/prefer routing (merged with env + per-run; EXCLUDE hard, PREFER soft). */
|
|
56
62
|
routing?: RoutingPreferences;
|
|
@@ -60,9 +66,83 @@ export interface RuntimeSettings {
|
|
|
60
66
|
packages?: string[];
|
|
61
67
|
autoload?: boolean;
|
|
62
68
|
};
|
|
69
|
+
/** Semantic memory retrieval (Phase 14). Absent → BM25 keyword retrieval (the default). `local` uses the
|
|
70
|
+
* zero-dep HashEmbedder; `openai-compatible` calls a `{baseUrl}/embeddings` endpoint (key by env NAME). */
|
|
71
|
+
embedding?: {
|
|
72
|
+
provider: 'local' | 'openai-compatible';
|
|
73
|
+
baseUrl?: string;
|
|
74
|
+
apiKeyEnv?: string;
|
|
75
|
+
model?: string;
|
|
76
|
+
};
|
|
77
|
+
/** Intent classification (Phase 15). `aiFallback: true` consults one model call ONLY when the offline
|
|
78
|
+
* heuristic is ambiguous; the heuristic stays the default and any model failure degrades back to it. */
|
|
79
|
+
intent?: {
|
|
80
|
+
aiFallback?: boolean;
|
|
81
|
+
};
|
|
82
|
+
/** Organization identity (Phase 17). When set, the `organization` memory scope becomes available — a
|
|
83
|
+
* store above user/project/repository, shared across every project/repo that configures the same name.
|
|
84
|
+
* Absent → the scope is unavailable and `remember({scope:'organization'})` errors clearly. */
|
|
85
|
+
organization?: string;
|
|
86
|
+
/** Encryption at rest (Phase 18). `encrypt: true` transparently AES-256-GCM-encrypts every store file;
|
|
87
|
+
* the key is resolved from the env var NAMED by `keyEnv` (never a value). Missing key ⇒ a hard CONFIG
|
|
88
|
+
* error at load. Absent → plaintext store (the default); a mixed store reads pre-encryption files fine. */
|
|
89
|
+
storage?: {
|
|
90
|
+
encrypt: boolean;
|
|
91
|
+
keyEnv: string;
|
|
92
|
+
};
|
|
93
|
+
/** Action capabilities (Phase 3.1/3.3). `catalog` enriches the planner catalog (default off);
|
|
94
|
+
* `planning` (3.3, default OFF) derives the capabilities a goal needs BEFORE planning — offline BM25
|
|
95
|
+
* first, one model call only when that finds nothing AND no call budget is set AND this is not a dry
|
|
96
|
+
* run — resolves them against the run's permissions, and reports gaps. ADVISORY: it never blocks a
|
|
97
|
+
* run and never grants anything, and with it off the planner prompt and every result field are
|
|
98
|
+
* byte-identical. `aliases.<curatedId> = <providerId>` CLAIMS a curated id for a provider;
|
|
99
|
+
* `pins.<capabilityId> = <providerId>` SELECTS among already-eligible providers. */
|
|
100
|
+
capabilities?: {
|
|
101
|
+
catalog?: boolean;
|
|
102
|
+
planning?: boolean;
|
|
103
|
+
aliases?: Record<string, string>;
|
|
104
|
+
pins?: Record<string, string>;
|
|
105
|
+
};
|
|
106
|
+
/** Concurrency caps (Phase 19). `maxParallelSteps` is the orchestrator's batch bound (config rung under
|
|
107
|
+
* the per-run override); `perTool`/`perSkill` cap concurrent uses of a given tool/skill within that batch;
|
|
108
|
+
* `perProvider` caps concurrent calls to a given provider across the router (bites under fan-out). */
|
|
109
|
+
concurrency?: {
|
|
110
|
+
maxParallelSteps?: number;
|
|
111
|
+
perTool?: Record<string, number>;
|
|
112
|
+
perSkill?: Record<string, number>;
|
|
113
|
+
perProvider?: Record<string, number>;
|
|
114
|
+
perAgent?: Record<string, number>;
|
|
115
|
+
};
|
|
116
|
+
/** Multi-agent core (Phase 3.4). Absent ⇒ agent execution is entirely inert: every prompt, every
|
|
117
|
+
* OrchestrateInput key, every observation key, and every rendered plan line is byte-identical to
|
|
118
|
+
* 2.6.0. `definitions` are operator-authored ceilings; the per-task caps here are defaults a
|
|
119
|
+
* definition may only LOWER. `maxInnerCalls` is both the budget reservation an agent step costs and
|
|
120
|
+
* the worker's hard inner-model-call ceiling. */
|
|
121
|
+
agents?: {
|
|
122
|
+
enabled?: boolean;
|
|
123
|
+
maxToolCalls?: number;
|
|
124
|
+
maxDurationMs?: number;
|
|
125
|
+
maxInnerCalls?: number;
|
|
126
|
+
definitions?: Record<string, AgentDefinition>;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** One user-added MCP server (Phase 3.2). `tokenEnv` names an env var; values live only in `.env`. */
|
|
130
|
+
export interface McpServerSettings {
|
|
131
|
+
transport: 'stdio' | 'http';
|
|
132
|
+
url?: string;
|
|
133
|
+
command?: string;
|
|
134
|
+
args?: string[];
|
|
135
|
+
tokenEnv?: string;
|
|
136
|
+
enabled?: boolean;
|
|
137
|
+
timeoutMs?: number;
|
|
138
|
+
capabilities?: Record<string, string[]>;
|
|
63
139
|
}
|
|
64
140
|
export interface RuntimeConfig {
|
|
65
141
|
runtime?: RuntimeSettings;
|
|
142
|
+
/** MCP servers to connect (Phase 3.2). Absent ⇒ MCP is entirely inert. */
|
|
143
|
+
mcp?: {
|
|
144
|
+
servers?: Record<string, McpServerSettings>;
|
|
145
|
+
};
|
|
66
146
|
/** Tool permission grants (deny-by-default for write/exec; see PermissionPolicy). */
|
|
67
147
|
permissions?: PermissionPolicy;
|
|
68
148
|
/** The existing router configuration (providers, strategy, learning, verification, budget, policy, tasks…). */
|
|
@@ -89,6 +169,12 @@ export interface RuntimeRunInput {
|
|
|
89
169
|
};
|
|
90
170
|
/** Dry-run: plan + report what WOULD happen, performing zero mutations (plan/execute/orchestrate). */
|
|
91
171
|
dryRun?: boolean;
|
|
172
|
+
/** Stream the answer token-by-token in chat mode: emits `response.delta` lifecycle events (Phase 13).
|
|
173
|
+
* Text output only; ignored for JSON output and for dry runs. */
|
|
174
|
+
stream?: boolean;
|
|
175
|
+
/** Phase 22: when the plan exceeds the call budget, run the phases that fit and pause resumably instead
|
|
176
|
+
* of the default notify-and-wait. Orchestration modes only. */
|
|
177
|
+
partial?: boolean;
|
|
92
178
|
/** Caller idempotency identity (dedup enforced from Phase 7). */
|
|
93
179
|
requestId?: string;
|
|
94
180
|
/** Per-run exclude/prefer routing (highest precedence; merged with config + env). */
|
|
@@ -116,9 +202,12 @@ export interface RuntimeResult {
|
|
|
116
202
|
runId: string;
|
|
117
203
|
mode: ModeResolution;
|
|
118
204
|
status: RuntimeStatus;
|
|
205
|
+
/** `streamed` is true when `text` was already delivered via `response.delta` events (Phase 13) — a
|
|
206
|
+
* renderer that showed the deltas live should not reprint it. */
|
|
119
207
|
response?: {
|
|
120
208
|
text?: string;
|
|
121
209
|
json?: unknown;
|
|
210
|
+
streamed?: boolean;
|
|
122
211
|
};
|
|
123
212
|
/** Present when the runtime needs the user to disambiguate. Never a failure. */
|
|
124
213
|
clarification?: Clarification;
|
|
@@ -127,6 +216,9 @@ export interface RuntimeResult {
|
|
|
127
216
|
error?: RouterErrorInfo;
|
|
128
217
|
/** Artifact references produced by this run (empty until the Artifact Store lands in Phase 7). */
|
|
129
218
|
artifacts: ArtifactRef[];
|
|
219
|
+
/** Structured capability gaps when a plan referenced something unavailable (Phase 3.1). Additive:
|
|
220
|
+
* present only when a gap was detected; `status`/`error` semantics are unchanged. */
|
|
221
|
+
capabilityGaps?: MissingCapabilityReport;
|
|
130
222
|
/** What memory was retrieved into context and/or captured this run (Phase 3). */
|
|
131
223
|
memory?: MemoryTrace;
|
|
132
224
|
/** How the model context was compiled this run: metrics + loss validation (Phase 4). */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the permission-gated tool layer and whose validate() confirms every step produced evidence (a tool
|
|
6
6
|
* `ok`). Anything richer than composing tools belongs in a JS-module skill instead.
|
|
7
7
|
*/
|
|
8
|
+
import type { ActionCapabilityDecl } from '../capabilities/capability.js';
|
|
8
9
|
import type { Skill } from './skill.js';
|
|
9
10
|
export interface ManifestStep {
|
|
10
11
|
/** A registered tool id (filesystem, shell, git, or a user-registered tool). */
|
|
@@ -19,6 +20,8 @@ export interface SkillManifest {
|
|
|
19
20
|
name: string;
|
|
20
21
|
description: string;
|
|
21
22
|
runtimeCompatibility?: string;
|
|
23
|
+
/** ACTION capabilities this skill provides (Phase 3.1). Advisory metadata, never authorization. */
|
|
24
|
+
provides?: ActionCapabilityDecl[];
|
|
22
25
|
steps: ManifestStep[];
|
|
23
26
|
}
|
|
24
27
|
/** Validate an arbitrary parsed object as a SkillManifest. Returns the manifest or a reason it is invalid. */
|
package/dist/skills/manifest.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the permission-gated tool layer and whose validate() confirms every step produced evidence (a tool
|
|
6
6
|
* `ok`). Anything richer than composing tools belongs in a JS-module skill instead.
|
|
7
7
|
*/
|
|
8
|
+
import { CURATED_ID_RE, NAMESPACED_ID_RE } from '../capabilities/capability.js';
|
|
8
9
|
/** Validate an arbitrary parsed object as a SkillManifest. Returns the manifest or a reason it is invalid. */
|
|
9
10
|
export function parseManifest(raw) {
|
|
10
11
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
@@ -25,6 +26,27 @@ export function parseManifest(raw) {
|
|
|
25
26
|
return { error: `manifest.steps[${i}].tool must be a tool id` };
|
|
26
27
|
steps.push({ tool: step.tool, ...(typeof step.description === 'string' ? { description: step.description } : {}), ...('input' in step ? { input: step.input } : {}) });
|
|
27
28
|
}
|
|
29
|
+
// Phase 3.1: optional `provides:` — a declaration that cannot be honored must be loud.
|
|
30
|
+
let provides;
|
|
31
|
+
if ('provides' in o && o.provides !== undefined) {
|
|
32
|
+
if (!Array.isArray(o.provides))
|
|
33
|
+
return { error: 'manifest.provides must be an array' };
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const [i, raw] of o.provides.entries()) {
|
|
36
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
37
|
+
return { error: `manifest.provides[${i}] must be a mapping` };
|
|
38
|
+
const d = raw;
|
|
39
|
+
const id = typeof d.id === 'string' ? d.id.trim() : '';
|
|
40
|
+
if (!id || !(CURATED_ID_RE.test(id) || NAMESPACED_ID_RE.test(id)))
|
|
41
|
+
return { error: `manifest.provides[${i}].id must be a capability id ([a-z0-9_] or tool:/skill:/mcp: prefixed)` };
|
|
42
|
+
if (d.effects !== undefined) {
|
|
43
|
+
if (!Array.isArray(d.effects) || !d.effects.every((e) => e === 'read' || e === 'write'))
|
|
44
|
+
return { error: `manifest.provides[${i}].effects must be an array of 'read' | 'write'` };
|
|
45
|
+
}
|
|
46
|
+
out.push({ id, ...(Array.isArray(d.effects) ? { effects: d.effects } : {}), ...(typeof d.description === 'string' ? { description: d.description } : {}) });
|
|
47
|
+
}
|
|
48
|
+
provides = out;
|
|
49
|
+
}
|
|
28
50
|
return {
|
|
29
51
|
manifest: {
|
|
30
52
|
id: o.id,
|
|
@@ -32,6 +54,7 @@ export function parseManifest(raw) {
|
|
|
32
54
|
name: o.name,
|
|
33
55
|
description: o.description,
|
|
34
56
|
...(typeof o.runtimeCompatibility === 'string' ? { runtimeCompatibility: o.runtimeCompatibility } : {}),
|
|
57
|
+
...(provides ? { provides } : {}),
|
|
35
58
|
steps,
|
|
36
59
|
},
|
|
37
60
|
};
|
|
@@ -45,6 +68,7 @@ export function manifestToSkill(m, sourcePath) {
|
|
|
45
68
|
name: m.name,
|
|
46
69
|
description: m.description,
|
|
47
70
|
...(m.runtimeCompatibility ? { runtimeCompatibility: m.runtimeCompatibility } : {}),
|
|
71
|
+
...(m.provides ? { provides: m.provides } : {}),
|
|
48
72
|
tools,
|
|
49
73
|
async plan() {
|
|
50
74
|
return { steps: m.steps.map((s) => ({ description: s.description ?? `run ${s.tool}`, tool: s.tool })) };
|
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* A registry of skills, keyed by id, with a simple tool matcher.
|
|
3
|
+
*
|
|
4
|
+
* An optional change listener (Phase 3.1) lets the Runtime ingest ACTION-capability declarations at the
|
|
5
|
+
* one real registration choke point — builtins, autoloaded manifests, npm skill packs, and user
|
|
6
|
+
* `registerSkill` all pass through `register()`. The zero-arg constructor is preserved (public API).
|
|
7
|
+
*/
|
|
2
8
|
import type { Skill } from './skill.js';
|
|
3
9
|
export interface MatchCriteria {
|
|
4
10
|
/** Tool ids currently registered — a skill matches only if all its required tools are present. */
|
|
5
11
|
tools?: string[];
|
|
6
12
|
}
|
|
13
|
+
export interface SkillRegistryListener {
|
|
14
|
+
onRegister?: (skill: Skill) => void;
|
|
15
|
+
onUnregister?: (id: string) => void;
|
|
16
|
+
}
|
|
7
17
|
export declare class SkillRegistry {
|
|
8
18
|
private readonly skills;
|
|
19
|
+
private listener;
|
|
20
|
+
/** Attach the change listener (called once by the Runtime before builtin registration). */
|
|
21
|
+
attach(listener: SkillRegistryListener): this;
|
|
9
22
|
register(skill: Skill): this;
|
|
23
|
+
/** Remove a skill (and, via the listener, its capability declarations). */
|
|
24
|
+
unregister(id: string): boolean;
|
|
10
25
|
get(id: string): Skill | undefined;
|
|
11
26
|
has(id: string): boolean;
|
|
12
27
|
list(): Skill[];
|
package/dist/skills/registry.js
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* A registry of skills, keyed by id, with a simple tool matcher.
|
|
3
|
+
*
|
|
4
|
+
* An optional change listener (Phase 3.1) lets the Runtime ingest ACTION-capability declarations at the
|
|
5
|
+
* one real registration choke point — builtins, autoloaded manifests, npm skill packs, and user
|
|
6
|
+
* `registerSkill` all pass through `register()`. The zero-arg constructor is preserved (public API).
|
|
7
|
+
*/
|
|
2
8
|
export class SkillRegistry {
|
|
3
9
|
skills = new Map();
|
|
10
|
+
listener = {};
|
|
11
|
+
/** Attach the change listener (called once by the Runtime before builtin registration). */
|
|
12
|
+
attach(listener) {
|
|
13
|
+
this.listener = listener ?? {};
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
4
16
|
register(skill) {
|
|
5
17
|
this.skills.set(skill.id, skill);
|
|
18
|
+
this.listener.onRegister?.(skill);
|
|
6
19
|
return this;
|
|
7
20
|
}
|
|
21
|
+
/** Remove a skill (and, via the listener, its capability declarations). */
|
|
22
|
+
unregister(id) {
|
|
23
|
+
const had = this.skills.delete(id);
|
|
24
|
+
if (had)
|
|
25
|
+
this.listener.onUnregister?.(id);
|
|
26
|
+
return had;
|
|
27
|
+
}
|
|
8
28
|
get(id) {
|
|
9
29
|
return this.skills.get(id);
|
|
10
30
|
}
|
package/dist/skills/skill.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { AI } from '../plugin/ai.js';
|
|
|
9
9
|
import type { CapabilityRequirement } from '../types.js';
|
|
10
10
|
import type { ArtifactRef } from '../runtime/types.js';
|
|
11
11
|
import type { PermissionPolicy } from '../runtime/policy.js';
|
|
12
|
+
import type { ActionCapabilityDecl } from '../capabilities/capability.js';
|
|
12
13
|
import type { ToolResult } from '../tools/tool.js';
|
|
13
14
|
export interface SkillContext {
|
|
14
15
|
input: unknown;
|
|
@@ -50,8 +51,12 @@ export interface Skill {
|
|
|
50
51
|
runtimeCompatibility?: string;
|
|
51
52
|
name: string;
|
|
52
53
|
description: string;
|
|
53
|
-
/**
|
|
54
|
+
/** MODEL capabilities required FROM the routed model (unrelated to action capabilities — see
|
|
55
|
+
* `provides`). Advisory in Phase 5; consumed by the router when threaded into a request. */
|
|
54
56
|
capabilities?: CapabilityRequirement[];
|
|
57
|
+
/** ACTION capabilities this skill provides (Phase 3.1). Advisory metadata, never authorization.
|
|
58
|
+
* Absent ⇒ one inferred `skill:<id>` entry. A declared curated id is namespaced to this skill. */
|
|
59
|
+
provides?: ActionCapabilityDecl[];
|
|
55
60
|
/** Tool ids the skill uses (matched against the registered tools). */
|
|
56
61
|
tools?: string[];
|
|
57
62
|
/** Other skill ids this skill depends on. */
|
package/dist/store/area.d.ts
CHANGED
|
@@ -8,6 +8,15 @@ export interface IntegrityIssue {
|
|
|
8
8
|
problem: 'unreadable' | 'checksum-mismatch' | 'invalid-json';
|
|
9
9
|
detail?: string;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Transforms the STORED STRING of each file (Phase 18). With a codec, `FileArea` writes `encode(json)` and
|
|
13
|
+
* reads `decode(raw)` before parsing — the only hook encryption needs. `decode` must pass a non-envelope
|
|
14
|
+
* string through unchanged (mixed/legacy stores) and may throw on an undecryptable one (→ corruption path).
|
|
15
|
+
*/
|
|
16
|
+
export interface ContentCodec {
|
|
17
|
+
encode(plain: string): string;
|
|
18
|
+
decode(stored: string): string;
|
|
19
|
+
}
|
|
11
20
|
export interface Area {
|
|
12
21
|
readonly enabled: boolean;
|
|
13
22
|
/** Strict read: throws on corruption (use for an explicit single-record read). */
|
|
@@ -23,10 +32,15 @@ export interface Area {
|
|
|
23
32
|
}
|
|
24
33
|
export declare class FileArea implements Area {
|
|
25
34
|
private readonly dir;
|
|
35
|
+
private readonly codec?;
|
|
26
36
|
readonly enabled = true;
|
|
27
37
|
private ensured;
|
|
28
38
|
private counter;
|
|
29
|
-
constructor(dir: string);
|
|
39
|
+
constructor(dir: string, codec?: ContentCodec | undefined);
|
|
40
|
+
/** Transform stored bytes → plaintext string. Without a codec this is the identity. */
|
|
41
|
+
private decode;
|
|
42
|
+
/** Transform a plaintext string → stored bytes. Without a codec this is the identity. */
|
|
43
|
+
private encode;
|
|
30
44
|
private ensure;
|
|
31
45
|
private jsonPath;
|
|
32
46
|
private logPath;
|
package/dist/store/area.js
CHANGED
|
@@ -19,11 +19,21 @@ function checksum(data) {
|
|
|
19
19
|
}
|
|
20
20
|
export class FileArea {
|
|
21
21
|
dir;
|
|
22
|
+
codec;
|
|
22
23
|
enabled = true;
|
|
23
24
|
ensured = false;
|
|
24
25
|
counter = 0;
|
|
25
|
-
constructor(dir) {
|
|
26
|
+
constructor(dir, codec) {
|
|
26
27
|
this.dir = dir;
|
|
28
|
+
this.codec = codec;
|
|
29
|
+
}
|
|
30
|
+
/** Transform stored bytes → plaintext string. Without a codec this is the identity. */
|
|
31
|
+
decode(raw) {
|
|
32
|
+
return this.codec ? this.codec.decode(raw) : raw;
|
|
33
|
+
}
|
|
34
|
+
/** Transform a plaintext string → stored bytes. Without a codec this is the identity. */
|
|
35
|
+
encode(plain) {
|
|
36
|
+
return this.codec ? this.codec.encode(plain) : plain;
|
|
27
37
|
}
|
|
28
38
|
ensure() {
|
|
29
39
|
if (!this.ensured) {
|
|
@@ -41,7 +51,7 @@ export class FileArea {
|
|
|
41
51
|
this.ensure();
|
|
42
52
|
this.counter += 1;
|
|
43
53
|
const tmp = `${path}.tmp-${process.pid}-${this.counter}`;
|
|
44
|
-
writeFileSync(tmp, contents);
|
|
54
|
+
writeFileSync(tmp, this.encode(contents));
|
|
45
55
|
renameSync(tmp, path);
|
|
46
56
|
}
|
|
47
57
|
readJson(id) {
|
|
@@ -49,7 +59,7 @@ export class FileArea {
|
|
|
49
59
|
const path = this.jsonPath(id);
|
|
50
60
|
if (!existsSync(path))
|
|
51
61
|
return undefined;
|
|
52
|
-
const wrapper = JSON.parse(readFileSync(path, 'utf8'));
|
|
62
|
+
const wrapper = JSON.parse(this.decode(readFileSync(path, 'utf8')));
|
|
53
63
|
if (wrapper.checksum !== checksum(wrapper.data))
|
|
54
64
|
throw new Error(`integrity check failed for ${path}`);
|
|
55
65
|
return wrapper.data;
|
|
@@ -85,7 +95,8 @@ export class FileArea {
|
|
|
85
95
|
appendLine(id, obj) {
|
|
86
96
|
assertId(id);
|
|
87
97
|
this.ensure();
|
|
88
|
-
|
|
98
|
+
// Each JSONL line is encrypted independently (its own IV), so appends stay append-only.
|
|
99
|
+
appendFileSync(this.logPath(id), this.encode(JSON.stringify(obj)) + '\n');
|
|
89
100
|
}
|
|
90
101
|
readLines(id) {
|
|
91
102
|
assertId(id);
|
|
@@ -97,10 +108,10 @@ export class FileArea {
|
|
|
97
108
|
if (!line)
|
|
98
109
|
continue;
|
|
99
110
|
try {
|
|
100
|
-
out.push(JSON.parse(line));
|
|
111
|
+
out.push(JSON.parse(this.decode(line)));
|
|
101
112
|
}
|
|
102
113
|
catch {
|
|
103
|
-
/* skip a corrupt line rather than failing the whole log (surfaced by check()) */
|
|
114
|
+
/* skip a corrupt/undecryptable line rather than failing the whole log (surfaced by check()) */
|
|
104
115
|
}
|
|
105
116
|
}
|
|
106
117
|
return out;
|
|
@@ -117,7 +128,7 @@ export class FileArea {
|
|
|
117
128
|
}
|
|
118
129
|
if (f.endsWith('.json')) {
|
|
119
130
|
try {
|
|
120
|
-
const wrapper = JSON.parse(readFileSync(path, 'utf8'));
|
|
131
|
+
const wrapper = JSON.parse(this.decode(readFileSync(path, 'utf8')));
|
|
121
132
|
if (wrapper.checksum !== checksum(wrapper.data))
|
|
122
133
|
issues.push({ file: path, problem: 'checksum-mismatch' });
|
|
123
134
|
}
|
|
@@ -129,7 +140,7 @@ export class FileArea {
|
|
|
129
140
|
const lines = readFileSync(path, 'utf8').split('\n').filter(Boolean);
|
|
130
141
|
lines.forEach((line, i) => {
|
|
131
142
|
try {
|
|
132
|
-
JSON.parse(line);
|
|
143
|
+
JSON.parse(this.decode(line));
|
|
133
144
|
}
|
|
134
145
|
catch {
|
|
135
146
|
issues.push({ file: path, problem: 'invalid-json', detail: `line ${i + 1}` });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transparent encryption at rest (Phase 18) — AES-256-GCM via `node:crypto` (zero new deps). A `ContentCodec`
|
|
3
|
+
* transforms the STORED STRING of a store file: `encode` wraps plaintext as a self-identifying envelope
|
|
4
|
+
* `aienc1:<base64(iv|tag|ciphertext)>`; `decode` reverses it. `decode` is deliberately lenient on the way IN —
|
|
5
|
+
* a string WITHOUT the `aienc1:` prefix is returned unchanged, so a store that predates encryption keeps
|
|
6
|
+
* reading after it is enabled (new writes are always encrypted). A wrong key / tampered payload makes `decode`
|
|
7
|
+
* THROW, which the store maps onto its existing corruption path (soft-skip in bulk reads, reported by check()).
|
|
8
|
+
*
|
|
9
|
+
* SECURITY: the derived key is a Buffer held only inside the codec closure; it is never serialized, logged, or
|
|
10
|
+
* placed in an error. The AES key is sha256(secret), so the raw secret length is irrelevant.
|
|
11
|
+
*/
|
|
12
|
+
import type { ContentCodec } from './area.js';
|
|
13
|
+
export declare const ENVELOPE_PREFIX = "aienc1:";
|
|
14
|
+
/** Derive the 32-byte AES-256 key from an arbitrary secret (sha256 → fixed length, secret length irrelevant). */
|
|
15
|
+
export declare function deriveKey(secret: string): Buffer;
|
|
16
|
+
/** Encrypt a UTF-8 string into the self-identifying `aienc1:` envelope. A fresh random IV per call. */
|
|
17
|
+
export declare function encryptString(plain: string, key: Buffer): string;
|
|
18
|
+
/** Decrypt an `aienc1:` envelope; a string without the prefix is returned unchanged (mixed/legacy store). */
|
|
19
|
+
export declare function decryptString(stored: string, key: Buffer): string;
|
|
20
|
+
/** Build the codec `FileArea` uses to encrypt/decrypt its file contents. */
|
|
21
|
+
export declare function makeCodec(key: Buffer): ContentCodec;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transparent encryption at rest (Phase 18) — AES-256-GCM via `node:crypto` (zero new deps). A `ContentCodec`
|
|
3
|
+
* transforms the STORED STRING of a store file: `encode` wraps plaintext as a self-identifying envelope
|
|
4
|
+
* `aienc1:<base64(iv|tag|ciphertext)>`; `decode` reverses it. `decode` is deliberately lenient on the way IN —
|
|
5
|
+
* a string WITHOUT the `aienc1:` prefix is returned unchanged, so a store that predates encryption keeps
|
|
6
|
+
* reading after it is enabled (new writes are always encrypted). A wrong key / tampered payload makes `decode`
|
|
7
|
+
* THROW, which the store maps onto its existing corruption path (soft-skip in bulk reads, reported by check()).
|
|
8
|
+
*
|
|
9
|
+
* SECURITY: the derived key is a Buffer held only inside the codec closure; it is never serialized, logged, or
|
|
10
|
+
* placed in an error. The AES key is sha256(secret), so the raw secret length is irrelevant.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash, randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
13
|
+
export const ENVELOPE_PREFIX = 'aienc1:';
|
|
14
|
+
const IV_LEN = 12; // GCM standard nonce
|
|
15
|
+
const TAG_LEN = 16; // GCM auth tag
|
|
16
|
+
/** Derive the 32-byte AES-256 key from an arbitrary secret (sha256 → fixed length, secret length irrelevant). */
|
|
17
|
+
export function deriveKey(secret) {
|
|
18
|
+
return createHash('sha256').update(secret).digest();
|
|
19
|
+
}
|
|
20
|
+
/** Encrypt a UTF-8 string into the self-identifying `aienc1:` envelope. A fresh random IV per call. */
|
|
21
|
+
export function encryptString(plain, key) {
|
|
22
|
+
const iv = randomBytes(IV_LEN);
|
|
23
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
24
|
+
const ct = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
|
|
25
|
+
const tag = cipher.getAuthTag();
|
|
26
|
+
return ENVELOPE_PREFIX + Buffer.concat([iv, tag, ct]).toString('base64');
|
|
27
|
+
}
|
|
28
|
+
/** Decrypt an `aienc1:` envelope; a string without the prefix is returned unchanged (mixed/legacy store). */
|
|
29
|
+
export function decryptString(stored, key) {
|
|
30
|
+
if (!stored.startsWith(ENVELOPE_PREFIX))
|
|
31
|
+
return stored; // plaintext (pre-encryption) record — read as-is
|
|
32
|
+
const buf = Buffer.from(stored.slice(ENVELOPE_PREFIX.length), 'base64');
|
|
33
|
+
if (buf.length < IV_LEN + TAG_LEN)
|
|
34
|
+
throw new Error('encrypted record is truncated');
|
|
35
|
+
const iv = buf.subarray(0, IV_LEN);
|
|
36
|
+
const tag = buf.subarray(IV_LEN, IV_LEN + TAG_LEN);
|
|
37
|
+
const ct = buf.subarray(IV_LEN + TAG_LEN);
|
|
38
|
+
const decipher = createDecipheriv('aes-256-gcm', key, iv);
|
|
39
|
+
decipher.setAuthTag(tag);
|
|
40
|
+
// GCM `final()` throws on a wrong key or tampered ciphertext — surfaces as store corruption to the caller.
|
|
41
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');
|
|
42
|
+
}
|
|
43
|
+
/** Build the codec `FileArea` uses to encrypt/decrypt its file contents. */
|
|
44
|
+
export function makeCodec(key) {
|
|
45
|
+
return {
|
|
46
|
+
encode: (plain) => encryptString(plain, key),
|
|
47
|
+
decode: (stored) => decryptString(stored, key),
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/store/paths.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `repository` identity (the git repo root) backs repository-scoped memory across those packages.
|
|
6
6
|
*/
|
|
7
7
|
/** Where a memory record physically lives. */
|
|
8
|
-
export type StoreScope = 'user' | 'project' | 'repository';
|
|
8
|
+
export type StoreScope = 'user' | 'project' | 'repository' | 'organization';
|
|
9
9
|
export declare function resolveHome(env?: NodeJS.ProcessEnv): string;
|
|
10
10
|
/** Walk up from a directory to find the git repo root (dir containing `.git`). Undefined if none. */
|
|
11
11
|
export declare function findRepoRoot(start: string): string | undefined;
|
|
@@ -13,3 +13,7 @@ export declare function findRepoRoot(start: string): string | undefined;
|
|
|
13
13
|
export declare function projectId(root: string, name?: string): string;
|
|
14
14
|
/** Stable id for the enclosing git repository (falls back to the project id when not a repo). */
|
|
15
15
|
export declare function repositoryId(root: string, name?: string): string;
|
|
16
|
+
/** Stable id for a configured organization name: `<slug>-<hash>`. Org identity is config-supplied (an org
|
|
17
|
+
* has no filesystem source), so it is derived from the name alone — the same name yields the same store,
|
|
18
|
+
* shared across every project/repo that configures it. */
|
|
19
|
+
export declare function organizationId(name: string): string;
|
package/dist/store/paths.js
CHANGED
|
@@ -46,3 +46,9 @@ export function repositoryId(root, name) {
|
|
|
46
46
|
return projectId(root, name);
|
|
47
47
|
return `${slug(basename(repo))}-${hash8(repo)}`;
|
|
48
48
|
}
|
|
49
|
+
/** Stable id for a configured organization name: `<slug>-<hash>`. Org identity is config-supplied (an org
|
|
50
|
+
* has no filesystem source), so it is derived from the name alone — the same name yields the same store,
|
|
51
|
+
* shared across every project/repo that configures it. */
|
|
52
|
+
export function organizationId(name) {
|
|
53
|
+
return `${slug(name)}-${hash8(name)}`;
|
|
54
|
+
}
|
package/dist/store/store.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* per-project lock, and an aggregate integrity check. In stateless mode every area is a NullArea.
|
|
5
5
|
*/
|
|
6
6
|
import type { Clock } from '../util/clock.js';
|
|
7
|
-
import type { Area, IntegrityIssue } from './area.js';
|
|
7
|
+
import type { Area, IntegrityIssue, ContentCodec } from './area.js';
|
|
8
8
|
import type { StoreScope } from './paths.js';
|
|
9
9
|
export declare const STORE_VERSION = 1;
|
|
10
10
|
export interface RuntimeStoreOptions {
|
|
@@ -14,31 +14,43 @@ export interface RuntimeStoreOptions {
|
|
|
14
14
|
clock?: Clock;
|
|
15
15
|
/** When false, a stateless (in-memory / no-op) store is returned. */
|
|
16
16
|
enabled?: boolean;
|
|
17
|
+
/** Organization identity (config-supplied). When set, the `organization` memory scope becomes available. */
|
|
18
|
+
organization?: string;
|
|
19
|
+
/** Transparent encryption (Phase 18). When set, every file area encrypts/decrypts its contents. */
|
|
20
|
+
cipher?: ContentCodec;
|
|
17
21
|
}
|
|
18
22
|
export interface StorePaths {
|
|
19
23
|
home: string;
|
|
20
24
|
project: string;
|
|
21
25
|
repository: string;
|
|
22
26
|
user: string;
|
|
27
|
+
/** Present only when an organization is configured. */
|
|
28
|
+
organization?: string;
|
|
23
29
|
}
|
|
24
30
|
export declare class RuntimeStore {
|
|
25
31
|
readonly enabled: boolean;
|
|
26
32
|
readonly projectId: string;
|
|
27
33
|
readonly repositoryId: string;
|
|
34
|
+
/** Present only when an organization is configured — the availability signal for the `organization` scope. */
|
|
35
|
+
readonly organizationId?: string;
|
|
28
36
|
private readonly clock;
|
|
29
37
|
private readonly home;
|
|
30
38
|
private readonly projectDir;
|
|
31
39
|
private readonly repoDir;
|
|
32
40
|
private readonly userDir;
|
|
41
|
+
private readonly orgDir?;
|
|
42
|
+
private readonly cipher?;
|
|
33
43
|
private readonly areas;
|
|
34
44
|
private metaStamped;
|
|
35
45
|
constructor(options: RuntimeStoreOptions);
|
|
36
46
|
paths(): StorePaths;
|
|
37
47
|
private area;
|
|
38
|
-
/** Memory area for a physical scope. */
|
|
48
|
+
/** Memory area for a physical scope. `organization` requires a configured org (guarded by callers). */
|
|
39
49
|
memory(scope: StoreScope): Area;
|
|
40
50
|
conversations(): Area;
|
|
41
51
|
indexes(): Area;
|
|
52
|
+
/** MCP state: dynamic servers, enable/disable overrides, and per-server tool caches (Phase 3.2). */
|
|
53
|
+
mcp(): Area;
|
|
42
54
|
cache(): Area;
|
|
43
55
|
executions(): Area;
|
|
44
56
|
artifacts(): Area;
|
|
@@ -54,6 +66,6 @@ export declare class RuntimeStore {
|
|
|
54
66
|
* otherwise proceeds anyway (advisory). Hard mutual exclusion for executions is a Phase 7 lease.
|
|
55
67
|
*/
|
|
56
68
|
withLock<T>(fn: () => T, staleMs?: number): T;
|
|
57
|
-
/** Aggregate integrity check across the project areas. */
|
|
69
|
+
/** Aggregate integrity check across the project areas (plus the org area when configured). */
|
|
58
70
|
check(): IntegrityIssue[];
|
|
59
71
|
}
|