@code-yeongyu/senpi-agent-core 2026.9.30 → 2026.10.1-2
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/README.md +49 -20
- package/dist/agent-loop.d.ts +27 -3
- package/dist/agent-loop.js +192 -56
- package/dist/agent.d.ts +21 -7
- package/dist/agent.js +38 -18
- package/dist/harness/execution/tools.d.ts +1 -1
- package/dist/harness/execution/tools.js +0 -2
- package/dist/harness/messages.js +1 -0
- package/dist/harness/pico3/bash.d.ts +10 -0
- package/dist/harness/pico3/bash.js +25 -0
- package/dist/harness/pico3/bounded.d.ts +21 -0
- package/dist/harness/pico3/bounded.js +95 -0
- package/dist/harness/pico3/chord.d.ts +41 -0
- package/dist/harness/pico3/chord.js +147 -0
- package/dist/harness/pico3/context.d.ts +16 -0
- package/dist/harness/pico3/context.js +88 -0
- package/dist/harness/pico3/harness.d.ts +198 -0
- package/dist/harness/pico3/harness.js +645 -0
- package/dist/harness/pico3/hooks.d.ts +10 -0
- package/dist/harness/pico3/hooks.js +33 -0
- package/dist/harness/pico3/index.d.ts +19 -0
- package/dist/harness/pico3/index.js +15 -0
- package/dist/harness/pico3/jsonl.d.ts +58 -0
- package/dist/harness/pico3/jsonl.js +322 -0
- package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
- package/dist/harness/pico3/kinds/collapse.js +191 -0
- package/dist/harness/pico3/kinds/entries.d.ts +78 -0
- package/dist/harness/pico3/kinds/entries.js +13 -0
- package/dist/harness/pico3/kinds/frames.d.ts +9 -0
- package/dist/harness/pico3/kinds/frames.js +75 -0
- package/dist/harness/pico3/kinds/generation.d.ts +94 -0
- package/dist/harness/pico3/kinds/generation.js +504 -0
- package/dist/harness/pico3/kinds/job.d.ts +44 -0
- package/dist/harness/pico3/kinds/job.js +143 -0
- package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
- package/dist/harness/pico3/kinds/plugin.js +35 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
- package/dist/harness/pico3/kinds/post-tools.js +145 -0
- package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
- package/dist/harness/pico3/kinds/task-api.js +45 -0
- package/dist/harness/pico3/kinds/tool.d.ts +43 -0
- package/dist/harness/pico3/kinds/tool.js +373 -0
- package/dist/harness/pico3/legacy-tracker.d.ts +11 -0
- package/dist/harness/pico3/legacy-tracker.js +39 -0
- package/dist/harness/pico3/membrane.d.ts +23 -0
- package/dist/harness/pico3/membrane.js +140 -0
- package/dist/harness/pico3/memory.d.ts +53 -0
- package/dist/harness/pico3/memory.js +265 -0
- package/dist/harness/pico3/scheduler.d.ts +49 -0
- package/dist/harness/pico3/scheduler.js +437 -0
- package/dist/harness/pico3/session.d.ts +264 -0
- package/dist/harness/pico3/session.js +1328 -0
- package/dist/harness/pico3/system.d.ts +126 -0
- package/dist/harness/pico3/system.js +244 -0
- package/dist/harness/pico3/types.d.ts +973 -0
- package/dist/harness/pico3/types.js +94 -0
- package/dist/harness/pico3/view.d.ts +34 -0
- package/dist/harness/pico3/view.js +404 -0
- package/dist/harness/runtime/drive/tool-placement.js +2 -27
- package/dist/harness/telemetry.d.ts +36 -36
- package/dist/harness/tools/image.js +1 -1
- package/dist/proxy.d.ts +2 -2
- package/dist/types.d.ts +111 -34
- package/package.json +9 -5
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** Coerce an in-memory value (e.g. a pi-ai message) to its stored representation. */
|
|
2
|
+
export const toStored = (value) => JSON.parse(JSON.stringify(value));
|
|
3
|
+
export function defineEntry(kind) {
|
|
4
|
+
if (kind.startsWith("pi."))
|
|
5
|
+
throw new Error(`entry kind names beginning with "pi." are reserved: ${kind}`);
|
|
6
|
+
return Object.freeze({ kind, is: (entry) => entry?.kind === kind });
|
|
7
|
+
}
|
|
8
|
+
/** First writer wins, including when the stored winner is null. */
|
|
9
|
+
export function memoOnce(slot, key, candidate) {
|
|
10
|
+
if (!Object.hasOwn(slot, "memos"))
|
|
11
|
+
slot.memos = {};
|
|
12
|
+
const memos = slot.memos;
|
|
13
|
+
if (Object.hasOwn(memos, key))
|
|
14
|
+
return memos[key];
|
|
15
|
+
memos[key] = candidate;
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
/** Author an ordinary kind. Names may not begin with `pi.`. */
|
|
19
|
+
export function defineTask(definition) {
|
|
20
|
+
if (definition.name.startsWith("pi."))
|
|
21
|
+
throw new Error(`task kind names beginning with "pi." are reserved: ${definition.name}`);
|
|
22
|
+
return Object.freeze({ ...definition });
|
|
23
|
+
}
|
|
24
|
+
/** Thrown when a scan-shaped read follows a same-batch write to its domain. Poisons the transaction. */
|
|
25
|
+
export class ReadAfterWrite extends Error {
|
|
26
|
+
constructor(read, write) {
|
|
27
|
+
super(`${read} after ${write} in the same transaction: the answer would not include the buffered write`);
|
|
28
|
+
this.name = "ReadAfterWrite";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Invocation tokens and invokers
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/** An unforgeable capability: only the scheduler creates these, one per invocation. */
|
|
35
|
+
export class InvocationToken {
|
|
36
|
+
#alive = true;
|
|
37
|
+
constructor(taskId, mode) {
|
|
38
|
+
this.taskId = taskId;
|
|
39
|
+
this.mode = mode;
|
|
40
|
+
}
|
|
41
|
+
get alive() {
|
|
42
|
+
return this.#alive;
|
|
43
|
+
}
|
|
44
|
+
/** Called by the scheduler when the invocation returns. */
|
|
45
|
+
revoke() {
|
|
46
|
+
this.#alive = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Errors
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
export class Forbidden extends Error {
|
|
53
|
+
constructor(what) {
|
|
54
|
+
super(`forbidden: ${what}`);
|
|
55
|
+
this.name = "Forbidden";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class ConversationBusy extends Error {
|
|
59
|
+
constructor(id) {
|
|
60
|
+
super(`conversation ${id} is busy`);
|
|
61
|
+
this.name = "ConversationBusy";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class GenerationInProgress extends Error {
|
|
65
|
+
constructor(id) {
|
|
66
|
+
super(`conversation ${id} already has a live generation`);
|
|
67
|
+
this.name = "GenerationInProgress";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class CollapseInProgress extends Error {
|
|
71
|
+
constructor(id) {
|
|
72
|
+
super(`conversation ${id} already has a live collapse`);
|
|
73
|
+
this.name = "CollapseInProgress";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export class Faulted extends Error {
|
|
77
|
+
constructor(cause) {
|
|
78
|
+
super(`Session faulted: ${String(cause)}`);
|
|
79
|
+
this.name = "Faulted";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export class Closed extends Error {
|
|
83
|
+
constructor() {
|
|
84
|
+
super("Session is closed");
|
|
85
|
+
this.name = "Closed";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export class TaskContractFault extends Error {
|
|
89
|
+
constructor(kind, what) {
|
|
90
|
+
super(`kind ${kind} broke its contract: ${what}`);
|
|
91
|
+
this.name = "TaskContractFault";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CommitResult, Session } from "./session.ts";
|
|
2
|
+
import type { Conversation, ConversationView, Envelope } from "./types.ts";
|
|
3
|
+
export declare const WATCH_CAPACITY = 256;
|
|
4
|
+
export interface Watch {
|
|
5
|
+
readonly view: ConversationView;
|
|
6
|
+
readonly revision: number;
|
|
7
|
+
readonly closed: boolean;
|
|
8
|
+
start(listener: (envelope: Envelope) => void): void;
|
|
9
|
+
stop(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function applyEnvelope(view: ConversationView, envelope: Envelope): ConversationView;
|
|
12
|
+
export declare class ViewManager {
|
|
13
|
+
private readonly records;
|
|
14
|
+
private readonly deliveries;
|
|
15
|
+
private readonly session;
|
|
16
|
+
private readonly onReport;
|
|
17
|
+
constructor(session: Session, onReport: (error: unknown) => void);
|
|
18
|
+
watch(conversation: Conversation, entries: EntryList): Watch;
|
|
19
|
+
/** Runs on the Session line after persistence and in-memory indexes update. */
|
|
20
|
+
update(result: CommitResult<unknown>): void;
|
|
21
|
+
/** Runs after the Session line; listeners are synchronous and ordered. */
|
|
22
|
+
deliver(): void;
|
|
23
|
+
close(): void;
|
|
24
|
+
private build;
|
|
25
|
+
private document;
|
|
26
|
+
private config;
|
|
27
|
+
private turn;
|
|
28
|
+
private compaction;
|
|
29
|
+
private tasks;
|
|
30
|
+
private plugins;
|
|
31
|
+
}
|
|
32
|
+
type EntryList = ConversationView["entries"];
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=view.d.ts.map
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
2
|
+
import { track } from "./legacy-tracker.js";
|
|
3
|
+
export const WATCH_CAPACITY = 256;
|
|
4
|
+
export function applyEnvelope(view, envelope) {
|
|
5
|
+
return applyImmutable(view, envelope.ops);
|
|
6
|
+
}
|
|
7
|
+
export class ViewManager {
|
|
8
|
+
constructor(session, onReport) {
|
|
9
|
+
this.records = new Map();
|
|
10
|
+
this.deliveries = [];
|
|
11
|
+
this.session = session;
|
|
12
|
+
this.onReport = onReport;
|
|
13
|
+
}
|
|
14
|
+
watch(conversation, entries) {
|
|
15
|
+
let record = this.records.get(conversation.id);
|
|
16
|
+
if (record === undefined) {
|
|
17
|
+
const tracker = track(this.build(conversation, entries));
|
|
18
|
+
tracker.flush();
|
|
19
|
+
record = { conversationId: conversation.id, tracker, watchers: new Set(), revision: 0 };
|
|
20
|
+
this.records.set(conversation.id, record);
|
|
21
|
+
}
|
|
22
|
+
const watcher = new WatchImpl(structuredClone(record.tracker.target), record.revision, this.onReport, () => {
|
|
23
|
+
record.watchers.delete(watcher);
|
|
24
|
+
if (record.watchers.size === 0 && this.records.get(conversation.id) === record)
|
|
25
|
+
this.records.delete(conversation.id);
|
|
26
|
+
});
|
|
27
|
+
record.watchers.add(watcher);
|
|
28
|
+
return watcher;
|
|
29
|
+
}
|
|
30
|
+
/** Runs on the Session line after persistence and in-memory indexes update. */
|
|
31
|
+
update(result) {
|
|
32
|
+
for (const record of [...this.records.values()]) {
|
|
33
|
+
const conversation = this.session.conversationRecords.get(record.conversationId);
|
|
34
|
+
if (conversation === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
try {
|
|
37
|
+
const changes = result.changes;
|
|
38
|
+
const appended = changes.entries.filter((entry) => entry.conversationId === record.conversationId);
|
|
39
|
+
const documentChanges = changes.docs.filter((change) => change.ref.doc === "session" || change.ref.conversationId === record.conversationId);
|
|
40
|
+
const taskChanged = changes.tasks.some((task) => task.conversationId === record.conversationId);
|
|
41
|
+
const events = changes.events
|
|
42
|
+
.filter((scoped) => scoped.conversationId === record.conversationId)
|
|
43
|
+
.map((scoped) => scoped.event);
|
|
44
|
+
if (appended.length === 0 && documentChanges.length === 0 && !taskChanged && events.length === 0)
|
|
45
|
+
continue;
|
|
46
|
+
const state = record.tracker.state;
|
|
47
|
+
applyEntries(state.entries, appended);
|
|
48
|
+
const rewindableChanged = documentChanges.some((change) => change.ref.doc === "rewindable" && change.ref.conversationId === record.conversationId);
|
|
49
|
+
const stickyChanged = documentChanges.some((change) => change.ref.doc === "sticky" && change.ref.conversationId === record.conversationId);
|
|
50
|
+
const configChanged = events.some((event) => event.type === "config.changed");
|
|
51
|
+
const pluginChanged = documentChanges.some((change) => touchesKey(change.ops, "plugins"));
|
|
52
|
+
const rewindable = rewindableChanged || configChanged || pluginChanged
|
|
53
|
+
? this.document({ doc: "rewindable", conversationId: record.conversationId })
|
|
54
|
+
: undefined;
|
|
55
|
+
const sticky = stickyChanged || taskChanged || configChanged || pluginChanged
|
|
56
|
+
? this.document({ doc: "sticky", conversationId: record.conversationId })
|
|
57
|
+
: undefined;
|
|
58
|
+
if (configChanged)
|
|
59
|
+
syncRecord(state.config, this.config(rewindable, sticky));
|
|
60
|
+
if (stickyChanged)
|
|
61
|
+
syncArray(state.inbox, structuredClone(sticky.inbox));
|
|
62
|
+
if (stickyChanged || taskChanged) {
|
|
63
|
+
syncOptional(state, "turn", this.turn(record.conversationId, sticky).turn);
|
|
64
|
+
syncOptional(state, "compaction", this.compaction(record.conversationId).compaction);
|
|
65
|
+
syncRecord(state.tasks, this.tasks(record.conversationId, sticky));
|
|
66
|
+
}
|
|
67
|
+
if (pluginChanged) {
|
|
68
|
+
const session = this.document({ doc: "session" });
|
|
69
|
+
syncRecord(state.plugins, this.plugins(rewindable, sticky, session));
|
|
70
|
+
}
|
|
71
|
+
const ops = record.tracker.flush();
|
|
72
|
+
if (ops.length === 0 && events.length === 0)
|
|
73
|
+
continue;
|
|
74
|
+
const envelope = Object.freeze({
|
|
75
|
+
revision: ++record.revision,
|
|
76
|
+
ops: Object.freeze(ops),
|
|
77
|
+
events: Object.freeze(events),
|
|
78
|
+
});
|
|
79
|
+
this.deliveries.push({ envelope, watchers: [...record.watchers] });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.records.delete(record.conversationId);
|
|
83
|
+
for (const watcher of [...record.watchers])
|
|
84
|
+
watcher.fail(error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Runs after the Session line; listeners are synchronous and ordered. */
|
|
89
|
+
deliver() {
|
|
90
|
+
for (const delivery of this.deliveries.splice(0)) {
|
|
91
|
+
for (const watcher of delivery.watchers)
|
|
92
|
+
watcher.accept(delivery.envelope);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
close() {
|
|
96
|
+
const records = [...this.records.values()];
|
|
97
|
+
this.records.clear();
|
|
98
|
+
this.deliveries.length = 0;
|
|
99
|
+
for (const record of records)
|
|
100
|
+
for (const watcher of [...record.watchers])
|
|
101
|
+
watcher.stop();
|
|
102
|
+
}
|
|
103
|
+
build(conversation, entries) {
|
|
104
|
+
const rewindable = this.document({ doc: "rewindable", conversationId: conversation.id });
|
|
105
|
+
const sticky = this.document({ doc: "sticky", conversationId: conversation.id });
|
|
106
|
+
const session = this.document({ doc: "session" });
|
|
107
|
+
const { sections: _sections, ...publicConversation } = conversation;
|
|
108
|
+
return {
|
|
109
|
+
conversation: structuredClone(publicConversation),
|
|
110
|
+
entries: structuredClone(entries),
|
|
111
|
+
config: this.config(rewindable, sticky),
|
|
112
|
+
inbox: structuredClone(sticky.inbox),
|
|
113
|
+
...this.turn(conversation.id, sticky),
|
|
114
|
+
...this.compaction(conversation.id),
|
|
115
|
+
tasks: this.tasks(conversation.id, sticky),
|
|
116
|
+
plugins: this.plugins(rewindable, sticky, session),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
document(ref) {
|
|
120
|
+
const value = this.session.loadedDocument(ref);
|
|
121
|
+
if (value === undefined)
|
|
122
|
+
throw new Error(`view document ${ref.doc} is not loaded`);
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
config(rewindable, sticky) {
|
|
126
|
+
const out = {};
|
|
127
|
+
for (const [key, doc] of this.session.defaults.route) {
|
|
128
|
+
const source = doc === "rewindable" ? rewindable : sticky;
|
|
129
|
+
const value = key in source ? source[key] : this.session.defaults[doc][key];
|
|
130
|
+
if (value !== undefined)
|
|
131
|
+
out[key] = structuredClone(value);
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
turn(conversationId, sticky) {
|
|
136
|
+
const live = [...this.session.liveTasks.values()].filter((task) => task.conversationId === conversationId);
|
|
137
|
+
const turnTasks = live.filter((task) => this.session.kinds.get(task.kind)?.turn === true);
|
|
138
|
+
if (turnTasks.length === 0)
|
|
139
|
+
return {};
|
|
140
|
+
const generation = turnTasks.find((task) => task.kind === "pi.generation");
|
|
141
|
+
const postTools = turnTasks.find((task) => task.kind === "pi.post_tools");
|
|
142
|
+
const inputTask = generation ?? postTools;
|
|
143
|
+
const inputs = inputTask === undefined ? [] : inputIds(inputTask);
|
|
144
|
+
const turn = {
|
|
145
|
+
inputs,
|
|
146
|
+
...(generation === undefined
|
|
147
|
+
? {}
|
|
148
|
+
: { generation: generationStatus(generation, sticky.turn.message !== undefined) }),
|
|
149
|
+
...(sticky.turn.message === undefined ? {} : { message: structuredClone(sticky.turn.message) }),
|
|
150
|
+
tools: sticky.turn.tools.map(stripPrivateToolState),
|
|
151
|
+
};
|
|
152
|
+
return { turn };
|
|
153
|
+
}
|
|
154
|
+
compaction(conversationId) {
|
|
155
|
+
const task = [...this.session.liveTasks.values()].find((candidate) => candidate.conversationId === conversationId && candidate.kind === "pi.collapse");
|
|
156
|
+
if (task === undefined)
|
|
157
|
+
return {};
|
|
158
|
+
const input = task.input;
|
|
159
|
+
const checkpoint = task.checkpoint;
|
|
160
|
+
return {
|
|
161
|
+
compaction: {
|
|
162
|
+
taskId: task.id,
|
|
163
|
+
reason: input.reason,
|
|
164
|
+
stage: checkpoint?.phase === "retrying" ? "retrying" : "summarizing",
|
|
165
|
+
attempt: checkpoint?.attempt ?? 1,
|
|
166
|
+
...(checkpoint?.phase === "retrying" && checkpoint.untilMs !== undefined
|
|
167
|
+
? { retryAt: checkpoint.untilMs }
|
|
168
|
+
: {}),
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
tasks(conversationId, sticky) {
|
|
173
|
+
const out = {};
|
|
174
|
+
for (const task of this.session.liveTasks.values()) {
|
|
175
|
+
const kind = this.session.kinds.get(task.kind);
|
|
176
|
+
if (task.conversationId !== conversationId ||
|
|
177
|
+
kind === undefined ||
|
|
178
|
+
kind.turn === true ||
|
|
179
|
+
task.kind === "pi.collapse")
|
|
180
|
+
continue;
|
|
181
|
+
const slot = sticky.tasks[task.id];
|
|
182
|
+
out[task.id] = {
|
|
183
|
+
kind: task.kind,
|
|
184
|
+
...(task.background ? { background: true } : {}),
|
|
185
|
+
...(task.abort ? { marked: true } : {}),
|
|
186
|
+
status: describe(kind, task, slot),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
plugins(rewindable, sticky, session) {
|
|
192
|
+
const out = {};
|
|
193
|
+
const sessionPlugins = (session.plugins ?? {});
|
|
194
|
+
for (const [id, registration] of this.session.namespaces) {
|
|
195
|
+
if (registration.project === undefined)
|
|
196
|
+
continue;
|
|
197
|
+
const merged = {
|
|
198
|
+
...registration.defaults.rewindable,
|
|
199
|
+
...registration.defaults.sticky,
|
|
200
|
+
...registration.defaults.session,
|
|
201
|
+
...(rewindable.plugins[id] ?? {}),
|
|
202
|
+
...(sticky.plugins[id] ?? {}),
|
|
203
|
+
...(sessionPlugins[id] ?? {}),
|
|
204
|
+
};
|
|
205
|
+
out[id] = strictJson(registration.project(structuredClone(merged)), `namespace ${id} view`);
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
class WatchImpl {
|
|
211
|
+
constructor(view, revision, onReport, onStop) {
|
|
212
|
+
this.buffer = [];
|
|
213
|
+
this.stopped = false;
|
|
214
|
+
this.view = view;
|
|
215
|
+
this.initialRevision = revision;
|
|
216
|
+
this.onReport = onReport;
|
|
217
|
+
this.onStop = onStop;
|
|
218
|
+
}
|
|
219
|
+
get revision() {
|
|
220
|
+
return this.initialRevision;
|
|
221
|
+
}
|
|
222
|
+
get closed() {
|
|
223
|
+
return this.stopped;
|
|
224
|
+
}
|
|
225
|
+
start(listener) {
|
|
226
|
+
if (this.listener !== undefined || this.stopped)
|
|
227
|
+
return;
|
|
228
|
+
this.listener = listener;
|
|
229
|
+
for (const envelope of this.buffer.splice(0)) {
|
|
230
|
+
if (this.stopped)
|
|
231
|
+
break;
|
|
232
|
+
this.deliver(envelope);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
stop() {
|
|
236
|
+
if (this.stopped)
|
|
237
|
+
return;
|
|
238
|
+
this.stopped = true;
|
|
239
|
+
this.buffer.length = 0;
|
|
240
|
+
this.onStop();
|
|
241
|
+
}
|
|
242
|
+
accept(envelope) {
|
|
243
|
+
if (this.stopped)
|
|
244
|
+
return;
|
|
245
|
+
if (this.listener === undefined) {
|
|
246
|
+
if (this.buffer.length >= WATCH_CAPACITY) {
|
|
247
|
+
this.stop();
|
|
248
|
+
this.report(new Error(`watch capacity ${WATCH_CAPACITY} exceeded before start()`));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
this.buffer.push(envelope);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.deliver(envelope);
|
|
255
|
+
}
|
|
256
|
+
deliver(envelope) {
|
|
257
|
+
try {
|
|
258
|
+
this.listener(envelope);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
this.fail(error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
fail(error) {
|
|
265
|
+
this.stop();
|
|
266
|
+
this.report(error);
|
|
267
|
+
}
|
|
268
|
+
report(error) {
|
|
269
|
+
try {
|
|
270
|
+
this.onReport(error);
|
|
271
|
+
}
|
|
272
|
+
catch { }
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function applyEntries(entries, appended) {
|
|
276
|
+
for (const entry of appended) {
|
|
277
|
+
if (entry.head !== undefined) {
|
|
278
|
+
const retained = entries.findIndex((candidate) => candidate.id >= entry.head);
|
|
279
|
+
const remove = retained < 0 ? entries.length : retained;
|
|
280
|
+
if (remove > 0)
|
|
281
|
+
entries.splice(0, remove);
|
|
282
|
+
}
|
|
283
|
+
entries.push(structuredClone(entry));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function inputIds(task) {
|
|
287
|
+
const inputs = task.input.inputs;
|
|
288
|
+
return Array.isArray(inputs) ? inputs.filter((value) => typeof value === "number") : [];
|
|
289
|
+
}
|
|
290
|
+
function generationStatus(task, streaming) {
|
|
291
|
+
if (task.status === "pending" && task.after.length > 0)
|
|
292
|
+
return { stage: "waiting", on: "compaction" };
|
|
293
|
+
const checkpoint = task.checkpoint;
|
|
294
|
+
if (checkpoint?.phase === "requesting")
|
|
295
|
+
return { stage: streaming ? "streaming" : "requesting", attempt: checkpoint.attempt ?? 1 };
|
|
296
|
+
if (checkpoint?.phase === "retrying")
|
|
297
|
+
return {
|
|
298
|
+
stage: "retrying",
|
|
299
|
+
attempt: checkpoint.attempt ?? 1,
|
|
300
|
+
retryAt: checkpoint.untilMs ?? 0,
|
|
301
|
+
lastError: checkpoint.lastError ?? "",
|
|
302
|
+
};
|
|
303
|
+
if (checkpoint?.phase === "deferred")
|
|
304
|
+
return { stage: "deferred", attempt: checkpoint.attempt ?? 1, pollAt: checkpoint.pollAt ?? 0 };
|
|
305
|
+
return { stage: "preparing" };
|
|
306
|
+
}
|
|
307
|
+
function stripPrivateToolState(slot) {
|
|
308
|
+
const { memos: _memos, ...view } = structuredClone(slot);
|
|
309
|
+
return view;
|
|
310
|
+
}
|
|
311
|
+
function describe(kind, task, slot) {
|
|
312
|
+
if (typeof kind.describe !== "function")
|
|
313
|
+
return { phase: task.checkpoint?.phase ?? (task.status === "pending" ? "pending" : "running") };
|
|
314
|
+
const publicSlot = slot === undefined ? undefined : structuredClone(slot);
|
|
315
|
+
if (publicSlot !== undefined)
|
|
316
|
+
delete publicSlot.memos;
|
|
317
|
+
const value = kind.describe({
|
|
318
|
+
...task,
|
|
319
|
+
...(publicSlot === undefined ? {} : { slot: publicSlot }),
|
|
320
|
+
});
|
|
321
|
+
return strictJson(value, `task kind ${task.kind} describe`);
|
|
322
|
+
}
|
|
323
|
+
function strictJson(value, what) {
|
|
324
|
+
const encoded = JSON.stringify(value);
|
|
325
|
+
if (encoded === undefined)
|
|
326
|
+
throw new Error(`${what} returned a non-JSON value`);
|
|
327
|
+
return JSON.parse(encoded);
|
|
328
|
+
}
|
|
329
|
+
function touchesKey(ops, key) {
|
|
330
|
+
return ops.some((op) => op[0] === "r" || op[1][0] === key);
|
|
331
|
+
}
|
|
332
|
+
function syncOptional(target, key, next) {
|
|
333
|
+
const record = target;
|
|
334
|
+
if (next === undefined) {
|
|
335
|
+
delete record[key];
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const current = record[key];
|
|
339
|
+
if (same(current, next))
|
|
340
|
+
return;
|
|
341
|
+
if (isRecord(current) && isRecord(next))
|
|
342
|
+
syncRecord(current, next);
|
|
343
|
+
else
|
|
344
|
+
record[key] = structuredClone(next);
|
|
345
|
+
}
|
|
346
|
+
function syncRecord(target, next) {
|
|
347
|
+
for (const key of Object.keys(target))
|
|
348
|
+
if (!(key in next))
|
|
349
|
+
delete target[key];
|
|
350
|
+
for (const [key, value] of Object.entries(next)) {
|
|
351
|
+
const current = target[key];
|
|
352
|
+
if (same(current, value))
|
|
353
|
+
continue;
|
|
354
|
+
if (Array.isArray(current) && Array.isArray(value)) {
|
|
355
|
+
syncArray(current, value);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (isRecord(current) && isRecord(value)) {
|
|
359
|
+
syncRecord(current, value);
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
target[key] = structuredClone(value);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function syncArray(target, next) {
|
|
366
|
+
let prefix = 0;
|
|
367
|
+
while (prefix < target.length && prefix < next.length) {
|
|
368
|
+
if (same(target[prefix], next[prefix])) {
|
|
369
|
+
prefix++;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const current = target[prefix];
|
|
373
|
+
const replacement = next[prefix];
|
|
374
|
+
if (!isRecord(current) || !isRecord(replacement) || !sameArrayItem(current, replacement))
|
|
375
|
+
break;
|
|
376
|
+
syncRecord(current, replacement);
|
|
377
|
+
prefix++;
|
|
378
|
+
}
|
|
379
|
+
if (prefix === target.length && prefix === next.length)
|
|
380
|
+
return;
|
|
381
|
+
if (prefix === target.length) {
|
|
382
|
+
target.push(...structuredClone(next.slice(prefix)));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
target.splice(prefix, target.length - prefix, ...structuredClone(next.slice(prefix)));
|
|
386
|
+
}
|
|
387
|
+
function sameArrayItem(current, next) {
|
|
388
|
+
if (typeof current.id === "number" || typeof next.id === "number")
|
|
389
|
+
return current.id === next.id;
|
|
390
|
+
if (typeof current.callId === "string" || typeof next.callId === "string")
|
|
391
|
+
return current.callId === next.callId;
|
|
392
|
+
if (typeof current.type === "string" || typeof next.type === "string")
|
|
393
|
+
return current.type === next.type;
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
function isRecord(value) {
|
|
397
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
398
|
+
}
|
|
399
|
+
function same(a, b) {
|
|
400
|
+
if (Object.is(a, b))
|
|
401
|
+
return true;
|
|
402
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
403
|
+
}
|
|
404
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { insertEntry, insertUsage } from "../../session/commit.js";
|
|
2
2
|
import { SessionInvariantError } from "../../session/session.js";
|
|
3
3
|
import { operationScopeOf, } from "../../session/types.js";
|
|
4
|
-
import { branchTip, deleteValue,
|
|
4
|
+
import { branchTip, deleteValue, operationToolArgsPrefix, pendingEntry, setValue } from "../../session/values.js";
|
|
5
5
|
export async function readToolBatchSource(lane, drive, batch) {
|
|
6
6
|
return lane.command(async (_state, reader) => {
|
|
7
7
|
const entry = (await reader.getEntries([batch.assistantEntryId], drive.context)).get(batch.assistantEntryId);
|
|
@@ -132,22 +132,6 @@ async function commitPlacement(lane, drive, capability, read) {
|
|
|
132
132
|
};
|
|
133
133
|
});
|
|
134
134
|
const complete = completedCalls.every((call) => call.status === "completed");
|
|
135
|
-
let nextConfiguration = state.configuration;
|
|
136
|
-
const addedNames = [];
|
|
137
|
-
for (const item of read.items) {
|
|
138
|
-
for (const name of item.message.addedToolNames ?? []) {
|
|
139
|
-
if (!nextConfiguration.activeToolNames.includes(name) && !addedNames.includes(name)) {
|
|
140
|
-
addedNames.push(name);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (addedNames.length !== 0) {
|
|
145
|
-
nextConfiguration = {
|
|
146
|
-
...nextConfiguration,
|
|
147
|
-
activeToolNames: [...nextConfiguration.activeToolNames, ...addedNames],
|
|
148
|
-
};
|
|
149
|
-
writes.push(setValue(laneConfig(lane.name), nextConfiguration));
|
|
150
|
-
}
|
|
151
135
|
writes.push(setValue(branchTip(lane.name), parentId));
|
|
152
136
|
let nextRun;
|
|
153
137
|
if (complete) {
|
|
@@ -171,7 +155,7 @@ async function commitPlacement(lane, drive, capability, read) {
|
|
|
171
155
|
kind: "commit",
|
|
172
156
|
writes,
|
|
173
157
|
operationState: nextRun,
|
|
174
|
-
lane: { tipId: parentId, configuration:
|
|
158
|
+
lane: { tipId: parentId, configuration: state.configuration },
|
|
175
159
|
materialize: () => complete,
|
|
176
160
|
events: (commit) => {
|
|
177
161
|
const events = [];
|
|
@@ -191,15 +175,6 @@ async function commitPlacement(lane, drive, capability, read) {
|
|
|
191
175
|
});
|
|
192
176
|
}
|
|
193
177
|
}
|
|
194
|
-
if (addedNames.length !== 0) {
|
|
195
|
-
events.push({
|
|
196
|
-
type: "config_update",
|
|
197
|
-
lane: lane.name,
|
|
198
|
-
property: "activeTools",
|
|
199
|
-
previous: state.configuration.activeToolNames,
|
|
200
|
-
value: nextConfiguration.activeToolNames,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
178
|
return events;
|
|
204
179
|
},
|
|
205
180
|
};
|