@eminent337/aery-core 0.67.120 → 0.67.121
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 +14 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +31 -1
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +4 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +7 -3
- package/dist/agent.js.map +1 -1
- package/dist/harness/agent-harness.d.ts +103 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +788 -0
- package/dist/harness/agent-harness.js.map +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +88 -0
- package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
- package/dist/harness/compaction/branch-summarization.js +243 -0
- package/dist/harness/compaction/branch-summarization.js.map +1 -0
- package/dist/harness/compaction/compaction.d.ts +122 -0
- package/dist/harness/compaction/compaction.d.ts.map +1 -0
- package/dist/harness/compaction/compaction.js +631 -0
- package/dist/harness/compaction/compaction.js.map +1 -0
- package/dist/harness/compaction/utils.d.ts +38 -0
- package/dist/harness/compaction/utils.d.ts.map +1 -0
- package/dist/harness/compaction/utils.js +153 -0
- package/dist/harness/compaction/utils.js.map +1 -0
- package/dist/harness/env/nodejs.d.ts +50 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +487 -0
- package/dist/harness/env/nodejs.js.map +1 -0
- package/dist/harness/execution-env.d.ts +4 -0
- package/dist/harness/execution-env.d.ts.map +1 -0
- package/dist/harness/execution-env.js +3 -0
- package/dist/harness/execution-env.js.map +1 -0
- package/dist/harness/factory.d.ts +6 -0
- package/dist/harness/factory.d.ts.map +1 -0
- package/dist/harness/factory.js +9 -0
- package/dist/harness/factory.js.map +1 -0
- package/dist/harness/messages.d.ts +51 -0
- package/dist/harness/messages.d.ts.map +1 -0
- package/dist/harness/messages.js +102 -0
- package/dist/harness/messages.js.map +1 -0
- package/dist/harness/prompt-templates.d.ts +47 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +201 -0
- package/dist/harness/prompt-templates.js.map +1 -0
- package/dist/harness/session/jsonl-repo.d.ts +26 -0
- package/dist/harness/session/jsonl-repo.d.ts.map +1 -0
- package/dist/harness/session/jsonl-repo.js +97 -0
- package/dist/harness/session/jsonl-repo.js.map +1 -0
- package/dist/harness/session/jsonl-storage.d.ts +33 -0
- package/dist/harness/session/jsonl-storage.d.ts.map +1 -0
- package/dist/harness/session/jsonl-storage.js +159 -0
- package/dist/harness/session/jsonl-storage.js.map +1 -0
- package/dist/harness/session/memory-repo.d.ts +18 -0
- package/dist/harness/session/memory-repo.d.ts.map +1 -0
- package/dist/harness/session/memory-repo.js +42 -0
- package/dist/harness/session/memory-repo.js.map +1 -0
- package/dist/harness/session/memory-storage.d.ts +26 -0
- package/dist/harness/session/memory-storage.d.ts.map +1 -0
- package/dist/harness/session/memory-storage.js +89 -0
- package/dist/harness/session/memory-storage.js.map +1 -0
- package/dist/harness/session/repo/jsonl.d.ts +20 -0
- package/dist/harness/session/repo/jsonl.d.ts.map +1 -0
- package/dist/harness/session/repo/jsonl.js +92 -0
- package/dist/harness/session/repo/jsonl.js.map +1 -0
- package/dist/harness/session/repo/memory.d.ts +18 -0
- package/dist/harness/session/repo/memory.d.ts.map +1 -0
- package/dist/harness/session/repo/memory.js +42 -0
- package/dist/harness/session/repo/memory.js.map +1 -0
- package/dist/harness/session/repo/shared.d.ts +10 -0
- package/dist/harness/session/repo/shared.d.ts.map +1 -0
- package/dist/harness/session/repo/shared.js +31 -0
- package/dist/harness/session/repo/shared.js.map +1 -0
- package/dist/harness/session/repo-utils.d.ts +10 -0
- package/dist/harness/session/repo-utils.d.ts.map +1 -0
- package/dist/harness/session/repo-utils.js +31 -0
- package/dist/harness/session/repo-utils.js.map +1 -0
- package/dist/harness/session/session.d.ts +32 -0
- package/dist/harness/session/session.d.ts.map +1 -0
- package/dist/harness/session/session.js +196 -0
- package/dist/harness/session/session.js.map +1 -0
- package/dist/harness/session/storage/jsonl.d.ts +30 -0
- package/dist/harness/session/storage/jsonl.d.ts.map +1 -0
- package/dist/harness/session/storage/jsonl.js +170 -0
- package/dist/harness/session/storage/jsonl.js.map +1 -0
- package/dist/harness/session/storage/memory.d.ts +26 -0
- package/dist/harness/session/storage/memory.d.ts.map +1 -0
- package/dist/harness/session/storage/memory.js +90 -0
- package/dist/harness/session/storage/memory.js.map +1 -0
- package/dist/harness/session/uuid.d.ts +2 -0
- package/dist/harness/session/uuid.d.ts.map +1 -0
- package/dist/harness/session/uuid.js +50 -0
- package/dist/harness/session/uuid.js.map +1 -0
- package/dist/harness/skills.d.ts +43 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +255 -0
- package/dist/harness/skills.js.map +1 -0
- package/dist/harness/system-prompt.d.ts +3 -0
- package/dist/harness/system-prompt.d.ts.map +1 -0
- package/dist/harness/system-prompt.js +30 -0
- package/dist/harness/system-prompt.js.map +1 -0
- package/dist/harness/types.d.ts +578 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +56 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/utils/shell-output.d.ts +14 -0
- package/dist/harness/utils/shell-output.d.ts.map +1 -0
- package/dist/harness/utils/shell-output.js +125 -0
- package/dist/harness/utils/shell-output.js.map +1 -0
- package/dist/harness/utils/truncate.d.ts +70 -0
- package/dist/harness/utils/truncate.d.ts.map +1 -0
- package/dist/harness/utils/truncate.js +288 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +3 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +3 -0
- package/dist/node.js.map +1 -0
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +5 -2
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +50 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -2
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
import { streamSimple, } from "@eminent337/aery-ai";
|
|
2
|
+
import { runAgentLoop } from "../agent-loop.js";
|
|
3
|
+
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.js";
|
|
4
|
+
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.js";
|
|
5
|
+
import { convertToLlm } from "./messages.js";
|
|
6
|
+
import { formatPromptTemplateInvocation } from "./prompt-templates.js";
|
|
7
|
+
import { formatSkillInvocation } from "./skills.js";
|
|
8
|
+
function createUserMessage(text, images) {
|
|
9
|
+
const content = [{ type: "text", text }];
|
|
10
|
+
if (images)
|
|
11
|
+
content.push(...images);
|
|
12
|
+
return { role: "user", content, timestamp: Date.now() };
|
|
13
|
+
}
|
|
14
|
+
function createFailureMessage(model, error, aborted) {
|
|
15
|
+
return {
|
|
16
|
+
role: "assistant",
|
|
17
|
+
content: [{ type: "text", text: "" }],
|
|
18
|
+
api: model.api,
|
|
19
|
+
provider: model.provider,
|
|
20
|
+
model: model.id,
|
|
21
|
+
stopReason: aborted ? "aborted" : "error",
|
|
22
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
23
|
+
timestamp: Date.now(),
|
|
24
|
+
usage: {
|
|
25
|
+
input: 0,
|
|
26
|
+
output: 0,
|
|
27
|
+
cacheRead: 0,
|
|
28
|
+
cacheWrite: 0,
|
|
29
|
+
totalTokens: 0,
|
|
30
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function cloneStreamOptions(streamOptions) {
|
|
35
|
+
return {
|
|
36
|
+
...streamOptions,
|
|
37
|
+
headers: streamOptions?.headers ? { ...streamOptions.headers } : undefined,
|
|
38
|
+
metadata: streamOptions?.metadata ? { ...streamOptions.metadata } : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function mergeHeaders(...headers) {
|
|
42
|
+
const merged = {};
|
|
43
|
+
let hasHeaders = false;
|
|
44
|
+
for (const entry of headers) {
|
|
45
|
+
if (!entry)
|
|
46
|
+
continue;
|
|
47
|
+
Object.assign(merged, entry);
|
|
48
|
+
hasHeaders = true;
|
|
49
|
+
}
|
|
50
|
+
return hasHeaders ? merged : undefined;
|
|
51
|
+
}
|
|
52
|
+
function applyStreamOptionsPatch(base, patch) {
|
|
53
|
+
const result = cloneStreamOptions(base);
|
|
54
|
+
if (!patch)
|
|
55
|
+
return result;
|
|
56
|
+
if (Object.hasOwn(patch, "transport"))
|
|
57
|
+
result.transport = patch.transport;
|
|
58
|
+
if (Object.hasOwn(patch, "timeoutMs"))
|
|
59
|
+
result.timeoutMs = patch.timeoutMs;
|
|
60
|
+
if (Object.hasOwn(patch, "maxRetries"))
|
|
61
|
+
result.maxRetries = patch.maxRetries;
|
|
62
|
+
if (Object.hasOwn(patch, "maxRetryDelayMs"))
|
|
63
|
+
result.maxRetryDelayMs = patch.maxRetryDelayMs;
|
|
64
|
+
if (Object.hasOwn(patch, "cacheRetention"))
|
|
65
|
+
result.cacheRetention = patch.cacheRetention;
|
|
66
|
+
if (Object.hasOwn(patch, "headers")) {
|
|
67
|
+
if (patch.headers === undefined) {
|
|
68
|
+
result.headers = undefined;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const headers = { ...(result.headers ?? {}) };
|
|
72
|
+
for (const [key, value] of Object.entries(patch.headers)) {
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
delete headers[key];
|
|
75
|
+
else
|
|
76
|
+
headers[key] = value;
|
|
77
|
+
}
|
|
78
|
+
result.headers = Object.keys(headers).length > 0 ? headers : undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (Object.hasOwn(patch, "metadata")) {
|
|
82
|
+
if (patch.metadata === undefined) {
|
|
83
|
+
result.metadata = undefined;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const metadata = { ...(result.metadata ?? {}) };
|
|
87
|
+
for (const [key, value] of Object.entries(patch.metadata)) {
|
|
88
|
+
if (value === undefined)
|
|
89
|
+
delete metadata[key];
|
|
90
|
+
else
|
|
91
|
+
metadata[key] = value;
|
|
92
|
+
}
|
|
93
|
+
result.metadata = Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
const SUBSCRIBER_EVENT_TYPE = "*";
|
|
99
|
+
export class AgentHarness {
|
|
100
|
+
env;
|
|
101
|
+
session;
|
|
102
|
+
phase = "idle";
|
|
103
|
+
runAbortController;
|
|
104
|
+
runPromise;
|
|
105
|
+
pendingSessionWrites = [];
|
|
106
|
+
model;
|
|
107
|
+
thinkingLevel;
|
|
108
|
+
systemPrompt;
|
|
109
|
+
streamOptions;
|
|
110
|
+
getApiKeyAndHeaders;
|
|
111
|
+
resources;
|
|
112
|
+
tools = new Map();
|
|
113
|
+
activeToolNames;
|
|
114
|
+
steerQueue = [];
|
|
115
|
+
steeringQueueMode;
|
|
116
|
+
followUpQueue = [];
|
|
117
|
+
followUpQueueMode;
|
|
118
|
+
nextTurnQueue = [];
|
|
119
|
+
handlers = new Map();
|
|
120
|
+
constructor(options) {
|
|
121
|
+
this.env = options.env;
|
|
122
|
+
this.session = options.session;
|
|
123
|
+
this.resources = options.resources ?? {};
|
|
124
|
+
this.streamOptions = cloneStreamOptions(options.streamOptions);
|
|
125
|
+
this.systemPrompt = options.systemPrompt;
|
|
126
|
+
this.getApiKeyAndHeaders = options.getApiKeyAndHeaders;
|
|
127
|
+
for (const tool of options.tools ?? []) {
|
|
128
|
+
this.tools.set(tool.name, tool);
|
|
129
|
+
}
|
|
130
|
+
this.model = options.model;
|
|
131
|
+
this.thinkingLevel = options.thinkingLevel ?? "off";
|
|
132
|
+
this.activeToolNames = options.activeToolNames ?? (options.tools ?? []).map((tool) => tool.name);
|
|
133
|
+
this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
|
|
134
|
+
this.followUpQueueMode = options.followUpMode ?? "one-at-a-time";
|
|
135
|
+
}
|
|
136
|
+
getHandlers(type) {
|
|
137
|
+
return this.handlers.get(type);
|
|
138
|
+
}
|
|
139
|
+
async emitOwn(event, signal) {
|
|
140
|
+
for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
|
|
141
|
+
await listener(event, signal);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async emitAny(event, signal) {
|
|
145
|
+
for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
|
|
146
|
+
await listener(event, signal);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async emitHook(event) {
|
|
150
|
+
const handlers = this.getHandlers(event.type);
|
|
151
|
+
if (!handlers || handlers.size === 0)
|
|
152
|
+
return undefined;
|
|
153
|
+
let lastResult;
|
|
154
|
+
for (const handler of handlers) {
|
|
155
|
+
const result = await handler(event);
|
|
156
|
+
if (result !== undefined) {
|
|
157
|
+
lastResult = result;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return lastResult;
|
|
161
|
+
}
|
|
162
|
+
async emitBeforeProviderRequest(model, sessionId, streamOptions) {
|
|
163
|
+
const handlers = this.getHandlers("before_provider_request");
|
|
164
|
+
let current = cloneStreamOptions(streamOptions);
|
|
165
|
+
if (!handlers || handlers.size === 0)
|
|
166
|
+
return current;
|
|
167
|
+
for (const handler of handlers) {
|
|
168
|
+
const result = await handler({
|
|
169
|
+
type: "before_provider_request",
|
|
170
|
+
model,
|
|
171
|
+
sessionId,
|
|
172
|
+
streamOptions: cloneStreamOptions(current),
|
|
173
|
+
});
|
|
174
|
+
if (result?.streamOptions) {
|
|
175
|
+
current = applyStreamOptionsPatch(current, result.streamOptions);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return current;
|
|
179
|
+
}
|
|
180
|
+
async emitBeforeProviderPayload(model, payload) {
|
|
181
|
+
const handlers = this.getHandlers("before_provider_payload");
|
|
182
|
+
let current = payload;
|
|
183
|
+
if (!handlers || handlers.size === 0)
|
|
184
|
+
return current;
|
|
185
|
+
for (const handler of handlers) {
|
|
186
|
+
const result = await handler({ type: "before_provider_payload", model, payload: current });
|
|
187
|
+
if (result !== undefined) {
|
|
188
|
+
current = result.payload;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return current;
|
|
192
|
+
}
|
|
193
|
+
async emitQueueUpdate() {
|
|
194
|
+
await this.emitOwn({
|
|
195
|
+
type: "queue_update",
|
|
196
|
+
steer: [...this.steerQueue],
|
|
197
|
+
followUp: [...this.followUpQueue],
|
|
198
|
+
nextTurn: [...this.nextTurnQueue],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
startRunPromise() {
|
|
202
|
+
let finish = () => { };
|
|
203
|
+
this.runPromise = new Promise((resolve) => {
|
|
204
|
+
finish = resolve;
|
|
205
|
+
});
|
|
206
|
+
return () => {
|
|
207
|
+
this.runPromise = undefined;
|
|
208
|
+
finish();
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async createTurnState() {
|
|
212
|
+
const context = await this.session.buildContext();
|
|
213
|
+
const resources = this.getResources();
|
|
214
|
+
const sessionMetadata = await this.session.getMetadata();
|
|
215
|
+
const tools = [...this.tools.values()];
|
|
216
|
+
const activeTools = this.activeToolNames
|
|
217
|
+
.map((name) => this.tools.get(name))
|
|
218
|
+
.filter((tool) => tool !== undefined);
|
|
219
|
+
let systemPrompt = "You are a helpful assistant.";
|
|
220
|
+
if (typeof this.systemPrompt === "string") {
|
|
221
|
+
systemPrompt = this.systemPrompt;
|
|
222
|
+
}
|
|
223
|
+
else if (this.systemPrompt) {
|
|
224
|
+
systemPrompt = await this.systemPrompt({
|
|
225
|
+
env: this.env,
|
|
226
|
+
session: this.session,
|
|
227
|
+
model: this.model,
|
|
228
|
+
thinkingLevel: this.thinkingLevel,
|
|
229
|
+
activeTools,
|
|
230
|
+
resources,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
messages: context.messages,
|
|
235
|
+
resources,
|
|
236
|
+
streamOptions: cloneStreamOptions(this.streamOptions),
|
|
237
|
+
sessionId: sessionMetadata.id,
|
|
238
|
+
systemPrompt,
|
|
239
|
+
model: this.model,
|
|
240
|
+
thinkingLevel: this.thinkingLevel,
|
|
241
|
+
tools,
|
|
242
|
+
activeTools,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
createContext(turnState, systemPrompt) {
|
|
246
|
+
return {
|
|
247
|
+
systemPrompt: systemPrompt ?? turnState.systemPrompt,
|
|
248
|
+
messages: turnState.messages.slice(),
|
|
249
|
+
tools: turnState.activeTools.slice(),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
createStreamFn(getTurnState) {
|
|
253
|
+
return async (model, context, streamOptions) => {
|
|
254
|
+
const turnState = getTurnState();
|
|
255
|
+
const auth = await this.getApiKeyAndHeaders?.(model);
|
|
256
|
+
const snapshotOptions = {
|
|
257
|
+
...turnState.streamOptions,
|
|
258
|
+
headers: mergeHeaders(turnState.streamOptions.headers, auth?.headers),
|
|
259
|
+
};
|
|
260
|
+
const requestOptions = await this.emitBeforeProviderRequest(model, turnState.sessionId, snapshotOptions);
|
|
261
|
+
return streamSimple(model, context, {
|
|
262
|
+
cacheRetention: requestOptions.cacheRetention,
|
|
263
|
+
headers: requestOptions.headers,
|
|
264
|
+
maxRetries: requestOptions.maxRetries,
|
|
265
|
+
maxRetryDelayMs: requestOptions.maxRetryDelayMs,
|
|
266
|
+
metadata: requestOptions.metadata,
|
|
267
|
+
onPayload: async (payload) => await this.emitBeforeProviderPayload(model, payload),
|
|
268
|
+
onResponse: async (response) => {
|
|
269
|
+
const headers = { ...response.headers };
|
|
270
|
+
await this.emitOwn({ type: "after_provider_response", status: response.status, headers }, streamOptions?.signal);
|
|
271
|
+
},
|
|
272
|
+
reasoning: streamOptions?.reasoning,
|
|
273
|
+
signal: streamOptions?.signal,
|
|
274
|
+
sessionId: turnState.sessionId,
|
|
275
|
+
timeoutMs: requestOptions.timeoutMs,
|
|
276
|
+
transport: requestOptions.transport,
|
|
277
|
+
apiKey: auth?.apiKey,
|
|
278
|
+
});
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async drainQueuedMessages(queue, mode) {
|
|
282
|
+
const messages = mode === "all" ? queue.splice(0) : queue.splice(0, 1);
|
|
283
|
+
if (messages.length > 0)
|
|
284
|
+
await this.emitQueueUpdate();
|
|
285
|
+
return messages;
|
|
286
|
+
}
|
|
287
|
+
createLoopConfig(getTurnState, setTurnState) {
|
|
288
|
+
const turnState = getTurnState();
|
|
289
|
+
return {
|
|
290
|
+
model: turnState.model,
|
|
291
|
+
reasoning: turnState.thinkingLevel === "off" ? undefined : turnState.thinkingLevel,
|
|
292
|
+
convertToLlm,
|
|
293
|
+
transformContext: async (messages) => {
|
|
294
|
+
const result = await this.emitHook({ type: "context", messages: [...messages] });
|
|
295
|
+
return result?.messages ?? messages;
|
|
296
|
+
},
|
|
297
|
+
beforeToolCall: async ({ toolCall, args }) => {
|
|
298
|
+
const result = await this.emitHook({
|
|
299
|
+
type: "tool_call",
|
|
300
|
+
toolCallId: toolCall.id,
|
|
301
|
+
toolName: toolCall.name,
|
|
302
|
+
input: args,
|
|
303
|
+
});
|
|
304
|
+
return result ? { block: result.block, reason: result.reason } : undefined;
|
|
305
|
+
},
|
|
306
|
+
afterToolCall: async ({ toolCall, args, result, isError }) => {
|
|
307
|
+
const patch = await this.emitHook({
|
|
308
|
+
type: "tool_result",
|
|
309
|
+
toolCallId: toolCall.id,
|
|
310
|
+
toolName: toolCall.name,
|
|
311
|
+
input: args,
|
|
312
|
+
content: result.content,
|
|
313
|
+
details: result.details,
|
|
314
|
+
isError,
|
|
315
|
+
});
|
|
316
|
+
return patch
|
|
317
|
+
? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }
|
|
318
|
+
: undefined;
|
|
319
|
+
},
|
|
320
|
+
prepareNextTurn: async () => {
|
|
321
|
+
await this.flushPendingSessionWrites();
|
|
322
|
+
const nextTurnState = await this.createTurnState();
|
|
323
|
+
setTurnState(nextTurnState);
|
|
324
|
+
return {
|
|
325
|
+
context: this.createContext(nextTurnState),
|
|
326
|
+
model: nextTurnState.model,
|
|
327
|
+
thinkingLevel: nextTurnState.thinkingLevel,
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
getSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),
|
|
331
|
+
getFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
validateToolNames(toolNames) {
|
|
335
|
+
const missing = toolNames.filter((name) => !this.tools.has(name));
|
|
336
|
+
if (missing.length > 0)
|
|
337
|
+
throw new Error(`Unknown tool(s): ${missing.join(", ")}`);
|
|
338
|
+
}
|
|
339
|
+
async flushPendingSessionWrites() {
|
|
340
|
+
const writes = this.pendingSessionWrites;
|
|
341
|
+
this.pendingSessionWrites = [];
|
|
342
|
+
for (const write of writes) {
|
|
343
|
+
if (write.type === "message") {
|
|
344
|
+
await this.session.appendMessage(write.message);
|
|
345
|
+
}
|
|
346
|
+
else if (write.type === "model_change") {
|
|
347
|
+
await this.session.appendModelChange(write.provider, write.modelId);
|
|
348
|
+
}
|
|
349
|
+
else if (write.type === "thinking_level_change") {
|
|
350
|
+
await this.session.appendThinkingLevelChange(write.thinkingLevel);
|
|
351
|
+
}
|
|
352
|
+
else if (write.type === "custom") {
|
|
353
|
+
await this.session.appendCustomEntry(write.customType, write.data);
|
|
354
|
+
}
|
|
355
|
+
else if (write.type === "custom_message") {
|
|
356
|
+
await this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);
|
|
357
|
+
}
|
|
358
|
+
else if (write.type === "label") {
|
|
359
|
+
await this.session.appendLabel(write.targetId, write.label);
|
|
360
|
+
}
|
|
361
|
+
else if (write.type === "session_info") {
|
|
362
|
+
await this.session.appendSessionName(write.name ?? "");
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async handleAgentEvent(event, signal) {
|
|
367
|
+
await this.emitAny(event, signal);
|
|
368
|
+
if (event.type === "message_end") {
|
|
369
|
+
await this.session.appendMessage(event.message);
|
|
370
|
+
}
|
|
371
|
+
if (event.type === "turn_end") {
|
|
372
|
+
const hadPendingMutations = this.pendingSessionWrites.length > 0;
|
|
373
|
+
await this.flushPendingSessionWrites();
|
|
374
|
+
await this.emitOwn({
|
|
375
|
+
type: "save_point",
|
|
376
|
+
hadPendingMutations,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
if (event.type === "agent_end") {
|
|
380
|
+
await this.flushPendingSessionWrites();
|
|
381
|
+
this.phase = "idle";
|
|
382
|
+
await this.emitOwn({ type: "settled", nextTurnCount: this.nextTurnQueue.length }, signal);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async emitRunFailure(model, error, aborted, signal) {
|
|
386
|
+
const failureMessage = createFailureMessage(model, error, aborted);
|
|
387
|
+
await this.handleAgentEvent({ type: "message_start", message: failureMessage }, signal);
|
|
388
|
+
await this.handleAgentEvent({ type: "message_end", message: failureMessage }, signal);
|
|
389
|
+
await this.handleAgentEvent({ type: "turn_end", message: failureMessage, toolResults: [] }, signal);
|
|
390
|
+
await this.handleAgentEvent({ type: "agent_end", messages: [failureMessage] }, signal);
|
|
391
|
+
return [failureMessage];
|
|
392
|
+
}
|
|
393
|
+
async executeTurn(turnState, text, options) {
|
|
394
|
+
let activeTurnState = turnState;
|
|
395
|
+
let messages = [createUserMessage(text, options?.images)];
|
|
396
|
+
if (this.nextTurnQueue.length > 0) {
|
|
397
|
+
messages = [...this.nextTurnQueue, messages[0]];
|
|
398
|
+
this.nextTurnQueue = [];
|
|
399
|
+
await this.emitQueueUpdate();
|
|
400
|
+
}
|
|
401
|
+
const beforeResult = await this.emitHook({
|
|
402
|
+
type: "before_agent_start",
|
|
403
|
+
prompt: text,
|
|
404
|
+
images: options?.images,
|
|
405
|
+
systemPrompt: turnState.systemPrompt,
|
|
406
|
+
resources: turnState.resources,
|
|
407
|
+
});
|
|
408
|
+
if (beforeResult?.messages)
|
|
409
|
+
messages = [...messages, ...beforeResult.messages];
|
|
410
|
+
const abortController = new AbortController();
|
|
411
|
+
const getTurnState = () => activeTurnState;
|
|
412
|
+
const setTurnState = (nextTurnState) => {
|
|
413
|
+
activeTurnState = nextTurnState;
|
|
414
|
+
};
|
|
415
|
+
this.runAbortController = abortController;
|
|
416
|
+
const runResultPromise = (async () => {
|
|
417
|
+
try {
|
|
418
|
+
return await runAgentLoop(messages, this.createContext(turnState, beforeResult?.systemPrompt), this.createLoopConfig(getTurnState, setTurnState), (event) => this.handleAgentEvent(event, abortController.signal), abortController.signal, this.createStreamFn(getTurnState));
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
return await this.emitRunFailure(activeTurnState.model, error, abortController.signal.aborted, abortController.signal);
|
|
422
|
+
}
|
|
423
|
+
})();
|
|
424
|
+
try {
|
|
425
|
+
const newMessages = await runResultPromise;
|
|
426
|
+
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
427
|
+
const message = newMessages[i];
|
|
428
|
+
if (message.role === "assistant") {
|
|
429
|
+
return message;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
throw new Error("AgentHarness prompt completed without an assistant message");
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
try {
|
|
436
|
+
await this.flushPendingSessionWrites();
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
this.runAbortController = undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async prompt(text, options) {
|
|
444
|
+
if (this.phase !== "idle")
|
|
445
|
+
throw new Error("AgentHarness is busy");
|
|
446
|
+
this.phase = "turn";
|
|
447
|
+
const finishRunPromise = this.startRunPromise();
|
|
448
|
+
try {
|
|
449
|
+
const turnState = await this.createTurnState();
|
|
450
|
+
return await this.executeTurn(turnState, text, options);
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
this.phase = "idle";
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
456
|
+
finally {
|
|
457
|
+
finishRunPromise();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async skill(name, additionalInstructions) {
|
|
461
|
+
if (this.phase !== "idle")
|
|
462
|
+
throw new Error("AgentHarness is busy");
|
|
463
|
+
this.phase = "turn";
|
|
464
|
+
const finishRunPromise = this.startRunPromise();
|
|
465
|
+
try {
|
|
466
|
+
const turnState = await this.createTurnState();
|
|
467
|
+
const skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);
|
|
468
|
+
if (!skill)
|
|
469
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
470
|
+
return await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
this.phase = "idle";
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
finishRunPromise();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
async promptFromTemplate(name, args = []) {
|
|
481
|
+
if (this.phase !== "idle")
|
|
482
|
+
throw new Error("AgentHarness is busy");
|
|
483
|
+
this.phase = "turn";
|
|
484
|
+
const finishRunPromise = this.startRunPromise();
|
|
485
|
+
try {
|
|
486
|
+
const turnState = await this.createTurnState();
|
|
487
|
+
const template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
|
|
488
|
+
if (!template)
|
|
489
|
+
throw new Error(`Unknown prompt template: ${name}`);
|
|
490
|
+
return await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
this.phase = "idle";
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
finally {
|
|
497
|
+
finishRunPromise();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
steer(text, options) {
|
|
501
|
+
if (this.phase === "idle")
|
|
502
|
+
throw new Error("Cannot steer while idle");
|
|
503
|
+
this.steerQueue.push(createUserMessage(text, options?.images));
|
|
504
|
+
void this.emitQueueUpdate();
|
|
505
|
+
}
|
|
506
|
+
followUp(text, options) {
|
|
507
|
+
if (this.phase === "idle")
|
|
508
|
+
throw new Error("Cannot follow up while idle");
|
|
509
|
+
this.followUpQueue.push(createUserMessage(text, options?.images));
|
|
510
|
+
void this.emitQueueUpdate();
|
|
511
|
+
}
|
|
512
|
+
nextTurn(text, options) {
|
|
513
|
+
this.nextTurnQueue.push(createUserMessage(text, options?.images));
|
|
514
|
+
void this.emitQueueUpdate();
|
|
515
|
+
}
|
|
516
|
+
async appendMessage(message) {
|
|
517
|
+
if (this.phase === "idle") {
|
|
518
|
+
await this.session.appendMessage(message);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.pendingSessionWrites.push({ type: "message", message });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async compact(customInstructions) {
|
|
525
|
+
if (this.phase !== "idle")
|
|
526
|
+
throw new Error("compact() requires idle harness");
|
|
527
|
+
this.phase = "compaction";
|
|
528
|
+
const model = this.model;
|
|
529
|
+
if (!model)
|
|
530
|
+
throw new Error("No model set for compaction");
|
|
531
|
+
const auth = await this.getApiKeyAndHeaders?.(model);
|
|
532
|
+
if (!auth)
|
|
533
|
+
throw new Error("No auth available for compaction");
|
|
534
|
+
const branchEntries = await this.session.getBranch();
|
|
535
|
+
const preparation = prepareCompaction(branchEntries, DEFAULT_COMPACTION_SETTINGS);
|
|
536
|
+
if (!preparation)
|
|
537
|
+
throw new Error("Nothing to compact");
|
|
538
|
+
const hookResult = await this.emitHook({
|
|
539
|
+
type: "session_before_compact",
|
|
540
|
+
preparation,
|
|
541
|
+
branchEntries,
|
|
542
|
+
customInstructions,
|
|
543
|
+
signal: new AbortController().signal,
|
|
544
|
+
});
|
|
545
|
+
if (hookResult?.cancel) {
|
|
546
|
+
this.phase = "idle";
|
|
547
|
+
throw new Error("Compaction cancelled");
|
|
548
|
+
}
|
|
549
|
+
const provided = hookResult?.compaction;
|
|
550
|
+
const compactResult = provided
|
|
551
|
+
? { ok: true, value: provided }
|
|
552
|
+
: await compact(preparation, model, auth.apiKey, auth.headers, customInstructions, undefined, this.thinkingLevel);
|
|
553
|
+
if (!compactResult.ok)
|
|
554
|
+
throw compactResult.error;
|
|
555
|
+
const result = compactResult.value;
|
|
556
|
+
const entryId = await this.session.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, provided !== undefined);
|
|
557
|
+
const entry = await this.session.getEntry(entryId);
|
|
558
|
+
if (entry?.type === "compaction") {
|
|
559
|
+
await this.emitOwn({ type: "session_compact", compactionEntry: entry, fromHook: provided !== undefined });
|
|
560
|
+
}
|
|
561
|
+
this.phase = "idle";
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
async navigateTree(targetId, options) {
|
|
565
|
+
if (this.phase !== "idle")
|
|
566
|
+
throw new Error("navigateTree() requires idle harness");
|
|
567
|
+
this.phase = "branch_summary";
|
|
568
|
+
const oldLeafId = await this.session.getLeafId();
|
|
569
|
+
if (oldLeafId === targetId) {
|
|
570
|
+
this.phase = "idle";
|
|
571
|
+
return { cancelled: false };
|
|
572
|
+
}
|
|
573
|
+
const targetEntry = await this.session.getEntry(targetId);
|
|
574
|
+
if (!targetEntry)
|
|
575
|
+
throw new Error(`Entry ${targetId} not found`);
|
|
576
|
+
const { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);
|
|
577
|
+
const preparation = {
|
|
578
|
+
targetId,
|
|
579
|
+
oldLeafId,
|
|
580
|
+
commonAncestorId,
|
|
581
|
+
entriesToSummarize: entries,
|
|
582
|
+
userWantsSummary: options?.summarize ?? false,
|
|
583
|
+
customInstructions: options?.customInstructions,
|
|
584
|
+
replaceInstructions: options?.replaceInstructions,
|
|
585
|
+
label: options?.label,
|
|
586
|
+
};
|
|
587
|
+
const signal = new AbortController().signal;
|
|
588
|
+
const hookResult = await this.emitHook({
|
|
589
|
+
type: "session_before_tree",
|
|
590
|
+
preparation,
|
|
591
|
+
signal,
|
|
592
|
+
});
|
|
593
|
+
if (hookResult?.cancel) {
|
|
594
|
+
this.phase = "idle";
|
|
595
|
+
return { cancelled: true };
|
|
596
|
+
}
|
|
597
|
+
let summaryEntry;
|
|
598
|
+
let summaryText = hookResult?.summary?.summary;
|
|
599
|
+
let summaryDetails = hookResult?.summary?.details;
|
|
600
|
+
if (!summaryText && options?.summarize && entries.length > 0) {
|
|
601
|
+
const model = this.model;
|
|
602
|
+
if (!model)
|
|
603
|
+
throw new Error("No model set for branch summary");
|
|
604
|
+
const auth = await this.getApiKeyAndHeaders?.(model);
|
|
605
|
+
if (!auth)
|
|
606
|
+
throw new Error("No auth available for branch summary");
|
|
607
|
+
const branchSummary = await generateBranchSummary(entries, {
|
|
608
|
+
model,
|
|
609
|
+
apiKey: auth.apiKey,
|
|
610
|
+
headers: auth.headers,
|
|
611
|
+
signal: new AbortController().signal,
|
|
612
|
+
customInstructions: hookResult?.customInstructions ?? options?.customInstructions,
|
|
613
|
+
replaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,
|
|
614
|
+
});
|
|
615
|
+
if (branchSummary.aborted) {
|
|
616
|
+
this.phase = "idle";
|
|
617
|
+
return { cancelled: true };
|
|
618
|
+
}
|
|
619
|
+
if (branchSummary.error)
|
|
620
|
+
throw new Error(branchSummary.error);
|
|
621
|
+
summaryText = branchSummary.summary;
|
|
622
|
+
summaryDetails = {
|
|
623
|
+
readFiles: branchSummary.readFiles ?? [],
|
|
624
|
+
modifiedFiles: branchSummary.modifiedFiles ?? [],
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
let editorText;
|
|
628
|
+
let newLeafId;
|
|
629
|
+
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
630
|
+
newLeafId = targetEntry.parentId;
|
|
631
|
+
const content = targetEntry.message.content;
|
|
632
|
+
editorText =
|
|
633
|
+
typeof content === "string"
|
|
634
|
+
? content
|
|
635
|
+
: content
|
|
636
|
+
.filter((c) => c.type === "text")
|
|
637
|
+
.map((c) => c.text)
|
|
638
|
+
.join("");
|
|
639
|
+
}
|
|
640
|
+
else if (targetEntry.type === "custom_message") {
|
|
641
|
+
newLeafId = targetEntry.parentId;
|
|
642
|
+
editorText =
|
|
643
|
+
typeof targetEntry.content === "string"
|
|
644
|
+
? targetEntry.content
|
|
645
|
+
: targetEntry.content
|
|
646
|
+
.filter((c) => c.type === "text")
|
|
647
|
+
.map((c) => c.text)
|
|
648
|
+
.join("");
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
newLeafId = targetId;
|
|
652
|
+
}
|
|
653
|
+
const summaryId = await this.session.moveTo(newLeafId, summaryText
|
|
654
|
+
? {
|
|
655
|
+
summary: summaryText,
|
|
656
|
+
details: summaryDetails,
|
|
657
|
+
fromHook: hookResult?.summary !== undefined,
|
|
658
|
+
}
|
|
659
|
+
: undefined);
|
|
660
|
+
if (summaryId) {
|
|
661
|
+
summaryEntry = await this.session.getEntry(summaryId);
|
|
662
|
+
}
|
|
663
|
+
await this.emitOwn({
|
|
664
|
+
type: "session_tree",
|
|
665
|
+
newLeafId: await this.session.getLeafId(),
|
|
666
|
+
oldLeafId,
|
|
667
|
+
summaryEntry,
|
|
668
|
+
fromHook: hookResult?.summary !== undefined,
|
|
669
|
+
});
|
|
670
|
+
this.phase = "idle";
|
|
671
|
+
return { cancelled: false, editorText, summaryEntry };
|
|
672
|
+
}
|
|
673
|
+
getModel() {
|
|
674
|
+
return this.model;
|
|
675
|
+
}
|
|
676
|
+
/** Compatibility view for older harness consumers. */
|
|
677
|
+
get conversation() {
|
|
678
|
+
return { session: this.session, model: this.model };
|
|
679
|
+
}
|
|
680
|
+
/** Compatibility view for older harness consumers. */
|
|
681
|
+
get agent() {
|
|
682
|
+
return { state: { model: this.model } };
|
|
683
|
+
}
|
|
684
|
+
getThinkingLevel() {
|
|
685
|
+
return this.thinkingLevel;
|
|
686
|
+
}
|
|
687
|
+
async setModel(model) {
|
|
688
|
+
const previousModel = this.model;
|
|
689
|
+
this.model = model;
|
|
690
|
+
if (this.phase === "idle") {
|
|
691
|
+
await this.session.appendModelChange(model.provider, model.id);
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.pendingSessionWrites.push({ type: "model_change", provider: model.provider, modelId: model.id });
|
|
695
|
+
}
|
|
696
|
+
await this.emitOwn({ type: "model_select", model, previousModel, source: "set" });
|
|
697
|
+
}
|
|
698
|
+
async setThinkingLevel(level) {
|
|
699
|
+
const previousLevel = this.thinkingLevel;
|
|
700
|
+
this.thinkingLevel = level;
|
|
701
|
+
if (this.phase === "idle") {
|
|
702
|
+
await this.session.appendThinkingLevelChange(level);
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
this.pendingSessionWrites.push({ type: "thinking_level_change", thinkingLevel: level });
|
|
706
|
+
}
|
|
707
|
+
await this.emitOwn({ type: "thinking_level_select", level, previousLevel });
|
|
708
|
+
}
|
|
709
|
+
async setActiveTools(toolNames) {
|
|
710
|
+
this.validateToolNames(toolNames);
|
|
711
|
+
this.activeToolNames = [...toolNames];
|
|
712
|
+
}
|
|
713
|
+
getSteeringMode() {
|
|
714
|
+
return this.steeringQueueMode;
|
|
715
|
+
}
|
|
716
|
+
setSteeringMode(mode) {
|
|
717
|
+
this.steeringQueueMode = mode;
|
|
718
|
+
}
|
|
719
|
+
getFollowUpMode() {
|
|
720
|
+
return this.followUpQueueMode;
|
|
721
|
+
}
|
|
722
|
+
setFollowUpMode(mode) {
|
|
723
|
+
this.followUpQueueMode = mode;
|
|
724
|
+
}
|
|
725
|
+
getResources() {
|
|
726
|
+
return {
|
|
727
|
+
skills: this.resources.skills?.slice(),
|
|
728
|
+
promptTemplates: this.resources.promptTemplates?.slice(),
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
async setResources(resources) {
|
|
732
|
+
const previousResources = this.getResources();
|
|
733
|
+
this.resources = {
|
|
734
|
+
skills: resources.skills?.slice(),
|
|
735
|
+
promptTemplates: resources.promptTemplates?.slice(),
|
|
736
|
+
};
|
|
737
|
+
await this.emitOwn({ type: "resources_update", resources: this.getResources(), previousResources });
|
|
738
|
+
}
|
|
739
|
+
getStreamOptions() {
|
|
740
|
+
return cloneStreamOptions(this.streamOptions);
|
|
741
|
+
}
|
|
742
|
+
setStreamOptions(streamOptions) {
|
|
743
|
+
this.streamOptions = cloneStreamOptions(streamOptions);
|
|
744
|
+
}
|
|
745
|
+
async setTools(tools, activeToolNames) {
|
|
746
|
+
this.tools = new Map(tools.map((tool) => [tool.name, tool]));
|
|
747
|
+
if (activeToolNames) {
|
|
748
|
+
this.validateToolNames(activeToolNames);
|
|
749
|
+
this.activeToolNames = [...activeToolNames];
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this.validateToolNames(this.activeToolNames);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async abort() {
|
|
756
|
+
const clearedSteer = [...this.steerQueue];
|
|
757
|
+
const clearedFollowUp = [...this.followUpQueue];
|
|
758
|
+
this.steerQueue = [];
|
|
759
|
+
this.followUpQueue = [];
|
|
760
|
+
await this.emitQueueUpdate();
|
|
761
|
+
this.runAbortController?.abort();
|
|
762
|
+
await this.waitForIdle();
|
|
763
|
+
await this.emitOwn({ type: "abort", clearedSteer, clearedFollowUp });
|
|
764
|
+
return { clearedSteer, clearedFollowUp };
|
|
765
|
+
}
|
|
766
|
+
async waitForIdle() {
|
|
767
|
+
await this.runPromise;
|
|
768
|
+
}
|
|
769
|
+
subscribe(listener) {
|
|
770
|
+
let handlers = this.handlers.get(SUBSCRIBER_EVENT_TYPE);
|
|
771
|
+
if (!handlers) {
|
|
772
|
+
handlers = new Set();
|
|
773
|
+
this.handlers.set(SUBSCRIBER_EVENT_TYPE, handlers);
|
|
774
|
+
}
|
|
775
|
+
handlers.add(listener);
|
|
776
|
+
return () => handlers.delete(listener);
|
|
777
|
+
}
|
|
778
|
+
on(type, handler) {
|
|
779
|
+
let handlers = this.handlers.get(type);
|
|
780
|
+
if (!handlers) {
|
|
781
|
+
handlers = new Set();
|
|
782
|
+
this.handlers.set(type, handlers);
|
|
783
|
+
}
|
|
784
|
+
handlers.add(handler);
|
|
785
|
+
return () => handlers.delete(handler);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
//# sourceMappingURL=agent-harness.js.map
|