@vincemakes/kiso-runtime 0.1.19 → 0.1.21
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 +6 -0
- package/dist/compose.d.ts +32 -0
- package/dist/compose.js +117 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/recovery.d.ts +28 -0
- package/dist/recovery.js +68 -0
- package/dist/run.d.ts +20 -0
- package/dist/run.js +463 -0
- package/dist/session.d.ts +32 -13
- package/dist/session.js +45 -631
- package/package.json +5 -5
package/dist/agent.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface AgentDefinition {
|
|
|
42
42
|
readonly maxTurns?: number;
|
|
43
43
|
readonly maxTokens?: number;
|
|
44
44
|
readonly temperature?: number;
|
|
45
|
+
/**
|
|
46
|
+
* DEPRECATED (ADR-0044): the classic auto-compaction path is retired —
|
|
47
|
+
* the loop ignores this (microcompact absorbed the responsibility; old
|
|
48
|
+
* sessions' `compacted` events still replay). Kept so old definitions
|
|
49
|
+
* type-check; removed at 1.0.
|
|
50
|
+
*/
|
|
45
51
|
readonly compaction?: {
|
|
46
52
|
readonly thresholdTokens: number;
|
|
47
53
|
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手感批 B4 (pure move) — the E1/E2 composition helpers, moved verbatim
|
|
3
|
+
* from session.ts: the extension system-prompt appends, the extension
|
|
4
|
+
* hook composition (既有先行), and the loop's microcompact config lookup.
|
|
5
|
+
*/
|
|
6
|
+
import type { HookHost, KisoExtension } from "@vincemakes/kiso-core";
|
|
7
|
+
import type { SessionConfig } from "./session.js";
|
|
8
|
+
/**
|
|
9
|
+
* E2: the session's systemPrompt plus every extension's append, in LOAD
|
|
10
|
+
* order, \n\n-joined — deterministic (same extension list → same prompt).
|
|
11
|
+
* No appends → the base passes through byte-identical.
|
|
12
|
+
*/
|
|
13
|
+
export declare function composeSystemPrompt(base: string | undefined, extensions: readonly KisoExtension[]): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* E1: extension hooks compose AFTER the agent's own (既有先行 — the existing
|
|
16
|
+
* hook sees every event first). Observers all run, in order; onUserMessage
|
|
17
|
+
* and onPreTool — the FIRST decisive answer wins (the existing hook
|
|
18
|
+
* outranks extensions; defers fall through); onPostTool folds — each
|
|
19
|
+
* transforms the previous result. Returns the existing host unchanged when
|
|
20
|
+
* no extension provides hooks.
|
|
21
|
+
*/
|
|
22
|
+
export declare function composeHooks(existing: HookHost | undefined, extensions: readonly KisoExtension[]): HookHost | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* E2: the loop's microcompact config — the session's own microcompact wins;
|
|
25
|
+
* otherwise the FIRST extension providing a compaction config supplies it.
|
|
26
|
+
* An extension config without a threshold contributes nothing (a boundary
|
|
27
|
+
* needs a threshold to ever fire).
|
|
28
|
+
*/
|
|
29
|
+
export declare function microcompactFor(config: SessionConfig): {
|
|
30
|
+
readonly thresholdTokens: number;
|
|
31
|
+
readonly keepResults?: number;
|
|
32
|
+
} | undefined;
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手感批 B4 (pure move) — the E1/E2 composition helpers, moved verbatim
|
|
3
|
+
* from session.ts: the extension system-prompt appends, the extension
|
|
4
|
+
* hook composition (既有先行), and the loop's microcompact config lookup.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* E2: the session's systemPrompt plus every extension's append, in LOAD
|
|
8
|
+
* order, \n\n-joined — deterministic (same extension list → same prompt).
|
|
9
|
+
* No appends → the base passes through byte-identical.
|
|
10
|
+
*/
|
|
11
|
+
export function composeSystemPrompt(base, extensions) {
|
|
12
|
+
const appends = extensions.flatMap((e) => (e.systemPrompt?.append === undefined ? [] : [e.systemPrompt.append]));
|
|
13
|
+
if (appends.length === 0)
|
|
14
|
+
return base;
|
|
15
|
+
return base === undefined ? appends.join("\n\n") : `${base}\n\n${appends.join("\n\n")}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* E1: extension hooks compose AFTER the agent's own (既有先行 — the existing
|
|
19
|
+
* hook sees every event first). Observers all run, in order; onUserMessage
|
|
20
|
+
* and onPreTool — the FIRST decisive answer wins (the existing hook
|
|
21
|
+
* outranks extensions; defers fall through); onPostTool folds — each
|
|
22
|
+
* transforms the previous result. Returns the existing host unchanged when
|
|
23
|
+
* no extension provides hooks.
|
|
24
|
+
*/
|
|
25
|
+
export function composeHooks(existing, extensions) {
|
|
26
|
+
const extHooks = extensions.flatMap((e) => (e.hooks === undefined ? [] : [e.hooks]));
|
|
27
|
+
if (extHooks.length === 0)
|
|
28
|
+
return existing;
|
|
29
|
+
const out = { ...existing };
|
|
30
|
+
const sources = existing === undefined ? extHooks : [existing, ...extHooks];
|
|
31
|
+
const observers = (key) => {
|
|
32
|
+
const handlers = sources.map(key).filter((h) => h !== undefined);
|
|
33
|
+
if (handlers.length <= 1)
|
|
34
|
+
return handlers[0];
|
|
35
|
+
return async (payload, ctx) => {
|
|
36
|
+
for (const h of handlers)
|
|
37
|
+
await h(payload, ctx);
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
for (const key of ["onPreLlm", "onEvent", "onPreCompact", "onPostCompact", "onPause", "onStop"]) {
|
|
41
|
+
const handler = observers((h) => h[key]);
|
|
42
|
+
if (handler !== undefined)
|
|
43
|
+
out[key] = handler;
|
|
44
|
+
}
|
|
45
|
+
const messageHandlers = sources
|
|
46
|
+
.map((h) => h.onUserMessage)
|
|
47
|
+
.filter((h) => h !== undefined);
|
|
48
|
+
if (messageHandlers.length === 1) {
|
|
49
|
+
out.onUserMessage = messageHandlers[0]; // length 1 guarantees the element
|
|
50
|
+
}
|
|
51
|
+
else if (messageHandlers.length > 1) {
|
|
52
|
+
// 复审 E1-P2: the pipe + veto short-circuit — each handler sees the
|
|
53
|
+
// message as the PREVIOUS one left it (既有先行), and a null (veto)
|
|
54
|
+
// anywhere ends the chain immediately: never "no opinion" for the
|
|
55
|
+
// next handler to outvote. Adding an extension can therefore never
|
|
56
|
+
// make the chain MORE permissive (the approval chain's deny>ask>allow
|
|
57
|
+
// monotonicity, on the message side).
|
|
58
|
+
out.onUserMessage = async (msg, ctx) => {
|
|
59
|
+
let current = msg;
|
|
60
|
+
for (const h of messageHandlers) {
|
|
61
|
+
const r = await h(current, ctx);
|
|
62
|
+
if (r === null)
|
|
63
|
+
return null;
|
|
64
|
+
current = r;
|
|
65
|
+
}
|
|
66
|
+
return current;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const preToolHandlers = sources
|
|
70
|
+
.map((h) => h.onPreTool)
|
|
71
|
+
.filter((h) => h !== undefined);
|
|
72
|
+
if (preToolHandlers.length === 1) {
|
|
73
|
+
out.onPreTool = preToolHandlers[0];
|
|
74
|
+
}
|
|
75
|
+
else if (preToolHandlers.length > 1) {
|
|
76
|
+
out.onPreTool = async (call, ctx) => {
|
|
77
|
+
for (const h of preToolHandlers) {
|
|
78
|
+
const d = await h(call, ctx);
|
|
79
|
+
if (d.action !== "defer")
|
|
80
|
+
return d;
|
|
81
|
+
}
|
|
82
|
+
return { action: "defer" };
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const postToolHandlers = sources
|
|
86
|
+
.map((h) => h.onPostTool)
|
|
87
|
+
.filter((h) => h !== undefined);
|
|
88
|
+
if (postToolHandlers.length === 1) {
|
|
89
|
+
out.onPostTool = postToolHandlers[0];
|
|
90
|
+
}
|
|
91
|
+
else if (postToolHandlers.length > 1) {
|
|
92
|
+
out.onPostTool = async (call, result, ctx) => {
|
|
93
|
+
let r = result;
|
|
94
|
+
for (const h of postToolHandlers)
|
|
95
|
+
r = await h(call, r, ctx);
|
|
96
|
+
return r;
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* E2: the loop's microcompact config — the session's own microcompact wins;
|
|
103
|
+
* otherwise the FIRST extension providing a compaction config supplies it.
|
|
104
|
+
* An extension config without a threshold contributes nothing (a boundary
|
|
105
|
+
* needs a threshold to ever fire).
|
|
106
|
+
*/
|
|
107
|
+
export function microcompactFor(config) {
|
|
108
|
+
if (config.microcompact !== undefined)
|
|
109
|
+
return config.microcompact;
|
|
110
|
+
for (const ext of config.extensions ?? []) {
|
|
111
|
+
const c = ext.compaction;
|
|
112
|
+
if (c !== undefined && c.thresholdTokens !== undefined) {
|
|
113
|
+
return { thresholdTokens: c.thresholdTokens, ...(c.keepResults !== undefined ? { keepResults: c.keepResults } : {}) };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手感批 B4 (pure move) — the recovery support pieces, moved verbatim from
|
|
3
|
+
* session.ts: the open-run gate, the abort sentinel/race wrapper, and the
|
|
4
|
+
* merged abort signal.
|
|
5
|
+
*/
|
|
6
|
+
import type { AbortSignalLike, AbortSignalStub } from "@vincemakes/kiso-core";
|
|
7
|
+
import type { StoreRecord } from "./store.js";
|
|
8
|
+
/**
|
|
9
|
+
* The most recent run WITHOUT a terminal, or undefined when every recorded
|
|
10
|
+
* run terminated. Recovery can only drive ONE run to its terminal, so an
|
|
11
|
+
* open run must be the exclusive reason a session refuses new runs (四).
|
|
12
|
+
*/
|
|
13
|
+
export declare function openRunId(records: readonly StoreRecord[]): string | undefined;
|
|
14
|
+
/** Sentinel: the signal aborted while the recovery awaited a decision. */
|
|
15
|
+
export declare const ABORTED: unique symbol;
|
|
16
|
+
/** Resolve with the decision, or ABORTED when the signal fires first. */
|
|
17
|
+
export declare function abortable<T>(promise: Promise<T>, signal: AbortSignalLike): Promise<T | typeof ABORTED>;
|
|
18
|
+
/**
|
|
19
|
+
* A signal that fires when ANY source fires — the run's own controller and
|
|
20
|
+
* an optional external signal (the CLI's Ctrl+C, a fixture's flip).
|
|
21
|
+
*/
|
|
22
|
+
export declare class MergedSignal implements AbortSignalStub {
|
|
23
|
+
#private;
|
|
24
|
+
constructor(...sources: readonly AbortSignalLike[]);
|
|
25
|
+
get aborted(): boolean;
|
|
26
|
+
addEventListener(_type: string, listener: (this: AbortSignalStub, ev: unknown) => void): void;
|
|
27
|
+
removeEventListener(_type: string, listener: (this: AbortSignalStub, ev: unknown) => void): void;
|
|
28
|
+
}
|
package/dist/recovery.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手感批 B4 (pure move) — the recovery support pieces, moved verbatim from
|
|
3
|
+
* session.ts: the open-run gate, the abort sentinel/race wrapper, and the
|
|
4
|
+
* merged abort signal.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The most recent run WITHOUT a terminal, or undefined when every recorded
|
|
8
|
+
* run terminated. Recovery can only drive ONE run to its terminal, so an
|
|
9
|
+
* open run must be the exclusive reason a session refuses new runs (四).
|
|
10
|
+
*/
|
|
11
|
+
export function openRunId(records) {
|
|
12
|
+
const terminated = new Set(records.filter((r) => r.event.type === "terminal").map((r) => r.runId));
|
|
13
|
+
for (let i = records.length - 1; i >= 0; i--) {
|
|
14
|
+
const runId = records[i].runId;
|
|
15
|
+
if (!terminated.has(runId))
|
|
16
|
+
return runId;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
/** Sentinel: the signal aborted while the recovery awaited a decision. */
|
|
21
|
+
export const ABORTED = Symbol("kiso-resume-aborted");
|
|
22
|
+
/** Resolve with the decision, or ABORTED when the signal fires first. */
|
|
23
|
+
export async function abortable(promise, signal) {
|
|
24
|
+
if (signal.aborted)
|
|
25
|
+
return ABORTED;
|
|
26
|
+
return new Promise((resolve) => {
|
|
27
|
+
const onAbort = () => {
|
|
28
|
+
signal.removeEventListener("abort", onAbort);
|
|
29
|
+
resolve(ABORTED);
|
|
30
|
+
};
|
|
31
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
32
|
+
promise.then((value) => {
|
|
33
|
+
signal.removeEventListener("abort", onAbort);
|
|
34
|
+
resolve(value);
|
|
35
|
+
}, (err) => {
|
|
36
|
+
signal.removeEventListener("abort", onAbort);
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A signal that fires when ANY source fires — the run's own controller and
|
|
43
|
+
* an optional external signal (the CLI's Ctrl+C, a fixture's flip).
|
|
44
|
+
*/
|
|
45
|
+
export class MergedSignal {
|
|
46
|
+
#sources;
|
|
47
|
+
#listeners = new Set();
|
|
48
|
+
constructor(...sources) {
|
|
49
|
+
this.#sources = sources;
|
|
50
|
+
for (const source of sources) {
|
|
51
|
+
if (source.aborted)
|
|
52
|
+
continue;
|
|
53
|
+
source.addEventListener("abort", () => {
|
|
54
|
+
for (const listener of this.#listeners)
|
|
55
|
+
listener();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
get aborted() {
|
|
60
|
+
return this.#sources.some((s) => s.aborted);
|
|
61
|
+
}
|
|
62
|
+
addEventListener(_type, listener) {
|
|
63
|
+
this.#listeners.add(() => listener.call(this, undefined));
|
|
64
|
+
}
|
|
65
|
+
removeEventListener(_type, listener) {
|
|
66
|
+
this.#listeners.delete(listener);
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 手感批 B4 (pure move) — the Run class (a single turn: write-ahead
|
|
3
|
+
* persistence, the loop drive, the durable recovery state machine), moved
|
|
4
|
+
* verbatim from session.ts.
|
|
5
|
+
*/
|
|
6
|
+
import { type AbortSignalLike, type Adapter, type Event } from "@vincemakes/kiso-core";
|
|
7
|
+
import type { SessionStore } from "./store.js";
|
|
8
|
+
import { type AgentSession, type SessionConfig } from "./session.js";
|
|
9
|
+
/**
|
|
10
|
+
* A single turn. Async-iterable, so `for await (const ev of session.run(x))`
|
|
11
|
+
* is the natural shape; the handle also carries the runId and the abort.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Run implements AsyncIterable<Event> {
|
|
14
|
+
#private;
|
|
15
|
+
runId: string;
|
|
16
|
+
constructor(store: SessionStore, adapter: Adapter, config: SessionConfig, session: AgentSession, input: string | undefined, externalSignal: AbortSignalLike | undefined, resume: boolean);
|
|
17
|
+
/** Cancel the run: propagates to the adapter (SDK) and future executions. */
|
|
18
|
+
abort(): void;
|
|
19
|
+
[Symbol.asyncIterator](): AsyncIterator<Event>;
|
|
20
|
+
}
|