@vincemakes/kiso-runtime 0.51.0 → 0.52.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/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/lock-adapter.js +31 -4
- package/dist/tasks/journal.d.ts +9 -0
- package/dist/tasks/journal.js +5 -0
- package/dist/tasks/manager.d.ts +6 -1
- package/dist/tasks/manager.js +9 -1
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -31,9 +31,13 @@ export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js
|
|
|
31
31
|
export type { Event, SessionMeta, StoreRecord } from "./store.js";
|
|
32
32
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
33
33
|
export type { KisoExtension } from "./extensions.js";
|
|
34
|
+
export { TaskManager } from "./tasks/manager.js";
|
|
35
|
+
export type { TaskInfo, TaskListener, TaskTransition } from "./tasks/manager.js";
|
|
36
|
+
export type { TaskState } from "./tasks/journal.js";
|
|
34
37
|
export { executionForCallId, executionLedger } from "./ledger.js";
|
|
35
38
|
export type { ExecutionRecord, ExecutionStatus } from "./ledger.js";
|
|
36
39
|
export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
|
|
37
40
|
export type { ProjectArtifact, ProjectArtifacts, TrustDecision, TrustRecord } from "./trust.js";
|
|
41
|
+
export { runsACheck } from "./compaction-policy.js";
|
|
38
42
|
export { canonicalizeUsage } from "./usage/canonical.js";
|
|
39
43
|
export { estimateTokens } from "./estimate-tokens.js";
|
package/dist/index.js
CHANGED
|
@@ -31,10 +31,16 @@ export { Run, RunClosedError } from "./run.js";
|
|
|
31
31
|
export { SessionStore, StaleWriterError, StoreCorruptionError } from "./store.js";
|
|
32
32
|
// extensions
|
|
33
33
|
export { disposeExtensions, loadExtensions, loadProjectExtensions } from "./extensions.js";
|
|
34
|
+
// tasks (ADR-0058): a host that wires background tasks needs the manager
|
|
35
|
+
export { TaskManager } from "./tasks/manager.js";
|
|
34
36
|
// ledger
|
|
35
37
|
export { executionForCallId, executionLedger } from "./ledger.js";
|
|
36
38
|
// trust
|
|
37
39
|
export { kisoHome, projectArtifacts, recordTrust, trustFor } from "./trust.js";
|
|
40
|
+
// compaction — the phase rule's test-runner check, for a host that sets its
|
|
41
|
+
// own `contextPolicy.tiers.isCheck` (the terminal puts the person's configured
|
|
42
|
+
// `checks` in front of the built-in runner table).
|
|
43
|
+
export { runsACheck } from "./compaction-policy.js";
|
|
38
44
|
// usage — the canonical accounting schema (E2/1.3.0, R4b-1 ruling:
|
|
39
45
|
// additive minor, one function; the signature is frozen the moment this
|
|
40
46
|
// line lands — changing it is a MAJOR ritual). R5b-④a set the final
|
package/dist/lock-adapter.js
CHANGED
|
@@ -183,6 +183,16 @@ function unavailable(err) {
|
|
|
183
183
|
return new LockUnavailableError(`${e.code ?? "?"}: ${e.message}`);
|
|
184
184
|
}
|
|
185
185
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
186
|
+
/**
|
|
187
|
+
* The tokens of the lock handles THIS process holds, or is in the middle of
|
|
188
|
+
* publishing. A lock naming our pid is a same-process writer's residue only
|
|
189
|
+
* when its token is in here; any other token was left by a PREVIOUS process
|
|
190
|
+
* that had the same pid — in a container node is pid 1, so a SIGKILLed
|
|
191
|
+
* container's lock names the next container's pid too. Kept on globalThis so
|
|
192
|
+
* a second copy of this module in the same process shares one answer.
|
|
193
|
+
*/
|
|
194
|
+
const HELD_KEY = Symbol.for("kiso.lock-adapter.held-tokens");
|
|
195
|
+
const held = (globalThis[HELD_KEY] ??= new Set());
|
|
186
196
|
class NativeLockHandle {
|
|
187
197
|
pid;
|
|
188
198
|
token;
|
|
@@ -205,6 +215,7 @@ class NativeLockHandle {
|
|
|
205
215
|
if (this.#released)
|
|
206
216
|
return;
|
|
207
217
|
this.#released = true;
|
|
218
|
+
held.delete(this.token);
|
|
208
219
|
try {
|
|
209
220
|
renameSync(this.#lockPath, this.#tombstone);
|
|
210
221
|
}
|
|
@@ -258,8 +269,20 @@ class NativeLockHandle {
|
|
|
258
269
|
class NativeLock {
|
|
259
270
|
name = "link-lock";
|
|
260
271
|
async acquire(lockPath, sessionId, cancelled) {
|
|
261
|
-
|
|
272
|
+
// Our token is registered before any file can name it; a handle's
|
|
273
|
+
// release drops it, and a failed acquire drops it here.
|
|
262
274
|
const token = randomUUID();
|
|
275
|
+
held.add(token);
|
|
276
|
+
try {
|
|
277
|
+
return await this.#take(lockPath, sessionId, cancelled, token);
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
held.delete(token);
|
|
281
|
+
throw err;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async #take(lockPath, sessionId, cancelled, token) {
|
|
285
|
+
const pid = process.pid;
|
|
263
286
|
// The staging name is PER-ATTEMPT: a later rename-away in the same
|
|
264
287
|
// acquire must never atomically overwrite an earlier attempt's
|
|
265
288
|
// abandoned staging (that would clobber the displacement residue,
|
|
@@ -276,7 +299,11 @@ class NativeLock {
|
|
|
276
299
|
cancelled();
|
|
277
300
|
const staging = `${lockPath}.staging-${pid}-${token}-${attempt}`;
|
|
278
301
|
const seen = readLockIdentity(lockPath);
|
|
279
|
-
|
|
302
|
+
// A lock naming OUR pid with a token this process never issued is a
|
|
303
|
+
// previous process's (a killed container's node was pid 1 too): a
|
|
304
|
+
// dead holder, however alive the pid looks.
|
|
305
|
+
const stalePid = seen !== null && seen.pid === pid && seen.token !== undefined && !held.has(seen.token);
|
|
306
|
+
if (seen === null || seen.pid === undefined || stalePid || !isAlive(seen.pid)) {
|
|
280
307
|
// Fresh path, dead holder, or stale residue — take it over
|
|
281
308
|
// by identity confirmation: rename-away → verify → link.
|
|
282
309
|
if (readyDir !== undefined)
|
|
@@ -366,8 +393,8 @@ class NativeLock {
|
|
|
366
393
|
return new NativeLockHandle(lockPath, tombstone, { pid, token });
|
|
367
394
|
}
|
|
368
395
|
if (seen.pid === pid && seen.token !== undefined) {
|
|
369
|
-
// Same-process writer
|
|
370
|
-
//
|
|
396
|
+
// Same-process writer (round 5): another store in THIS process
|
|
397
|
+
// holds the lock (its token is in `held`) — it will release; retry until
|
|
371
398
|
// it does (never a spurious self-conflict). A legacy
|
|
372
399
|
// bare-pid lock naming our own process is refused like any
|
|
373
400
|
// live foreign owner.
|
package/dist/tasks/journal.d.ts
CHANGED
|
@@ -78,6 +78,15 @@ export type TaskRecord = {
|
|
|
78
78
|
readonly ts: number;
|
|
79
79
|
readonly pids: readonly number[];
|
|
80
80
|
}
|
|
81
|
+
/** An adopted command whose hand-over to the task failed after the task
|
|
82
|
+
* was recorded (its output could not be prepared). The command is
|
|
83
|
+
* running — it was never "not run" — and nothing here owns its end: the
|
|
84
|
+
* verdict is `unknown`, and no terminal is written for it. */
|
|
85
|
+
| {
|
|
86
|
+
readonly type: "handover_failed";
|
|
87
|
+
readonly ts: number;
|
|
88
|
+
readonly reason: string;
|
|
89
|
+
}
|
|
81
90
|
/** `error`: the command never started (no shell, a missing cwd) — then
|
|
82
91
|
* there is no exit code and none is invented. */
|
|
83
92
|
| {
|
package/dist/tasks/journal.js
CHANGED
|
@@ -111,6 +111,11 @@ export function verdictOf(records, runnerAlive, now = Date.now()) {
|
|
|
111
111
|
const planned = records.find((r) => r.type === "planned");
|
|
112
112
|
if (planned === undefined)
|
|
113
113
|
return { kind: "not_run" };
|
|
114
|
+
// a stop that left survivors, or a hand-over that failed, after the command
|
|
115
|
+
// ran: its end is not known, whether or not whoever recorded that is still
|
|
116
|
+
// alive (an adopted task's owner is the kiso itself)
|
|
117
|
+
if (has("command_started") && (has("stop_unconfirmed") || has("handover_failed")))
|
|
118
|
+
return { kind: "unknown" };
|
|
114
119
|
if (has("command_started"))
|
|
115
120
|
return runnerAlive ? { kind: "running", ready: has("ready") } : { kind: "unknown" };
|
|
116
121
|
if (has("runner_started"))
|
package/dist/tasks/manager.d.ts
CHANGED
|
@@ -132,8 +132,13 @@ export interface AdoptedTask {
|
|
|
132
132
|
readonly outputPath: string;
|
|
133
133
|
ready(match: string): void;
|
|
134
134
|
ended(exitCode: number | null, signal: string | null): void;
|
|
135
|
-
/** A stop left these pids alive: no terminal — the task
|
|
135
|
+
/** A stop left these pids alive: no terminal — the task is `unknown`. An
|
|
136
|
+
* empty list means survivors that could not be named. */
|
|
136
137
|
unconfirmed(pids: readonly number[]): void;
|
|
138
|
+
/** The hand-over failed after the task was recorded: the command still
|
|
139
|
+
* runs, under the caller. No terminal — the task is `unknown`, with the
|
|
140
|
+
* reason on record. */
|
|
141
|
+
handoverFailed(reason: string): void;
|
|
137
142
|
}
|
|
138
143
|
/** Who moved a running foreground command to the background (3e). */
|
|
139
144
|
export type DetachBy = "person" | "steer";
|
package/dist/tasks/manager.js
CHANGED
|
@@ -199,6 +199,11 @@ export class TaskManager {
|
|
|
199
199
|
return;
|
|
200
200
|
appendRecord(journal, { type: "stop_unconfirmed", ts: Date.now(), pids });
|
|
201
201
|
},
|
|
202
|
+
handoverFailed: (reason) => {
|
|
203
|
+
if (!this.#local.delete(id))
|
|
204
|
+
return;
|
|
205
|
+
appendRecord(journal, { type: "handover_failed", ts: Date.now(), reason });
|
|
206
|
+
},
|
|
202
207
|
};
|
|
203
208
|
}
|
|
204
209
|
/** Every task of the session, oldest first. */
|
|
@@ -367,9 +372,12 @@ export class TaskManager {
|
|
|
367
372
|
const asked = live.filter((id) => this.stop(id, by));
|
|
368
373
|
const deadline = Date.now() + graceMs;
|
|
369
374
|
let left = asked;
|
|
370
|
-
|
|
375
|
+
// a stop answered "unknown" will not change: not waited for any more, still reported
|
|
376
|
+
let waiting = asked;
|
|
377
|
+
while (waiting.length > 0 && Date.now() < deadline) {
|
|
371
378
|
await new Promise((r) => setTimeout(r, 100));
|
|
372
379
|
left = left.filter((id) => kindOf(id) !== "ended");
|
|
380
|
+
waiting = waiting.filter((id) => kindOf(id) !== "ended" && kindOf(id) !== "unknown");
|
|
373
381
|
}
|
|
374
382
|
return left;
|
|
375
383
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "kiso runtime \u2014 durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"test": "vitest run"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@vincemakes/kiso-core": "0.
|
|
28
|
+
"@vincemakes/kiso-core": "0.52.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
|
-
"@vincemakes/kiso-provider-anthropic": "0.
|
|
32
|
-
"@vincemakes/kiso-provider-openai": "0.
|
|
33
|
-
"@vincemakes/kiso-provider-openai-responses": "0.
|
|
31
|
+
"@vincemakes/kiso-provider-anthropic": "0.52.0",
|
|
32
|
+
"@vincemakes/kiso-provider-openai": "0.52.0",
|
|
33
|
+
"@vincemakes/kiso-provider-openai-responses": "0.52.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@vincemakes/kiso-evals": "0.
|
|
47
|
+
"@vincemakes/kiso-evals": "0.52.0",
|
|
48
48
|
"@types/node": "^26.1.2",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^3.0.0"
|