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.
- package/CHANGELOG.md +78 -0
- package/LICENSE +15 -0
- package/README.md +489 -0
- package/dist/artifacts/artifacts.d.ts +28 -0
- package/dist/artifacts/artifacts.js +46 -0
- package/dist/benchmark/benchmark.d.ts +23 -0
- package/dist/benchmark/benchmark.js +40 -0
- package/dist/cli/cli.d.ts +6 -0
- package/dist/cli/cli.js +161 -0
- package/dist/cli/commands/cleanup.d.ts +47 -0
- package/dist/cli/commands/cleanup.js +96 -0
- package/dist/cli/commands/config.d.ts +8 -0
- package/dist/cli/commands/config.js +28 -0
- package/dist/cli/commands/doctor.d.ts +57 -0
- package/dist/cli/commands/doctor.js +86 -0
- package/dist/cli/commands/executions.d.ts +9 -0
- package/dist/cli/commands/executions.js +25 -0
- package/dist/cli/commands/info.d.ts +43 -0
- package/dist/cli/commands/info.js +53 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.js +75 -0
- package/dist/cli/commands/inspect.d.ts +16 -0
- package/dist/cli/commands/inspect.js +60 -0
- package/dist/cli/commands/phase2.d.ts +22 -0
- package/dist/cli/commands/phase2.js +83 -0
- package/dist/cli/commands/route.d.ts +14 -0
- package/dist/cli/commands/route.js +49 -0
- package/dist/cli/commands/run.d.ts +11 -0
- package/dist/cli/commands/run.js +37 -0
- package/dist/cli/commands/setup.d.ts +34 -0
- package/dist/cli/commands/setup.js +104 -0
- package/dist/cli/commands/skills.d.ts +28 -0
- package/dist/cli/commands/skills.js +48 -0
- package/dist/cli/commands/test.d.ts +7 -0
- package/dist/cli/commands/test.js +29 -0
- package/dist/cli/context.d.ts +12 -0
- package/dist/cli/context.js +16 -0
- package/dist/cli/interactive/repl.d.ts +6 -0
- package/dist/cli/interactive/repl.js +45 -0
- package/dist/cli/interactive/session.d.ts +36 -0
- package/dist/cli/interactive/session.js +356 -0
- package/dist/cli/prompt.d.ts +6 -0
- package/dist/cli/prompt.js +18 -0
- package/dist/cli/render.d.ts +7 -0
- package/dist/cli/render.js +14 -0
- package/dist/comparison/analysis.d.ts +46 -0
- package/dist/comparison/analysis.js +177 -0
- package/dist/comparison/comparator.d.ts +46 -0
- package/dist/comparison/comparator.js +270 -0
- package/dist/comparison/comparison.d.ts +140 -0
- package/dist/comparison/comparison.js +9 -0
- package/dist/comparison/render.d.ts +7 -0
- package/dist/comparison/render.js +66 -0
- package/dist/config/defaults.d.ts +52 -0
- package/dist/config/defaults.js +56 -0
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +50 -0
- package/dist/config/providerDefaults.d.ts +17 -0
- package/dist/config/providerDefaults.js +61 -0
- package/dist/config/schema.d.ts +9 -0
- package/dist/config/schema.js +78 -0
- package/dist/context/budget.d.ts +13 -0
- package/dist/context/budget.js +17 -0
- package/dist/context/compiler.d.ts +61 -0
- package/dist/context/compiler.js +125 -0
- package/dist/context/tokens.d.ts +19 -0
- package/dist/context/tokens.js +38 -0
- package/dist/conversations/conversations.d.ts +38 -0
- package/dist/conversations/conversations.js +64 -0
- package/dist/core/capabilities/evidence.d.ts +40 -0
- package/dist/core/capabilities/evidence.js +102 -0
- package/dist/core/capabilities/overlay.d.ts +15 -0
- package/dist/core/capabilities/overlay.js +0 -0
- package/dist/core/capabilities/taxonomy.d.ts +19 -0
- package/dist/core/capabilities/taxonomy.js +25 -0
- package/dist/core/fallback/errors.d.ts +30 -0
- package/dist/core/fallback/errors.js +80 -0
- package/dist/core/fallback/fallback.d.ts +40 -0
- package/dist/core/fallback/fallback.js +82 -0
- package/dist/core/fallback/retryPolicy.d.ts +11 -0
- package/dist/core/fallback/retryPolicy.js +14 -0
- package/dist/core/health/health.d.ts +3 -0
- package/dist/core/health/health.js +5 -0
- package/dist/core/health/monitor.d.ts +23 -0
- package/dist/core/health/monitor.js +82 -0
- package/dist/core/policies/budget.d.ts +19 -0
- package/dist/core/policies/budget.js +37 -0
- package/dist/core/registry/builtinTasks.d.ts +8 -0
- package/dist/core/registry/builtinTasks.js +54 -0
- package/dist/core/registry/registry.d.ts +18 -0
- package/dist/core/registry/registry.js +33 -0
- package/dist/core/registry/taskRegistry.d.ts +15 -0
- package/dist/core/registry/taskRegistry.js +30 -0
- package/dist/core/router/confidence.d.ts +7 -0
- package/dist/core/router/confidence.js +20 -0
- package/dist/core/router/dimensions.d.ts +16 -0
- package/dist/core/router/dimensions.js +60 -0
- package/dist/core/router/executor.d.ts +16 -0
- package/dist/core/router/executor.js +25 -0
- package/dist/core/router/filter.d.ts +34 -0
- package/dist/core/router/filter.js +113 -0
- package/dist/core/router/normalize.d.ts +30 -0
- package/dist/core/router/normalize.js +119 -0
- package/dist/core/router/request.d.ts +4 -0
- package/dist/core/router/request.js +21 -0
- package/dist/core/router/router.d.ts +32 -0
- package/dist/core/router/router.js +195 -0
- package/dist/core/router/routingPrefs.d.ts +11 -0
- package/dist/core/router/routingPrefs.js +30 -0
- package/dist/core/router/scorer.d.ts +19 -0
- package/dist/core/router/scorer.js +50 -0
- package/dist/core/router/weights.d.ts +9 -0
- package/dist/core/router/weights.js +31 -0
- package/dist/core/validation/validator.d.ts +16 -0
- package/dist/core/validation/validator.js +33 -0
- package/dist/discovery/modelCatalog.d.ts +28 -0
- package/dist/discovery/modelCatalog.js +105 -0
- package/dist/discovery/openapi.d.ts +25 -0
- package/dist/discovery/openapi.js +76 -0
- package/dist/executions/checkpoint.d.ts +26 -0
- package/dist/executions/checkpoint.js +114 -0
- package/dist/executions/execution.d.ts +51 -0
- package/dist/executions/execution.js +8 -0
- package/dist/executions/store.d.ts +52 -0
- package/dist/executions/store.js +124 -0
- package/dist/generation/generateAdapter.d.ts +17 -0
- package/dist/generation/generateAdapter.js +30 -0
- package/dist/index.d.ts +147 -0
- package/dist/index.js +107 -0
- package/dist/learning/feedback.d.ts +9 -0
- package/dist/learning/feedback.js +18 -0
- package/dist/learning/learningStore.d.ts +68 -0
- package/dist/learning/learningStore.js +138 -0
- package/dist/learning/performanceStore.d.ts +27 -0
- package/dist/learning/performanceStore.js +0 -0
- package/dist/marketplace/presets.d.ts +24 -0
- package/dist/marketplace/presets.js +52 -0
- package/dist/mcp/mcp.d.ts +31 -0
- package/dist/mcp/mcp.js +54 -0
- package/dist/memory/bm25.d.ts +16 -0
- package/dist/memory/bm25.js +56 -0
- package/dist/memory/classifier.d.ts +14 -0
- package/dist/memory/classifier.js +17 -0
- package/dist/memory/memory.d.ts +80 -0
- package/dist/memory/memory.js +191 -0
- package/dist/orchestration/executor.d.ts +35 -0
- package/dist/orchestration/executor.js +65 -0
- package/dist/orchestration/orchestrator.d.ts +42 -0
- package/dist/orchestration/orchestrator.js +63 -0
- package/dist/orchestration/plan.d.ts +37 -0
- package/dist/orchestration/plan.js +70 -0
- package/dist/orchestration/planner.d.ts +29 -0
- package/dist/orchestration/planner.js +69 -0
- package/dist/plugin/ai.d.ts +82 -0
- package/dist/plugin/ai.js +167 -0
- package/dist/probing/probe.d.ts +25 -0
- package/dist/probing/probe.js +63 -0
- package/dist/providers/factory.d.ts +18 -0
- package/dist/providers/factory.js +54 -0
- package/dist/providers/httpClient.d.ts +34 -0
- package/dist/providers/httpClient.js +80 -0
- package/dist/providers/httpProvider.d.ts +49 -0
- package/dist/providers/httpProvider.js +135 -0
- package/dist/providers/mock/demo.d.ts +13 -0
- package/dist/providers/mock/demo.js +58 -0
- package/dist/providers/mock/mockProvider.d.ts +35 -0
- package/dist/providers/mock/mockProvider.js +121 -0
- package/dist/providers/mock/scenarios.d.ts +44 -0
- package/dist/providers/mock/scenarios.js +30 -0
- package/dist/providers/provider.d.ts +26 -0
- package/dist/providers/provider.js +11 -0
- package/dist/providers/wire/anthropicWire.d.ts +6 -0
- package/dist/providers/wire/anthropicWire.js +83 -0
- package/dist/providers/wire/openaiWire.d.ts +7 -0
- package/dist/providers/wire/openaiWire.js +81 -0
- package/dist/providers/wire/registry.d.ts +8 -0
- package/dist/providers/wire/registry.js +20 -0
- package/dist/providers/wire/types.d.ts +39 -0
- package/dist/providers/wire/types.js +24 -0
- package/dist/runtime/config.d.ts +31 -0
- package/dist/runtime/config.js +121 -0
- package/dist/runtime/context.d.ts +34 -0
- package/dist/runtime/context.js +11 -0
- package/dist/runtime/events.d.ts +99 -0
- package/dist/runtime/events.js +82 -0
- package/dist/runtime/host.d.ts +27 -0
- package/dist/runtime/host.js +7 -0
- package/dist/runtime/intent/classifier.d.ts +30 -0
- package/dist/runtime/intent/classifier.js +60 -0
- package/dist/runtime/intent/signals.d.ts +19 -0
- package/dist/runtime/intent/signals.js +46 -0
- package/dist/runtime/modes/availability.d.ts +11 -0
- package/dist/runtime/modes/availability.js +17 -0
- package/dist/runtime/modes/chat.d.ts +18 -0
- package/dist/runtime/modes/chat.js +67 -0
- package/dist/runtime/modes/modeResolver.d.ts +43 -0
- package/dist/runtime/modes/modeResolver.js +78 -0
- package/dist/runtime/policy.d.ts +72 -0
- package/dist/runtime/policy.js +59 -0
- package/dist/runtime/providerView.d.ts +62 -0
- package/dist/runtime/providerView.js +105 -0
- package/dist/runtime/routing.d.ts +26 -0
- package/dist/runtime/routing.js +65 -0
- package/dist/runtime/runtime.d.ts +191 -0
- package/dist/runtime/runtime.js +718 -0
- package/dist/runtime/types.d.ts +153 -0
- package/dist/runtime/types.js +9 -0
- package/dist/runtime/workspace/detectors.d.ts +15 -0
- package/dist/runtime/workspace/detectors.js +57 -0
- package/dist/runtime/workspace/workspace.d.ts +29 -0
- package/dist/runtime/workspace/workspace.js +116 -0
- package/dist/security/credentials.d.ts +26 -0
- package/dist/security/credentials.js +34 -0
- package/dist/security/redact.d.ts +16 -0
- package/dist/security/redact.js +57 -0
- package/dist/skills/builtins/fileAnalyzer.d.ts +7 -0
- package/dist/skills/builtins/fileAnalyzer.js +47 -0
- package/dist/skills/builtins/repositoryAnalyzer.d.ts +6 -0
- package/dist/skills/builtins/repositoryAnalyzer.js +47 -0
- package/dist/skills/discovery.d.ts +61 -0
- package/dist/skills/discovery.js +211 -0
- package/dist/skills/manifest.d.ts +30 -0
- package/dist/skills/manifest.js +75 -0
- package/dist/skills/registry.d.ts +15 -0
- package/dist/skills/registry.js +22 -0
- package/dist/skills/skill.d.ts +64 -0
- package/dist/skills/skill.js +8 -0
- package/dist/store/area.d.ts +54 -0
- package/dist/store/area.js +164 -0
- package/dist/store/paths.d.ts +15 -0
- package/dist/store/paths.js +48 -0
- package/dist/store/store.d.ts +59 -0
- package/dist/store/store.js +140 -0
- package/dist/telemetry/sinks/file.d.ts +12 -0
- package/dist/telemetry/sinks/file.js +28 -0
- package/dist/telemetry/telemetry.d.ts +36 -0
- package/dist/telemetry/telemetry.js +63 -0
- package/dist/tools/builtins/filesystem.d.ts +7 -0
- package/dist/tools/builtins/filesystem.js +53 -0
- package/dist/tools/builtins/git.d.ts +10 -0
- package/dist/tools/builtins/git.js +66 -0
- package/dist/tools/builtins/shell.d.ts +17 -0
- package/dist/tools/builtins/shell.js +91 -0
- package/dist/tools/jail.d.ts +12 -0
- package/dist/tools/jail.js +98 -0
- package/dist/tools/permissions.d.ts +25 -0
- package/dist/tools/permissions.js +24 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +20 -0
- package/dist/tools/runner.d.ts +23 -0
- package/dist/tools/runner.js +64 -0
- package/dist/tools/tool.d.ts +53 -0
- package/dist/tools/tool.js +24 -0
- package/dist/tools/untrusted.d.ts +13 -0
- package/dist/tools/untrusted.js +30 -0
- package/dist/types.d.ts +460 -0
- package/dist/types.js +12 -0
- package/dist/util/clock.d.ts +6 -0
- package/dist/util/clock.js +4 -0
- package/dist/util/extractJson.d.ts +8 -0
- package/dist/util/extractJson.js +54 -0
- package/dist/verification/verify.d.ts +26 -0
- package/dist/verification/verify.js +67 -0
- package/docs/GUIDE.md +358 -0
- package/docs/README.md +21 -0
- package/docs/architecture.md +78 -0
- package/docs/router.md +376 -0
- package/docs/security.md +55 -0
- package/package.json +67 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local skill discovery. Finds skills the user keeps in their workspace — declarative manifests
|
|
3
|
+
* (`*.skill.yaml`/`*.skill.json`) and JS-module skills (`*.skill.js`/`*.skill.mjs`, exporting
|
|
4
|
+
* `skills: Skill[]`). Two modes:
|
|
5
|
+
* • CONSENTED sources — the conventional `.ai-runtime/skills/` dir and any `skills.paths` in config —
|
|
6
|
+
* are discovered AND loadable (the user put them there / named them).
|
|
7
|
+
* • A repo-wide scan (`scanRepoForSkills`) only REPORTS candidates found elsewhere; nothing is loaded
|
|
8
|
+
* until the user explicitly enables it. Loading a module runs the user's code, so it is never
|
|
9
|
+
* implicit.
|
|
10
|
+
* Everything is path-jailed to the workspace root — discovery never reads or imports outside it.
|
|
11
|
+
*/
|
|
12
|
+
import type { Skill } from './skill.js';
|
|
13
|
+
export type SkillSourceKind = 'manifest' | 'module';
|
|
14
|
+
export interface SkillSource {
|
|
15
|
+
/** Absolute path, guaranteed inside the workspace root. */
|
|
16
|
+
path: string;
|
|
17
|
+
/** Path relative to the workspace root, for display. */
|
|
18
|
+
relative: string;
|
|
19
|
+
kind: SkillSourceKind;
|
|
20
|
+
/** True when the source is consented (conventional dir or a configured path) → safe to load. */
|
|
21
|
+
consented: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface LoadedSource {
|
|
24
|
+
source: SkillSource;
|
|
25
|
+
skills: Skill[];
|
|
26
|
+
status: 'loaded' | 'error';
|
|
27
|
+
detail?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Discover CONSENTED skill sources: the conventional `<root>/.ai-runtime/skills/` dir plus any configured
|
|
31
|
+
* `paths` (each jailed to the root). These are safe to load. Deterministic order.
|
|
32
|
+
*/
|
|
33
|
+
export declare function discoverSkills(opts: {
|
|
34
|
+
root: string;
|
|
35
|
+
paths?: string[];
|
|
36
|
+
}): SkillSource[];
|
|
37
|
+
/**
|
|
38
|
+
* Scan the whole workspace for skill-looking files and REPORT them (never loaded here). Skips vendored /
|
|
39
|
+
* build dirs and the already-consented `.ai-runtime/skills/`. Depth-capped and jailed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function scanRepoForSkills(opts: {
|
|
42
|
+
root: string;
|
|
43
|
+
maxDepth?: number;
|
|
44
|
+
}): SkillSource[];
|
|
45
|
+
/**
|
|
46
|
+
* Load a source into concrete Skills. Manifests are parsed as data; modules are dynamically imported
|
|
47
|
+
* (executing the file) and must export `skills: Skill[]` or a default Skill/Skill[]. Never throws — a
|
|
48
|
+
* bad source becomes `status: 'error'`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function loadSkillSource(source: SkillSource): Promise<LoadedSource>;
|
|
51
|
+
/**
|
|
52
|
+
* Load a skill pack by module specifier — an installed npm package (`ai-runtime-example-skills`) or any
|
|
53
|
+
* import()-able path. Unlike local file discovery, a package is NOT workspace-jailed: it is a declared
|
|
54
|
+
* dependency resolved from node_modules, trusted like any other npm dependency. Never throws — a bad
|
|
55
|
+
* package becomes `status: 'error'`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function loadSkillPackage(specifier: string): Promise<LoadedSource>;
|
|
58
|
+
/** Structural check that an imported value implements the Skill contract. */
|
|
59
|
+
export declare function isSkill(v: unknown): v is Skill;
|
|
60
|
+
/** True when a path is absolute and inside the root — a guard for callers resolving user paths. */
|
|
61
|
+
export declare function withinRoot(root: string, p: string): boolean;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local skill discovery. Finds skills the user keeps in their workspace — declarative manifests
|
|
3
|
+
* (`*.skill.yaml`/`*.skill.json`) and JS-module skills (`*.skill.js`/`*.skill.mjs`, exporting
|
|
4
|
+
* `skills: Skill[]`). Two modes:
|
|
5
|
+
* • CONSENTED sources — the conventional `.ai-runtime/skills/` dir and any `skills.paths` in config —
|
|
6
|
+
* are discovered AND loadable (the user put them there / named them).
|
|
7
|
+
* • A repo-wide scan (`scanRepoForSkills`) only REPORTS candidates found elsewhere; nothing is loaded
|
|
8
|
+
* until the user explicitly enables it. Loading a module runs the user's code, so it is never
|
|
9
|
+
* implicit.
|
|
10
|
+
* Everything is path-jailed to the workspace root — discovery never reads or imports outside it.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
|
+
import { join, relative, extname, isAbsolute } from 'node:path';
|
|
14
|
+
import { pathToFileURL } from 'node:url';
|
|
15
|
+
import { parse as parseYaml } from 'yaml';
|
|
16
|
+
import { resolveInJail, JailError } from '../tools/jail.js';
|
|
17
|
+
import { parseManifest, manifestToSkill } from './manifest.js';
|
|
18
|
+
const MANIFEST_EXT = new Set(['.yaml', '.yml', '.json']);
|
|
19
|
+
const MODULE_EXT = new Set(['.js', '.mjs']);
|
|
20
|
+
const SKILL_FILE = /\.skill\.(ya?ml|json|m?js)$/i;
|
|
21
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.cache']);
|
|
22
|
+
function kindOf(file) {
|
|
23
|
+
const ext = extname(file).toLowerCase();
|
|
24
|
+
if (MANIFEST_EXT.has(ext))
|
|
25
|
+
return 'manifest';
|
|
26
|
+
if (MODULE_EXT.has(ext))
|
|
27
|
+
return 'module';
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* List `*.skill.*` files directly under a directory (one level; sorted). Jail-checked: a directory that
|
|
32
|
+
* resolves (via a symlink) outside the workspace is never read — so its listing can't leak or be loaded.
|
|
33
|
+
*/
|
|
34
|
+
function skillFilesIn(root, dir) {
|
|
35
|
+
if (!existsSync(dir))
|
|
36
|
+
return [];
|
|
37
|
+
try {
|
|
38
|
+
resolveInJail(root, relative(root, dir)); // a symlinked-out dir throws → don't even read it
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
if (err instanceof JailError)
|
|
42
|
+
return [];
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
if (!statSync(dir).isDirectory())
|
|
46
|
+
return [];
|
|
47
|
+
return readdirSync(dir)
|
|
48
|
+
.filter((f) => SKILL_FILE.test(f))
|
|
49
|
+
.sort()
|
|
50
|
+
.map((f) => join(dir, f));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a SkillSource for a candidate file, jail-checking its REAL path. A file that resolves (via a
|
|
54
|
+
* symlink) outside the workspace is dropped — this is what makes `SkillSource.path` genuinely in-jail, so
|
|
55
|
+
* loadSkillSource can import it safely (loadSkillSource has no root and cannot re-verify).
|
|
56
|
+
*/
|
|
57
|
+
function toSource(root, absPath, consented) {
|
|
58
|
+
const kind = kindOf(absPath);
|
|
59
|
+
if (!kind)
|
|
60
|
+
return undefined;
|
|
61
|
+
try {
|
|
62
|
+
resolveInJail(root, relative(root, absPath)); // follows the file's final symlink; throws if it escapes root
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
if (err instanceof JailError)
|
|
66
|
+
return undefined;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
return { path: absPath, relative: relative(root, absPath) || absPath, kind, consented };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Discover CONSENTED skill sources: the conventional `<root>/.ai-runtime/skills/` dir plus any configured
|
|
73
|
+
* `paths` (each jailed to the root). These are safe to load. Deterministic order.
|
|
74
|
+
*/
|
|
75
|
+
export function discoverSkills(opts) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
const add = (absPath) => {
|
|
79
|
+
const src = toSource(opts.root, absPath, true);
|
|
80
|
+
if (src && !seen.has(src.path)) {
|
|
81
|
+
seen.add(src.path);
|
|
82
|
+
out.push(src);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
for (const f of skillFilesIn(opts.root, join(opts.root, '.ai-runtime', 'skills')))
|
|
86
|
+
add(f);
|
|
87
|
+
for (const p of opts.paths ?? []) {
|
|
88
|
+
let resolved;
|
|
89
|
+
try {
|
|
90
|
+
resolved = resolveInJail(opts.root, p);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err instanceof JailError)
|
|
94
|
+
continue; // a configured path outside the workspace is ignored
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
if (!existsSync(resolved))
|
|
98
|
+
continue;
|
|
99
|
+
if (statSync(resolved).isDirectory())
|
|
100
|
+
for (const f of skillFilesIn(opts.root, resolved))
|
|
101
|
+
add(f);
|
|
102
|
+
else
|
|
103
|
+
add(resolved);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Scan the whole workspace for skill-looking files and REPORT them (never loaded here). Skips vendored /
|
|
109
|
+
* build dirs and the already-consented `.ai-runtime/skills/`. Depth-capped and jailed.
|
|
110
|
+
*/
|
|
111
|
+
export function scanRepoForSkills(opts) {
|
|
112
|
+
const maxDepth = opts.maxDepth ?? 4;
|
|
113
|
+
const conventional = join(opts.root, '.ai-runtime', 'skills');
|
|
114
|
+
const out = [];
|
|
115
|
+
const walk = (dir, depth) => {
|
|
116
|
+
if (depth > maxDepth)
|
|
117
|
+
return;
|
|
118
|
+
let entries;
|
|
119
|
+
try {
|
|
120
|
+
entries = readdirSync(dir);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const name of entries.sort()) {
|
|
126
|
+
const abs = join(dir, name);
|
|
127
|
+
let st;
|
|
128
|
+
// lstat (not stat) so a symlinked directory is NOT traversed — the scan never reads outside the jail.
|
|
129
|
+
try {
|
|
130
|
+
st = lstatSync(abs);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (st.isDirectory()) {
|
|
136
|
+
if (SKIP_DIRS.has(name) || abs === conventional)
|
|
137
|
+
continue;
|
|
138
|
+
walk(abs, depth + 1);
|
|
139
|
+
}
|
|
140
|
+
else if (st.isFile() && SKILL_FILE.test(name)) {
|
|
141
|
+
const src = toSource(opts.root, abs, false); // toSource jail-checks (a symlinked-out file is dropped)
|
|
142
|
+
if (src)
|
|
143
|
+
out.push(src);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
walk(opts.root, 0);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load a source into concrete Skills. Manifests are parsed as data; modules are dynamically imported
|
|
152
|
+
* (executing the file) and must export `skills: Skill[]` or a default Skill/Skill[]. Never throws — a
|
|
153
|
+
* bad source becomes `status: 'error'`.
|
|
154
|
+
*/
|
|
155
|
+
export async function loadSkillSource(source) {
|
|
156
|
+
try {
|
|
157
|
+
if (source.kind === 'manifest') {
|
|
158
|
+
const text = readFileSync(source.path, 'utf8');
|
|
159
|
+
const raw = source.path.endsWith('.json') ? JSON.parse(text) : parseYaml(text);
|
|
160
|
+
const { manifest, error } = parseManifest(raw);
|
|
161
|
+
if (!manifest)
|
|
162
|
+
return { source, skills: [], status: 'error', detail: error };
|
|
163
|
+
return { source, skills: [manifestToSkill(manifest, source.relative)], status: 'loaded' };
|
|
164
|
+
}
|
|
165
|
+
// module — importing runs the file's top-level code (the user's own repo code).
|
|
166
|
+
const mod = (await import(pathToFileURL(source.path).href));
|
|
167
|
+
const raw = mod.skills ?? mod.default;
|
|
168
|
+
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
169
|
+
const skills = list.filter(isSkill);
|
|
170
|
+
if (skills.length === 0)
|
|
171
|
+
return { source, skills: [], status: 'error', detail: 'module exported no valid skills (need `export const skills: Skill[]`)' };
|
|
172
|
+
return { source, skills, status: 'loaded' };
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
return { source, skills: [], status: 'error', detail: err instanceof Error ? err.message : String(err) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Load a skill pack by module specifier — an installed npm package (`ai-runtime-example-skills`) or any
|
|
180
|
+
* import()-able path. Unlike local file discovery, a package is NOT workspace-jailed: it is a declared
|
|
181
|
+
* dependency resolved from node_modules, trusted like any other npm dependency. Never throws — a bad
|
|
182
|
+
* package becomes `status: 'error'`.
|
|
183
|
+
*/
|
|
184
|
+
export async function loadSkillPackage(specifier) {
|
|
185
|
+
const source = { path: specifier, relative: specifier, kind: 'module', consented: true };
|
|
186
|
+
try {
|
|
187
|
+
const mod = (await import(specifier));
|
|
188
|
+
const raw = mod.skills ?? mod.default;
|
|
189
|
+
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
190
|
+
const skills = list.filter(isSkill);
|
|
191
|
+
if (skills.length === 0)
|
|
192
|
+
return { source, skills: [], status: 'error', detail: `package '${specifier}' exported no valid skills (need \`export const skills: Skill[]\`)` };
|
|
193
|
+
return { source, skills, status: 'loaded' };
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
return { source, skills: [], status: 'error', detail: err instanceof Error ? err.message : String(err) };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Structural check that an imported value implements the Skill contract. */
|
|
200
|
+
export function isSkill(v) {
|
|
201
|
+
if (!v || typeof v !== 'object')
|
|
202
|
+
return false;
|
|
203
|
+
const s = v;
|
|
204
|
+
return typeof s.id === 'string' && typeof s.version === 'string' && typeof s.plan === 'function' && typeof s.execute === 'function' && typeof s.validate === 'function';
|
|
205
|
+
}
|
|
206
|
+
/** True when a path is absolute and inside the root — a guard for callers resolving user paths. */
|
|
207
|
+
export function withinRoot(root, p) {
|
|
208
|
+
const abs = isAbsolute(p) ? p : join(root, p);
|
|
209
|
+
const rel = relative(root, abs);
|
|
210
|
+
return !rel.startsWith('..') && !isAbsolute(rel);
|
|
211
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative skill manifests (`*.skill.yaml` / `*.skill.json`). A manifest describes a skill as an
|
|
3
|
+
* ordered sequence of steps over REGISTERED tools — pure data, no arbitrary code, so it is safe to load
|
|
4
|
+
* from a repo. `manifestToSkill` compiles one into a real Skill whose execute() runs the steps through
|
|
5
|
+
* the permission-gated tool layer and whose validate() confirms every step produced evidence (a tool
|
|
6
|
+
* `ok`). Anything richer than composing tools belongs in a JS-module skill instead.
|
|
7
|
+
*/
|
|
8
|
+
import type { Skill } from './skill.js';
|
|
9
|
+
export interface ManifestStep {
|
|
10
|
+
/** A registered tool id (filesystem, shell, git, or a user-registered tool). */
|
|
11
|
+
tool: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
/** Static input for the tool. When omitted, the skill's own input is passed through. */
|
|
14
|
+
input?: unknown;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillManifest {
|
|
17
|
+
id: string;
|
|
18
|
+
version: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
runtimeCompatibility?: string;
|
|
22
|
+
steps: ManifestStep[];
|
|
23
|
+
}
|
|
24
|
+
/** Validate an arbitrary parsed object as a SkillManifest. Returns the manifest or a reason it is invalid. */
|
|
25
|
+
export declare function parseManifest(raw: unknown): {
|
|
26
|
+
manifest?: SkillManifest;
|
|
27
|
+
error?: string;
|
|
28
|
+
};
|
|
29
|
+
/** Compile a manifest into a Skill. execute() runs each tool step in order; validate() checks evidence. */
|
|
30
|
+
export declare function manifestToSkill(m: SkillManifest, sourcePath: string): Skill;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative skill manifests (`*.skill.yaml` / `*.skill.json`). A manifest describes a skill as an
|
|
3
|
+
* ordered sequence of steps over REGISTERED tools — pure data, no arbitrary code, so it is safe to load
|
|
4
|
+
* from a repo. `manifestToSkill` compiles one into a real Skill whose execute() runs the steps through
|
|
5
|
+
* the permission-gated tool layer and whose validate() confirms every step produced evidence (a tool
|
|
6
|
+
* `ok`). Anything richer than composing tools belongs in a JS-module skill instead.
|
|
7
|
+
*/
|
|
8
|
+
/** Validate an arbitrary parsed object as a SkillManifest. Returns the manifest or a reason it is invalid. */
|
|
9
|
+
export function parseManifest(raw) {
|
|
10
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
11
|
+
return { error: 'manifest must be a mapping' };
|
|
12
|
+
const o = raw;
|
|
13
|
+
for (const key of ['id', 'version', 'name', 'description']) {
|
|
14
|
+
if (typeof o[key] !== 'string' || !o[key].trim())
|
|
15
|
+
return { error: `manifest.${key} must be a non-empty string` };
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(o.steps) || o.steps.length === 0)
|
|
18
|
+
return { error: 'manifest.steps must be a non-empty array' };
|
|
19
|
+
const steps = [];
|
|
20
|
+
for (const [i, s] of o.steps.entries()) {
|
|
21
|
+
if (!s || typeof s !== 'object' || Array.isArray(s))
|
|
22
|
+
return { error: `manifest.steps[${i}] must be a mapping` };
|
|
23
|
+
const step = s;
|
|
24
|
+
if (typeof step.tool !== 'string' || !step.tool.trim())
|
|
25
|
+
return { error: `manifest.steps[${i}].tool must be a tool id` };
|
|
26
|
+
steps.push({ tool: step.tool, ...(typeof step.description === 'string' ? { description: step.description } : {}), ...('input' in step ? { input: step.input } : {}) });
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
manifest: {
|
|
30
|
+
id: o.id,
|
|
31
|
+
version: o.version,
|
|
32
|
+
name: o.name,
|
|
33
|
+
description: o.description,
|
|
34
|
+
...(typeof o.runtimeCompatibility === 'string' ? { runtimeCompatibility: o.runtimeCompatibility } : {}),
|
|
35
|
+
steps,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Compile a manifest into a Skill. execute() runs each tool step in order; validate() checks evidence. */
|
|
40
|
+
export function manifestToSkill(m, sourcePath) {
|
|
41
|
+
const tools = [...new Set(m.steps.map((s) => s.tool))];
|
|
42
|
+
return {
|
|
43
|
+
id: m.id,
|
|
44
|
+
version: m.version,
|
|
45
|
+
name: m.name,
|
|
46
|
+
description: m.description,
|
|
47
|
+
...(m.runtimeCompatibility ? { runtimeCompatibility: m.runtimeCompatibility } : {}),
|
|
48
|
+
tools,
|
|
49
|
+
async plan() {
|
|
50
|
+
return { steps: m.steps.map((s) => ({ description: s.description ?? `run ${s.tool}`, tool: s.tool })) };
|
|
51
|
+
},
|
|
52
|
+
async execute(ctx) {
|
|
53
|
+
const outcomes = [];
|
|
54
|
+
let lastOutput = '';
|
|
55
|
+
for (const step of m.steps) {
|
|
56
|
+
if (ctx.signal?.aborted)
|
|
57
|
+
return { ok: false, output: lastOutput, data: { steps: outcomes, source: sourcePath }, error: { message: 'aborted' } };
|
|
58
|
+
const input = step.input !== undefined ? step.input : ctx.input;
|
|
59
|
+
const res = await ctx.callTool(step.tool, input);
|
|
60
|
+
outcomes.push({ tool: step.tool, ok: res.ok, ...(res.ok ? {} : { error: res.error?.message ?? 'failed' }) });
|
|
61
|
+
if (res.output !== undefined)
|
|
62
|
+
lastOutput = String(res.output);
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
return { ok: false, output: lastOutput, data: { steps: outcomes, source: sourcePath }, error: { message: `step '${step.tool}' failed: ${res.error?.message ?? 'unknown'}` } };
|
|
65
|
+
}
|
|
66
|
+
return { ok: true, output: lastOutput, data: { steps: outcomes, source: sourcePath } };
|
|
67
|
+
},
|
|
68
|
+
async validate(result) {
|
|
69
|
+
const steps = result.data?.steps ?? [];
|
|
70
|
+
const checks = steps.map((s) => ({ name: `step:${s.tool}`, ok: s.ok, ...(s.ok ? {} : { why: s.error ?? 'failed' }) }));
|
|
71
|
+
// Evidence-based: the skill succeeded only if it ran at least one step and every step's tool returned ok.
|
|
72
|
+
return { ok: result.ok && steps.length > 0 && steps.every((s) => s.ok), checks };
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** A registry of skills, keyed by id, with a simple capability/tool matcher. */
|
|
2
|
+
import type { Skill } from './skill.js';
|
|
3
|
+
export interface MatchCriteria {
|
|
4
|
+
/** Tool ids currently registered — a skill matches only if all its required tools are present. */
|
|
5
|
+
tools?: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare class SkillRegistry {
|
|
8
|
+
private readonly skills;
|
|
9
|
+
register(skill: Skill): this;
|
|
10
|
+
get(id: string): Skill | undefined;
|
|
11
|
+
has(id: string): boolean;
|
|
12
|
+
list(): Skill[];
|
|
13
|
+
/** Skills whose required tools are all available (capability-vs-model matching arrives with routing). */
|
|
14
|
+
match(criteria?: MatchCriteria): Skill[];
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A registry of skills, keyed by id, with a simple capability/tool matcher. */
|
|
2
|
+
export class SkillRegistry {
|
|
3
|
+
skills = new Map();
|
|
4
|
+
register(skill) {
|
|
5
|
+
this.skills.set(skill.id, skill);
|
|
6
|
+
return this;
|
|
7
|
+
}
|
|
8
|
+
get(id) {
|
|
9
|
+
return this.skills.get(id);
|
|
10
|
+
}
|
|
11
|
+
has(id) {
|
|
12
|
+
return this.skills.has(id);
|
|
13
|
+
}
|
|
14
|
+
list() {
|
|
15
|
+
return [...this.skills.values()];
|
|
16
|
+
}
|
|
17
|
+
/** Skills whose required tools are all available (capability-vs-model matching arrives with routing). */
|
|
18
|
+
match(criteria = {}) {
|
|
19
|
+
const tools = new Set(criteria.tools ?? []);
|
|
20
|
+
return this.list().filter((s) => (s.tools ?? []).every((t) => tools.has(t)));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Skill contract. A skill is a reusable capability (NOT a provider, NOT a tool). It plans a small
|
|
3
|
+
* sequence, executes it (composing tools + AI.run through the SkillContext), and validates the result
|
|
4
|
+
* against success conditions — never trusting a model's "I did it" claim. Skills are versioned so
|
|
5
|
+
* learning can reference an exact version and a runtime can check compatibility. QA-neutral by design;
|
|
6
|
+
* QA/dev skills live in packs, never in the core.
|
|
7
|
+
*/
|
|
8
|
+
import type { AI } from '../plugin/ai.js';
|
|
9
|
+
import type { CapabilityRequirement } from '../types.js';
|
|
10
|
+
import type { ArtifactRef } from '../runtime/types.js';
|
|
11
|
+
import type { PermissionPolicy } from '../runtime/policy.js';
|
|
12
|
+
import type { ToolResult } from '../tools/tool.js';
|
|
13
|
+
export interface SkillContext {
|
|
14
|
+
input: unknown;
|
|
15
|
+
/** The inner router facade for model work. */
|
|
16
|
+
ai: AI;
|
|
17
|
+
/** Invoke a registered tool, pre-bound to this run's resolved permissions/approval/workspace. */
|
|
18
|
+
callTool(id: string, input: unknown): Promise<ToolResult>;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
export interface SkillPlanStep {
|
|
22
|
+
description: string;
|
|
23
|
+
tool?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SkillPlan {
|
|
26
|
+
steps: SkillPlanStep[];
|
|
27
|
+
}
|
|
28
|
+
export interface SkillResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
output?: string;
|
|
31
|
+
data?: unknown;
|
|
32
|
+
artifacts?: ArtifactRef[];
|
|
33
|
+
error?: {
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface SkillValidation {
|
|
38
|
+
ok: boolean;
|
|
39
|
+
checks: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
ok: boolean;
|
|
42
|
+
why?: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
export interface Skill {
|
|
46
|
+
id: string;
|
|
47
|
+
/** Semver of the skill implementation (learning references this exact version). */
|
|
48
|
+
version: string;
|
|
49
|
+
/** Semver range of runtime versions this skill supports (advisory in Phase 5). */
|
|
50
|
+
runtimeCompatibility?: string;
|
|
51
|
+
name: string;
|
|
52
|
+
description: string;
|
|
53
|
+
/** Model capabilities the skill needs from whatever model AI.run selects. */
|
|
54
|
+
capabilities?: CapabilityRequirement[];
|
|
55
|
+
/** Tool ids the skill uses (matched against the registered tools). */
|
|
56
|
+
tools?: string[];
|
|
57
|
+
/** Other skill ids this skill depends on. */
|
|
58
|
+
dependencies?: string[];
|
|
59
|
+
/** Permissions the skill needs (advisory; the run's ExecutionPolicy is authoritative). */
|
|
60
|
+
permissions?: PermissionPolicy;
|
|
61
|
+
plan(ctx: SkillContext): Promise<SkillPlan>;
|
|
62
|
+
execute(ctx: SkillContext, plan: SkillPlan): Promise<SkillResult>;
|
|
63
|
+
validate(result: SkillResult, ctx: SkillContext): Promise<SkillValidation>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Skill contract. A skill is a reusable capability (NOT a provider, NOT a tool). It plans a small
|
|
3
|
+
* sequence, executes it (composing tools + AI.run through the SkillContext), and validates the result
|
|
4
|
+
* against success conditions — never trusting a model's "I did it" claim. Skills are versioned so
|
|
5
|
+
* learning can reference an exact version and a runtime can check compatibility. QA-neutral by design;
|
|
6
|
+
* QA/dev skills live in packs, never in the core.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single managed directory in the runtime store. JSON records are written atomically (tmp + rename)
|
|
3
|
+
* with a sha256 integrity wrapper so corruption is detectable; append logs are JSONL. `NullArea` is the
|
|
4
|
+
* no-op used in stateless mode. Ids are validated to prevent path traversal.
|
|
5
|
+
*/
|
|
6
|
+
export interface IntegrityIssue {
|
|
7
|
+
file: string;
|
|
8
|
+
problem: 'unreadable' | 'checksum-mismatch' | 'invalid-json';
|
|
9
|
+
detail?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Area {
|
|
12
|
+
readonly enabled: boolean;
|
|
13
|
+
/** Strict read: throws on corruption (use for an explicit single-record read). */
|
|
14
|
+
readJson<T>(id: string): T | undefined;
|
|
15
|
+
/** Fail-soft read: returns undefined on a missing OR corrupt record (use in bulk iteration). */
|
|
16
|
+
tryReadJson<T>(id: string): T | undefined;
|
|
17
|
+
writeJson(id: string, data: unknown): void;
|
|
18
|
+
remove(id: string): void;
|
|
19
|
+
listIds(): string[];
|
|
20
|
+
appendLine(id: string, obj: unknown): void;
|
|
21
|
+
readLines(id: string): unknown[];
|
|
22
|
+
check(): IntegrityIssue[];
|
|
23
|
+
}
|
|
24
|
+
export declare class FileArea implements Area {
|
|
25
|
+
private readonly dir;
|
|
26
|
+
readonly enabled = true;
|
|
27
|
+
private ensured;
|
|
28
|
+
private counter;
|
|
29
|
+
constructor(dir: string);
|
|
30
|
+
private ensure;
|
|
31
|
+
private jsonPath;
|
|
32
|
+
private logPath;
|
|
33
|
+
private atomicWrite;
|
|
34
|
+
readJson<T>(id: string): T | undefined;
|
|
35
|
+
tryReadJson<T>(id: string): T | undefined;
|
|
36
|
+
writeJson(id: string, data: unknown): void;
|
|
37
|
+
remove(id: string): void;
|
|
38
|
+
listIds(): string[];
|
|
39
|
+
appendLine(id: string, obj: unknown): void;
|
|
40
|
+
readLines(id: string): unknown[];
|
|
41
|
+
check(): IntegrityIssue[];
|
|
42
|
+
}
|
|
43
|
+
/** No-op area used in stateless mode. */
|
|
44
|
+
export declare class NullArea implements Area {
|
|
45
|
+
readonly enabled = false;
|
|
46
|
+
readJson<T>(): T | undefined;
|
|
47
|
+
tryReadJson<T>(): T | undefined;
|
|
48
|
+
writeJson(): void;
|
|
49
|
+
remove(): void;
|
|
50
|
+
listIds(): string[];
|
|
51
|
+
appendLine(): void;
|
|
52
|
+
readLines(): unknown[];
|
|
53
|
+
check(): IntegrityIssue[];
|
|
54
|
+
}
|