@vincemakes/kiso-runtime 0.1.9 → 0.1.10
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/dist/extensions.d.ts +9 -0
- package/dist/extensions.js +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/trust.d.ts +67 -0
- package/dist/trust.js +152 -0
- package/package.json +5 -5
package/dist/extensions.d.ts
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { KisoExtension } from "@vincemakes/kiso-core";
|
|
12
12
|
export type { KisoExtension };
|
|
13
|
+
/**
|
|
14
|
+
* E3 — project-level extensions: load <dir>/.kiso/extensions/*.mjs AFTER
|
|
15
|
+
* the trust gate (the CLI decides trust; this loader only loads). A name
|
|
16
|
+
* that exists in BOTH the user level and the project level is a LOUD
|
|
17
|
+
* startup error — silent shadowing of a user-level extension by a
|
|
18
|
+
* project-level one would change behavior without anyone noticing.
|
|
19
|
+
* `existing` are the already-loaded user-level extensions.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadProjectExtensions(dir: string, existing?: readonly KisoExtension[]): Promise<KisoExtension[]>;
|
|
13
22
|
export declare function loadExtensions(dir: string): Promise<KisoExtension[]>;
|
|
14
23
|
/**
|
|
15
24
|
* 发现#8 (P1): dispose every extension's external resources — each call
|
package/dist/extensions.js
CHANGED
|
@@ -11,6 +11,23 @@
|
|
|
11
11
|
import { readdir } from "node:fs/promises";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { pathToFileURL } from "node:url";
|
|
14
|
+
/**
|
|
15
|
+
* E3 — project-level extensions: load <dir>/.kiso/extensions/*.mjs AFTER
|
|
16
|
+
* the trust gate (the CLI decides trust; this loader only loads). A name
|
|
17
|
+
* that exists in BOTH the user level and the project level is a LOUD
|
|
18
|
+
* startup error — silent shadowing of a user-level extension by a
|
|
19
|
+
* project-level one would change behavior without anyone noticing.
|
|
20
|
+
* `existing` are the already-loaded user-level extensions.
|
|
21
|
+
*/
|
|
22
|
+
export async function loadProjectExtensions(dir, existing = []) {
|
|
23
|
+
const projectExts = await loadExtensions(join(dir, ".kiso", "extensions"));
|
|
24
|
+
for (const ext of projectExts) {
|
|
25
|
+
if (existing.some((e) => e.name === ext.name)) {
|
|
26
|
+
throw new Error(`[extensions] extension name "${ext.name}" exists in both the user-level and the project-level extensions — refusing to shadow`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return projectExts;
|
|
30
|
+
}
|
|
14
31
|
export async function loadExtensions(dir) {
|
|
15
32
|
let files;
|
|
16
33
|
try {
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/trust.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E3 — project-level capability is trusted by CONTENT DIGEST, not by
|
|
3
|
+
* directory (ADR-0037). A cloned repo's .kiso runs code on this machine
|
|
4
|
+
* (extensions load into the agent, mcp.json spawns servers, skills inject
|
|
5
|
+
* prompt text), so a trust decision is a decision about THE FILES, and it
|
|
6
|
+
* dies the moment the files change.
|
|
7
|
+
*
|
|
8
|
+
* projectArtifacts(cwd) discovers <cwd>/.kiso/{extensions/*.mjs, mcp.json,
|
|
9
|
+
* skills/<name>/SKILL.md} — the exact three artifact kinds — and returns a
|
|
10
|
+
* manifest (one sha256 per file) plus a BUNDLE digest: sha256 over the
|
|
11
|
+
* sorted relative paths and their contents. Any file change changes the
|
|
12
|
+
* bundle digest, which invalidates every prior trust record.
|
|
13
|
+
*
|
|
14
|
+
* The trust store (~/.kiso/trust.jsonl, KISO_HOME respected) is a simple
|
|
15
|
+
* append-only memo of human verdicts: {root, digest, decision, ts} per
|
|
16
|
+
* line, the LAST record matching (root, digest) wins. Its tolerance is
|
|
17
|
+
* deliberately different from the session store: trust.jsonl is NOT an
|
|
18
|
+
* event stream — a corrupt line means "no record" for that line (skip,
|
|
19
|
+
* never throw), because a lost grant only re-asks the human and a lost
|
|
20
|
+
* refusal also only re-asks; there is no trajectory to preserve.
|
|
21
|
+
*/
|
|
22
|
+
export type TrustDecision = "granted" | "refused";
|
|
23
|
+
export interface TrustRecord {
|
|
24
|
+
/** realpath of the trusted .kiso directory — the thing being trusted. */
|
|
25
|
+
readonly root: string;
|
|
26
|
+
/** bundle sha256 of the project's .kiso artifacts at decision time. */
|
|
27
|
+
readonly digest: string;
|
|
28
|
+
readonly decision: TrustDecision;
|
|
29
|
+
/** ISO timestamp of the decision. */
|
|
30
|
+
readonly ts: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectArtifact {
|
|
33
|
+
/** path relative to the .kiso dir, e.g. "extensions/lint-rules.mjs". */
|
|
34
|
+
readonly path: string;
|
|
35
|
+
readonly kind: "extension" | "mcp" | "skill";
|
|
36
|
+
/** sha256 (hex) of this file's content — the listing shows a short prefix. */
|
|
37
|
+
readonly digest: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ProjectArtifacts {
|
|
40
|
+
/** realpath of the .kiso directory — the trust record's root. */
|
|
41
|
+
readonly root: string;
|
|
42
|
+
readonly files: readonly ProjectArtifact[];
|
|
43
|
+
/** bundle sha256 (hex) over the sorted paths + contents. */
|
|
44
|
+
readonly digest: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Discover <cwd>/.kiso's artifacts. The skills scan is ONE level
|
|
48
|
+
* (<name>/SKILL.md) — the same scan the skills extension performs, so the
|
|
49
|
+
* digest covers exactly what gets loaded; anything deeper is inert and not
|
|
50
|
+
* part of the trust decision. Returns null when there is no .kiso dir or
|
|
51
|
+
* no recognized artifacts (an empty .kiso has nothing to gate).
|
|
52
|
+
*/
|
|
53
|
+
export declare function projectArtifacts(cwd: string): Promise<ProjectArtifacts | null>;
|
|
54
|
+
/**
|
|
55
|
+
* The last record matching (root, digest) — append-only, last wins. A
|
|
56
|
+
* corrupt line is skipped (trust is a memo, not an event stream — see the
|
|
57
|
+
* module comment). Callers pass the realpath'd root from projectArtifacts.
|
|
58
|
+
*/
|
|
59
|
+
export declare function trustFor(root: string, digest: string): TrustRecord | null;
|
|
60
|
+
/** Append one verdict. The same (root, digest) may be re-recorded — the
|
|
61
|
+
* newest record wins on read. */
|
|
62
|
+
export declare function recordTrust(record: {
|
|
63
|
+
root: string;
|
|
64
|
+
digest: string;
|
|
65
|
+
decision: TrustDecision;
|
|
66
|
+
ts?: string;
|
|
67
|
+
}): void;
|
package/dist/trust.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E3 — project-level capability is trusted by CONTENT DIGEST, not by
|
|
3
|
+
* directory (ADR-0037). A cloned repo's .kiso runs code on this machine
|
|
4
|
+
* (extensions load into the agent, mcp.json spawns servers, skills inject
|
|
5
|
+
* prompt text), so a trust decision is a decision about THE FILES, and it
|
|
6
|
+
* dies the moment the files change.
|
|
7
|
+
*
|
|
8
|
+
* projectArtifacts(cwd) discovers <cwd>/.kiso/{extensions/*.mjs, mcp.json,
|
|
9
|
+
* skills/<name>/SKILL.md} — the exact three artifact kinds — and returns a
|
|
10
|
+
* manifest (one sha256 per file) plus a BUNDLE digest: sha256 over the
|
|
11
|
+
* sorted relative paths and their contents. Any file change changes the
|
|
12
|
+
* bundle digest, which invalidates every prior trust record.
|
|
13
|
+
*
|
|
14
|
+
* The trust store (~/.kiso/trust.jsonl, KISO_HOME respected) is a simple
|
|
15
|
+
* append-only memo of human verdicts: {root, digest, decision, ts} per
|
|
16
|
+
* line, the LAST record matching (root, digest) wins. Its tolerance is
|
|
17
|
+
* deliberately different from the session store: trust.jsonl is NOT an
|
|
18
|
+
* event stream — a corrupt line means "no record" for that line (skip,
|
|
19
|
+
* never throw), because a lost grant only re-asks the human and a lost
|
|
20
|
+
* refusal also only re-asks; there is no trajectory to preserve.
|
|
21
|
+
*/
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
|
|
24
|
+
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
/**
|
|
28
|
+
* Discover <cwd>/.kiso's artifacts. The skills scan is ONE level
|
|
29
|
+
* (<name>/SKILL.md) — the same scan the skills extension performs, so the
|
|
30
|
+
* digest covers exactly what gets loaded; anything deeper is inert and not
|
|
31
|
+
* part of the trust decision. Returns null when there is no .kiso dir or
|
|
32
|
+
* no recognized artifacts (an empty .kiso has nothing to gate).
|
|
33
|
+
*/
|
|
34
|
+
export async function projectArtifacts(cwd) {
|
|
35
|
+
const kisoDir = join(cwd, ".kiso");
|
|
36
|
+
try {
|
|
37
|
+
await stat(kisoDir);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err.code === "ENOENT")
|
|
41
|
+
return null;
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
const root = await realpath(kisoDir);
|
|
45
|
+
const entries = [];
|
|
46
|
+
for (const f of await readdirOrEmpty(join(root, "extensions"))) {
|
|
47
|
+
if (!f.endsWith(".mjs"))
|
|
48
|
+
continue;
|
|
49
|
+
entries.push({ path: `extensions/${f}`, buf: await readFile(join(root, "extensions", f)) });
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
entries.push({ path: "mcp.json", buf: await readFile(join(root, "mcp.json")) });
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (!isMissing(err))
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
for (const dir of await readdirOrEmpty(join(root, "skills"))) {
|
|
59
|
+
try {
|
|
60
|
+
entries.push({ path: `skills/${dir}/SKILL.md`, buf: await readFile(join(root, "skills", dir, "SKILL.md")) });
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (!isMissing(err))
|
|
64
|
+
throw err; // a file named like a dir → ENOTDIR: inert, skip
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (entries.length === 0)
|
|
68
|
+
return null;
|
|
69
|
+
entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
70
|
+
const files = entries.map((e) => ({ path: e.path, kind: kindOf(e.path), digest: sha256(e.buf) }));
|
|
71
|
+
return { root, files, digest: bundleDigest(entries) };
|
|
72
|
+
}
|
|
73
|
+
function bundleDigest(entries) {
|
|
74
|
+
const h = createHash("sha256");
|
|
75
|
+
for (const e of entries) {
|
|
76
|
+
h.update(e.path);
|
|
77
|
+
h.update("\n");
|
|
78
|
+
h.update(e.buf);
|
|
79
|
+
h.update("\0");
|
|
80
|
+
}
|
|
81
|
+
return h.digest("hex");
|
|
82
|
+
}
|
|
83
|
+
function sha256(buf) {
|
|
84
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
85
|
+
}
|
|
86
|
+
function kindOf(path) {
|
|
87
|
+
if (path.startsWith("extensions/"))
|
|
88
|
+
return "extension";
|
|
89
|
+
if (path === "mcp.json")
|
|
90
|
+
return "mcp";
|
|
91
|
+
return "skill";
|
|
92
|
+
}
|
|
93
|
+
async function readdirOrEmpty(dir) {
|
|
94
|
+
try {
|
|
95
|
+
return await readdir(dir);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
if (isMissing(err))
|
|
99
|
+
return [];
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function isMissing(err) {
|
|
104
|
+
return err.code === "ENOENT" || err.code === "ENOTDIR";
|
|
105
|
+
}
|
|
106
|
+
function trustFile() {
|
|
107
|
+
return join(process.env.KISO_HOME ?? join(homedir(), ".kiso"), "trust.jsonl");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The last record matching (root, digest) — append-only, last wins. A
|
|
111
|
+
* corrupt line is skipped (trust is a memo, not an event stream — see the
|
|
112
|
+
* module comment). Callers pass the realpath'd root from projectArtifacts.
|
|
113
|
+
*/
|
|
114
|
+
export function trustFor(root, digest) {
|
|
115
|
+
let text;
|
|
116
|
+
try {
|
|
117
|
+
text = readFileSync(trustFile(), "utf8");
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null; // no trust file = no records
|
|
121
|
+
}
|
|
122
|
+
let found = null;
|
|
123
|
+
for (const line of text.split("\n")) {
|
|
124
|
+
if (line.trim() === "")
|
|
125
|
+
continue;
|
|
126
|
+
let rec;
|
|
127
|
+
try {
|
|
128
|
+
rec = JSON.parse(line);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue; // corrupt line = no record
|
|
132
|
+
}
|
|
133
|
+
if (!isTrustRecord(rec))
|
|
134
|
+
continue;
|
|
135
|
+
if (rec.root === root && rec.digest === digest)
|
|
136
|
+
found = rec;
|
|
137
|
+
}
|
|
138
|
+
return found;
|
|
139
|
+
}
|
|
140
|
+
/** Append one verdict. The same (root, digest) may be re-recorded — the
|
|
141
|
+
* newest record wins on read. */
|
|
142
|
+
export function recordTrust(record) {
|
|
143
|
+
const home = process.env.KISO_HOME ?? join(homedir(), ".kiso");
|
|
144
|
+
mkdirSync(home, { recursive: true });
|
|
145
|
+
appendFileSync(join(home, "trust.jsonl"), `${JSON.stringify({ root: record.root, digest: record.digest, decision: record.decision, ts: record.ts ?? new Date().toISOString() })}\n`, "utf8");
|
|
146
|
+
}
|
|
147
|
+
function isTrustRecord(v) {
|
|
148
|
+
if (typeof v !== "object" || v === null)
|
|
149
|
+
return false;
|
|
150
|
+
const r = v;
|
|
151
|
+
return typeof r.root === "string" && typeof r.digest === "string" && (r.decision === "granted" || r.decision === "refused") && typeof r.ts === "string";
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "kiso runtime — durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"test": "vitest run"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@vincemakes/kiso-core": "0.1.
|
|
24
|
+
"@vincemakes/kiso-core": "0.1.10"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@vincemakes/kiso-provider-anthropic": "0.1.
|
|
28
|
-
"@vincemakes/kiso-provider-openai": "0.1.
|
|
27
|
+
"@vincemakes/kiso-provider-anthropic": "0.1.10",
|
|
28
|
+
"@vincemakes/kiso-provider-openai": "0.1.10"
|
|
29
29
|
},
|
|
30
30
|
"peerDependenciesMeta": {
|
|
31
31
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@vincemakes/kiso-evals": "0.1.
|
|
39
|
+
"@vincemakes/kiso-evals": "0.1.10",
|
|
40
40
|
"@types/node": "^26.1.2",
|
|
41
41
|
"typescript": "^5.7.2",
|
|
42
42
|
"vitest": "^3.0.0"
|