@zaly/agent 0.0.3
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/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/agent-BaD2kax9.mjs +68 -0
- package/dist/agent-BaD2kax9.mjs.map +1 -0
- package/dist/agent-GDLdDc2w.mjs +902 -0
- package/dist/agent-GDLdDc2w.mjs.map +1 -0
- package/dist/bash-QNMTQ1PD.mjs +142 -0
- package/dist/bash-QNMTQ1PD.mjs.map +1 -0
- package/dist/compactions-3AjPqjeZ.mjs +190 -0
- package/dist/compactions-3AjPqjeZ.mjs.map +1 -0
- package/dist/edit-Qxl4w1C2.mjs +170 -0
- package/dist/edit-Qxl4w1C2.mjs.map +1 -0
- package/dist/env-UUFv5Mye.mjs +30 -0
- package/dist/env-UUFv5Mye.mjs.map +1 -0
- package/dist/fetch-D8cmmA3E.mjs +111 -0
- package/dist/fetch-D8cmmA3E.mjs.map +1 -0
- package/dist/find-Bah9SKWX.mjs +87 -0
- package/dist/find-Bah9SKWX.mjs.map +1 -0
- package/dist/grep-B2Ax7ewy.mjs +244 -0
- package/dist/grep-B2Ax7ewy.mjs.map +1 -0
- package/dist/index-node-esm-D6YQRKEz.mjs +1537 -0
- package/dist/index-node-esm-D6YQRKEz.mjs.map +1 -0
- package/dist/index.d.mts +1605 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +948 -0
- package/dist/index.mjs.map +1 -0
- package/dist/jsonl-Dw_VBp46.mjs +134 -0
- package/dist/jsonl-Dw_VBp46.mjs.map +1 -0
- package/dist/manager-D-lWBTHG.mjs +271 -0
- package/dist/manager-D-lWBTHG.mjs.map +1 -0
- package/dist/markdown-Bv4J8cg3.mjs +53 -0
- package/dist/markdown-Bv4J8cg3.mjs.map +1 -0
- package/dist/masker-Bv9KjkDF.mjs +396 -0
- package/dist/masker-Bv9KjkDF.mjs.map +1 -0
- package/dist/model-_3ZA2xqm.mjs +27 -0
- package/dist/model-_3ZA2xqm.mjs.map +1 -0
- package/dist/notify-DpG_m9iP.mjs +96 -0
- package/dist/notify-DpG_m9iP.mjs.map +1 -0
- package/dist/read-Djsx2NZl.mjs +205 -0
- package/dist/read-Djsx2NZl.mjs.map +1 -0
- package/dist/registry-CFmR5G3J.mjs +564 -0
- package/dist/registry-CFmR5G3J.mjs.map +1 -0
- package/dist/search-CRw247ES.mjs +121 -0
- package/dist/search-CRw247ES.mjs.map +1 -0
- package/dist/session/claude.d.mts +59 -0
- package/dist/session/claude.d.mts.map +1 -0
- package/dist/session/claude.mjs +455 -0
- package/dist/session/claude.mjs.map +1 -0
- package/dist/session-CUUdWFC5.mjs +450 -0
- package/dist/session-CUUdWFC5.mjs.map +1 -0
- package/dist/session-Ds5nJz5A.d.mts +346 -0
- package/dist/session-Ds5nJz5A.d.mts.map +1 -0
- package/dist/session.d.mts +37 -0
- package/dist/session.d.mts.map +1 -0
- package/dist/session.mjs +106 -0
- package/dist/session.mjs.map +1 -0
- package/dist/subagent-baqzjRMc.mjs +136 -0
- package/dist/subagent-baqzjRMc.mjs.map +1 -0
- package/dist/swarm-BJ3gQA1Z.mjs +194 -0
- package/dist/swarm-BJ3gQA1Z.mjs.map +1 -0
- package/dist/swarm-BuFx_xeY.mjs +109 -0
- package/dist/swarm-BuFx_xeY.mjs.map +1 -0
- package/dist/tasks-D8rARHZ1.mjs +65 -0
- package/dist/tasks-D8rARHZ1.mjs.map +1 -0
- package/dist/tasks-WmC270X2.mjs +615 -0
- package/dist/tasks-WmC270X2.mjs.map +1 -0
- package/dist/tools-DW1dbcCz.mjs +695 -0
- package/dist/tools-DW1dbcCz.mjs.map +1 -0
- package/dist/truncate-BSMXgiXj.mjs +102 -0
- package/dist/truncate-BSMXgiXj.mjs.map +1 -0
- package/dist/uuid-ByXP5SO5.mjs +59 -0
- package/dist/uuid-ByXP5SO5.mjs.map +1 -0
- package/dist/wakeup-BYqTnfSp.mjs +29 -0
- package/dist/wakeup-BYqTnfSp.mjs.map +1 -0
- package/dist/write-D3L-92vj.mjs +89 -0
- package/dist/write-D3L-92vj.mjs.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { Emitter } from "@zaly/shared";
|
|
2
|
+
import { Message, ReasoningEffort } from "@zaly/ai";
|
|
3
|
+
//#region src/session/types.d.ts
|
|
4
|
+
/** A single node in the session DAG. Same shape on disk (JSONL) as in
|
|
5
|
+
* memory — what we persist *is* what we navigate. Session settings live ONLY on
|
|
6
|
+
* `session-settings` nodes as a full snapshot of the cumulative session
|
|
7
|
+
* settings at that point. Other node types are pure markers / payload —
|
|
8
|
+
* consumers reading "the settings as of node X" use `SessionNodeView`,
|
|
9
|
+
* which decorates raw nodes with cumulative settings computed by `Session`'s
|
|
10
|
+
* chain-walk forward pass. */
|
|
11
|
+
type SessionN = {
|
|
12
|
+
uuid: string;
|
|
13
|
+
/** Absent only on the very first node (`session-start`). */
|
|
14
|
+
parentUuid?: string;
|
|
15
|
+
/** Wall-clock millisecond timestamp when the node was created. */
|
|
16
|
+
ts: number;
|
|
17
|
+
} & ({
|
|
18
|
+
type: "session-start";
|
|
19
|
+
} | {
|
|
20
|
+
type: "session-resume";
|
|
21
|
+
} | {
|
|
22
|
+
type: "session-settings";
|
|
23
|
+
settings: SessionSettings;
|
|
24
|
+
} | {
|
|
25
|
+
type: "message";
|
|
26
|
+
message: Message;
|
|
27
|
+
} | {
|
|
28
|
+
type: "mask-checkpoint";
|
|
29
|
+
threshold: number;
|
|
30
|
+
messageId: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "compact";
|
|
33
|
+
/** Whether the loop kicked off compaction itself or the user did. */
|
|
34
|
+
trigger: "manual" | "auto";
|
|
35
|
+
/** Last known cumulative input+output tokens at compaction time. */
|
|
36
|
+
preTokens?: number;
|
|
37
|
+
/** How long the compactor took, ms. */
|
|
38
|
+
durationMs?: number;
|
|
39
|
+
/** Number of message from before compaction that will be preserved */
|
|
40
|
+
tail: number;
|
|
41
|
+
/** Frozen summary message that becomes the head of the active
|
|
42
|
+
* chain reconstruction. The full Message (not just text) so the
|
|
43
|
+
* walker stays generic and future format changes don't require
|
|
44
|
+
* migrating old compact nodes. */
|
|
45
|
+
summary: Message<"system">;
|
|
46
|
+
});
|
|
47
|
+
type SessionNodeType = SessionN["type"];
|
|
48
|
+
type MaskCheckpoint = {
|
|
49
|
+
messageId: string;
|
|
50
|
+
threshold: number;
|
|
51
|
+
};
|
|
52
|
+
type SessionMessage = Message & {
|
|
53
|
+
ts: number;
|
|
54
|
+
id: string;
|
|
55
|
+
};
|
|
56
|
+
type SessionNode<T extends SessionNodeType = SessionNodeType> = Extract<SessionN, {
|
|
57
|
+
type: T;
|
|
58
|
+
}>;
|
|
59
|
+
/** A `SessionNode` decorated with the cumulative settings as of that
|
|
60
|
+
* node's position in the chain. Returned by `Session.node()` and
|
|
61
|
+
* `Session#chain` — consumers always see a defined `settings`. */
|
|
62
|
+
type SessionNodeView = SessionNode & {
|
|
63
|
+
settings: SessionSettings;
|
|
64
|
+
};
|
|
65
|
+
type SessionView = {
|
|
66
|
+
messages: Message[];
|
|
67
|
+
nodes: Map<string, SessionNodeView>;
|
|
68
|
+
compact?: SessionNode<"compact">;
|
|
69
|
+
maskCheckpoint?: MaskCheckpoint;
|
|
70
|
+
};
|
|
71
|
+
type SessionSettings = {
|
|
72
|
+
version?: number;
|
|
73
|
+
sessionId?: string;
|
|
74
|
+
cwd?: string;
|
|
75
|
+
workspace?: string;
|
|
76
|
+
modelId?: string;
|
|
77
|
+
reasoning?: ReasoningEffort;
|
|
78
|
+
};
|
|
79
|
+
type SessionUpdate = Omit<SessionSettings, "version" | "sessionId">;
|
|
80
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
81
|
+
type PartialNode = DistributiveOmit<SessionNode, "parentUuid" | "uuid" | "ts">;
|
|
82
|
+
/** Events a `Session` emits when its state changes. Subscribers fire
|
|
83
|
+
* synchronously; throws are routed to `onEmitError`. */
|
|
84
|
+
type SessionEvents = {
|
|
85
|
+
node: {
|
|
86
|
+
node: SessionNode;
|
|
87
|
+
};
|
|
88
|
+
navigate: {
|
|
89
|
+
head: string | undefined;
|
|
90
|
+
messages: readonly Message[];
|
|
91
|
+
};
|
|
92
|
+
compact: {
|
|
93
|
+
node: SessionNode<"compact">;
|
|
94
|
+
};
|
|
95
|
+
cwd: {
|
|
96
|
+
cwd: string;
|
|
97
|
+
prev?: string;
|
|
98
|
+
};
|
|
99
|
+
model: {
|
|
100
|
+
model: string;
|
|
101
|
+
prev?: string;
|
|
102
|
+
};
|
|
103
|
+
reasoning: {
|
|
104
|
+
effort: ReasoningEffort;
|
|
105
|
+
prev?: ReasoningEffort;
|
|
106
|
+
};
|
|
107
|
+
"session-start": {};
|
|
108
|
+
"session-resume": {};
|
|
109
|
+
};
|
|
110
|
+
type SessionOptions<T extends SessionStore = SessionStore> = {
|
|
111
|
+
store?: T;
|
|
112
|
+
/** JSONL file to persist the session to. If the file exists, its
|
|
113
|
+
* records are read into the DAG before any `add()` calls; either way
|
|
114
|
+
* the session opens it in append mode so subsequent commits land on
|
|
115
|
+
* disk in real time. Omit for in-memory sessions. */
|
|
116
|
+
path?: string;
|
|
117
|
+
/** Per-session directory for artifacts produced during this session —
|
|
118
|
+
* bash command logs (when output exceeds the inline cap), subagent
|
|
119
|
+
* traces, cached attachments, and any other side-channel data tools
|
|
120
|
+
* need to write somewhere session-scoped. */
|
|
121
|
+
dir?: string;
|
|
122
|
+
/** Initial session settings. Only used to fill in missing fields in the
|
|
123
|
+
* session's first `session-settings` node; */
|
|
124
|
+
defaults?: Omit<SessionSettings, "version">;
|
|
125
|
+
};
|
|
126
|
+
type SessionInit<T extends SessionStore = SessionStore> = SessionOptions & {
|
|
127
|
+
/** Storage backend. Required — `Session.load()` picks the right
|
|
128
|
+
* store based on options; direct construction passes one explicitly. */
|
|
129
|
+
store: T;
|
|
130
|
+
};
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/session/store.d.ts
|
|
133
|
+
/**
|
|
134
|
+
* Storage backend for a `Session`'s DAG.
|
|
135
|
+
*
|
|
136
|
+
* The store is the source of truth for nodes. `Session` reads through
|
|
137
|
+
* the store for chain walks, and writes new nodes via `write()`.
|
|
138
|
+
*
|
|
139
|
+
* Implementations:
|
|
140
|
+
* - `MemoryStore` — `Map<uuid, SessionNode>`, no persistence. Used
|
|
141
|
+
* by tests and ephemeral / in-process sessions.
|
|
142
|
+
* - `JsonlStore` — append-only JSONL file. Eager-loads all records on
|
|
143
|
+
* `load()`; future versions may add lazy reads + LRU caching.
|
|
144
|
+
*
|
|
145
|
+
* Async by design — even `get()` may involve I/O for backends that don't
|
|
146
|
+
* cache the full DAG in memory. Callers that need synchronous reads
|
|
147
|
+
* should pre-warm via `Session`'s chain walk (which populates Session's
|
|
148
|
+
* own caches as it goes).
|
|
149
|
+
*
|
|
150
|
+
* `root` is the most recently appended node. Loading a store sets `root`
|
|
151
|
+
* from the last persisted record. Writes advance `root` synchronously
|
|
152
|
+
* (the in-memory state must reflect the append before `write()` resolves)
|
|
153
|
+
* so subsequent `root` reads see the new node — even if the underlying
|
|
154
|
+
* I/O completes asynchronously.
|
|
155
|
+
*/
|
|
156
|
+
interface SessionStore {
|
|
157
|
+
/** The most recently appended node — drives chain walks. Undefined
|
|
158
|
+
* for an empty store. */
|
|
159
|
+
readonly root: SessionNode | undefined;
|
|
160
|
+
/** Lookup by uuid. Returns undefined for unknown ids. */
|
|
161
|
+
get: (id: string) => Promise<SessionNode | undefined>;
|
|
162
|
+
/** Append a node. Becomes the new `root`. The in-memory state must
|
|
163
|
+
* reflect the append synchronously (so a follow-up `root` read sees
|
|
164
|
+
* it) even when underlying I/O is still in flight. */
|
|
165
|
+
write: (node: SessionNode) => Promise<void>;
|
|
166
|
+
/** Optional flush + cleanup hook — called by `Session.close()`. */
|
|
167
|
+
close?: () => Promise<void>;
|
|
168
|
+
/** Optional bulk iterator for the full DAG — used by `/tree` UI and
|
|
169
|
+
* the masker's full-history scans. Backends with lazy reads can
|
|
170
|
+
* override with a more efficient bulk-scan implementation. */
|
|
171
|
+
all?: () => Iterable<SessionNode> | AsyncIterable<SessionNode>;
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/session/jsonl.d.ts
|
|
175
|
+
/**
|
|
176
|
+
* JSONL-backed `SessionStore` — append-only file, one record per line.
|
|
177
|
+
*
|
|
178
|
+
* Current implementation reads the entire file on `load()` and keeps the
|
|
179
|
+
* full DAG in memory. Future optimizations (lazy reads with offset
|
|
180
|
+
* index, LRU cache for huge sessions) can land behind this same
|
|
181
|
+
* interface without changing `Session`.
|
|
182
|
+
*
|
|
183
|
+
* Crash tolerance: a truncated last line (interrupted mid-write) is
|
|
184
|
+
* silently dropped; any other JSON parse error mid-file throws so real
|
|
185
|
+
* corruption surfaces loudly.
|
|
186
|
+
*/
|
|
187
|
+
declare class JsonlStore implements SessionStore {
|
|
188
|
+
#private;
|
|
189
|
+
readonly path: string;
|
|
190
|
+
constructor(path: string);
|
|
191
|
+
/** Load + open: hydrate the DAG from `path` (when the file exists),
|
|
192
|
+
* then open the writer in append mode. New nodes land on disk as
|
|
193
|
+
* they're committed. */
|
|
194
|
+
static load(path: string): Promise<JsonlStore>;
|
|
195
|
+
get root(): SessionNode | undefined;
|
|
196
|
+
get(id: string): Promise<SessionNode | undefined>;
|
|
197
|
+
write(node: SessionNode): Promise<void>;
|
|
198
|
+
close(): Promise<void>;
|
|
199
|
+
all(): AsyncIterable<SessionNode>;
|
|
200
|
+
}
|
|
201
|
+
declare class JsonlReader<T> {
|
|
202
|
+
#private;
|
|
203
|
+
readonly path: string;
|
|
204
|
+
constructor(path: string);
|
|
205
|
+
private generator;
|
|
206
|
+
next(): Promise<T | undefined>;
|
|
207
|
+
close(): Promise<void>;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/session/memory.d.ts
|
|
211
|
+
/**
|
|
212
|
+
* In-memory `SessionStore` — a `Map<uuid, SessionNode>` plus a `root`
|
|
213
|
+
* pointer. No persistence. Construction takes optional initial nodes
|
|
214
|
+
* for tests, replay tools, and the Claude session importer.
|
|
215
|
+
*/
|
|
216
|
+
declare class MemoryStore implements SessionStore {
|
|
217
|
+
#private;
|
|
218
|
+
constructor(nodes?: Iterable<SessionNode>);
|
|
219
|
+
get root(): SessionNode | undefined;
|
|
220
|
+
get(id: string): Promise<SessionNode | undefined>;
|
|
221
|
+
write(node: SessionNode): Promise<void>;
|
|
222
|
+
all(): Iterable<SessionNode>;
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/session/session.d.ts
|
|
226
|
+
/**
|
|
227
|
+
* Conversation primitive. Owns a DAG of message + compaction + meta
|
|
228
|
+
* nodes via a `SessionStore` backend; the *active* message list is
|
|
229
|
+
* whatever you get by walking `parentUuid` backwards from the store's
|
|
230
|
+
* root until you hit a `compact` node (or the start).
|
|
231
|
+
*
|
|
232
|
+
* **Meta is snapshot-based.** `session-meta` nodes carry the full
|
|
233
|
+
* cumulative meta as of their commit time. Other node types (start,
|
|
234
|
+
* resume, message, compact) don't carry meta — `Session` reconstructs
|
|
235
|
+
* the cumulative meta at any point by finding the most recent
|
|
236
|
+
* `session-meta` ancestor in a forward pass over the chain. Public
|
|
237
|
+
* APIs return `SessionNodeView` (raw node + decorated `meta`).
|
|
238
|
+
*
|
|
239
|
+
* Designed so the loop, the persistor, the TUI, and any replay tool
|
|
240
|
+
* all read from one source of truth and never mutate the DAG behind
|
|
241
|
+
* each other's backs:
|
|
242
|
+
*
|
|
243
|
+
* - **Run loop** (`Agent`) calls `add()` on each new message and
|
|
244
|
+
* `compact()` on overflow.
|
|
245
|
+
* - **TUI** subscribes to the `node` event for streaming render.
|
|
246
|
+
* - **Persistor** is the `SessionStore` — `MemoryStore` for ephemeral
|
|
247
|
+
* sessions, `JsonlStore` for file-backed.
|
|
248
|
+
*
|
|
249
|
+
* Construction:
|
|
250
|
+
* - Use `await Session.load(opts)` for the recommended path — picks
|
|
251
|
+
* the right store (Memory if no path, JSONL if path).
|
|
252
|
+
* - For tests / direct embedding, `new Session({ store })` works with
|
|
253
|
+
* any pre-built store.
|
|
254
|
+
*/
|
|
255
|
+
declare class Session<T extends SessionStore = SessionStore> extends Emitter<SessionEvents> {
|
|
256
|
+
#private;
|
|
257
|
+
protected constructor(opts: SessionInit<T>);
|
|
258
|
+
/** Recommended one-step path: pick the right store backend, hydrate
|
|
259
|
+
* if applicable, return a ready-to-use session.
|
|
260
|
+
*
|
|
261
|
+
* - `path` provided → `JsonlStore` (file-backed, hydrates from disk
|
|
262
|
+
* if the file exists, opens for append either way).
|
|
263
|
+
* - No `path` → `MemoryStore` (ephemeral, no persistence). */
|
|
264
|
+
static load<T extends SessionStore = SessionStore>(opts: SessionOptions<T> & {
|
|
265
|
+
store: T;
|
|
266
|
+
}): Promise<Session<T>>;
|
|
267
|
+
static load(opts: SessionOptions & {
|
|
268
|
+
path: string;
|
|
269
|
+
}): Promise<Session<JsonlStore>>;
|
|
270
|
+
static load(opts?: SessionOptions & {}): Promise<Session<MemoryStore>>;
|
|
271
|
+
private static create;
|
|
272
|
+
/** Efficiently fetch the most recent user message by walking backward until we hit
|
|
273
|
+
* a user message, without reconstructing the full chain. Useful for a session picker. */
|
|
274
|
+
static lastMessage(opts?: SessionOptions): Promise<Message<"user"> | undefined>;
|
|
275
|
+
/** Active conversation — chain from current head back to the most
|
|
276
|
+
* recent compact (exclusive), in chronological order. This is what
|
|
277
|
+
* the agent sees on the next request. */
|
|
278
|
+
get messages(): readonly Message[];
|
|
279
|
+
get maskCheckpoint(): MaskCheckpoint | undefined;
|
|
280
|
+
get id(): string;
|
|
281
|
+
get dir(): string;
|
|
282
|
+
get path(): string | undefined;
|
|
283
|
+
get started(): boolean;
|
|
284
|
+
/** Current session settings, merged with defaults. */
|
|
285
|
+
get settings(): SessionSettings;
|
|
286
|
+
update(settings: SessionUpdate, opts?: {
|
|
287
|
+
force?: boolean;
|
|
288
|
+
}): Promise<string>;
|
|
289
|
+
/** Explicitly mark the session as started — writes a `session-start`
|
|
290
|
+
* marker on a fresh session, `session-resume` on a hydrated one.
|
|
291
|
+
* Idempotent; subsequent calls are no-ops. Optional `meta` is
|
|
292
|
+
* applied via `update()` after the marker lands. */
|
|
293
|
+
start(settings?: SessionUpdate): Promise<string | undefined>;
|
|
294
|
+
/** Look up the decorated view for a node id — raw node plus the
|
|
295
|
+
* cumulative meta as of that node's position. Returns the
|
|
296
|
+
* message-typed view, or `undefined` if the id doesn't refer to a
|
|
297
|
+
* message in this session. */
|
|
298
|
+
node(id?: string | Message): Promise<(SessionNodeView & {
|
|
299
|
+
type: "message";
|
|
300
|
+
}) | undefined>;
|
|
301
|
+
/** Current head uuid — the root of the store, which is the most
|
|
302
|
+
* recently appended node. */
|
|
303
|
+
get head(): string | undefined;
|
|
304
|
+
/** Full DAG iterator — backed by `store.all()`. Yields raw nodes
|
|
305
|
+
* (no meta decoration). */
|
|
306
|
+
nodes(): Iterable<SessionNode> | AsyncIterable<SessionNode>;
|
|
307
|
+
get root(): SessionNode | undefined;
|
|
308
|
+
/** Append a message. The new node is parented to the current head,
|
|
309
|
+
* head advances, and a `node` event fires. Optional `meta` carries
|
|
310
|
+
* usage / model / finish info — populate it for assistant turns
|
|
311
|
+
* the agent commits after a step. Returns the assigned uuid.
|
|
312
|
+
*
|
|
313
|
+
* If `message.id` / `message.ts` are set (e.g. round-tripped from a
|
|
314
|
+
* prior load), they're used as the node's uuid / timestamp; otherwise
|
|
315
|
+
* fresh values are generated. The committed message always carries
|
|
316
|
+
* both — `m.id === node.uuid` and `m.ts === node.ts`. */
|
|
317
|
+
add(message: Message): Promise<string>;
|
|
318
|
+
/** Mark a compaction boundary. Subsequent `add()` calls land after
|
|
319
|
+
* this node; their messages form the new active conversation. The
|
|
320
|
+
* pre-compact chain stays in the store but is no longer part of
|
|
321
|
+
* `messages`. Also commits a fresh `session-meta` snapshot right
|
|
322
|
+
* after the compact so post-compact lazy walks always have a nearby
|
|
323
|
+
* meta anchor. */
|
|
324
|
+
compact(opts: {
|
|
325
|
+
trigger?: "manual" | "auto";
|
|
326
|
+
preTokens?: number;
|
|
327
|
+
durationMs?: number;
|
|
328
|
+
tail: number;
|
|
329
|
+
summary: Message<"system">;
|
|
330
|
+
}): Promise<string>;
|
|
331
|
+
addMaskCheckpoint(opts: MaskCheckpoint): Promise<string>;
|
|
332
|
+
checkout(uuid: string): Promise<void>;
|
|
333
|
+
/** Pre-active history of the current chain — messages from before
|
|
334
|
+
* the most recent compact (and earlier compacts), in chronological
|
|
335
|
+
* order. Useful for TUI scrollback that wants to render context the
|
|
336
|
+
* agent itself no longer sees.
|
|
337
|
+
*
|
|
338
|
+
* `limit` truncates from the front (oldest), so the most recent
|
|
339
|
+
* history messages always make it through. */
|
|
340
|
+
history(limit?: number): Promise<readonly Message[]>;
|
|
341
|
+
/** Flush + close the underlying store. Subsequent writes throw. */
|
|
342
|
+
close(): Promise<void>;
|
|
343
|
+
}
|
|
344
|
+
//#endregion
|
|
345
|
+
export { SessionStore as a, SessionEvents as c, SessionNode as d, SessionNodeView as f, SessionView as g, SessionUpdate as h, JsonlStore as i, SessionInit as l, SessionSettings as m, MemoryStore as n, MaskCheckpoint as o, SessionOptions as p, JsonlReader as r, PartialNode as s, Session as t, SessionMessage as u };
|
|
346
|
+
//# sourceMappingURL=session-Ds5nJz5A.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-Ds5nJz5A.d.mts","names":[],"sources":["../src/session/types.ts","../src/session/store.ts","../src/session/jsonl.ts","../src/session/memory.ts","../src/session/session.ts"],"mappings":";;;;;;;;;;KAYK;EACH;;EAEA;;EAEA;;EAEI;;EACA;;EACA;EAA0B,UAAU;;EACpC;EAAiB,SAAS;;EAC1B;EAAyB;EAAmB;;EAE5C;;EAEA;;EAEA;;EAEA;;EAEA;;;;;EAKA,SAAS;;KAGV,kBAAkB;KAEX;EAAmB;EAAmB;;KAEtC,iBAAiB;EAAY;EAAY;;KAEzC,YAAY,UAAU,kBAAkB,mBAAmB,QACrE;EACE,MAAM;;;;;KAME,kBAAkB;EAAgB,UAAU;;KAE5C;EACV,UAAU;EACV,OAAO,YAAY;EACnB,UAAU;EACV,iBAAiB;;KAGP;EACV;EACA;EACA;EACA;EACA;EACA,YAAY;;KAGF,gBAAgB,KAAK;KAE5B,iBAAiB,GAAG,UAAU,eAAe,oBAAoB,KAAK,GAAG;KAElE,cAAc,iBAAiB;;;KAM/B;EACV;IAAQ,MAAM;;EACd;IAAY;IAA0B,mBAAmB;;EACzD;IAAW,MAAM;;EAEjB;IAAO;IAAa;;EACpB;IAAS;IAAe;;EACxB;IAAa,QAAQ;IAAiB,OAAO;;EAE7C;EACA;;KAKU,eAAe,UAAU,eAAe;EAClD,QAAQ;;;;;EAKR;;;;;EAKA;;;EAGA,WAAW,KAAK;;KAGN,YAAY,UAAU,eAAe,gBAAgB;;;EAG/D,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;UC7FQ;;;WAGN,MAAM;;EAGf,MAAM,eAAe,QAAQ;;;;EAK7B,QAAQ,MAAM,gBAAgB;;EAG9B,cAAc;;;;EAKd,YAAY,SAAS,eAAe,cAAc;;;;;;;;;;;;;;;;cCvBvC,sBAAsB;;WACxB;EAKT,YAAY;;;;SAQC,KAAK,eAAe,QAAQ;MAmBrC,QAAQ;EAIN,IAAI,aAAa,QAAQ;EAIzB,MAAM,MAAM,cAAc;EAS1B,SAAS;EAIR,OAAO,cAAc;;cAMjB,YAAY;;WAMF;EAArB,YAAqB;UAsBN;EAgCT,QAAQ,QAAQ;EAMhB,SAAK;;;;;;;;;cC3IA,uBAAuB;;EAIlC,YAAY,QAAQ,SAAS;MAUzB,QAAQ;EAIN,IAAI,aAAa,QAAQ;EAIzB,MAAM,MAAM,cAAc;EAK/B,OAAO,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCoBN,QAAQ,UAAU,eAAe,sBAAsB,QAAQ;;YAYjE,YAAa,MAAM,YAAY;;;;;;;SAe3B,KAAK,UAAU,eAAe,cACzC,MAAM,eAAe;IAAO,OAAO;MAClC,QAAQ,QAAQ;SACN,KAAK,MAAM;IAAmB;MAAiB,QAAQ,QAAQ;SAC/D,KAAK,OAAO,sBAAsB,QAAQ,QAAQ;iBAO1C;;;SAUR,YAAY,OAAM,iBAAsB,QAAQ;;;;MA4BzD,qBAAqB;MAIrB,kBAAkB;MAIlB;MAIA;MAIA;MAIA;;MAKA,YAAY;EAIV,OAAO,UAAU,eAAe;IAAS;MAAoB;;;;;EAe7D,MAAM,WAAW,gBAAgB;;;;;EAcjC,KAAK,cAAc,UAAU,SAAS;IAAoB;;;;MAS5D;;;EAMJ,SAAS,SAAS,eAAe,cAAc;MAI3C,QAAQ;;;;;;;;;;EAeN,IAAI,SAAS,UAAU;;;;;;;EAavB,QAAQ;IACZ;IACA;IACA;IACA;IACA,SAAS;MACP;EAmBE,kBAAkB,MAAM,iBAAiB;EAKzC,SAAS,eAAe;;;;;;;;EAaxB,QAAQ,iBAAiB,iBAAiB;;EAM1C,SAAS"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { a as SessionStore, c as SessionEvents, d as SessionNode, f as SessionNodeView, g as SessionView, h as SessionUpdate, i as JsonlStore, l as SessionInit, m as SessionSettings, n as MemoryStore, o as MaskCheckpoint, p as SessionOptions, r as JsonlReader, s as PartialNode, t as Session, u as SessionMessage } from "./session-Ds5nJz5A.mjs";
|
|
2
|
+
import { Stats } from "node:fs";
|
|
3
|
+
//#region src/session/manager.d.ts
|
|
4
|
+
type SessionInfo = {
|
|
5
|
+
/** uuidv7 session id. Absent for non-managed external paths until the
|
|
6
|
+
* session is loaded — the file's session-start node carries the
|
|
7
|
+
* authoritative id. */
|
|
8
|
+
id: string;
|
|
9
|
+
/** Absolute path to the session file. Typically within `zalyPaths.sessions`. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Data directory used for session artifacts */
|
|
12
|
+
dir: string;
|
|
13
|
+
/** The session's workspace containing its .zaly/ resources */
|
|
14
|
+
workspace: string;
|
|
15
|
+
/** Populated when listing with `sort: true` */
|
|
16
|
+
mtime?: number;
|
|
17
|
+
stat?: Stats;
|
|
18
|
+
};
|
|
19
|
+
type SessionFilter = {
|
|
20
|
+
/** (partial) uuid for a session */
|
|
21
|
+
id?: string;
|
|
22
|
+
/** sessions with this workspace */
|
|
23
|
+
workspace?: string;
|
|
24
|
+
/** glob pattern matching any workspace sessions */
|
|
25
|
+
pattern?: string;
|
|
26
|
+
};
|
|
27
|
+
type SessionListOpts = {
|
|
28
|
+
filter?: string | SessionFilter;
|
|
29
|
+
sort?: boolean;
|
|
30
|
+
};
|
|
31
|
+
declare function listSessions(opts?: SessionListOpts): Promise<SessionInfo[]>;
|
|
32
|
+
declare function resumeSession(filter: string | SessionFilter): Promise<Session | undefined>;
|
|
33
|
+
declare function loadSession(opts?: Partial<SessionInfo>): Promise<Session>;
|
|
34
|
+
declare function sessionInfo(opts?: Partial<SessionInfo>): SessionInfo;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { JsonlReader, JsonlStore, type MaskCheckpoint, MemoryStore, type PartialNode, Session, type SessionEvents, SessionFilter, SessionInfo, type SessionInit, SessionListOpts, type SessionMessage, type SessionNode, type SessionNodeView, type SessionOptions, type SessionSettings, type SessionStore, type SessionUpdate, type SessionView, listSessions, loadSession, resumeSession, sessionInfo };
|
|
37
|
+
//# sourceMappingURL=session.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.mts","names":[],"sources":["../src/session/manager.ts"],"mappings":";;;KAUY;;;;EAIV;;EAEA;;EAEA;;EAEA;;EAEA;EACA,OAAO;;KAGG;;EAEV;;EAEA;;EAEA;;KAGU;EACV,kBAAkB;EAClB;;iBAcoB,aAAa,OAAM,kBAAoB,QAAA;iBAyCvC,cAAc,iBAAiB,gBAAgB,QAAQ;iBAKvD,YAAY,OAAM,QAAQ,eAAoB,QAAQ;iBAc5D,YAAY,OAAM,QAAQ,eAAoB"}
|
package/dist/session.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { n as isUuidv7Like, r as uuidv7 } from "./uuid-ByXP5SO5.mjs";
|
|
2
|
+
import { n as JsonlStore, t as JsonlReader } from "./jsonl-Dw_VBp46.mjs";
|
|
3
|
+
import { r as MemoryStore, t as Session } from "./session-CUUdWFC5.mjs";
|
|
4
|
+
import { decodePath, encodePath, normPath, safeStatAsync } from "@zaly/shared";
|
|
5
|
+
import { basename, dirname, join, relative } from "pathe";
|
|
6
|
+
import { glob } from "@zaly/shared/glob";
|
|
7
|
+
import { mkdir, stat } from "node:fs/promises";
|
|
8
|
+
import { zalyPaths } from "@zaly/shared/paths";
|
|
9
|
+
//#region src/session/manager.ts
|
|
10
|
+
async function toFilter(filter) {
|
|
11
|
+
if (isUuidv7Like(filter)) return { id: filter };
|
|
12
|
+
if ((await safeStatAsync(filter))?.isDirectory()) return { workspace: filter };
|
|
13
|
+
return { pattern: filter };
|
|
14
|
+
}
|
|
15
|
+
function workspaceSlug(workspace) {
|
|
16
|
+
return encodePath(normPath(workspace));
|
|
17
|
+
}
|
|
18
|
+
async function listSessions(opts = {}) {
|
|
19
|
+
const filter = typeof opts.filter === "string" ? await toFilter(opts.filter) : opts.filter ?? {};
|
|
20
|
+
let slug = filter.workspace ? workspaceSlug(filter.workspace) : void 0;
|
|
21
|
+
slug ??= filter.pattern ? `*${filter.pattern}*` : void 0;
|
|
22
|
+
slug ??= "*";
|
|
23
|
+
const isGlob = slug.includes("*");
|
|
24
|
+
const root = isGlob ? zalyPaths.sessions : join(zalyPaths.sessions, slug);
|
|
25
|
+
const pattern = [];
|
|
26
|
+
if (isGlob) pattern.push(slug);
|
|
27
|
+
if (filter.id) pattern.push(filter.id.length >= 36 ? filter.id.slice(0, 36) : `${filter.id}*`);
|
|
28
|
+
else pattern.push("*");
|
|
29
|
+
pattern.push("session.jsonl");
|
|
30
|
+
const paths = glob(pattern.join("/"), {
|
|
31
|
+
cwd: root,
|
|
32
|
+
depth: pattern.length,
|
|
33
|
+
ignore: false,
|
|
34
|
+
type: "file"
|
|
35
|
+
});
|
|
36
|
+
const ret = [];
|
|
37
|
+
for await (const rel of paths) {
|
|
38
|
+
const path = normPath(root, rel);
|
|
39
|
+
ret.push(sessionInfo({ path }));
|
|
40
|
+
}
|
|
41
|
+
if (!opts.sort) return ret;
|
|
42
|
+
return (await Promise.all(ret.map(async (session) => {
|
|
43
|
+
const s = await stat(session.path).catch(() => void 0);
|
|
44
|
+
session.mtime = s?.mtimeMs;
|
|
45
|
+
session.stat = s;
|
|
46
|
+
return session;
|
|
47
|
+
}))).filter((e) => e.mtime !== void 0).toSorted((a, b) => b.mtime - a.mtime);
|
|
48
|
+
}
|
|
49
|
+
async function resumeSession(filter) {
|
|
50
|
+
const list = await listSessions({
|
|
51
|
+
filter,
|
|
52
|
+
sort: true
|
|
53
|
+
});
|
|
54
|
+
return list.length === 0 ? void 0 : loadSession(list[0]);
|
|
55
|
+
}
|
|
56
|
+
async function loadSession(opts = {}) {
|
|
57
|
+
const info = sessionInfo(opts);
|
|
58
|
+
await mkdir(info.dir, { recursive: true });
|
|
59
|
+
return Session.load({
|
|
60
|
+
defaults: {
|
|
61
|
+
cwd: normPath(),
|
|
62
|
+
sessionId: info.id,
|
|
63
|
+
workspace: info.workspace
|
|
64
|
+
},
|
|
65
|
+
dir: info.dir,
|
|
66
|
+
path: info.path
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function sessionInfo(opts = {}) {
|
|
70
|
+
if (opts.path && !relative(zalyPaths.sessions, opts.path).startsWith("..")) {
|
|
71
|
+
const path = normPath(opts.path);
|
|
72
|
+
const dir = normPath(opts.dir ?? dirname(path));
|
|
73
|
+
const id = opts.id ?? basename(dir);
|
|
74
|
+
const slug = basename(dirname(dir));
|
|
75
|
+
return {
|
|
76
|
+
dir,
|
|
77
|
+
id,
|
|
78
|
+
path,
|
|
79
|
+
workspace: normPath(opts.workspace ?? decodePath(slug))
|
|
80
|
+
};
|
|
81
|
+
} else if (opts.path) {
|
|
82
|
+
const path = normPath(opts.path);
|
|
83
|
+
const dir = normPath(opts.dir ?? dirname(path));
|
|
84
|
+
const workspace = normPath(opts.workspace);
|
|
85
|
+
return {
|
|
86
|
+
dir,
|
|
87
|
+
id: uuidv7(),
|
|
88
|
+
path,
|
|
89
|
+
workspace
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const workspace = normPath(opts.workspace);
|
|
93
|
+
const slug = workspaceSlug(workspace);
|
|
94
|
+
const id = opts.id ?? uuidv7();
|
|
95
|
+
const dir = opts.dir ? normPath(opts.dir) : normPath(zalyPaths.sessions, slug, id);
|
|
96
|
+
return {
|
|
97
|
+
dir,
|
|
98
|
+
id,
|
|
99
|
+
path: normPath(dir, "session.jsonl"),
|
|
100
|
+
workspace
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
export { JsonlReader, JsonlStore, MemoryStore, Session, listSessions, loadSession, resumeSession, sessionInfo };
|
|
105
|
+
|
|
106
|
+
//# sourceMappingURL=session.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.mjs","names":[],"sources":["../src/session/manager.ts"],"sourcesContent":["import type { Stats } from \"node:fs\"\n\nimport { decodePath, encodePath, normPath, safeStatAsync } from \"@zaly/shared\"\nimport { glob } from \"@zaly/shared/glob\"\nimport { zalyPaths } from \"@zaly/shared/paths\"\nimport { mkdir, stat } from \"node:fs/promises\"\nimport { basename, dirname, join, relative } from \"pathe\"\nimport { isUuidv7Like, uuidv7 } from \"../utils/uuid.ts\"\nimport { Session } from \"./session.ts\"\n\nexport type SessionInfo = {\n /** uuidv7 session id. Absent for non-managed external paths until the\n * session is loaded — the file's session-start node carries the\n * authoritative id. */\n id: string\n /** Absolute path to the session file. Typically within `zalyPaths.sessions`. */\n path: string\n /** Data directory used for session artifacts */\n dir: string\n /** The session's workspace containing its .zaly/ resources */\n workspace: string\n /** Populated when listing with `sort: true` */\n mtime?: number\n stat?: Stats\n}\n\nexport type SessionFilter = {\n /** (partial) uuid for a session */\n id?: string\n /** sessions with this workspace */\n workspace?: string\n /** glob pattern matching any workspace sessions */\n pattern?: string\n}\n\nexport type SessionListOpts = {\n filter?: string | SessionFilter\n sort?: boolean\n}\n\nasync function toFilter(filter: string): Promise<SessionFilter> {\n if (isUuidv7Like(filter)) return { id: filter }\n const s = await safeStatAsync(filter)\n if (s?.isDirectory()) return { workspace: filter }\n return { pattern: filter }\n}\n\nfunction workspaceSlug(workspace?: string) {\n return encodePath(normPath(workspace))\n}\n\nexport async function listSessions(opts: SessionListOpts = {}) {\n const filter = typeof opts.filter === \"string\" ? await toFilter(opts.filter) : (opts.filter ?? {})\n let slug = filter.workspace ? workspaceSlug(filter.workspace) : undefined\n slug ??= filter.pattern ? `*${filter.pattern}*` : undefined\n slug ??= \"*\"\n const isGlob = slug.includes(\"*\")\n const root = isGlob ? zalyPaths.sessions : join(zalyPaths.sessions, slug)\n\n const pattern: string[] = []\n if (isGlob) pattern.push(slug)\n\n if (filter.id) pattern.push(filter.id.length >= 36 ? filter.id.slice(0, 36) : `${filter.id}*`)\n else pattern.push(\"*\")\n\n pattern.push(\"session.jsonl\")\n\n const paths = glob(pattern.join(\"/\"), {\n cwd: root,\n depth: pattern.length,\n ignore: false,\n type: \"file\",\n })\n const ret: SessionInfo[] = []\n for await (const rel of paths) {\n const path = normPath(root, rel)\n ret.push(sessionInfo({ path }))\n }\n if (!opts.sort) return ret\n const stats = await Promise.all(\n ret.map(async (session) => {\n const s = await stat(session.path).catch(() => undefined)\n session.mtime = s?.mtimeMs\n session.stat = s\n return session\n })\n )\n return stats\n .filter((e): e is SessionInfo & { mtime: number } => e.mtime !== undefined)\n .toSorted((a, b) => b.mtime - a.mtime) // newest first\n}\n\nexport async function resumeSession(filter: string | SessionFilter): Promise<Session | undefined> {\n const list = await listSessions({ filter, sort: true })\n return list.length === 0 ? undefined : loadSession(list[0])\n}\n\nexport async function loadSession(opts: Partial<SessionInfo> = {}): Promise<Session> {\n const info = sessionInfo(opts)\n await mkdir(info.dir, { recursive: true })\n return Session.load({\n defaults: {\n cwd: normPath(),\n sessionId: info.id,\n workspace: info.workspace,\n },\n dir: info.dir,\n path: info.path,\n })\n}\n\nexport function sessionInfo(opts: Partial<SessionInfo> = {}): SessionInfo {\n if (opts.path && !relative(zalyPaths.sessions, opts.path).startsWith(\"..\")) {\n // Managed session\n const path = normPath(opts.path)\n const dir = normPath(opts.dir ?? dirname(path))\n const id = opts.id ?? basename(dir)\n const slug = basename(dirname(dir))\n const workspace = normPath(opts.workspace ?? decodePath(slug))\n return { dir, id, path, workspace }\n } else if (opts.path) {\n // Existing session, but not in a managed location\n const path = normPath(opts.path)\n const dir = normPath(opts.dir ?? dirname(path))\n const workspace = normPath(opts.workspace)\n return { dir, id: uuidv7(), path, workspace }\n }\n // New session\n const workspace = normPath(opts.workspace) // defaults to cwd\n const slug = workspaceSlug(workspace)\n const id = opts.id ?? uuidv7()\n const dir = opts.dir ? normPath(opts.dir) : normPath(zalyPaths.sessions, slug, id)\n const path = normPath(dir, \"session.jsonl\")\n return { dir, id, path, workspace }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { r as uuidv7 } from "./uuid-ByXP5SO5.mjs";
|
|
2
|
+
import { join } from "pathe";
|
|
3
|
+
import { AiError, defineTool, stringifyContent } from "@zaly/ai";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
//#region src/tools/subagent.ts
|
|
7
|
+
const subagentTool = defineTool({
|
|
8
|
+
name: "subagent",
|
|
9
|
+
desc: "Spawn a focused subagent with the same model and permissions. Use for tasks that benefit from a clean context window: deep code exploration, multi-step research, or anything where dragging the intermediate steps through your own context would be expensive. The subagent's final answer comes back as the result; full transcript is available at the returned `sessionPath` if you need more detail.",
|
|
10
|
+
parallel: true,
|
|
11
|
+
params: Type.Object({
|
|
12
|
+
description: Type.String({ description: "Short description of what the subagent should do. Shown to the user in the TUI. Keep under ~10 words." }),
|
|
13
|
+
prompt: Type.String({ description: "System prompt for the subagent — defines its role and constraints. The subagent does NOT inherit your system prompt; spell out what it needs to know." }),
|
|
14
|
+
task: Type.String({ description: "The actual task the subagent should perform, as if you were sending it as a user message. Be specific about the deliverable." })
|
|
15
|
+
}),
|
|
16
|
+
async call(args, ctx) {
|
|
17
|
+
const parent = ctx.agent;
|
|
18
|
+
if (!parent) throw new AiError({
|
|
19
|
+
code: "MISSING_TOOL_CONTEXT",
|
|
20
|
+
message: "subagent requires an Agent reference on the context (set up by the agent harness)."
|
|
21
|
+
});
|
|
22
|
+
const id = uuidv7();
|
|
23
|
+
const sessionPath = join(tmpdir(), `zaly-subagent-${id}.jsonl`);
|
|
24
|
+
const startedAt = Date.now();
|
|
25
|
+
const { Session } = await import("./session-CUUdWFC5.mjs").then((n) => n.n);
|
|
26
|
+
const child = await parent.child({
|
|
27
|
+
prompt: [args.prompt],
|
|
28
|
+
session: await Session.load({ path: sessionPath })
|
|
29
|
+
});
|
|
30
|
+
const depth = child.depth;
|
|
31
|
+
let textBuffer = "";
|
|
32
|
+
let cursor = 0;
|
|
33
|
+
let finalResult;
|
|
34
|
+
let stopReason;
|
|
35
|
+
child.on("stream-event", ({ event }) => {
|
|
36
|
+
if (event.type === "text-delta" && typeof event.delta === "string") textBuffer += event.delta;
|
|
37
|
+
});
|
|
38
|
+
child.on("stop", ({ kind }) => {
|
|
39
|
+
stopReason = kind;
|
|
40
|
+
});
|
|
41
|
+
child.send({
|
|
42
|
+
content: args.task,
|
|
43
|
+
role: "user"
|
|
44
|
+
});
|
|
45
|
+
const runDone = child.run().catch((error) => {
|
|
46
|
+
stopReason = "error";
|
|
47
|
+
throw error;
|
|
48
|
+
});
|
|
49
|
+
const buildMeta = (running) => ({
|
|
50
|
+
depth,
|
|
51
|
+
durationMs: Date.now() - startedAt,
|
|
52
|
+
id,
|
|
53
|
+
sessionPath,
|
|
54
|
+
status: running ? "running" : "done",
|
|
55
|
+
stop: stopReason,
|
|
56
|
+
usage: {
|
|
57
|
+
input: child.totalUsage.input,
|
|
58
|
+
output: child.totalUsage.output
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
/** Pull the last assistant text out of the child's session — the
|
|
62
|
+
* user-visible "answer." Falls back to whatever's accumulated in the
|
|
63
|
+
* text buffer if the assistant hasn't committed a message yet. */
|
|
64
|
+
const finalText = () => {
|
|
65
|
+
for (let i = child.messages.length - 1; i >= 0; i--) {
|
|
66
|
+
const m = child.messages[i];
|
|
67
|
+
if (m.role !== "assistant") continue;
|
|
68
|
+
const text = stringifyContent(m.content).trim();
|
|
69
|
+
if (text !== "") return text;
|
|
70
|
+
}
|
|
71
|
+
return textBuffer.trim();
|
|
72
|
+
};
|
|
73
|
+
const buildResult = (running) => {
|
|
74
|
+
if (!running && finalResult) return {
|
|
75
|
+
...finalResult,
|
|
76
|
+
running: false
|
|
77
|
+
};
|
|
78
|
+
const meta = buildMeta(running);
|
|
79
|
+
const slice = textBuffer.slice(cursor);
|
|
80
|
+
cursor = textBuffer.length;
|
|
81
|
+
const parts = [{
|
|
82
|
+
data: meta,
|
|
83
|
+
tag: "subagent",
|
|
84
|
+
type: "meta"
|
|
85
|
+
}];
|
|
86
|
+
if (running) {
|
|
87
|
+
if (slice !== "") parts.push({
|
|
88
|
+
text: slice,
|
|
89
|
+
type: "text"
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
const answer = finalText();
|
|
93
|
+
if (answer !== "") parts.push({
|
|
94
|
+
text: answer,
|
|
95
|
+
type: "text"
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
content: parts,
|
|
100
|
+
isError: false,
|
|
101
|
+
running
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
abort: () => {
|
|
106
|
+
child.stop();
|
|
107
|
+
},
|
|
108
|
+
done: runDone.then(async () => {
|
|
109
|
+
await child.session.close();
|
|
110
|
+
finalResult = buildResult(false);
|
|
111
|
+
}, async (error) => {
|
|
112
|
+
stopReason ??= "error";
|
|
113
|
+
await child.session.close().catch(() => void 0);
|
|
114
|
+
const meta = buildMeta(false);
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
finalResult = {
|
|
117
|
+
content: [{
|
|
118
|
+
data: meta,
|
|
119
|
+
tag: "subagent",
|
|
120
|
+
type: "meta"
|
|
121
|
+
}, {
|
|
122
|
+
text: `subagent failed: ${message}`,
|
|
123
|
+
type: "text"
|
|
124
|
+
}],
|
|
125
|
+
isError: true
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
128
|
+
hasNew: () => textBuffer.length > cursor,
|
|
129
|
+
poll: () => buildResult(child.status !== "idle" && child.status !== "paused")
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
//#endregion
|
|
134
|
+
export { subagentTool };
|
|
135
|
+
|
|
136
|
+
//# sourceMappingURL=subagent-baqzjRMc.mjs.map
|