@vincemakes/kiso-runtime 0.52.0 → 0.54.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/dist/environment.d.ts +34 -0
- package/dist/environment.js +159 -0
- package/dist/fork.d.ts +27 -0
- package/dist/fork.js +86 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/internal.d.ts +2 -1
- package/dist/internal.js +2 -1
- package/dist/profile.d.ts +19 -0
- package/dist/profile.js +14 -0
- package/dist/provider/metadata.d.ts +5 -0
- package/dist/provider/metadata.js +36 -2
- package/dist/provider/model-snapshot.d.ts +11 -0
- package/dist/provider/model-snapshot.js +361 -0
- package/dist/session.d.ts +33 -0
- package/dist/session.js +153 -3
- package/dist/store.d.ts +11 -0
- package/dist/store.js +101 -5
- package/package.json +6 -6
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Work's added folders (ADR-0058 Amendment 19): one small file per session, `<id>.environment.json`.
|
|
3
|
+
*
|
|
4
|
+
* It is apart from `<id>.meta.json` on purpose: the sidecar's writers replace the whole file with no lock, and a rename in one
|
|
5
|
+
* process could write back a list read a moment earlier. This file has one writer, under its own short lock.
|
|
6
|
+
*
|
|
7
|
+
* The file tools read it on every call, so nothing holds a stale copy. It decides what the file tools may reach and nothing else:
|
|
8
|
+
* what the model has been told is the conversation's business, and authorisation never reads the conversation.
|
|
9
|
+
*
|
|
10
|
+
* A damaged file, or one of a version this kiso does not know, honours no added folder: the workspace keeps working.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ENVIRONMENT_NOTE_HEAD = "Environment update from kiso (a fact about this session, not a request from the person)";
|
|
13
|
+
/** The line a host puts before a person's next message after a change, kept out of the bubble and the title. Best effort. */
|
|
14
|
+
export declare const isEnvironmentNote: (text: string) => boolean;
|
|
15
|
+
/** A folder that cannot be added or removed, in words for the person. */
|
|
16
|
+
export declare class EnvironmentRefusedError extends Error {
|
|
17
|
+
constructor(message: string);
|
|
18
|
+
}
|
|
19
|
+
export declare const environmentFile: (id: string) => string;
|
|
20
|
+
export interface EnvironmentState {
|
|
21
|
+
readonly added: readonly string[];
|
|
22
|
+
/** the file is there and cannot be honoured; `added` is empty */
|
|
23
|
+
readonly damaged: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function readEnvironment(root: string, id: string): EnvironmentState;
|
|
26
|
+
/** Add `folder` to the Work. `primary` is where it started; `stops` are folders the new reach never enters. */
|
|
27
|
+
export declare function addFolder(root: string, id: string, options: {
|
|
28
|
+
readonly primary: string;
|
|
29
|
+
readonly folder: string;
|
|
30
|
+
readonly stops: readonly string[];
|
|
31
|
+
}): Promise<readonly string[]>;
|
|
32
|
+
export declare function removeFolder(root: string, id: string, folder: string): Promise<readonly string[]>;
|
|
33
|
+
/** A fork takes the parent's current list. */
|
|
34
|
+
export declare function copyEnvironment(root: string, from: string, to: string): void;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Work's added folders (ADR-0058 Amendment 19): one small file per session, `<id>.environment.json`.
|
|
3
|
+
*
|
|
4
|
+
* It is apart from `<id>.meta.json` on purpose: the sidecar's writers replace the whole file with no lock, and a rename in one
|
|
5
|
+
* process could write back a list read a moment earlier. This file has one writer, under its own short lock.
|
|
6
|
+
*
|
|
7
|
+
* The file tools read it on every call, so nothing holds a stale copy. It decides what the file tools may reach and nothing else:
|
|
8
|
+
* what the model has been told is the conversation's business, and authorisation never reads the conversation.
|
|
9
|
+
*
|
|
10
|
+
* A damaged file, or one of a version this kiso does not know, honours no added folder: the workspace keeps working.
|
|
11
|
+
*/
|
|
12
|
+
import { closeSync, copyFileSync, existsSync, fsyncSync, openSync, readFileSync, realpathSync, renameSync, statSync, writeSync } from "node:fs";
|
|
13
|
+
import { dirname, isAbsolute, join, relative } from "node:path";
|
|
14
|
+
import { LockedError, nativeLockAdapter } from "./lock-adapter.js";
|
|
15
|
+
const VERSION = 1;
|
|
16
|
+
const LOCK_WAIT_MS = 2_000;
|
|
17
|
+
export const ENVIRONMENT_NOTE_HEAD = "Environment update from kiso (a fact about this session, not a request from the person)";
|
|
18
|
+
/** The line a host puts before a person's next message after a change, kept out of the bubble and the title. Best effort. */
|
|
19
|
+
export const isEnvironmentNote = (text) => text.startsWith(ENVIRONMENT_NOTE_HEAD);
|
|
20
|
+
/** A folder that cannot be added or removed, in words for the person. */
|
|
21
|
+
export class EnvironmentRefusedError extends Error {
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "EnvironmentRefusedError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export const environmentFile = (id) => `${id}.environment.json`;
|
|
28
|
+
export function readEnvironment(root, id) {
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = readFileSync(join(root, environmentFile(id)), "utf8");
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return { added: [], damaged: err.code !== "ENOENT" };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(raw);
|
|
38
|
+
if (parsed.version !== VERSION || !Array.isArray(parsed.added) || !parsed.added.every((p) => typeof p === "string" && isAbsolute(p)))
|
|
39
|
+
return { added: [], damaged: true };
|
|
40
|
+
return { added: parsed.added, damaged: false };
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { added: [], damaged: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const within = (root, target) => {
|
|
47
|
+
const rel = relative(root, target);
|
|
48
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* The list's own lock: the session store's identity-confirmed link lock (ADR-0050), never taken from a live holder and released
|
|
52
|
+
* only by its owner. A dead holder is taken over by the adapter's rules; a live one is waited for briefly, then refused in words.
|
|
53
|
+
*/
|
|
54
|
+
async function withLock(path, id, work) {
|
|
55
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
56
|
+
let handle;
|
|
57
|
+
for (;;) {
|
|
58
|
+
try {
|
|
59
|
+
handle = await nativeLockAdapter.acquire(`${path}.lock`, id, () => undefined);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (!(err instanceof LockedError) || Date.now() > deadline) {
|
|
64
|
+
throw err instanceof LockedError ? new EnvironmentRefusedError("this Work's folders are being changed elsewhere: try again") : err;
|
|
65
|
+
}
|
|
66
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
if (!handle.verify())
|
|
71
|
+
throw new EnvironmentRefusedError("this Work's folders are being changed elsewhere: try again");
|
|
72
|
+
return work();
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
handle.release();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function replace(path, added) {
|
|
79
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
80
|
+
const fd = openSync(tmp, "w", 0o600);
|
|
81
|
+
try {
|
|
82
|
+
writeSync(fd, `${JSON.stringify({ version: VERSION, added })}\n`);
|
|
83
|
+
fsyncSync(fd);
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
closeSync(fd);
|
|
87
|
+
}
|
|
88
|
+
renameSync(tmp, path);
|
|
89
|
+
// the rename is the commit: from here a failure must not be reported as one. A platform whose directories cannot be opened or
|
|
90
|
+
// flushed (Windows) keeps the rename.
|
|
91
|
+
try {
|
|
92
|
+
const dir = openSync(dirname(path), "r");
|
|
93
|
+
try {
|
|
94
|
+
fsyncSync(dir);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
closeSync(dir);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// the change stands
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function update(root, id, change) {
|
|
105
|
+
const path = join(root, environmentFile(id));
|
|
106
|
+
return withLock(path, id, () => {
|
|
107
|
+
const now = readEnvironment(root, id);
|
|
108
|
+
if (now.damaged)
|
|
109
|
+
throw new EnvironmentRefusedError(`this Work's folder list cannot be read: remove ${path} to start it again`);
|
|
110
|
+
const next = change(now.added);
|
|
111
|
+
replace(path, next);
|
|
112
|
+
return next;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Add `folder` to the Work. `primary` is where it started; `stops` are folders the new reach never enters. */
|
|
116
|
+
export async function addFolder(root, id, options) {
|
|
117
|
+
let real;
|
|
118
|
+
try {
|
|
119
|
+
real = realpathSync(options.folder);
|
|
120
|
+
if (!statSync(real).isDirectory())
|
|
121
|
+
throw new Error("not a directory");
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
throw new EnvironmentRefusedError(`${options.folder} is not a folder on this computer`);
|
|
125
|
+
}
|
|
126
|
+
const realOrSelf = (p) => {
|
|
127
|
+
try {
|
|
128
|
+
return realpathSync(p);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return p;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
if (options.stops.map(realOrSelf).some((stop) => within(stop, real)))
|
|
135
|
+
throw new EnvironmentRefusedError(`${real} can't be added: the file tools do not reach this folder`);
|
|
136
|
+
const primary = realOrSelf(options.primary);
|
|
137
|
+
if (real === primary)
|
|
138
|
+
throw new EnvironmentRefusedError("this is already the Work's primary folder");
|
|
139
|
+
return update(root, id, (added) => {
|
|
140
|
+
if (added.includes(real))
|
|
141
|
+
throw new EnvironmentRefusedError("this folder is already in the Work");
|
|
142
|
+
if ([primary, ...added.map(realOrSelf)].some((r) => within(r, real) || within(real, r)))
|
|
143
|
+
throw new EnvironmentRefusedError("a folder inside, or containing, another of the Work's folders can't be added");
|
|
144
|
+
return [...added, real];
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
export async function removeFolder(root, id, folder) {
|
|
148
|
+
return update(root, id, (added) => {
|
|
149
|
+
if (!added.includes(folder))
|
|
150
|
+
throw new EnvironmentRefusedError("this folder is not in the Work");
|
|
151
|
+
return added.filter((p) => p !== folder);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** A fork takes the parent's current list. */
|
|
155
|
+
export function copyEnvironment(root, from, to) {
|
|
156
|
+
const source = join(root, environmentFile(from));
|
|
157
|
+
if (existsSync(source))
|
|
158
|
+
copyFileSync(source, join(root, environmentFile(to)));
|
|
159
|
+
}
|
package/dist/fork.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fork (owner 2026-11-03: one rule for the terminal, the web app and the products that embed kiso): a new session
|
|
3
|
+
* holding the conversation up to the end of one of the parent's runs, and nothing after it. It is the parent's local
|
|
4
|
+
* files under a new id: the log's lines up to that run's `terminal`, byte for byte (no line names its session and none
|
|
5
|
+
* chains to another), and a sidecar with the parent's execution profile, the name "… (fork)" and the fork's origin. Not
|
|
6
|
+
* carried: the lock and the background tasks, which belong to the parent's process, and the parent's summary, which may
|
|
7
|
+
* describe runs after the cut. The parent is only read; lines it appends meanwhile are after the cut. The sidecar is
|
|
8
|
+
* written first: the runtime reads a session's profile before its first event.
|
|
9
|
+
*/
|
|
10
|
+
import { type ForkOrigin } from "./profile.js";
|
|
11
|
+
/** A fork the store will not make, said in words (the caller shows them). */
|
|
12
|
+
export declare class ForkRefusedError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/** Fork `parent` in `root` after the run that ended at `afterSeq` (absent: the last finished run). */
|
|
16
|
+
export declare function forkSession(root: string, parent: string, afterSeq?: number): {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly origin: ForkOrigin;
|
|
19
|
+
};
|
|
20
|
+
/** Whether a text block is the fork note (the apps show the person's words, never this). */
|
|
21
|
+
export declare const isForkNote: (text: string) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* What a fork's model is told, once, with the fork's first input after the copied history (never in the system prompt:
|
|
24
|
+
* a cache is a prefix, and a note there made the fork's first request read the whole history fresh, 0% against 88%
|
|
25
|
+
* measured on co, plan fork rev 2): what the conversation it continues does not bring with it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const forkNote: (parentTitle: string) => string;
|
package/dist/fork.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A fork (owner 2026-11-03: one rule for the terminal, the web app and the products that embed kiso): a new session
|
|
3
|
+
* holding the conversation up to the end of one of the parent's runs, and nothing after it. It is the parent's local
|
|
4
|
+
* files under a new id: the log's lines up to that run's `terminal`, byte for byte (no line names its session and none
|
|
5
|
+
* chains to another), and a sidecar with the parent's execution profile, the name "… (fork)" and the fork's origin. Not
|
|
6
|
+
* carried: the lock and the background tasks, which belong to the parent's process, and the parent's summary, which may
|
|
7
|
+
* describe runs after the cut. The parent is only read; lines it appends meanwhile are after the cut. The sidecar is
|
|
8
|
+
* written first: the runtime reads a session's profile before its first event.
|
|
9
|
+
*/
|
|
10
|
+
import { copyEnvironment } from "./environment.js";
|
|
11
|
+
import { closeSync, fsyncSync, openSync, readFileSync, renameSync, writeSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { readSessionName, tenantsOf, writeForkSidecar } from "./profile.js";
|
|
14
|
+
/** A fork the store will not make, said in words (the caller shows them). */
|
|
15
|
+
export class ForkRefusedError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "ForkRefusedError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** The id a fork is given: the time to the second and four random characters, the way a session's id is made. */
|
|
22
|
+
const newId = (now = new Date()) => `${now.toISOString().slice(0, 19).replace(/:/g, "-")}-${Math.floor(Math.random() * 0x10000).toString(16).padStart(4, "0")}`;
|
|
23
|
+
/** Write a file whole or not at all: a temp file, its bytes synced, then renamed over the name. */
|
|
24
|
+
function durable(path, bytes) {
|
|
25
|
+
const tmp = `${path}.fork-${process.pid}`;
|
|
26
|
+
const fd = openSync(tmp, "w", 0o600);
|
|
27
|
+
try {
|
|
28
|
+
writeSync(fd, bytes);
|
|
29
|
+
fsyncSync(fd);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
closeSync(fd);
|
|
33
|
+
}
|
|
34
|
+
renameSync(tmp, path);
|
|
35
|
+
}
|
|
36
|
+
/** Fork `parent` in `root` after the run that ended at `afterSeq` (absent: the last finished run). */
|
|
37
|
+
export function forkSession(root, parent, afterSeq) {
|
|
38
|
+
let log;
|
|
39
|
+
try {
|
|
40
|
+
log = readFileSync(join(root, `${parent}.jsonl`));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new ForkRefusedError(`no session ${parent} to fork`);
|
|
44
|
+
}
|
|
45
|
+
// the lines, with where each ends, and the event each holds
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (let from = 0; from < log.length;) {
|
|
48
|
+
const nl = log.indexOf(10, from);
|
|
49
|
+
if (nl < 0)
|
|
50
|
+
break; // a line still being written is after any cut
|
|
51
|
+
const event = JSON.parse(log.subarray(from, nl).toString("utf8")).event;
|
|
52
|
+
lines.push({ end: nl + 1, seq: event["seq"], type: event["type"], event });
|
|
53
|
+
from = nl + 1;
|
|
54
|
+
}
|
|
55
|
+
const cut = afterSeq === undefined ? [...lines].reverse().find((l) => l.type === "terminal") : lines.find((l) => l.seq === afterSeq);
|
|
56
|
+
if (cut === undefined)
|
|
57
|
+
throw new ForkRefusedError(afterSeq === undefined ? "this session has no finished run to fork from yet" : `no event ${afterSeq} in this session`);
|
|
58
|
+
if (cut.type !== "terminal")
|
|
59
|
+
throw new ForkRefusedError("a fork starts at the end of a run: pick a reply that finished");
|
|
60
|
+
const kept = lines.filter((l) => l.end <= cut.end);
|
|
61
|
+
// an execution still waiting for a verdict belongs to the world the parent ran in: settle it there first
|
|
62
|
+
const resolved = new Set(kept.filter((l) => l.type === "tool_execution_resolved").map((l) => l.event["executionId"]));
|
|
63
|
+
if (kept.some((l) => l.type === "uncertain_pending" && !resolved.has(l.event["executionId"])))
|
|
64
|
+
throw new ForkRefusedError("an action before this point is still waiting for your verdict in the original session: settle it there, then fork");
|
|
65
|
+
// its name, else its first line (only this log is read)
|
|
66
|
+
const first = kept.find((l) => l.type === "user_input")?.event;
|
|
67
|
+
const said = typeof first?.via?.line === "string" ? first.via.line : typeof first?.content === "string" ? first.content : Array.isArray(first?.content) ? first.content.map((p) => (typeof p.text === "string" ? p.text : "")).join(" ") : "";
|
|
68
|
+
const title = readSessionName(root, parent) ?? said.replace(/\s+/g, " ").trim().slice(0, 60);
|
|
69
|
+
const id = newId();
|
|
70
|
+
const origin = { session: parent, afterSeq: cut.seq, at: Date.now(), title };
|
|
71
|
+
writeForkSidecar(root, id, { profile: tenantsOf(root, parent)["profile"], name: `${(title || "Session").slice(0, 110)} (fork)`, forkedFrom: origin });
|
|
72
|
+
copyEnvironment(root, parent, id); // before the log: a fork that is listed has its folders
|
|
73
|
+
durable(join(root, `${id}.jsonl`), log.subarray(0, cut.end));
|
|
74
|
+
return { id, origin };
|
|
75
|
+
}
|
|
76
|
+
const FORK_NOTE_HEAD = 'This session was forked from the session "';
|
|
77
|
+
/** Whether a text block is the fork note (the apps show the person's words, never this). */
|
|
78
|
+
export const isForkNote = (text) => text.startsWith(FORK_NOTE_HEAD);
|
|
79
|
+
/**
|
|
80
|
+
* What a fork's model is told, once, with the fork's first input after the copied history (never in the system prompt:
|
|
81
|
+
* a cache is a prefix, and a note there made the fork's first request read the whole history fresh, 0% against 88%
|
|
82
|
+
* measured on co, plan fork rev 2): what the conversation it continues does not bring with it.
|
|
83
|
+
*/
|
|
84
|
+
export const forkNote = (parentTitle) => `${FORK_NOTE_HEAD}${parentTitle}" at the end of one of its runs. The conversation above it is copied; two things are not. ` +
|
|
85
|
+
"Background tasks started there are not running here (a task id from that conversation names nothing in this session). " +
|
|
86
|
+
"The folder is shared with it: its files are as they are on disk now, not as they were when the run above ended, and the original session may still change them.";
|
package/dist/index.d.ts
CHANGED
|
@@ -21,13 +21,17 @@
|
|
|
21
21
|
export { AgentRuntime, AgentRuntime as Agent, createAgent } from "./agent.js";
|
|
22
22
|
export type { AgentDefinition, PermissionPolicy, PermissionRule } from "./agent.js";
|
|
23
23
|
export { AgentSession, AgentSession as Session } from "./session.js";
|
|
24
|
-
export type { SideQueryOptions } from "./session.js";
|
|
24
|
+
export type { FollowUpOptions, SideQueryOptions } from "./session.js";
|
|
25
25
|
export { PoisonedSessionError, ResumeBlockedError } from "./session.js";
|
|
26
26
|
export type { ApprovalRequest, CompactInfo, ContextPolicy, SessionConfig, SummarizeResult } from "./session.js";
|
|
27
27
|
export { DEFAULT_CHECKPOINT_SECTIONS } from "./summarize.js";
|
|
28
28
|
export type { CheckpointSection } from "./summarize.js";
|
|
29
29
|
export { Run, RunClosedError } from "./run.js";
|
|
30
30
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
31
|
+
export { ForkRefusedError } from "./fork.js";
|
|
32
|
+
export { addFolder, EnvironmentRefusedError, isEnvironmentNote, readEnvironment, removeFolder } from "./environment.js";
|
|
33
|
+
export type { EnvironmentState } from "./environment.js";
|
|
34
|
+
export type { ForkOrigin } from "./profile.js";
|
|
31
35
|
export type { Event, SessionMeta, StoreRecord } from "./store.js";
|
|
32
36
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
33
37
|
export type { KisoExtension } from "./extensions.js";
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,8 @@ export { DEFAULT_CHECKPOINT_SECTIONS } from "./summarize.js";
|
|
|
29
29
|
export { Run, RunClosedError } from "./run.js";
|
|
30
30
|
// store
|
|
31
31
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
32
|
+
export { ForkRefusedError } from "./fork.js";
|
|
33
|
+
export { addFolder, EnvironmentRefusedError, isEnvironmentNote, readEnvironment, removeFolder } from "./environment.js";
|
|
32
34
|
// extensions
|
|
33
35
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
34
36
|
// tasks (ADR-0058): a host that wires background tasks needs the manager
|
package/dist/internal.d.ts
CHANGED
|
@@ -29,7 +29,8 @@ export { canonicalizeUsageForModel } from "./usage/canonical.js";
|
|
|
29
29
|
export { BUILTIN_MANIFESTS, resolveContinuationScope, type ModelRef, type ProviderManifest } from "./provider/manifest.js";
|
|
30
30
|
export * from "./checkpoint.js";
|
|
31
31
|
export * from "./request-budget.js";
|
|
32
|
-
export {
|
|
32
|
+
export { isForkNote } from "./fork.js";
|
|
33
|
+
export { readForkOrigin, readProfile, readSessionName, readSummary, writeSessionName, writeSummary, type ExecutionProfile, type ProfileReadResult } from "./profile.js";
|
|
33
34
|
export { SUMMARY_MIGRATION_MARKER, listSessionSidecars, migrateSummaries, sessionSummary, summarizeEvents, summaryMigrationPending, type SessionListing, type SessionSummary } from "./session-summary.js";
|
|
34
35
|
export * from "./tasks/journal.js";
|
|
35
36
|
export * from "./tasks/manager.js";
|
package/dist/internal.js
CHANGED
|
@@ -38,7 +38,8 @@ export { BUILTIN_MANIFESTS, resolveContinuationScope } from "./provider/manifest
|
|
|
38
38
|
export * from "./checkpoint.js";
|
|
39
39
|
export * from "./request-budget.js";
|
|
40
40
|
// 0.40.0: the listing reads the recorded workspace and profile name.
|
|
41
|
-
export {
|
|
41
|
+
export { isForkNote } from "./fork.js";
|
|
42
|
+
export { readForkOrigin, readProfile, readSessionName, readSummary, writeSessionName, writeSummary } from "./profile.js";
|
|
42
43
|
export { SUMMARY_MIGRATION_MARKER, listSessionSidecars, migrateSummaries, sessionSummary, summarizeEvents, summaryMigrationPending } from "./session-summary.js";
|
|
43
44
|
// ADR-0058 §6: the task journal and the TaskManager (internal until the
|
|
44
45
|
// public shape is proven by the shell and delegate steps)
|
package/dist/profile.d.ts
CHANGED
|
@@ -94,6 +94,25 @@ export declare function readSessionName(root: string, sessionId: string): string
|
|
|
94
94
|
/** Name the session, or clear its name (`null`). Atomic and fail-closed
|
|
95
95
|
* like every tenant's write; the others' bytes ride through. */
|
|
96
96
|
export declare function writeSessionName(root: string, sessionId: string, name: string | null): void;
|
|
97
|
+
/** Where a fork came from (SessionStore.fork): the parent session, the seq of the parent's `terminal` it was cut after,
|
|
98
|
+
* when, and the parent's title then. The sidecar's fourth tenant; lineage, not authority. */
|
|
99
|
+
export interface ForkOrigin {
|
|
100
|
+
readonly session: string;
|
|
101
|
+
readonly afterSeq: number;
|
|
102
|
+
readonly at: number;
|
|
103
|
+
readonly title: string;
|
|
104
|
+
}
|
|
105
|
+
/** The fork's origin, or null (not a fork, no sidecar, unreadable). */
|
|
106
|
+
export declare function readForkOrigin(root: string, sessionId: string): ForkOrigin | null;
|
|
107
|
+
/** A new fork's sidecar, whole: the parent's execution profile, the fork's name, and its origin. The parent's summary is
|
|
108
|
+
* not carried: it may describe runs after the cut, and the fork writes its own. */
|
|
109
|
+
export declare function writeForkSidecar(root: string, sessionId: string, tenants: {
|
|
110
|
+
readonly profile?: unknown;
|
|
111
|
+
readonly name: string;
|
|
112
|
+
readonly forkedFrom: ForkOrigin;
|
|
113
|
+
}): void;
|
|
114
|
+
/** The parent's tenants, read for a fork. */
|
|
115
|
+
export declare function tenantsOf(root: string, sessionId: string): Record<string, unknown>;
|
|
97
116
|
export type ProfileReadResult = {
|
|
98
117
|
readonly kind: "ok";
|
|
99
118
|
readonly profile: ExecutionProfile;
|
package/dist/profile.js
CHANGED
|
@@ -98,6 +98,20 @@ export function writeSessionName(root, sessionId, name) {
|
|
|
98
98
|
delete tenants.name;
|
|
99
99
|
writeTenants(root, sessionId, name === null ? tenants : { ...tenants, name });
|
|
100
100
|
}
|
|
101
|
+
/** The fork's origin, or null (not a fork, no sidecar, unreadable). */
|
|
102
|
+
export function readForkOrigin(root, sessionId) {
|
|
103
|
+
const f = currentTenants(root, sessionId).forkedFrom;
|
|
104
|
+
return f !== undefined && typeof f.session === "string" && typeof f.afterSeq === "number" && typeof f.at === "number" && typeof f.title === "string" ? f : null;
|
|
105
|
+
}
|
|
106
|
+
/** A new fork's sidecar, whole: the parent's execution profile, the fork's name, and its origin. The parent's summary is
|
|
107
|
+
* not carried: it may describe runs after the cut, and the fork writes its own. */
|
|
108
|
+
export function writeForkSidecar(root, sessionId, tenants) {
|
|
109
|
+
writeTenants(root, sessionId, { ...(tenants.profile !== undefined ? { profile: tenants.profile } : {}), name: tenants.name, forkedFrom: tenants.forkedFrom });
|
|
110
|
+
}
|
|
111
|
+
/** The parent's tenants, read for a fork. */
|
|
112
|
+
export function tenantsOf(root, sessionId) {
|
|
113
|
+
return currentTenants(root, sessionId);
|
|
114
|
+
}
|
|
101
115
|
function writeTenants(root, sessionId, tenants) {
|
|
102
116
|
const path = profilePath(root, sessionId);
|
|
103
117
|
const tmpDir = mkdtempSync(join(root, ".meta-"));
|
|
@@ -185,6 +185,11 @@ export interface ResolvedContextWindow {
|
|
|
185
185
|
* facts about a route and stay on lookupModelMetadata.
|
|
186
186
|
*/
|
|
187
187
|
export declare function lookupContextWindow(model: string, endpoint?: string, upstream?: string): ResolvedContextWindow | null;
|
|
188
|
+
/**
|
|
189
|
+
* What a model takes in (owner, 2026-11-11): the table's row for this route when it says, else the release snapshot for the
|
|
190
|
+
* model's weights (whether a model reads pictures belongs to the weights), else null, unknown. A host decides what unknown means.
|
|
191
|
+
*/
|
|
192
|
+
export declare function lookupInputModalities(model: string, endpoint?: string): readonly string[] | null;
|
|
188
193
|
/**
|
|
189
194
|
* XP-1 §4.2 — native-only resolution. The rules, in order:
|
|
190
195
|
* default/default → NO wire fields (the byte-identity anchor — a
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* does not move; core is untouched.
|
|
20
20
|
*/
|
|
21
21
|
import { OPENROUTER_ROWS } from "./openrouter-rows.js";
|
|
22
|
+
import { MODEL_SNAPSHOT, MODEL_SNAPSHOT_AS_OF } from "./model-snapshot.js";
|
|
22
23
|
/**
|
|
23
24
|
* REG-1 — the OFF-PEAK rates, read from the vendor's pricing table on
|
|
24
25
|
* 2026-09-17. The table carries TWO sets: off-peak and peak, the peak
|
|
@@ -632,14 +633,47 @@ export function lookupContextWindow(model, endpoint, upstream) {
|
|
|
632
633
|
}
|
|
633
634
|
const identity = modelIdentity(model);
|
|
634
635
|
let found = null;
|
|
636
|
+
let rowed = route !== null;
|
|
635
637
|
for (const entry of ENTRIES) {
|
|
636
638
|
const tokens = entry.capabilities.contextWindow;
|
|
637
|
-
if (
|
|
639
|
+
if (modelIdentity(entry.model) !== identity)
|
|
640
|
+
continue;
|
|
641
|
+
rowed = true;
|
|
642
|
+
if (tokens === null)
|
|
638
643
|
continue;
|
|
639
644
|
if (found === null || tokens < found.tokens)
|
|
640
645
|
found = { tokens, source: "model", from: entry.model };
|
|
641
646
|
}
|
|
642
|
-
|
|
647
|
+
if (found !== null || rowed)
|
|
648
|
+
return found; // a row that states no window lends none (CW-1): the snapshot does not overrule it
|
|
649
|
+
// last, for a model the table has no row for at all: the release's snapshot (kiso.work), by the same identity
|
|
650
|
+
const snap = snapshotOf(model);
|
|
651
|
+
return snap?.window != null ? { tokens: snap.window, source: "model", from: `${snap.key} (kiso.work, ${MODEL_SNAPSHOT_AS_OF})` } : null;
|
|
652
|
+
}
|
|
653
|
+
let dashed = null;
|
|
654
|
+
/** The release snapshot's row for a model: by its folded name, by the weights it names (an alias), by a name that names the
|
|
655
|
+
* same weights, and with dots and dashes read alike (`claude-fable-5.2` is the vendor's `claude-fable-5-2`). */
|
|
656
|
+
function snapshotOf(model) {
|
|
657
|
+
const folded = model.slice(model.lastIndexOf("/") + 1).toLowerCase();
|
|
658
|
+
const identity = modelIdentity(model);
|
|
659
|
+
const names = [folded, identity, ...Object.entries(MODEL_ALIASES).filter(([, a]) => a.is === identity).map(([n]) => n)];
|
|
660
|
+
for (const n of names)
|
|
661
|
+
if (MODEL_SNAPSHOT[n] !== undefined)
|
|
662
|
+
return { key: n, ...MODEL_SNAPSHOT[n] };
|
|
663
|
+
dashed ??= new Map(Object.keys(MODEL_SNAPSHOT).map((k) => [k.replace(/\./g, "-"), k]));
|
|
664
|
+
for (const n of names) {
|
|
665
|
+
const k = dashed.get(n.replace(/\./g, "-"));
|
|
666
|
+
if (k !== undefined)
|
|
667
|
+
return { key: k, ...MODEL_SNAPSHOT[k] };
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* What a model takes in (owner, 2026-11-11): the table's row for this route when it says, else the release snapshot for the
|
|
673
|
+
* model's weights (whether a model reads pictures belongs to the weights), else null, unknown. A host decides what unknown means.
|
|
674
|
+
*/
|
|
675
|
+
export function lookupInputModalities(model, endpoint) {
|
|
676
|
+
return lookupModelMetadata(model, endpoint)?.capabilities.inputModalities ?? snapshotOf(model)?.input ?? null;
|
|
643
677
|
}
|
|
644
678
|
function originOf(endpoint) {
|
|
645
679
|
try {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED by scripts/model-snapshot.mjs from https://kiso.work/models/all.json (as of 2026-11-11).
|
|
3
|
+
* Do not edit by hand: a release runs the script and commits the diff.
|
|
4
|
+
*/
|
|
5
|
+
export declare const MODEL_SNAPSHOT_AS_OF = "2026-11-11";
|
|
6
|
+
export declare const MODEL_SNAPSHOT_SOURCE = "https://kiso.work/models/all.json";
|
|
7
|
+
/** Keyed by the model's name with its vendor prefix off, in lower case. */
|
|
8
|
+
export declare const MODEL_SNAPSHOT: Readonly<Record<string, {
|
|
9
|
+
readonly input: readonly string[] | null;
|
|
10
|
+
readonly window: number | null;
|
|
11
|
+
}>>;
|