@elyracode/agent-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +488 -0
- package/dist/agent-loop.d.ts +24 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +479 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent.d.ts +118 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +402 -0
- package/dist/agent.js.map +1 -0
- package/dist/harness/agent-harness.d.ts +78 -0
- package/dist/harness/agent-harness.d.ts.map +1 -0
- package/dist/harness/agent-harness.js +602 -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 +616 -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 +44 -0
- package/dist/harness/env/nodejs.d.ts.map +1 -0
- package/dist/harness/env/nodejs.js +348 -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/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 +45 -0
- package/dist/harness/prompt-templates.d.ts.map +1 -0
- package/dist/harness/prompt-templates.js +200 -0
- package/dist/harness/prompt-templates.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/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/skills.d.ts +41 -0
- package/dist/harness/skills.d.ts.map +1 -0
- package/dist/harness/skills.js +259 -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 +497 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +16 -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 +97 -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 +205 -0
- package/dist/harness/utils/truncate.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +69 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +278 -0
- package/dist/proxy.js.map +1 -0
- package/dist/types.d.ts +386 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import { Agent } from "../agent.js";
|
|
2
|
+
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.js";
|
|
3
|
+
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.js";
|
|
4
|
+
import { formatPromptTemplateInvocation } from "./prompt-templates.js";
|
|
5
|
+
import { formatSkillInvocation } from "./skills.js";
|
|
6
|
+
function createUserMessage(text, images) {
|
|
7
|
+
const content = [{ type: "text", text }];
|
|
8
|
+
if (images)
|
|
9
|
+
content.push(...images);
|
|
10
|
+
return { role: "user", content, timestamp: Date.now() };
|
|
11
|
+
}
|
|
12
|
+
export class AgentHarness {
|
|
13
|
+
agent;
|
|
14
|
+
env;
|
|
15
|
+
session;
|
|
16
|
+
model;
|
|
17
|
+
thinkingLevel;
|
|
18
|
+
activeToolNames;
|
|
19
|
+
nextTurnQueue = [];
|
|
20
|
+
phase = "idle";
|
|
21
|
+
steerQueue = [];
|
|
22
|
+
followUpQueue = [];
|
|
23
|
+
pendingSessionWrites = [];
|
|
24
|
+
resources;
|
|
25
|
+
systemPrompt;
|
|
26
|
+
getApiKeyAndHeaders;
|
|
27
|
+
tools = new Map();
|
|
28
|
+
listeners = new Set();
|
|
29
|
+
hooks = new Map();
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.agent = new Agent({
|
|
32
|
+
initialState: {
|
|
33
|
+
model: options.model,
|
|
34
|
+
thinkingLevel: options.thinkingLevel,
|
|
35
|
+
tools: options.tools ?? [],
|
|
36
|
+
},
|
|
37
|
+
steeringMode: options.steeringMode,
|
|
38
|
+
followUpMode: options.followUpMode,
|
|
39
|
+
});
|
|
40
|
+
this.env = options.env;
|
|
41
|
+
this.session = options.session;
|
|
42
|
+
this.resources = options.resources ?? {};
|
|
43
|
+
this.systemPrompt = options.systemPrompt;
|
|
44
|
+
this.getApiKeyAndHeaders = options.getApiKeyAndHeaders;
|
|
45
|
+
for (const tool of options.tools ?? []) {
|
|
46
|
+
this.tools.set(tool.name, tool);
|
|
47
|
+
}
|
|
48
|
+
this.model = options.model;
|
|
49
|
+
this.thinkingLevel = options.thinkingLevel ?? this.agent.state.thinkingLevel;
|
|
50
|
+
this.activeToolNames = options.activeToolNames ?? (options.tools ?? []).map((tool) => tool.name);
|
|
51
|
+
this.agent.state.model = this.model;
|
|
52
|
+
this.agent.state.thinkingLevel = this.thinkingLevel;
|
|
53
|
+
this.agent.getApiKey = async (provider) => {
|
|
54
|
+
const model = this.model;
|
|
55
|
+
if (!this.getApiKeyAndHeaders || model.provider !== provider)
|
|
56
|
+
return undefined;
|
|
57
|
+
return (await this.getApiKeyAndHeaders(model))?.apiKey;
|
|
58
|
+
};
|
|
59
|
+
this.agent.transformContext = async (messages) => {
|
|
60
|
+
const result = await this.emitHook({ type: "context", messages: [...messages] });
|
|
61
|
+
return result?.messages ?? messages;
|
|
62
|
+
};
|
|
63
|
+
this.agent.beforeToolCall = async ({ toolCall, args }) => {
|
|
64
|
+
const result = await this.emitHook({
|
|
65
|
+
type: "tool_call",
|
|
66
|
+
toolCallId: toolCall.id,
|
|
67
|
+
toolName: toolCall.name,
|
|
68
|
+
input: args,
|
|
69
|
+
});
|
|
70
|
+
return result ? { block: result.block, reason: result.reason } : undefined;
|
|
71
|
+
};
|
|
72
|
+
this.agent.afterToolCall = async ({ toolCall, args, result, isError }) => {
|
|
73
|
+
const patch = await this.emitHook({
|
|
74
|
+
type: "tool_result",
|
|
75
|
+
toolCallId: toolCall.id,
|
|
76
|
+
toolName: toolCall.name,
|
|
77
|
+
input: args,
|
|
78
|
+
content: result.content,
|
|
79
|
+
details: result.details,
|
|
80
|
+
isError,
|
|
81
|
+
});
|
|
82
|
+
return patch
|
|
83
|
+
? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }
|
|
84
|
+
: undefined;
|
|
85
|
+
};
|
|
86
|
+
this.agent.onPayload = async (payload) => {
|
|
87
|
+
const result = await this.emitHook({ type: "before_provider_request", payload });
|
|
88
|
+
return result?.payload ?? payload;
|
|
89
|
+
};
|
|
90
|
+
this.agent.onResponse = async (response) => {
|
|
91
|
+
const headers = { ...response.headers };
|
|
92
|
+
await this.emitOwn({ type: "after_provider_response", status: response.status, headers }, this.agent.signal);
|
|
93
|
+
};
|
|
94
|
+
this.agent.prepareNextTurn = async () => {
|
|
95
|
+
await this.flushPendingSessionWrites();
|
|
96
|
+
const turnState = await this.createTurnState();
|
|
97
|
+
this.applyTurnState(turnState);
|
|
98
|
+
return {
|
|
99
|
+
context: {
|
|
100
|
+
systemPrompt: turnState.systemPrompt,
|
|
101
|
+
messages: turnState.messages.slice(),
|
|
102
|
+
tools: turnState.activeTools.slice(),
|
|
103
|
+
},
|
|
104
|
+
model: turnState.model,
|
|
105
|
+
thinkingLevel: turnState.thinkingLevel,
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
this.agent.subscribe(async (event, signal) => {
|
|
109
|
+
await this.handleAgentEvent(event, signal);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async emitOwn(event, signal) {
|
|
113
|
+
for (const listener of this.listeners) {
|
|
114
|
+
await listener(event, signal);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async emitAny(event, signal) {
|
|
118
|
+
for (const listener of this.listeners) {
|
|
119
|
+
await listener(event, signal);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async emitHook(event) {
|
|
123
|
+
const handlers = this.hooks.get(event.type);
|
|
124
|
+
if (!handlers || handlers.size === 0)
|
|
125
|
+
return undefined;
|
|
126
|
+
let lastResult;
|
|
127
|
+
for (const handler of handlers) {
|
|
128
|
+
const result = await handler(event);
|
|
129
|
+
if (result !== undefined) {
|
|
130
|
+
lastResult = result;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return lastResult;
|
|
134
|
+
}
|
|
135
|
+
async emitQueueUpdate() {
|
|
136
|
+
await this.emitOwn({
|
|
137
|
+
type: "queue_update",
|
|
138
|
+
steer: [...this.steerQueue],
|
|
139
|
+
followUp: [...this.followUpQueue],
|
|
140
|
+
nextTurn: [...this.nextTurnQueue],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async createTurnState() {
|
|
144
|
+
const context = await this.session.buildContext();
|
|
145
|
+
const resources = this.getResources();
|
|
146
|
+
const tools = [...this.tools.values()];
|
|
147
|
+
const activeTools = this.activeToolNames
|
|
148
|
+
.map((name) => this.tools.get(name))
|
|
149
|
+
.filter((tool) => tool !== undefined);
|
|
150
|
+
let systemPrompt = "You are a helpful assistant.";
|
|
151
|
+
if (typeof this.systemPrompt === "string") {
|
|
152
|
+
systemPrompt = this.systemPrompt;
|
|
153
|
+
}
|
|
154
|
+
else if (this.systemPrompt) {
|
|
155
|
+
systemPrompt = await this.systemPrompt({
|
|
156
|
+
env: this.env,
|
|
157
|
+
session: this.session,
|
|
158
|
+
model: this.model,
|
|
159
|
+
thinkingLevel: this.thinkingLevel,
|
|
160
|
+
activeTools,
|
|
161
|
+
resources,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
messages: context.messages,
|
|
166
|
+
resources,
|
|
167
|
+
systemPrompt,
|
|
168
|
+
model: this.model,
|
|
169
|
+
thinkingLevel: this.thinkingLevel,
|
|
170
|
+
tools,
|
|
171
|
+
activeTools,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
applyTurnState(turnState) {
|
|
175
|
+
this.agent.state.messages = turnState.messages;
|
|
176
|
+
this.agent.state.systemPrompt = turnState.systemPrompt;
|
|
177
|
+
this.agent.state.model = turnState.model;
|
|
178
|
+
this.agent.state.thinkingLevel = turnState.thinkingLevel;
|
|
179
|
+
this.agent.state.tools = turnState.activeTools;
|
|
180
|
+
}
|
|
181
|
+
validateToolNames(toolNames) {
|
|
182
|
+
const missing = toolNames.filter((name) => !this.tools.has(name));
|
|
183
|
+
if (missing.length > 0)
|
|
184
|
+
throw new Error(`Unknown tool(s): ${missing.join(", ")}`);
|
|
185
|
+
}
|
|
186
|
+
async flushPendingSessionWrites() {
|
|
187
|
+
const writes = this.pendingSessionWrites;
|
|
188
|
+
this.pendingSessionWrites = [];
|
|
189
|
+
for (const write of writes) {
|
|
190
|
+
if (write.type === "message") {
|
|
191
|
+
await this.session.appendMessage(write.message);
|
|
192
|
+
}
|
|
193
|
+
else if (write.type === "model_change") {
|
|
194
|
+
await this.session.appendModelChange(write.provider, write.modelId);
|
|
195
|
+
}
|
|
196
|
+
else if (write.type === "thinking_level_change") {
|
|
197
|
+
await this.session.appendThinkingLevelChange(write.thinkingLevel);
|
|
198
|
+
}
|
|
199
|
+
else if (write.type === "custom") {
|
|
200
|
+
await this.session.appendCustomEntry(write.customType, write.data);
|
|
201
|
+
}
|
|
202
|
+
else if (write.type === "custom_message") {
|
|
203
|
+
await this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);
|
|
204
|
+
}
|
|
205
|
+
else if (write.type === "label") {
|
|
206
|
+
await this.session.appendLabel(write.targetId, write.label);
|
|
207
|
+
}
|
|
208
|
+
else if (write.type === "session_info") {
|
|
209
|
+
await this.session.appendSessionName(write.name ?? "");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async handleAgentEvent(event, signal) {
|
|
214
|
+
await this.emitAny(event, signal);
|
|
215
|
+
if (event.type === "message_start" && event.message.role === "user") {
|
|
216
|
+
const steerIndex = this.steerQueue.indexOf(event.message);
|
|
217
|
+
if (steerIndex !== -1) {
|
|
218
|
+
this.steerQueue.splice(steerIndex, 1);
|
|
219
|
+
await this.emitQueueUpdate();
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const followUpIndex = this.followUpQueue.indexOf(event.message);
|
|
223
|
+
if (followUpIndex !== -1) {
|
|
224
|
+
this.followUpQueue.splice(followUpIndex, 1);
|
|
225
|
+
await this.emitQueueUpdate();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (event.type === "message_end") {
|
|
230
|
+
await this.session.appendMessage(event.message);
|
|
231
|
+
}
|
|
232
|
+
if (event.type === "turn_end") {
|
|
233
|
+
const hadPendingMutations = this.pendingSessionWrites.length > 0;
|
|
234
|
+
await this.flushPendingSessionWrites();
|
|
235
|
+
await this.emitOwn({
|
|
236
|
+
type: "save_point",
|
|
237
|
+
hadPendingMutations,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (event.type === "agent_end") {
|
|
241
|
+
await this.flushPendingSessionWrites();
|
|
242
|
+
this.phase = "idle";
|
|
243
|
+
await this.emitOwn({ type: "settled", nextTurnCount: this.nextTurnQueue.length }, signal);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async executeTurn(turnState, text, options) {
|
|
247
|
+
this.applyTurnState(turnState);
|
|
248
|
+
const beforeLength = this.agent.state.messages.length;
|
|
249
|
+
let messages = [createUserMessage(text, options?.images)];
|
|
250
|
+
if (this.nextTurnQueue.length > 0) {
|
|
251
|
+
messages = [...this.nextTurnQueue, messages[0]];
|
|
252
|
+
this.nextTurnQueue = [];
|
|
253
|
+
await this.emitQueueUpdate();
|
|
254
|
+
}
|
|
255
|
+
const beforeResult = await this.emitHook({
|
|
256
|
+
type: "before_agent_start",
|
|
257
|
+
prompt: text,
|
|
258
|
+
images: options?.images,
|
|
259
|
+
systemPrompt: turnState.systemPrompt,
|
|
260
|
+
resources: turnState.resources,
|
|
261
|
+
});
|
|
262
|
+
if (beforeResult?.messages)
|
|
263
|
+
messages = [...beforeResult.messages, ...messages];
|
|
264
|
+
if (beforeResult?.systemPrompt)
|
|
265
|
+
this.agent.state.systemPrompt = beforeResult.systemPrompt;
|
|
266
|
+
try {
|
|
267
|
+
await this.agent.prompt(messages);
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
await this.flushPendingSessionWrites();
|
|
271
|
+
}
|
|
272
|
+
let response;
|
|
273
|
+
const newMessages = this.agent.state.messages.slice(beforeLength);
|
|
274
|
+
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
275
|
+
const message = newMessages[i];
|
|
276
|
+
if (message.role === "assistant") {
|
|
277
|
+
response = message;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!response)
|
|
282
|
+
throw new Error("AgentHarness prompt completed without an assistant message");
|
|
283
|
+
return response;
|
|
284
|
+
}
|
|
285
|
+
async prompt(text, options) {
|
|
286
|
+
if (this.phase !== "idle")
|
|
287
|
+
throw new Error("AgentHarness is busy");
|
|
288
|
+
this.phase = "turn";
|
|
289
|
+
try {
|
|
290
|
+
const turnState = await this.createTurnState();
|
|
291
|
+
return await this.executeTurn(turnState, text, options);
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
this.phase = "idle";
|
|
295
|
+
throw error;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async skill(name, additionalInstructions) {
|
|
299
|
+
if (this.phase !== "idle")
|
|
300
|
+
throw new Error("AgentHarness is busy");
|
|
301
|
+
this.phase = "turn";
|
|
302
|
+
try {
|
|
303
|
+
const turnState = await this.createTurnState();
|
|
304
|
+
const skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);
|
|
305
|
+
if (!skill)
|
|
306
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
307
|
+
return await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
this.phase = "idle";
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async promptFromTemplate(name, args = []) {
|
|
315
|
+
if (this.phase !== "idle")
|
|
316
|
+
throw new Error("AgentHarness is busy");
|
|
317
|
+
this.phase = "turn";
|
|
318
|
+
try {
|
|
319
|
+
const turnState = await this.createTurnState();
|
|
320
|
+
const template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
|
|
321
|
+
if (!template)
|
|
322
|
+
throw new Error(`Unknown prompt template: ${name}`);
|
|
323
|
+
return await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
this.phase = "idle";
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
steer(text, options) {
|
|
331
|
+
if (this.phase === "idle")
|
|
332
|
+
throw new Error("Cannot steer while idle");
|
|
333
|
+
const message = createUserMessage(text, options?.images);
|
|
334
|
+
this.steerQueue.push(message);
|
|
335
|
+
this.agent.steer(message);
|
|
336
|
+
void this.emitQueueUpdate();
|
|
337
|
+
}
|
|
338
|
+
followUp(text, options) {
|
|
339
|
+
if (this.phase === "idle")
|
|
340
|
+
throw new Error("Cannot follow up while idle");
|
|
341
|
+
const message = createUserMessage(text, options?.images);
|
|
342
|
+
this.followUpQueue.push(message);
|
|
343
|
+
this.agent.followUp(message);
|
|
344
|
+
void this.emitQueueUpdate();
|
|
345
|
+
}
|
|
346
|
+
nextTurn(text, options) {
|
|
347
|
+
this.nextTurnQueue.push(createUserMessage(text, options?.images));
|
|
348
|
+
void this.emitQueueUpdate();
|
|
349
|
+
}
|
|
350
|
+
async appendMessage(message) {
|
|
351
|
+
if (this.phase === "idle") {
|
|
352
|
+
await this.session.appendMessage(message);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
this.pendingSessionWrites.push({ type: "message", message });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async compact(customInstructions) {
|
|
359
|
+
if (this.phase !== "idle")
|
|
360
|
+
throw new Error("compact() requires idle harness");
|
|
361
|
+
this.phase = "compaction";
|
|
362
|
+
const model = this.model;
|
|
363
|
+
if (!model)
|
|
364
|
+
throw new Error("No model set for compaction");
|
|
365
|
+
const auth = await this.getApiKeyAndHeaders?.(model);
|
|
366
|
+
if (!auth)
|
|
367
|
+
throw new Error("No auth available for compaction");
|
|
368
|
+
const branchEntries = await this.session.getBranch();
|
|
369
|
+
const preparation = prepareCompaction(branchEntries, DEFAULT_COMPACTION_SETTINGS);
|
|
370
|
+
if (!preparation)
|
|
371
|
+
throw new Error("Nothing to compact");
|
|
372
|
+
const hookResult = await this.emitHook({
|
|
373
|
+
type: "session_before_compact",
|
|
374
|
+
preparation,
|
|
375
|
+
branchEntries,
|
|
376
|
+
customInstructions,
|
|
377
|
+
signal: new AbortController().signal,
|
|
378
|
+
});
|
|
379
|
+
if (hookResult?.cancel) {
|
|
380
|
+
this.phase = "idle";
|
|
381
|
+
throw new Error("Compaction cancelled");
|
|
382
|
+
}
|
|
383
|
+
const provided = hookResult?.compaction;
|
|
384
|
+
const result = provided ??
|
|
385
|
+
(await compact(preparation, model, auth.apiKey, auth.headers, customInstructions, undefined, this.thinkingLevel));
|
|
386
|
+
const entryId = await this.session.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, provided !== undefined);
|
|
387
|
+
const entry = await this.session.getEntry(entryId);
|
|
388
|
+
if (entry?.type === "compaction") {
|
|
389
|
+
await this.emitOwn({ type: "session_compact", compactionEntry: entry, fromHook: provided !== undefined });
|
|
390
|
+
}
|
|
391
|
+
this.phase = "idle";
|
|
392
|
+
return result;
|
|
393
|
+
}
|
|
394
|
+
async navigateTree(targetId, options) {
|
|
395
|
+
if (this.phase !== "idle")
|
|
396
|
+
throw new Error("navigateTree() requires idle harness");
|
|
397
|
+
this.phase = "branch_summary";
|
|
398
|
+
const oldLeafId = await this.session.getLeafId();
|
|
399
|
+
if (oldLeafId === targetId) {
|
|
400
|
+
this.phase = "idle";
|
|
401
|
+
return { cancelled: false };
|
|
402
|
+
}
|
|
403
|
+
const targetEntry = await this.session.getEntry(targetId);
|
|
404
|
+
if (!targetEntry)
|
|
405
|
+
throw new Error(`Entry ${targetId} not found`);
|
|
406
|
+
const { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);
|
|
407
|
+
const preparation = {
|
|
408
|
+
targetId,
|
|
409
|
+
oldLeafId,
|
|
410
|
+
commonAncestorId,
|
|
411
|
+
entriesToSummarize: entries,
|
|
412
|
+
userWantsSummary: options?.summarize ?? false,
|
|
413
|
+
customInstructions: options?.customInstructions,
|
|
414
|
+
replaceInstructions: options?.replaceInstructions,
|
|
415
|
+
label: options?.label,
|
|
416
|
+
};
|
|
417
|
+
const signal = new AbortController().signal;
|
|
418
|
+
const hookResult = await this.emitHook({
|
|
419
|
+
type: "session_before_tree",
|
|
420
|
+
preparation,
|
|
421
|
+
signal,
|
|
422
|
+
});
|
|
423
|
+
if (hookResult?.cancel) {
|
|
424
|
+
this.phase = "idle";
|
|
425
|
+
return { cancelled: true };
|
|
426
|
+
}
|
|
427
|
+
let summaryEntry;
|
|
428
|
+
let summaryText = hookResult?.summary?.summary;
|
|
429
|
+
let summaryDetails = hookResult?.summary?.details;
|
|
430
|
+
if (!summaryText && options?.summarize && entries.length > 0) {
|
|
431
|
+
const model = this.model;
|
|
432
|
+
if (!model)
|
|
433
|
+
throw new Error("No model set for branch summary");
|
|
434
|
+
const auth = await this.getApiKeyAndHeaders?.(model);
|
|
435
|
+
if (!auth)
|
|
436
|
+
throw new Error("No auth available for branch summary");
|
|
437
|
+
const branchSummary = await generateBranchSummary(entries, {
|
|
438
|
+
model,
|
|
439
|
+
apiKey: auth.apiKey,
|
|
440
|
+
headers: auth.headers,
|
|
441
|
+
signal: new AbortController().signal,
|
|
442
|
+
customInstructions: hookResult?.customInstructions ?? options?.customInstructions,
|
|
443
|
+
replaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,
|
|
444
|
+
});
|
|
445
|
+
if (branchSummary.aborted) {
|
|
446
|
+
this.phase = "idle";
|
|
447
|
+
return { cancelled: true };
|
|
448
|
+
}
|
|
449
|
+
if (branchSummary.error)
|
|
450
|
+
throw new Error(branchSummary.error);
|
|
451
|
+
summaryText = branchSummary.summary;
|
|
452
|
+
summaryDetails = {
|
|
453
|
+
readFiles: branchSummary.readFiles ?? [],
|
|
454
|
+
modifiedFiles: branchSummary.modifiedFiles ?? [],
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
let editorText;
|
|
458
|
+
let newLeafId;
|
|
459
|
+
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
460
|
+
newLeafId = targetEntry.parentId;
|
|
461
|
+
const content = targetEntry.message.content;
|
|
462
|
+
editorText =
|
|
463
|
+
typeof content === "string"
|
|
464
|
+
? content
|
|
465
|
+
: content
|
|
466
|
+
.filter((c) => c.type === "text")
|
|
467
|
+
.map((c) => c.text)
|
|
468
|
+
.join("");
|
|
469
|
+
}
|
|
470
|
+
else if (targetEntry.type === "custom_message") {
|
|
471
|
+
newLeafId = targetEntry.parentId;
|
|
472
|
+
editorText =
|
|
473
|
+
typeof targetEntry.content === "string"
|
|
474
|
+
? targetEntry.content
|
|
475
|
+
: targetEntry.content
|
|
476
|
+
.filter((c) => c.type === "text")
|
|
477
|
+
.map((c) => c.text)
|
|
478
|
+
.join("");
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
newLeafId = targetId;
|
|
482
|
+
}
|
|
483
|
+
const summaryId = await this.session.moveTo(newLeafId, summaryText
|
|
484
|
+
? {
|
|
485
|
+
summary: summaryText,
|
|
486
|
+
details: summaryDetails,
|
|
487
|
+
fromHook: hookResult?.summary !== undefined,
|
|
488
|
+
}
|
|
489
|
+
: undefined);
|
|
490
|
+
if (summaryId) {
|
|
491
|
+
summaryEntry = await this.session.getEntry(summaryId);
|
|
492
|
+
}
|
|
493
|
+
await this.emitOwn({
|
|
494
|
+
type: "session_tree",
|
|
495
|
+
newLeafId: await this.session.getLeafId(),
|
|
496
|
+
oldLeafId,
|
|
497
|
+
summaryEntry,
|
|
498
|
+
fromHook: hookResult?.summary !== undefined,
|
|
499
|
+
});
|
|
500
|
+
this.phase = "idle";
|
|
501
|
+
return { cancelled: false, editorText, summaryEntry };
|
|
502
|
+
}
|
|
503
|
+
async setModel(model) {
|
|
504
|
+
const previousModel = this.model;
|
|
505
|
+
this.model = model;
|
|
506
|
+
if (this.phase === "idle") {
|
|
507
|
+
this.agent.state.model = model;
|
|
508
|
+
await this.session.appendModelChange(model.provider, model.id);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
this.pendingSessionWrites.push({ type: "model_change", provider: model.provider, modelId: model.id });
|
|
512
|
+
}
|
|
513
|
+
await this.emitOwn({ type: "model_select", model, previousModel, source: "set" });
|
|
514
|
+
}
|
|
515
|
+
async setThinkingLevel(level) {
|
|
516
|
+
const previousLevel = this.thinkingLevel;
|
|
517
|
+
this.thinkingLevel = level;
|
|
518
|
+
if (this.phase === "idle") {
|
|
519
|
+
this.agent.state.thinkingLevel = level;
|
|
520
|
+
await this.session.appendThinkingLevelChange(level);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
this.pendingSessionWrites.push({ type: "thinking_level_change", thinkingLevel: level });
|
|
524
|
+
}
|
|
525
|
+
await this.emitOwn({ type: "thinking_level_select", level, previousLevel });
|
|
526
|
+
}
|
|
527
|
+
async setActiveTools(toolNames) {
|
|
528
|
+
this.validateToolNames(toolNames);
|
|
529
|
+
this.activeToolNames = [...toolNames];
|
|
530
|
+
if (this.phase === "idle") {
|
|
531
|
+
this.agent.state.tools = this.activeToolNames.map((name) => this.tools.get(name));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
get steeringMode() {
|
|
535
|
+
return this.agent.steeringMode;
|
|
536
|
+
}
|
|
537
|
+
set steeringMode(mode) {
|
|
538
|
+
this.agent.steeringMode = mode;
|
|
539
|
+
}
|
|
540
|
+
get followUpMode() {
|
|
541
|
+
return this.agent.followUpMode;
|
|
542
|
+
}
|
|
543
|
+
set followUpMode(mode) {
|
|
544
|
+
this.agent.followUpMode = mode;
|
|
545
|
+
}
|
|
546
|
+
getResources() {
|
|
547
|
+
return {
|
|
548
|
+
skills: this.resources.skills?.slice(),
|
|
549
|
+
promptTemplates: this.resources.promptTemplates?.slice(),
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
async setResources(resources) {
|
|
553
|
+
const previousResources = this.getResources();
|
|
554
|
+
this.resources = {
|
|
555
|
+
skills: resources.skills?.slice(),
|
|
556
|
+
promptTemplates: resources.promptTemplates?.slice(),
|
|
557
|
+
};
|
|
558
|
+
await this.emitOwn({ type: "resources_update", resources: this.getResources(), previousResources });
|
|
559
|
+
}
|
|
560
|
+
async setTools(tools, activeToolNames) {
|
|
561
|
+
this.tools = new Map(tools.map((tool) => [tool.name, tool]));
|
|
562
|
+
if (activeToolNames) {
|
|
563
|
+
this.validateToolNames(activeToolNames);
|
|
564
|
+
this.activeToolNames = [...activeToolNames];
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
this.validateToolNames(this.activeToolNames);
|
|
568
|
+
}
|
|
569
|
+
if (this.phase === "idle") {
|
|
570
|
+
this.agent.state.tools = this.activeToolNames.map((name) => this.tools.get(name));
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async abort() {
|
|
574
|
+
const clearedSteer = [...this.steerQueue];
|
|
575
|
+
const clearedFollowUp = [...this.followUpQueue];
|
|
576
|
+
this.steerQueue = [];
|
|
577
|
+
this.followUpQueue = [];
|
|
578
|
+
this.agent.clearAllQueues();
|
|
579
|
+
await this.emitQueueUpdate();
|
|
580
|
+
this.agent.abort();
|
|
581
|
+
await this.agent.waitForIdle();
|
|
582
|
+
await this.emitOwn({ type: "abort", clearedSteer, clearedFollowUp });
|
|
583
|
+
return { clearedSteer, clearedFollowUp };
|
|
584
|
+
}
|
|
585
|
+
async waitForIdle() {
|
|
586
|
+
await this.agent.waitForIdle();
|
|
587
|
+
}
|
|
588
|
+
subscribe(listener) {
|
|
589
|
+
this.listeners.add(listener);
|
|
590
|
+
return () => this.listeners.delete(listener);
|
|
591
|
+
}
|
|
592
|
+
on(type, handler) {
|
|
593
|
+
let handlers = this.hooks.get(type);
|
|
594
|
+
if (!handlers) {
|
|
595
|
+
handlers = new Set();
|
|
596
|
+
this.hooks.set(type, handlers);
|
|
597
|
+
}
|
|
598
|
+
handlers.add(handler);
|
|
599
|
+
return () => handlers.delete(handler);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
//# sourceMappingURL=agent-harness.js.map
|