@vincemakes/kiso-runtime 0.39.1 → 0.40.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/agent.d.ts +14 -0
- package/dist/agent.js +26 -3
- package/dist/compaction-policy.d.ts +82 -0
- package/dist/compaction-policy.js +197 -0
- package/dist/compose.d.ts +5 -0
- package/dist/compose.js +12 -1
- package/dist/context-anchor.d.ts +25 -0
- package/dist/context-anchor.js +46 -0
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +4 -0
- package/dist/profile.d.ts +16 -2
- package/dist/profile.js +42 -3
- package/dist/run.d.ts +1 -1
- package/dist/run.js +35 -9
- package/dist/session-summary.d.ts +87 -0
- package/dist/session-summary.js +166 -0
- package/dist/session.d.ts +79 -4
- package/dist/session.js +283 -20
- package/dist/store.d.ts +8 -0
- package/dist/store.js +27 -2
- package/dist/summarize.d.ts +113 -7
- package/dist/summarize.js +160 -30
- package/package.json +6 -6
package/dist/run.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import { type AgentSession, type SessionConfig } from "./session.js";
|
|
|
13
13
|
export declare class Run implements AsyncIterable<Event> {
|
|
14
14
|
#private;
|
|
15
15
|
runId: string;
|
|
16
|
-
constructor(store: SessionStore, adapter: Adapter, config: SessionConfig, session: AgentSession, input: string | readonly ContentBlock[] | undefined, externalSignal: AbortSignalLike | undefined, resume: boolean, source?: import("@vincemakes/kiso-core").MessageSource);
|
|
16
|
+
constructor(store: SessionStore, adapter: Adapter, config: SessionConfig, session: AgentSession, input: string | readonly ContentBlock[] | undefined, externalSignal: AbortSignalLike | undefined, resume: boolean, source?: import("@vincemakes/kiso-core").MessageSource, via?: import("@vincemakes/kiso-core").UserInputVia);
|
|
17
17
|
/** Cancel the run: propagates to the adapter (SDK) and future executions. */
|
|
18
18
|
abort(): void;
|
|
19
19
|
[Symbol.asyncIterator](): AsyncIterator<Event>;
|
package/dist/run.js
CHANGED
|
@@ -7,7 +7,7 @@ import { denialResult, loop } from "@vincemakes/kiso-core";
|
|
|
7
7
|
import { ABORTED, MergedSignal, abortable, openRunId } from "./recovery.js";
|
|
8
8
|
import { resolveReasoning } from "./provider/metadata.js";
|
|
9
9
|
import { deriveRecoveryPlan, invocationSeqOf } from "./recovery-plan.js";
|
|
10
|
-
import { composeApprovalChain, composeSystemPrompt,
|
|
10
|
+
import { composeApprovalChain, composeSystemPrompt, runBasePrompt } from "./compose.js";
|
|
11
11
|
import { truncationGuard } from "./truncation-guard.js";
|
|
12
12
|
import { DEFAULT_STREAM_IDLE_MS, idleGuard } from "./idle-guard.js";
|
|
13
13
|
import { RequestTracer, traceGuard } from "./trace/guard.js";
|
|
@@ -30,6 +30,7 @@ export class Run {
|
|
|
30
30
|
#input;
|
|
31
31
|
#resume;
|
|
32
32
|
#source;
|
|
33
|
+
#via;
|
|
33
34
|
#abort = new AbortController();
|
|
34
35
|
#externalSignal;
|
|
35
36
|
#decisionIds = [];
|
|
@@ -38,7 +39,10 @@ export class Run {
|
|
|
38
39
|
// TV-1B: durable PROVENANCE for the input (e.g. the verification
|
|
39
40
|
// seed's source:"system") — who produced the line, never a
|
|
40
41
|
// provider-role escalation. Absent = plain user input.
|
|
41
|
-
source
|
|
42
|
+
source,
|
|
43
|
+
// 0.40.0: how a person composed the turn (a skill + the typed line) —
|
|
44
|
+
// recorded for display, never projected into a request.
|
|
45
|
+
via) {
|
|
42
46
|
this.#store = store;
|
|
43
47
|
this.#adapter = adapter;
|
|
44
48
|
this.#config = config;
|
|
@@ -47,6 +51,7 @@ export class Run {
|
|
|
47
51
|
this.#externalSignal = externalSignal;
|
|
48
52
|
this.#resume = resume;
|
|
49
53
|
this.#source = source;
|
|
54
|
+
this.#via = via;
|
|
50
55
|
this.runId = crypto.randomUUID();
|
|
51
56
|
}
|
|
52
57
|
/** Cancel the run: propagates to the adapter (SDK) and future executions. */
|
|
@@ -77,10 +82,7 @@ export class Run {
|
|
|
77
82
|
// session's base prompt and the extension appends: generated
|
|
78
83
|
// machinery never outranks the deliberate extension text (the E2
|
|
79
84
|
// "append lands at the END" contract holds). "" when empty.
|
|
80
|
-
const
|
|
81
|
-
const basePrompt = toolTable === "" ? this.#config.systemPrompt
|
|
82
|
-
: this.#config.systemPrompt === undefined ? toolTable
|
|
83
|
-
: `${this.#config.systemPrompt}\n\n${toolTable}`;
|
|
85
|
+
const basePrompt = runBasePrompt(this.#config.systemPrompt, this.#config.registry);
|
|
84
86
|
// E3 — the ledger's parts: the base as CONFIGURED (what the CLI
|
|
85
87
|
// handed the runtime — the tool table is generated machinery, R3)
|
|
86
88
|
// and the extension appends in load order (R4 attribution). The
|
|
@@ -113,12 +115,30 @@ export class Run {
|
|
|
113
115
|
// extension providing a compaction config supplies it. E6: the
|
|
114
116
|
// contextPolicy override beats the session's own, and its minTurns
|
|
115
117
|
// no-fire guard may omit the config below the floor.
|
|
116
|
-
const microcompact = microcompactFor(this.#config, log.all);
|
|
117
118
|
// E2: the session's own systemPrompt first, then every extension
|
|
118
119
|
// append in LOAD order — deterministic (same extensions → same
|
|
119
120
|
// prompt); no appends → byte-identical to the extension-less run.
|
|
120
121
|
const systemPrompt = composeSystemPrompt(basePrompt, this.#config.extensions ?? []);
|
|
121
122
|
const approvalChain = composeApprovalChain(this.#config.extensions ?? []);
|
|
123
|
+
// ADR-0055 Amendment 1 (A1b): the compaction point the kernel asks
|
|
124
|
+
// before every request — the run's own prefix, so an in-band
|
|
125
|
+
// summary reads it at the cache-hit price.
|
|
126
|
+
// The loop resolves the same setting when it builds each request; a
|
|
127
|
+
// setting that cannot run refuses the run there, before any compaction.
|
|
128
|
+
let wireReasoning;
|
|
129
|
+
try {
|
|
130
|
+
wireReasoning = this.#config.reasoning !== undefined ? resolveReasoningOrThrow(this.#config.model, this.#config.reasoning, this.#config.baseUrl).reasoning : undefined;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
wireReasoning = undefined;
|
|
134
|
+
}
|
|
135
|
+
const compact = this.#session.compactionPoint({
|
|
136
|
+
...(systemPrompt !== undefined ? { systemPrompt } : {}),
|
|
137
|
+
tools: () => this.#config.registry.snapshot().specs,
|
|
138
|
+
...(wireReasoning !== undefined ? { reasoning: wireReasoning } : {}),
|
|
139
|
+
signal,
|
|
140
|
+
...(this.#config.maxRetries !== undefined ? { maxRetries: this.#config.maxRetries } : {}),
|
|
141
|
+
});
|
|
122
142
|
const loopConfig = () => ({
|
|
123
143
|
// 0.1.40 (R-C item 3): the truncation guard gates the model
|
|
124
144
|
// stream — a truncated turn's tool batch never executes.
|
|
@@ -134,7 +154,7 @@ export class Run {
|
|
|
134
154
|
...(this.#config.maxTurns !== undefined ? { maxTurns: this.#config.maxTurns } : {}),
|
|
135
155
|
...(this.#config.maxTokens !== undefined ? { maxTokens: this.#config.maxTokens } : {}),
|
|
136
156
|
...(this.#config.temperature !== undefined ? { temperature: this.#config.temperature } : {}),
|
|
137
|
-
...(
|
|
157
|
+
...(compact !== undefined ? { compact } : {}),
|
|
138
158
|
...(this.#config.maxRetries !== undefined ? { maxRetries: this.#config.maxRetries } : {}),
|
|
139
159
|
// MG-1 (A5): the kernel stamps committed envelopes with this.
|
|
140
160
|
...(this.#config.continuationScope !== undefined ? { continuationScope: this.#config.continuationScope } : {}),
|
|
@@ -265,8 +285,11 @@ export class Run {
|
|
|
265
285
|
// session. The prompt is also the first event the consumer
|
|
266
286
|
// sees, so what was asked and what happened live in the same
|
|
267
287
|
// stream.
|
|
268
|
-
const inputEvent = log.append({ type: "user_input", content: this.#input, ...(this.#source !== undefined ? { source: this.#source } : {}) });
|
|
288
|
+
const inputEvent = log.append({ type: "user_input", content: this.#input, ...(this.#source !== undefined ? { source: this.#source } : {}), ...(this.#via !== undefined ? { via: this.#via } : {}) });
|
|
269
289
|
await this.#session.persist(this.runId, inputEvent);
|
|
290
|
+
// 0.40.0 dogfood: the list's row says "open" from here — a run
|
|
291
|
+
// killed mid-way reads as interrupted, which is the truth
|
|
292
|
+
this.#session.recordSummary(true);
|
|
270
293
|
yield inputEvent;
|
|
271
294
|
// 2. The loop projects from the session log — multi-turn context
|
|
272
295
|
// is the projection, not a second copy.
|
|
@@ -285,6 +308,9 @@ export class Run {
|
|
|
285
308
|
// the flush itself failed (poisoned session) — the error
|
|
286
309
|
// already poisoned everything; nothing more can be done.
|
|
287
310
|
}
|
|
311
|
+
// 0.40.0 dogfood: the row at the run's end — its terminal, or the
|
|
312
|
+
// pause it stopped at (pending asks counted); best-effort
|
|
313
|
+
this.#session.recordSummary(this.#session.log.all.at(-1)?.type !== "terminal");
|
|
288
314
|
// The run is over (or abandoned): its unanswered approvals must
|
|
289
315
|
// fall back to the direct-persist path, so a late approve() is
|
|
290
316
|
// still durable.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 0.40.0 dogfood (item 2) — a session's SUMMARY, the one thing the
|
|
3
|
+
* session list reads.
|
|
4
|
+
*
|
|
5
|
+
* The list used to read every session's whole event log, three times, to
|
|
6
|
+
* draw one row each: on the owner's machine that is 118 sessions, 584 MB,
|
|
7
|
+
* 3.58M events, about 8.3 s before the picker appears. The sidecar the
|
|
8
|
+
* session already keeps (`<id>.meta.json`) is read in 2 ms for all 118. So
|
|
9
|
+
* the list reads the sidecar ONLY, and the sidecar carries what the row
|
|
10
|
+
* shows, as its own tenant beside the execution profile:
|
|
11
|
+
*
|
|
12
|
+
* title, turns, last activity, state (open or the terminal's outcome),
|
|
13
|
+
* uncertain executions, pending asks, whether the workspace is unknown,
|
|
14
|
+
* and where the summary came from (a run, or the one-time migration).
|
|
15
|
+
*
|
|
16
|
+
* The runtime writes it at a run's start (state open — a run killed there
|
|
17
|
+
* reads "interrupted" in the list, which is the truth) and at its end. It
|
|
18
|
+
* is OBSERVATION: nothing in recovery, projection or a request reads it
|
|
19
|
+
* (the §6 purity rule), and a failed write never fails a run.
|
|
20
|
+
*
|
|
21
|
+
* `sessionSummary` is a pure function of the durable records, shared by the
|
|
22
|
+
* live writes and the one-time migration so the two can never disagree.
|
|
23
|
+
*/
|
|
24
|
+
import type { Event } from "@vincemakes/kiso-core";
|
|
25
|
+
import { type StoreRecord } from "./store.js";
|
|
26
|
+
export interface SessionSummary {
|
|
27
|
+
/** the session's first substantive prompt (`sessionTitle`); null when unknown */
|
|
28
|
+
readonly title: string | null;
|
|
29
|
+
/** user turns; null when unknown (an unreadable log) */
|
|
30
|
+
readonly turns: number | null;
|
|
31
|
+
/** ms since the epoch — the last record's stamp, or the file's mtime */
|
|
32
|
+
readonly updatedAt: number;
|
|
33
|
+
/** "open" while a run has no terminal event; else the terminal's outcome kind */
|
|
34
|
+
readonly state: string | null;
|
|
35
|
+
readonly uncertain: number;
|
|
36
|
+
readonly asks: number;
|
|
37
|
+
/** the profile records no workspace (a session from before 0.40.0) */
|
|
38
|
+
readonly workspaceUnknown: boolean;
|
|
39
|
+
/** "run" — written by a run; "migration" — the one-time pass over a legacy log */
|
|
40
|
+
readonly source: "run" | "migration";
|
|
41
|
+
}
|
|
42
|
+
/** The summary of a session's events — the core both writers share.
|
|
43
|
+
* `open`, `updatedAt` and `asks` come from whoever knows them: a live
|
|
44
|
+
* session knows its own run and its resolvers; the migration derives
|
|
45
|
+
* them from the records. */
|
|
46
|
+
export declare function summarizeEvents(events: readonly Event[], opts: {
|
|
47
|
+
open: boolean;
|
|
48
|
+
updatedAt: number;
|
|
49
|
+
asks: number;
|
|
50
|
+
workspaceUnknown: boolean;
|
|
51
|
+
source: "run" | "migration";
|
|
52
|
+
}): SessionSummary;
|
|
53
|
+
/** The summary of a session's durable RECORDS — the one-time migration's
|
|
54
|
+
* input (a legacy log read exactly once, never through a live session). */
|
|
55
|
+
export declare function sessionSummary(records: readonly StoreRecord[], opts: {
|
|
56
|
+
workspaceUnknown: boolean;
|
|
57
|
+
source: "run" | "migration";
|
|
58
|
+
fallbackUpdatedAt?: number;
|
|
59
|
+
}): SessionSummary;
|
|
60
|
+
/** The migration's marker: written once every legacy session has a
|
|
61
|
+
* summary. From then on the list never reads a log (lead's ruling A): a
|
|
62
|
+
* session still without a summary shows "no summary". */
|
|
63
|
+
export declare const SUMMARY_MIGRATION_MARKER = ".summaries-migrated";
|
|
64
|
+
export interface SessionListing {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
/** the log file's mtime — the last-activity fallback, never a guess at content */
|
|
67
|
+
readonly mtime: number;
|
|
68
|
+
readonly summary: SessionSummary | null;
|
|
69
|
+
readonly workspace: string | null;
|
|
70
|
+
readonly profileName: string | null;
|
|
71
|
+
}
|
|
72
|
+
/** Every session in `root`, from the DIRECTORY and the sidecars only — no
|
|
73
|
+
* log is opened. The 0.40.0 dogfood's list path. */
|
|
74
|
+
export declare function listSessionSidecars(root: string): SessionListing[];
|
|
75
|
+
/** Whether the one-time migration still has work: the marker is absent. */
|
|
76
|
+
export declare function summaryMigrationPending(root: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The ONE-TIME migration (lead's ruling A). Each session without a summary
|
|
79
|
+
* has its log read exactly once — through the store's plain loader, never a
|
|
80
|
+
* live session, so the profile contract cannot throw it — and its summary
|
|
81
|
+
* written with `source: "migration"`. A log that cannot be read gets what
|
|
82
|
+
* CAN be said (last activity from the file's mtime; title and turns
|
|
83
|
+
* unknown) and never throws the list. Resumable: each summary is written on
|
|
84
|
+
* its own, so a crash mid-way leaves the done ones done; the marker lands
|
|
85
|
+
* only when every session has one.
|
|
86
|
+
*/
|
|
87
|
+
export declare function migrateSummaries(root: string, load: (id: string) => readonly StoreRecord[], onEach?: (done: number, total: number) => void): number;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 0.40.0 dogfood (item 2) — a session's SUMMARY, the one thing the
|
|
3
|
+
* session list reads.
|
|
4
|
+
*
|
|
5
|
+
* The list used to read every session's whole event log, three times, to
|
|
6
|
+
* draw one row each: on the owner's machine that is 118 sessions, 584 MB,
|
|
7
|
+
* 3.58M events, about 8.3 s before the picker appears. The sidecar the
|
|
8
|
+
* session already keeps (`<id>.meta.json`) is read in 2 ms for all 118. So
|
|
9
|
+
* the list reads the sidecar ONLY, and the sidecar carries what the row
|
|
10
|
+
* shows, as its own tenant beside the execution profile:
|
|
11
|
+
*
|
|
12
|
+
* title, turns, last activity, state (open or the terminal's outcome),
|
|
13
|
+
* uncertain executions, pending asks, whether the workspace is unknown,
|
|
14
|
+
* and where the summary came from (a run, or the one-time migration).
|
|
15
|
+
*
|
|
16
|
+
* The runtime writes it at a run's start (state open — a run killed there
|
|
17
|
+
* reads "interrupted" in the list, which is the truth) and at its end. It
|
|
18
|
+
* is OBSERVATION: nothing in recovery, projection or a request reads it
|
|
19
|
+
* (the §6 purity rule), and a failed write never fails a run.
|
|
20
|
+
*
|
|
21
|
+
* `sessionSummary` is a pure function of the durable records, shared by the
|
|
22
|
+
* live writes and the one-time migration so the two can never disagree.
|
|
23
|
+
*/
|
|
24
|
+
import { existsSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { readProfile, readSummary, writeSummary } from "./profile.js";
|
|
27
|
+
import { executionLedger } from "./ledger.js";
|
|
28
|
+
import { openRunId } from "./recovery.js";
|
|
29
|
+
import { sessionTitle } from "./store.js";
|
|
30
|
+
/** Pending asks from the records alone: a permission request of the OPEN
|
|
31
|
+
* run with no decision and no expiry — the session's own rule, restated
|
|
32
|
+
* over records so the migration needs no live session. */
|
|
33
|
+
function pendingAsks(records) {
|
|
34
|
+
const open = openRunId(records);
|
|
35
|
+
if (open === undefined)
|
|
36
|
+
return 0;
|
|
37
|
+
const decided = new Set();
|
|
38
|
+
const requested = new Set();
|
|
39
|
+
for (const r of records) {
|
|
40
|
+
const e = r.event;
|
|
41
|
+
if (e.type === "permission_requested" && r.runId === open && typeof e.decisionId === "string")
|
|
42
|
+
requested.add(e.decisionId);
|
|
43
|
+
else if ((e.type === "permission_decided" || e.type === "permission_expired") && typeof e.decisionId === "string")
|
|
44
|
+
decided.add(e.decisionId);
|
|
45
|
+
}
|
|
46
|
+
let n = 0;
|
|
47
|
+
for (const id of requested)
|
|
48
|
+
if (!decided.has(id))
|
|
49
|
+
n += 1;
|
|
50
|
+
return n;
|
|
51
|
+
}
|
|
52
|
+
/** The summary of a session's events — the core both writers share.
|
|
53
|
+
* `open`, `updatedAt` and `asks` come from whoever knows them: a live
|
|
54
|
+
* session knows its own run and its resolvers; the migration derives
|
|
55
|
+
* them from the records. */
|
|
56
|
+
export function summarizeEvents(events, opts) {
|
|
57
|
+
let state = opts.open ? "open" : null;
|
|
58
|
+
if (!opts.open) {
|
|
59
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
60
|
+
const e = events[i];
|
|
61
|
+
if (e.type === "terminal") {
|
|
62
|
+
state = e.outcome.kind;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const title = sessionTitle(events.map((event) => ({ runId: "", ts: 0, event })));
|
|
68
|
+
return {
|
|
69
|
+
title: title === "(no prompt)" ? null : title,
|
|
70
|
+
turns: events.filter((e) => e.type === "user_input").length,
|
|
71
|
+
updatedAt: opts.updatedAt,
|
|
72
|
+
state,
|
|
73
|
+
uncertain: [...executionLedger(events).values()].filter((x) => x.status === "uncertain").length,
|
|
74
|
+
asks: opts.asks,
|
|
75
|
+
workspaceUnknown: opts.workspaceUnknown,
|
|
76
|
+
source: opts.source,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** The summary of a session's durable RECORDS — the one-time migration's
|
|
80
|
+
* input (a legacy log read exactly once, never through a live session). */
|
|
81
|
+
export function sessionSummary(records, opts) {
|
|
82
|
+
return summarizeEvents(records.map((r) => r.event), {
|
|
83
|
+
open: openRunId(records) !== undefined,
|
|
84
|
+
updatedAt: records.at(-1)?.ts ?? opts.fallbackUpdatedAt ?? 0,
|
|
85
|
+
asks: pendingAsks(records),
|
|
86
|
+
workspaceUnknown: opts.workspaceUnknown,
|
|
87
|
+
source: opts.source,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// ── the list's read path, and the one-time migration ───────────────────
|
|
91
|
+
/** The migration's marker: written once every legacy session has a
|
|
92
|
+
* summary. From then on the list never reads a log (lead's ruling A): a
|
|
93
|
+
* session still without a summary shows "no summary". */
|
|
94
|
+
export const SUMMARY_MIGRATION_MARKER = ".summaries-migrated";
|
|
95
|
+
/** Every session in `root`, from the DIRECTORY and the sidecars only — no
|
|
96
|
+
* log is opened. The 0.40.0 dogfood's list path. */
|
|
97
|
+
export function listSessionSidecars(root) {
|
|
98
|
+
let entries;
|
|
99
|
+
try {
|
|
100
|
+
entries = readdirSync(root);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const entry of entries.sort()) {
|
|
107
|
+
if (!entry.endsWith(".jsonl"))
|
|
108
|
+
continue;
|
|
109
|
+
const id = entry.slice(0, -".jsonl".length);
|
|
110
|
+
let mtime = 0;
|
|
111
|
+
try {
|
|
112
|
+
const st = statSync(join(root, entry));
|
|
113
|
+
if (st.size === 0)
|
|
114
|
+
continue; // an empty log is no session (the old list skipped it too)
|
|
115
|
+
mtime = st.mtimeMs;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const profile = readProfile(root, id);
|
|
121
|
+
out.push({
|
|
122
|
+
id,
|
|
123
|
+
mtime,
|
|
124
|
+
summary: readSummary(root, id),
|
|
125
|
+
workspace: profile.kind === "ok" ? profile.profile.workspace : null,
|
|
126
|
+
profileName: profile.kind === "ok" ? profile.profile.profileName : null,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/** Whether the one-time migration still has work: the marker is absent. */
|
|
132
|
+
export function summaryMigrationPending(root) {
|
|
133
|
+
return !existsSync(join(root, SUMMARY_MIGRATION_MARKER));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The ONE-TIME migration (lead's ruling A). Each session without a summary
|
|
137
|
+
* has its log read exactly once — through the store's plain loader, never a
|
|
138
|
+
* live session, so the profile contract cannot throw it — and its summary
|
|
139
|
+
* written with `source: "migration"`. A log that cannot be read gets what
|
|
140
|
+
* CAN be said (last activity from the file's mtime; title and turns
|
|
141
|
+
* unknown) and never throws the list. Resumable: each summary is written on
|
|
142
|
+
* its own, so a crash mid-way leaves the done ones done; the marker lands
|
|
143
|
+
* only when every session has one.
|
|
144
|
+
*/
|
|
145
|
+
export function migrateSummaries(root, load, onEach) {
|
|
146
|
+
const todo = listSessionSidecars(root).filter((s) => s.summary === null);
|
|
147
|
+
todo.forEach((s, i) => {
|
|
148
|
+
let summary;
|
|
149
|
+
try {
|
|
150
|
+
summary = sessionSummary(load(s.id), { workspaceUnknown: s.workspace === null, source: "migration", fallbackUpdatedAt: s.mtime });
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
summary = { title: null, turns: null, updatedAt: s.mtime, state: null, uncertain: 0, asks: 0, workspaceUnknown: s.workspace === null, source: "migration" };
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
writeSummary(root, s.id, summary);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// a sidecar that cannot be written leaves this session without a
|
|
160
|
+
// summary; the list says so, and the marker still lands below
|
|
161
|
+
}
|
|
162
|
+
onEach?.(i + 1, todo.length);
|
|
163
|
+
});
|
|
164
|
+
writeFileSync(join(root, SUMMARY_MIGRATION_MARKER), `${new Date().toISOString()}\n`);
|
|
165
|
+
return todo.length;
|
|
166
|
+
}
|
package/dist/session.d.ts
CHANGED
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
* support in recovery.ts, the E1/E2 composition helpers in compose.ts —
|
|
28
28
|
* same package, same exports (index.ts re-exports all four).
|
|
29
29
|
*/
|
|
30
|
-
import { EventLog, type AbortSignalLike, type Adapter, type Event, type KisoExtension, type Message, type PermissionDecision, type Tool } from "@vincemakes/kiso-core";
|
|
30
|
+
import { EventLog, type AbortSignalLike, type Adapter, type Event, type KisoExtension, type Message, type PermissionDecision, type Tool, type ToolSpec, type EventInput } from "@vincemakes/kiso-core";
|
|
31
31
|
import { type TaskAssessment } from "./task-assessment.js";
|
|
32
32
|
import { type ContinuationScope } from "@vincemakes/kiso-core";
|
|
33
|
-
import type
|
|
33
|
+
import { type ReasoningSetting } from "./provider/metadata.js";
|
|
34
34
|
import { type SessionStore } from "./store.js";
|
|
35
35
|
import { Run } from "./run.js";
|
|
36
36
|
/** TUI2-R3v2 ③ — one off-trajectory model request (session.sideQuery).
|
|
@@ -89,15 +89,21 @@ export interface CompactInfo {
|
|
|
89
89
|
/** The covered content's estimated tokens (the chars/4 proxy). */
|
|
90
90
|
readonly tokens: number;
|
|
91
91
|
}
|
|
92
|
-
/** @deprecated the canonical name is `Session` (root export, 1.1.0); this alias is removed in the next major. */
|
|
93
92
|
export declare class AgentSession {
|
|
94
93
|
#private;
|
|
95
94
|
readonly id: string;
|
|
96
95
|
readonly log: EventLog;
|
|
96
|
+
/** 0.40.0: what this open's drift acknowledgement replaced — the CLI
|
|
97
|
+
* says so once. Null when the session opened without material drift. */
|
|
98
|
+
readonly driftAcknowledgement: import("./agent.js").DriftAcknowledgement | null;
|
|
99
|
+
/** 0.40.0: when the provider last billed this session (epoch ms) — the
|
|
100
|
+
* record's time on load, the write's time after. A provider's prompt
|
|
101
|
+
* cache expires with time, so an old bill means a cold prefix. */
|
|
102
|
+
get lastUsageAt(): number | undefined;
|
|
97
103
|
/** Permanently invalidate the session after a rejected disk write (round 1). */
|
|
98
104
|
poison(reason: string): void;
|
|
99
105
|
ensureHealthy(): void;
|
|
100
|
-
constructor(id: string, log: EventLog, store: SessionStore, adapter: Adapter, config: SessionConfig);
|
|
106
|
+
constructor(id: string, log: EventLog, store: SessionStore, adapter: Adapter, config: SessionConfig, lastUsageAt?: number);
|
|
101
107
|
/** Write-ahead through the store; a rejected write POISONS the session
|
|
102
108
|
* (round 1/round 4): the in-memory log no longer matches the disk — whatever
|
|
103
109
|
* the cause (stale handle, corruption, a live external writer, an I/O
|
|
@@ -108,6 +114,37 @@ export declare class AgentSession {
|
|
|
108
114
|
endRun(run: Run): void;
|
|
109
115
|
/** The conversation so far, as the model sees it. */
|
|
110
116
|
projected(): readonly Message[];
|
|
117
|
+
/** 0.40.0: the context as the last bill measured it, plus what the log
|
|
118
|
+
* appended since — or undefined when no bill describes it any more
|
|
119
|
+
* (context-anchor.ts). `events` defaults to the session's log. */
|
|
120
|
+
contextAnchor(events?: readonly Event[]): number | undefined;
|
|
121
|
+
/** 0.40.0: what the context holds — the anchored figure, or the estimate
|
|
122
|
+
* over the projection when no bill describes it. The thresholds and the
|
|
123
|
+
* ctx row read this, never the bare estimate. */
|
|
124
|
+
contextUsed(): number;
|
|
125
|
+
/**
|
|
126
|
+
* ADR-0055 Amendment 1 (A1b) — the kernel's compaction point for ONE run,
|
|
127
|
+
* or undefined when nothing in-run is armed (the kernel then never asks).
|
|
128
|
+
*
|
|
129
|
+
* With `contextPolicy.tiers`: the §1a figure against the tiers — soft
|
|
130
|
+
* waits for a phase end (A3), hard and emergency fire now, an overflow
|
|
131
|
+
* fires once. The summary is in-band (A2) with ONE fallback to the
|
|
132
|
+
* serialised form; if both fail, an emergency or an overflow may prune
|
|
133
|
+
* once (A4b), and otherwise nothing happens and the run goes on.
|
|
134
|
+
*
|
|
135
|
+
* Without tiers, an EXPLICITLY configured `microcompact` keeps its old
|
|
136
|
+
* standing prune here, for SDK callers — the kernel no longer has one.
|
|
137
|
+
*/
|
|
138
|
+
compactionPoint(run: {
|
|
139
|
+
readonly systemPrompt?: string;
|
|
140
|
+
readonly tools: () => readonly ToolSpec[];
|
|
141
|
+
readonly reasoning?: {
|
|
142
|
+
readonly thinking?: "adaptive" | "enabled" | "disabled";
|
|
143
|
+
readonly effort?: string;
|
|
144
|
+
};
|
|
145
|
+
readonly signal?: AbortSignalLike;
|
|
146
|
+
readonly maxRetries?: number;
|
|
147
|
+
}): ((events: readonly Event[], messages: readonly Message[], why: "request" | "overflow") => Promise<readonly EventInput[]>) | undefined;
|
|
111
148
|
/**
|
|
112
149
|
* merge round B (/model): replace the adapter for SUBSEQUENT runs. The
|
|
113
150
|
* kernel reads the adapter through the loop-config closure at each
|
|
@@ -141,12 +178,18 @@ export declare class AgentSession {
|
|
|
141
178
|
/** XP-1: the reasoning axes travel with the binding too; absent =
|
|
142
179
|
* fresh defaults (a new binding never inherits stale effort). */
|
|
143
180
|
readonly reasoning?: ReasoningSetting;
|
|
181
|
+
/** 0.40.0: the config profile that named this binding; absent = none
|
|
182
|
+
* (a direct provider/model, or an SDK caller) — recorded as null. */
|
|
183
|
+
readonly profileName?: string | null;
|
|
144
184
|
/** CTX-1: the compaction threshold follows the live model's window.
|
|
145
185
|
* Absent KEEPS the current one — a caller that does not know the new
|
|
146
186
|
* model's window must not silently reset the policy to nothing. */
|
|
147
187
|
readonly microcompact?: {
|
|
148
188
|
readonly thresholdTokens: number;
|
|
149
189
|
};
|
|
190
|
+
/** A1b: the new model's window, for the in-run tiers. Absent keeps the
|
|
191
|
+
* current one, as `microcompact` does. */
|
|
192
|
+
readonly contextWindow?: number;
|
|
150
193
|
}): void;
|
|
151
194
|
/**
|
|
152
195
|
* CTX-1: the compaction threshold alone, with NO profile revision.
|
|
@@ -165,6 +208,9 @@ export declare class AgentSession {
|
|
|
165
208
|
* every open.
|
|
166
209
|
*/
|
|
167
210
|
setMicrocompactThreshold(thresholdTokens: number): void;
|
|
211
|
+
/** A1b: the window the in-run tiers are drawn from, for a restored or
|
|
212
|
+
* re-bound session (the CLI's one binding step calls both). */
|
|
213
|
+
setContextWindow(windowTokens: number): void;
|
|
168
214
|
/** E2: the adapter identity (anthropic / openai-compat / openai-responses) — the route
|
|
169
215
|
* key the canonical consumer (CLI usage, the trace block) keys on. The
|
|
170
216
|
* per-run tracer reads the SAME live binding; one source, one
|
|
@@ -191,6 +237,15 @@ export declare class AgentSession {
|
|
|
191
237
|
};
|
|
192
238
|
/** XP-1: the selected reasoning axes (resolution happens per request). */
|
|
193
239
|
get reasoning(): ReasoningSetting;
|
|
240
|
+
/**
|
|
241
|
+
* The 0.40.0 dogfood (item 2): the session list's row, written into the
|
|
242
|
+
* sidecar's `summary` tenant at a run's start (`open`) and at its end.
|
|
243
|
+
* Computed from the IN-MEMORY log — never a re-read of the file — and
|
|
244
|
+
* best-effort: a summary that cannot be written leaves the list saying
|
|
245
|
+
* less, and never fails the run. Observation only: nothing that decides
|
|
246
|
+
* recovery, projection or a request reads it.
|
|
247
|
+
*/
|
|
248
|
+
recordSummary(open: boolean): void;
|
|
194
249
|
/**
|
|
195
250
|
* TUI2-R3v2 ③ — ONE model request that belongs to no run (the
|
|
196
251
|
* safer-options seam, adjudicated 2026-08-18).
|
|
@@ -239,6 +294,7 @@ export declare class AgentSession {
|
|
|
239
294
|
run(input: string | readonly import("@vincemakes/kiso-core").ContentBlock[], options?: {
|
|
240
295
|
signal?: AbortSignalLike;
|
|
241
296
|
source?: import("@vincemakes/kiso-core").MessageSource;
|
|
297
|
+
via?: import("@vincemakes/kiso-core").UserInputVia;
|
|
242
298
|
}): Run;
|
|
243
299
|
/**
|
|
244
300
|
* Continue the interrupted run (Area 2): apply durable decisions,
|
|
@@ -265,6 +321,8 @@ export declare class AgentSession {
|
|
|
265
321
|
onStart?: (info: CompactInfo) => void;
|
|
266
322
|
drop?: boolean;
|
|
267
323
|
focus?: string;
|
|
324
|
+
manualBudget?: boolean;
|
|
325
|
+
onProgress?: (progress: import("./summarize.js").SummaryProgress) => void;
|
|
268
326
|
}): Promise<SummarizeResult | null>;
|
|
269
327
|
/**
|
|
270
328
|
* E6 — the run-start context policy (candidate A + the crux drop arm).
|
|
@@ -404,6 +462,19 @@ export interface ContextPolicy {
|
|
|
404
462
|
readonly keepResults?: number;
|
|
405
463
|
readonly minTurns?: number;
|
|
406
464
|
};
|
|
465
|
+
/**
|
|
466
|
+
* ADR-0055 Amendment 1 (A1b) — the IN-RUN tiers, drawn from the window:
|
|
467
|
+
* soft `min(0.5·w, 400K)` waits for a phase end, hard `min(0.8·w, 700K)`
|
|
468
|
+
* and emergency `w − reserve` fire at the next request, an overflow once.
|
|
469
|
+
* Present, the kernel asks before every request; the run-start summary
|
|
470
|
+
* and the standing microcompact above do not apply. `isCheck` is the
|
|
471
|
+
* phase detector's rule 1 (default: the runner table).
|
|
472
|
+
*/
|
|
473
|
+
readonly tiers?: {
|
|
474
|
+
readonly windowTokens: number;
|
|
475
|
+
readonly isCheck?: (command: string) => boolean;
|
|
476
|
+
readonly maxFailures?: number;
|
|
477
|
+
};
|
|
407
478
|
}
|
|
408
479
|
export interface SessionConfig {
|
|
409
480
|
readonly model: string;
|
|
@@ -423,6 +494,10 @@ export interface SessionConfig {
|
|
|
423
494
|
readonly reasoning?: import("./provider/metadata.js").ReasoningSetting;
|
|
424
495
|
/** XP-1 internal: a legacy session's deferred revision-1 write. */
|
|
425
496
|
readonly profilePending?: true;
|
|
497
|
+
/** 0.40.0: the config profile name in force — recorded per revision. */
|
|
498
|
+
readonly profileName?: string;
|
|
499
|
+
/** 0.40.0: set when this open acknowledged material drift. */
|
|
500
|
+
readonly driftAcknowledgement?: import("./agent.js").DriftAcknowledgement;
|
|
426
501
|
readonly systemPrompt?: string;
|
|
427
502
|
readonly tools?: readonly Tool<any>[];
|
|
428
503
|
readonly registry: import("@vincemakes/kiso-core").ToolRegistry;
|