omk-agent-core 0.80.8

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.
Files changed (102) hide show
  1. package/README.md +488 -0
  2. package/dist/agent-loop.d.ts +24 -0
  3. package/dist/agent-loop.d.ts.map +1 -0
  4. package/dist/agent-loop.js +553 -0
  5. package/dist/agent-loop.js.map +1 -0
  6. package/dist/agent.d.ts +118 -0
  7. package/dist/agent.d.ts.map +1 -0
  8. package/dist/agent.js +402 -0
  9. package/dist/agent.js.map +1 -0
  10. package/dist/harness/agent-harness.d.ts +95 -0
  11. package/dist/harness/agent-harness.d.ts.map +1 -0
  12. package/dist/harness/agent-harness.js +996 -0
  13. package/dist/harness/agent-harness.js.map +1 -0
  14. package/dist/harness/compaction/branch-summarization.d.ts +53 -0
  15. package/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
  16. package/dist/harness/compaction/branch-summarization.js +175 -0
  17. package/dist/harness/compaction/branch-summarization.js.map +1 -0
  18. package/dist/harness/compaction/compaction.d.ts +117 -0
  19. package/dist/harness/compaction/compaction.d.ts.map +1 -0
  20. package/dist/harness/compaction/compaction.js +568 -0
  21. package/dist/harness/compaction/compaction.js.map +1 -0
  22. package/dist/harness/compaction/utils.d.ts +25 -0
  23. package/dist/harness/compaction/utils.d.ts.map +1 -0
  24. package/dist/harness/compaction/utils.js +131 -0
  25. package/dist/harness/compaction/utils.js.map +1 -0
  26. package/dist/harness/env/nodejs.d.ts +51 -0
  27. package/dist/harness/env/nodejs.d.ts.map +1 -0
  28. package/dist/harness/env/nodejs.js +510 -0
  29. package/dist/harness/env/nodejs.js.map +1 -0
  30. package/dist/harness/messages.d.ts +51 -0
  31. package/dist/harness/messages.d.ts.map +1 -0
  32. package/dist/harness/messages.js +102 -0
  33. package/dist/harness/messages.js.map +1 -0
  34. package/dist/harness/prompt-templates.d.ts +48 -0
  35. package/dist/harness/prompt-templates.d.ts.map +1 -0
  36. package/dist/harness/prompt-templates.js +230 -0
  37. package/dist/harness/prompt-templates.js.map +1 -0
  38. package/dist/harness/session/jsonl-repo.d.ts +26 -0
  39. package/dist/harness/session/jsonl-repo.d.ts.map +1 -0
  40. package/dist/harness/session/jsonl-repo.js +101 -0
  41. package/dist/harness/session/jsonl-repo.js.map +1 -0
  42. package/dist/harness/session/jsonl-storage.d.ts +36 -0
  43. package/dist/harness/session/jsonl-storage.d.ts.map +1 -0
  44. package/dist/harness/session/jsonl-storage.js +273 -0
  45. package/dist/harness/session/jsonl-storage.js.map +1 -0
  46. package/dist/harness/session/memory-repo.d.ts +18 -0
  47. package/dist/harness/session/memory-repo.d.ts.map +1 -0
  48. package/dist/harness/session/memory-repo.js +42 -0
  49. package/dist/harness/session/memory-repo.js.map +1 -0
  50. package/dist/harness/session/memory-storage.d.ts +25 -0
  51. package/dist/harness/session/memory-storage.d.ts.map +1 -0
  52. package/dist/harness/session/memory-storage.js +114 -0
  53. package/dist/harness/session/memory-storage.js.map +1 -0
  54. package/dist/harness/session/repo-utils.d.ts +11 -0
  55. package/dist/harness/session/repo-utils.d.ts.map +1 -0
  56. package/dist/harness/session/repo-utils.js +39 -0
  57. package/dist/harness/session/repo-utils.js.map +1 -0
  58. package/dist/harness/session/session.d.ts +33 -0
  59. package/dist/harness/session/session.d.ts.map +1 -0
  60. package/dist/harness/session/session.js +210 -0
  61. package/dist/harness/session/session.js.map +1 -0
  62. package/dist/harness/session/uuid.d.ts +2 -0
  63. package/dist/harness/session/uuid.d.ts.map +1 -0
  64. package/dist/harness/session/uuid.js +50 -0
  65. package/dist/harness/session/uuid.js.map +1 -0
  66. package/dist/harness/skills.d.ts +44 -0
  67. package/dist/harness/skills.d.ts.map +1 -0
  68. package/dist/harness/skills.js +311 -0
  69. package/dist/harness/skills.js.map +1 -0
  70. package/dist/harness/system-prompt.d.ts +3 -0
  71. package/dist/harness/system-prompt.d.ts.map +1 -0
  72. package/dist/harness/system-prompt.js +30 -0
  73. package/dist/harness/system-prompt.js.map +1 -0
  74. package/dist/harness/types.d.ts +616 -0
  75. package/dist/harness/types.d.ts.map +1 -0
  76. package/dist/harness/types.js +94 -0
  77. package/dist/harness/types.js.map +1 -0
  78. package/dist/harness/utils/shell-output.d.ts +14 -0
  79. package/dist/harness/utils/shell-output.d.ts.map +1 -0
  80. package/dist/harness/utils/shell-output.js +126 -0
  81. package/dist/harness/utils/shell-output.js.map +1 -0
  82. package/dist/harness/utils/truncate.d.ts +70 -0
  83. package/dist/harness/utils/truncate.d.ts.map +1 -0
  84. package/dist/harness/utils/truncate.js +297 -0
  85. package/dist/harness/utils/truncate.js.map +1 -0
  86. package/dist/index.d.ts +20 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +25 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/node.d.ts +3 -0
  91. package/dist/node.d.ts.map +1 -0
  92. package/dist/node.js +3 -0
  93. package/dist/node.js.map +1 -0
  94. package/dist/proxy.d.ts +69 -0
  95. package/dist/proxy.d.ts.map +1 -0
  96. package/dist/proxy.js +278 -0
  97. package/dist/proxy.js.map +1 -0
  98. package/dist/types.d.ts +393 -0
  99. package/dist/types.d.ts.map +1 -0
  100. package/dist/types.js +2 -0
  101. package/dist/types.js.map +1 -0
  102. package/package.json +60 -0
@@ -0,0 +1,996 @@
1
+ import { streamSimple } from "omk-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
+ import { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from "./types.js";
9
+ function createUserMessage(text, images) {
10
+ const content = [{ type: "text", text }];
11
+ if (images)
12
+ content.push(...images);
13
+ return { role: "user", content, timestamp: Date.now() };
14
+ }
15
+ function createFailureMessage(model, error, aborted) {
16
+ return {
17
+ role: "assistant",
18
+ content: [{ type: "text", text: "" }],
19
+ api: model.api,
20
+ provider: model.provider,
21
+ model: model.id,
22
+ stopReason: aborted ? "aborted" : "error",
23
+ errorMessage: error instanceof Error ? error.message : String(error),
24
+ timestamp: Date.now(),
25
+ usage: {
26
+ input: 0,
27
+ output: 0,
28
+ cacheRead: 0,
29
+ cacheWrite: 0,
30
+ totalTokens: 0,
31
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
32
+ },
33
+ };
34
+ }
35
+ function cloneStreamOptions(streamOptions) {
36
+ return {
37
+ ...streamOptions,
38
+ headers: streamOptions?.headers ? { ...streamOptions.headers } : undefined,
39
+ metadata: streamOptions?.metadata ? { ...streamOptions.metadata } : undefined,
40
+ };
41
+ }
42
+ function mergeHeaders(...headers) {
43
+ const merged = {};
44
+ let hasHeaders = false;
45
+ for (const entry of headers) {
46
+ if (!entry)
47
+ continue;
48
+ Object.assign(merged, entry);
49
+ hasHeaders = true;
50
+ }
51
+ return hasHeaders ? merged : undefined;
52
+ }
53
+ function findDuplicateNames(names) {
54
+ const seen = new Set();
55
+ const duplicates = new Set();
56
+ for (const name of names) {
57
+ if (seen.has(name))
58
+ duplicates.add(name);
59
+ seen.add(name);
60
+ }
61
+ return [...duplicates];
62
+ }
63
+ function applyStreamOptionsPatch(base, patch) {
64
+ const result = cloneStreamOptions(base);
65
+ if (!patch)
66
+ return result;
67
+ if (Object.hasOwn(patch, "transport"))
68
+ result.transport = patch.transport;
69
+ if (Object.hasOwn(patch, "timeoutMs"))
70
+ result.timeoutMs = patch.timeoutMs;
71
+ if (Object.hasOwn(patch, "maxRetries"))
72
+ result.maxRetries = patch.maxRetries;
73
+ if (Object.hasOwn(patch, "maxRetryDelayMs"))
74
+ result.maxRetryDelayMs = patch.maxRetryDelayMs;
75
+ if (Object.hasOwn(patch, "cacheRetention"))
76
+ result.cacheRetention = patch.cacheRetention;
77
+ if (Object.hasOwn(patch, "headers")) {
78
+ if (patch.headers === undefined) {
79
+ result.headers = undefined;
80
+ }
81
+ else {
82
+ const headers = { ...(result.headers ?? {}) };
83
+ for (const [key, value] of Object.entries(patch.headers)) {
84
+ if (value === undefined)
85
+ delete headers[key];
86
+ else
87
+ headers[key] = value;
88
+ }
89
+ result.headers = Object.keys(headers).length > 0 ? headers : undefined;
90
+ }
91
+ }
92
+ if (Object.hasOwn(patch, "metadata")) {
93
+ if (patch.metadata === undefined) {
94
+ result.metadata = undefined;
95
+ }
96
+ else {
97
+ const metadata = { ...(result.metadata ?? {}) };
98
+ for (const [key, value] of Object.entries(patch.metadata)) {
99
+ if (value === undefined)
100
+ delete metadata[key];
101
+ else
102
+ metadata[key] = value;
103
+ }
104
+ result.metadata = Object.keys(metadata).length > 0 ? metadata : undefined;
105
+ }
106
+ }
107
+ return result;
108
+ }
109
+ const SUBSCRIBER_EVENT_TYPE = "*";
110
+ function normalizeHarnessError(error, fallbackCode) {
111
+ if (error instanceof AgentHarnessError)
112
+ return error;
113
+ const cause = toError(error);
114
+ if (cause instanceof SessionError)
115
+ return new AgentHarnessError("session", cause.message, cause);
116
+ if (cause instanceof CompactionError)
117
+ return new AgentHarnessError("compaction", cause.message, cause);
118
+ if (cause instanceof BranchSummaryError)
119
+ return new AgentHarnessError("branch_summary", cause.message, cause);
120
+ return new AgentHarnessError(fallbackCode, cause.message, cause);
121
+ }
122
+ function normalizeHookError(error) {
123
+ return normalizeHarnessError(error, "hook");
124
+ }
125
+ export class AgentHarness {
126
+ env;
127
+ session;
128
+ phase = "idle";
129
+ runAbortController;
130
+ runPromise;
131
+ pendingSessionWrites = [];
132
+ model;
133
+ thinkingLevel;
134
+ systemPrompt;
135
+ streamOptions;
136
+ getApiKeyAndHeaders;
137
+ resources;
138
+ tools = new Map();
139
+ activeToolNames;
140
+ steerQueue = [];
141
+ steeringQueueMode;
142
+ followUpQueue = [];
143
+ followUpQueueMode;
144
+ nextTurnQueue = [];
145
+ handlers = new Map();
146
+ constructor(options) {
147
+ this.env = options.env;
148
+ this.session = options.session;
149
+ this.resources = options.resources ?? {};
150
+ this.streamOptions = cloneStreamOptions(options.streamOptions);
151
+ this.systemPrompt = options.systemPrompt;
152
+ this.getApiKeyAndHeaders = options.getApiKeyAndHeaders;
153
+ this.validateUniqueNames((options.tools ?? []).map((tool) => tool.name), "Duplicate tool name(s)");
154
+ for (const tool of options.tools ?? []) {
155
+ this.tools.set(tool.name, tool);
156
+ }
157
+ this.model = options.model;
158
+ this.thinkingLevel = options.thinkingLevel ?? "off";
159
+ this.activeToolNames = options.activeToolNames
160
+ ? [...options.activeToolNames]
161
+ : (options.tools ?? []).map((tool) => tool.name);
162
+ this.validateUniqueNames(this.activeToolNames, "Duplicate active tool name(s)");
163
+ this.validateToolNames(this.activeToolNames);
164
+ this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
165
+ this.followUpQueueMode = options.followUpMode ?? "one-at-a-time";
166
+ }
167
+ getHandlers(type) {
168
+ return this.handlers.get(type);
169
+ }
170
+ async emitOwn(event, signal) {
171
+ for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
172
+ try {
173
+ await listener(event, signal);
174
+ }
175
+ catch (error) {
176
+ throw normalizeHookError(error);
177
+ }
178
+ }
179
+ }
180
+ async emitAny(event, signal) {
181
+ for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
182
+ try {
183
+ await listener(event, signal);
184
+ }
185
+ catch (error) {
186
+ throw normalizeHookError(error);
187
+ }
188
+ }
189
+ }
190
+ async emitHook(event) {
191
+ const handlers = this.getHandlers(event.type);
192
+ if (!handlers || handlers.size === 0)
193
+ return undefined;
194
+ let lastResult;
195
+ for (const handler of handlers) {
196
+ try {
197
+ const result = await handler(event);
198
+ if (result !== undefined) {
199
+ lastResult = result;
200
+ }
201
+ }
202
+ catch (error) {
203
+ throw normalizeHookError(error);
204
+ }
205
+ }
206
+ return lastResult;
207
+ }
208
+ async emitBeforeProviderRequest(model, sessionId, streamOptions) {
209
+ const handlers = this.getHandlers("before_provider_request");
210
+ let current = cloneStreamOptions(streamOptions);
211
+ if (!handlers || handlers.size === 0)
212
+ return current;
213
+ for (const handler of handlers) {
214
+ try {
215
+ const result = await handler({
216
+ type: "before_provider_request",
217
+ model,
218
+ sessionId,
219
+ streamOptions: cloneStreamOptions(current),
220
+ });
221
+ if (result?.streamOptions) {
222
+ current = applyStreamOptionsPatch(current, result.streamOptions);
223
+ }
224
+ }
225
+ catch (error) {
226
+ throw normalizeHookError(error);
227
+ }
228
+ }
229
+ return current;
230
+ }
231
+ async emitBeforeProviderPayload(model, payload) {
232
+ const handlers = this.getHandlers("before_provider_payload");
233
+ let current = payload;
234
+ if (!handlers || handlers.size === 0)
235
+ return current;
236
+ for (const handler of handlers) {
237
+ try {
238
+ const result = await handler({ type: "before_provider_payload", model, payload: current });
239
+ if (result !== undefined) {
240
+ current = result.payload;
241
+ }
242
+ }
243
+ catch (error) {
244
+ throw normalizeHookError(error);
245
+ }
246
+ }
247
+ return current;
248
+ }
249
+ async emitQueueUpdate() {
250
+ await this.emitOwn({
251
+ type: "queue_update",
252
+ steer: [...this.steerQueue],
253
+ followUp: [...this.followUpQueue],
254
+ nextTurn: [...this.nextTurnQueue],
255
+ });
256
+ }
257
+ startRunPromise() {
258
+ let finish = () => { };
259
+ const runPromise = new Promise((resolve) => {
260
+ finish = resolve;
261
+ });
262
+ this.runPromise = runPromise;
263
+ return {
264
+ runPromise,
265
+ finishRunPromise: () => {
266
+ // A settled/agent_end listener may have started a new run while this
267
+ // one was still unwinding; only clear state still owned by this run.
268
+ if (this.runPromise === runPromise) {
269
+ this.runPromise = undefined;
270
+ }
271
+ finish();
272
+ },
273
+ };
274
+ }
275
+ async createTurnState() {
276
+ const context = await this.session.buildContext();
277
+ const resources = this.getResources();
278
+ const sessionMetadata = await this.session.getMetadata();
279
+ const tools = [...this.tools.values()];
280
+ const activeTools = this.activeToolNames
281
+ .map((name) => this.tools.get(name))
282
+ .filter((tool) => tool !== undefined);
283
+ let systemPrompt = "You are a helpful assistant.";
284
+ if (typeof this.systemPrompt === "string") {
285
+ systemPrompt = this.systemPrompt;
286
+ }
287
+ else if (this.systemPrompt) {
288
+ systemPrompt = await this.systemPrompt({
289
+ env: this.env,
290
+ session: this.session,
291
+ model: this.model,
292
+ thinkingLevel: this.thinkingLevel,
293
+ activeTools,
294
+ resources,
295
+ });
296
+ }
297
+ return {
298
+ messages: context.messages,
299
+ resources,
300
+ streamOptions: cloneStreamOptions(this.streamOptions),
301
+ sessionId: sessionMetadata.id,
302
+ systemPrompt,
303
+ model: this.model,
304
+ thinkingLevel: this.thinkingLevel,
305
+ tools,
306
+ activeTools,
307
+ };
308
+ }
309
+ createContext(turnState, systemPrompt) {
310
+ return {
311
+ systemPrompt: systemPrompt ?? turnState.systemPrompt,
312
+ messages: turnState.messages.slice(),
313
+ tools: turnState.activeTools.slice(),
314
+ };
315
+ }
316
+ createStreamFn(getTurnState) {
317
+ return async (model, context, streamOptions) => {
318
+ const turnState = getTurnState();
319
+ const auth = await this.getApiKeyAndHeaders?.(model);
320
+ const snapshotOptions = {
321
+ ...turnState.streamOptions,
322
+ headers: mergeHeaders(turnState.streamOptions.headers, auth?.headers),
323
+ };
324
+ const requestOptions = await this.emitBeforeProviderRequest(model, turnState.sessionId, snapshotOptions);
325
+ return streamSimple(model, context, {
326
+ cacheRetention: requestOptions.cacheRetention,
327
+ headers: requestOptions.headers,
328
+ maxRetries: requestOptions.maxRetries,
329
+ maxRetryDelayMs: requestOptions.maxRetryDelayMs,
330
+ metadata: requestOptions.metadata,
331
+ onPayload: async (payload) => await this.emitBeforeProviderPayload(model, payload),
332
+ onResponse: async (response) => {
333
+ const headers = { ...response.headers };
334
+ await this.emitOwn({ type: "after_provider_response", status: response.status, headers }, streamOptions?.signal);
335
+ },
336
+ reasoning: streamOptions?.reasoning,
337
+ signal: streamOptions?.signal,
338
+ sessionId: turnState.sessionId,
339
+ timeoutMs: requestOptions.timeoutMs,
340
+ transport: requestOptions.transport,
341
+ apiKey: auth?.apiKey,
342
+ });
343
+ };
344
+ }
345
+ async drainQueuedMessages(queue, mode) {
346
+ const messages = mode === "all" ? queue.splice(0) : queue.splice(0, 1);
347
+ if (messages.length === 0)
348
+ return messages;
349
+ try {
350
+ await this.emitQueueUpdate();
351
+ return messages;
352
+ }
353
+ catch (error) {
354
+ queue.unshift(...messages);
355
+ throw normalizeHookError(error);
356
+ }
357
+ }
358
+ createLoopConfig(getTurnState, setTurnState) {
359
+ const turnState = getTurnState();
360
+ return {
361
+ model: turnState.model,
362
+ reasoning: turnState.thinkingLevel === "off" ? undefined : turnState.thinkingLevel,
363
+ convertToLlm,
364
+ transformContext: async (messages) => {
365
+ const result = await this.emitHook({ type: "context", messages: [...messages] });
366
+ return result?.messages ?? messages;
367
+ },
368
+ beforeToolCall: async ({ toolCall, args }) => {
369
+ const result = await this.emitHook({
370
+ type: "tool_call",
371
+ toolCallId: toolCall.id,
372
+ toolName: toolCall.name,
373
+ input: args,
374
+ });
375
+ return result ? { block: result.block, reason: result.reason } : undefined;
376
+ },
377
+ afterToolCall: async ({ toolCall, args, result, isError }) => {
378
+ const patch = await this.emitHook({
379
+ type: "tool_result",
380
+ toolCallId: toolCall.id,
381
+ toolName: toolCall.name,
382
+ input: args,
383
+ content: result.content,
384
+ details: result.details,
385
+ isError,
386
+ });
387
+ return patch
388
+ ? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }
389
+ : undefined;
390
+ },
391
+ prepareNextTurn: async () => {
392
+ await this.flushPendingSessionWrites();
393
+ const nextTurnState = await this.createTurnState();
394
+ setTurnState(nextTurnState);
395
+ return {
396
+ context: this.createContext(nextTurnState),
397
+ model: nextTurnState.model,
398
+ thinkingLevel: nextTurnState.thinkingLevel,
399
+ };
400
+ },
401
+ getSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),
402
+ getFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),
403
+ };
404
+ }
405
+ validateUniqueNames(names, message) {
406
+ const duplicates = findDuplicateNames(names);
407
+ if (duplicates.length > 0)
408
+ throw new AgentHarnessError("invalid_argument", `${message}: ${duplicates.join(", ")}`);
409
+ }
410
+ validateToolNames(toolNames, tools = this.tools) {
411
+ this.validateUniqueNames(toolNames, "Duplicate active tool name(s)");
412
+ const missing = toolNames.filter((name) => !tools.has(name));
413
+ if (missing.length > 0)
414
+ throw new AgentHarnessError("invalid_argument", `Unknown tool(s): ${missing.join(", ")}`);
415
+ }
416
+ async flushPendingSessionWrites() {
417
+ while (this.pendingSessionWrites.length > 0) {
418
+ const write = this.pendingSessionWrites[0];
419
+ if (write.type === "message") {
420
+ await this.session.appendMessage(write.message);
421
+ }
422
+ else if (write.type === "model_change") {
423
+ await this.session.appendModelChange(write.provider, write.modelId);
424
+ }
425
+ else if (write.type === "thinking_level_change") {
426
+ await this.session.appendThinkingLevelChange(write.thinkingLevel);
427
+ }
428
+ else if (write.type === "active_tools_change") {
429
+ await this.session.appendActiveToolsChange(write.activeToolNames);
430
+ }
431
+ else if (write.type === "custom") {
432
+ await this.session.appendCustomEntry(write.customType, write.data);
433
+ }
434
+ else if (write.type === "custom_message") {
435
+ await this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);
436
+ }
437
+ else if (write.type === "label") {
438
+ await this.session.appendLabel(write.targetId, write.label);
439
+ }
440
+ else if (write.type === "session_info") {
441
+ await this.session.appendSessionName(write.name ?? "");
442
+ }
443
+ else if (write.type === "leaf") {
444
+ await this.session.getStorage().setLeafId(write.targetId);
445
+ }
446
+ this.pendingSessionWrites.shift();
447
+ }
448
+ }
449
+ async handleAgentEvent(event, signal) {
450
+ if (event.type === "message_end") {
451
+ await this.session.appendMessage(event.message);
452
+ await this.emitAny(event, signal);
453
+ return;
454
+ }
455
+ if (event.type === "turn_end") {
456
+ let eventError;
457
+ try {
458
+ await this.emitAny(event, signal);
459
+ }
460
+ catch (error) {
461
+ eventError = error;
462
+ }
463
+ const hadPendingMutations = this.pendingSessionWrites.length > 0;
464
+ await this.flushPendingSessionWrites();
465
+ if (eventError)
466
+ throw eventError;
467
+ await this.emitOwn({ type: "save_point", hadPendingMutations });
468
+ return;
469
+ }
470
+ if (event.type === "agent_end") {
471
+ // Finish this run's cleanup before flipping to "idle": listeners observing
472
+ // agent_end/settled may start the next prompt() immediately, and that run
473
+ // must not inherit state still owned by the run that just completed.
474
+ await this.flushPendingSessionWrites();
475
+ if (this.runAbortController && this.runAbortController.signal === signal) {
476
+ this.runAbortController = undefined;
477
+ }
478
+ this.phase = "idle";
479
+ await this.emitAny(event, signal);
480
+ await this.emitOwn({ type: "settled", nextTurnCount: this.nextTurnQueue.length }, signal);
481
+ return;
482
+ }
483
+ await this.emitAny(event, signal);
484
+ }
485
+ async emitRunFailure(model, error, aborted, signal, completedMessages) {
486
+ const failureMessage = createFailureMessage(model, error, aborted);
487
+ const messages = [...completedMessages, failureMessage];
488
+ await this.handleAgentEvent({ type: "message_start", message: failureMessage }, signal);
489
+ await this.handleAgentEvent({ type: "message_end", message: failureMessage }, signal);
490
+ await this.handleAgentEvent({ type: "turn_end", message: failureMessage, toolResults: [] }, signal);
491
+ await this.handleAgentEvent({ type: "agent_end", messages }, signal);
492
+ return messages;
493
+ }
494
+ async executeTurn(turnState, text, options) {
495
+ let activeTurnState = turnState;
496
+ let messages = [createUserMessage(text, options?.images)];
497
+ if (this.nextTurnQueue.length > 0) {
498
+ const queuedMessages = this.nextTurnQueue.splice(0);
499
+ try {
500
+ await this.emitQueueUpdate();
501
+ }
502
+ catch (error) {
503
+ this.nextTurnQueue.unshift(...queuedMessages);
504
+ throw normalizeHookError(error);
505
+ }
506
+ messages = [...queuedMessages, messages[0]];
507
+ }
508
+ const beforeResult = await this.emitHook({
509
+ type: "before_agent_start",
510
+ prompt: text,
511
+ images: options?.images,
512
+ systemPrompt: turnState.systemPrompt,
513
+ resources: turnState.resources,
514
+ });
515
+ if (beforeResult?.messages)
516
+ messages = [...messages, ...beforeResult.messages];
517
+ const abortController = new AbortController();
518
+ const getTurnState = () => activeTurnState;
519
+ const setTurnState = (nextTurnState) => {
520
+ activeTurnState = nextTurnState;
521
+ };
522
+ this.runAbortController = abortController;
523
+ const completedMessages = [];
524
+ const runResultPromise = (async () => {
525
+ try {
526
+ return await runAgentLoop(messages, this.createContext(turnState, beforeResult?.systemPrompt), this.createLoopConfig(getTurnState, setTurnState), async (event) => {
527
+ if (event.type === "message_end")
528
+ completedMessages.push(event.message);
529
+ await this.handleAgentEvent(event, abortController.signal);
530
+ }, abortController.signal, this.createStreamFn(getTurnState));
531
+ }
532
+ catch (error) {
533
+ try {
534
+ return await this.emitRunFailure(activeTurnState.model, error, abortController.signal.aborted, abortController.signal, completedMessages);
535
+ }
536
+ catch (failureError) {
537
+ const cause = new AggregateError([toError(error), toError(failureError)], "Agent run failed and failure reporting failed");
538
+ throw new AgentHarnessError("unknown", cause.message, cause);
539
+ }
540
+ }
541
+ })();
542
+ try {
543
+ const newMessages = await runResultPromise;
544
+ for (let i = newMessages.length - 1; i >= 0; i--) {
545
+ const message = newMessages[i];
546
+ if (message.role === "assistant") {
547
+ return message;
548
+ }
549
+ }
550
+ throw new AgentHarnessError("invalid_state", "AgentHarness prompt completed without an assistant message");
551
+ }
552
+ finally {
553
+ try {
554
+ await this.flushPendingSessionWrites();
555
+ }
556
+ finally {
557
+ // Ownership check: a re-entrant run started from a settled/agent_end
558
+ // listener may have installed its own controller already.
559
+ if (this.runAbortController === abortController) {
560
+ this.runAbortController = undefined;
561
+ }
562
+ }
563
+ }
564
+ }
565
+ async prompt(text, options) {
566
+ if (this.phase !== "idle")
567
+ throw new AgentHarnessError("busy", "AgentHarness is busy");
568
+ this.phase = "turn";
569
+ const { runPromise, finishRunPromise } = this.startRunPromise();
570
+ try {
571
+ const turnState = await this.createTurnState();
572
+ return await this.executeTurn(turnState, text, options);
573
+ }
574
+ catch (error) {
575
+ // Only reset the phase if this call still owns the run; a listener may
576
+ // have started the next run while this one was unwinding.
577
+ if (this.runPromise === runPromise)
578
+ this.phase = "idle";
579
+ throw normalizeHarnessError(error, "unknown");
580
+ }
581
+ finally {
582
+ finishRunPromise();
583
+ }
584
+ }
585
+ async skill(name, additionalInstructions) {
586
+ if (this.phase !== "idle")
587
+ throw new AgentHarnessError("busy", "AgentHarness is busy");
588
+ this.phase = "turn";
589
+ const { runPromise, finishRunPromise } = this.startRunPromise();
590
+ try {
591
+ const turnState = await this.createTurnState();
592
+ const skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);
593
+ if (!skill)
594
+ throw new AgentHarnessError("invalid_argument", `Unknown skill: ${name}`);
595
+ return await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));
596
+ }
597
+ catch (error) {
598
+ if (this.runPromise === runPromise)
599
+ this.phase = "idle";
600
+ throw normalizeHarnessError(error, "unknown");
601
+ }
602
+ finally {
603
+ finishRunPromise();
604
+ }
605
+ }
606
+ async promptFromTemplate(name, args = []) {
607
+ if (this.phase !== "idle")
608
+ throw new AgentHarnessError("busy", "AgentHarness is busy");
609
+ this.phase = "turn";
610
+ const { runPromise, finishRunPromise } = this.startRunPromise();
611
+ try {
612
+ const turnState = await this.createTurnState();
613
+ const template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
614
+ if (!template)
615
+ throw new AgentHarnessError("invalid_argument", `Unknown prompt template: ${name}`);
616
+ return await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));
617
+ }
618
+ catch (error) {
619
+ if (this.runPromise === runPromise)
620
+ this.phase = "idle";
621
+ throw normalizeHarnessError(error, "unknown");
622
+ }
623
+ finally {
624
+ finishRunPromise();
625
+ }
626
+ }
627
+ async steer(text, options) {
628
+ if (this.phase === "idle")
629
+ throw new AgentHarnessError("invalid_state", "Cannot steer while idle");
630
+ this.steerQueue.push(createUserMessage(text, options?.images));
631
+ await this.emitQueueUpdate();
632
+ }
633
+ async followUp(text, options) {
634
+ if (this.phase === "idle")
635
+ throw new AgentHarnessError("invalid_state", "Cannot follow up while idle");
636
+ this.followUpQueue.push(createUserMessage(text, options?.images));
637
+ await this.emitQueueUpdate();
638
+ }
639
+ async nextTurn(text, options) {
640
+ this.nextTurnQueue.push(createUserMessage(text, options?.images));
641
+ await this.emitQueueUpdate();
642
+ }
643
+ async appendMessage(message) {
644
+ try {
645
+ if (this.phase === "idle") {
646
+ await this.session.appendMessage(message);
647
+ }
648
+ else {
649
+ this.pendingSessionWrites.push({ type: "message", message });
650
+ }
651
+ }
652
+ catch (error) {
653
+ throw normalizeHarnessError(error, "session");
654
+ }
655
+ }
656
+ async compact(customInstructions) {
657
+ if (this.phase !== "idle")
658
+ throw new AgentHarnessError("busy", "compact() requires idle harness");
659
+ this.phase = "compaction";
660
+ try {
661
+ const model = this.model;
662
+ if (!model)
663
+ throw new AgentHarnessError("invalid_state", "No model set for compaction");
664
+ const auth = await this.getApiKeyAndHeaders?.(model);
665
+ if (!auth)
666
+ throw new AgentHarnessError("auth", "No auth available for compaction");
667
+ const branchEntries = await this.session.getBranch();
668
+ const preparationResult = prepareCompaction(branchEntries, DEFAULT_COMPACTION_SETTINGS);
669
+ if (!preparationResult.ok)
670
+ throw preparationResult.error;
671
+ const preparation = preparationResult.value;
672
+ if (!preparation)
673
+ throw new AgentHarnessError("compaction", "Nothing to compact");
674
+ const hookResult = await this.emitHook({
675
+ type: "session_before_compact",
676
+ preparation,
677
+ branchEntries,
678
+ customInstructions,
679
+ signal: new AbortController().signal,
680
+ });
681
+ if (hookResult?.cancel)
682
+ throw new AgentHarnessError("compaction", "Compaction cancelled");
683
+ const provided = hookResult?.compaction;
684
+ const compactResult = provided
685
+ ? { ok: true, value: provided }
686
+ : await compact(preparation, model, auth.apiKey, auth.headers, customInstructions, undefined, this.thinkingLevel);
687
+ if (!compactResult.ok)
688
+ throw compactResult.error;
689
+ const result = compactResult.value;
690
+ const entryId = await this.session.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, result.details, provided !== undefined);
691
+ const entry = await this.session.getEntry(entryId);
692
+ if (entry?.type === "compaction") {
693
+ await this.emitOwn({ type: "session_compact", compactionEntry: entry, fromHook: provided !== undefined });
694
+ }
695
+ return result;
696
+ }
697
+ catch (error) {
698
+ throw normalizeHarnessError(error, "compaction");
699
+ }
700
+ finally {
701
+ this.phase = "idle";
702
+ }
703
+ }
704
+ async navigateTree(targetId, options) {
705
+ if (this.phase !== "idle")
706
+ throw new AgentHarnessError("busy", "navigateTree() requires idle harness");
707
+ this.phase = "branch_summary";
708
+ try {
709
+ const oldLeafId = await this.session.getLeafId();
710
+ if (oldLeafId === targetId)
711
+ return { cancelled: false };
712
+ const targetEntry = await this.session.getEntry(targetId);
713
+ if (!targetEntry)
714
+ throw new AgentHarnessError("invalid_argument", `Entry ${targetId} not found`);
715
+ const { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);
716
+ const preparation = {
717
+ targetId,
718
+ oldLeafId,
719
+ commonAncestorId,
720
+ entriesToSummarize: entries,
721
+ userWantsSummary: options?.summarize ?? false,
722
+ customInstructions: options?.customInstructions,
723
+ replaceInstructions: options?.replaceInstructions,
724
+ label: options?.label,
725
+ };
726
+ const signal = new AbortController().signal;
727
+ const hookResult = await this.emitHook({ type: "session_before_tree", preparation, signal });
728
+ if (hookResult?.cancel)
729
+ return { cancelled: true };
730
+ let summaryEntry;
731
+ let summaryText = hookResult?.summary?.summary;
732
+ let summaryDetails = hookResult?.summary?.details;
733
+ if (!summaryText && options?.summarize && entries.length > 0) {
734
+ const model = this.model;
735
+ if (!model)
736
+ throw new AgentHarnessError("invalid_state", "No model set for branch summary");
737
+ const auth = await this.getApiKeyAndHeaders?.(model);
738
+ if (!auth)
739
+ throw new AgentHarnessError("auth", "No auth available for branch summary");
740
+ const branchSummary = await generateBranchSummary(entries, {
741
+ model,
742
+ apiKey: auth.apiKey,
743
+ headers: auth.headers,
744
+ signal: new AbortController().signal,
745
+ customInstructions: hookResult?.customInstructions ?? options?.customInstructions,
746
+ replaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,
747
+ });
748
+ if (!branchSummary.ok) {
749
+ if (branchSummary.error.code === "aborted")
750
+ return { cancelled: true };
751
+ throw new AgentHarnessError("branch_summary", branchSummary.error.message, branchSummary.error);
752
+ }
753
+ summaryText = branchSummary.value.summary;
754
+ summaryDetails = {
755
+ readFiles: branchSummary.value.readFiles,
756
+ modifiedFiles: branchSummary.value.modifiedFiles,
757
+ };
758
+ }
759
+ let editorText;
760
+ let newLeafId;
761
+ if (targetEntry.type === "message" && targetEntry.message.role === "user") {
762
+ newLeafId = targetEntry.parentId;
763
+ const content = targetEntry.message.content;
764
+ editorText =
765
+ typeof content === "string"
766
+ ? content
767
+ : content
768
+ .filter((c) => c.type === "text")
769
+ .map((c) => c.text)
770
+ .join("");
771
+ }
772
+ else if (targetEntry.type === "custom_message") {
773
+ newLeafId = targetEntry.parentId;
774
+ editorText =
775
+ typeof targetEntry.content === "string"
776
+ ? targetEntry.content
777
+ : targetEntry.content
778
+ .filter((c) => c.type === "text")
779
+ .map((c) => c.text)
780
+ .join("");
781
+ }
782
+ else {
783
+ newLeafId = targetId;
784
+ }
785
+ const summaryId = await this.session.moveTo(newLeafId, summaryText
786
+ ? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }
787
+ : undefined);
788
+ if (summaryId) {
789
+ const entry = await this.session.getEntry(summaryId);
790
+ if (entry?.type === "branch_summary")
791
+ summaryEntry = entry;
792
+ }
793
+ await this.emitOwn({
794
+ type: "session_tree",
795
+ newLeafId: await this.session.getLeafId(),
796
+ oldLeafId,
797
+ summaryEntry,
798
+ fromHook: hookResult?.summary !== undefined,
799
+ });
800
+ return { cancelled: false, editorText, summaryEntry };
801
+ }
802
+ catch (error) {
803
+ throw normalizeHarnessError(error, "branch_summary");
804
+ }
805
+ finally {
806
+ this.phase = "idle";
807
+ }
808
+ }
809
+ getModel() {
810
+ return this.model;
811
+ }
812
+ async setModel(model) {
813
+ try {
814
+ const previousModel = this.model;
815
+ if (this.phase === "idle") {
816
+ await this.session.appendModelChange(model.provider, model.id);
817
+ }
818
+ else {
819
+ this.pendingSessionWrites.push({ type: "model_change", provider: model.provider, modelId: model.id });
820
+ }
821
+ this.model = model;
822
+ await this.emitOwn({ type: "model_update", model, previousModel, source: "set" });
823
+ }
824
+ catch (error) {
825
+ throw normalizeHarnessError(error, "session");
826
+ }
827
+ }
828
+ getThinkingLevel() {
829
+ return this.thinkingLevel;
830
+ }
831
+ async setThinkingLevel(level) {
832
+ try {
833
+ const previousLevel = this.thinkingLevel;
834
+ if (this.phase === "idle") {
835
+ await this.session.appendThinkingLevelChange(level);
836
+ }
837
+ else {
838
+ this.pendingSessionWrites.push({ type: "thinking_level_change", thinkingLevel: level });
839
+ }
840
+ this.thinkingLevel = level;
841
+ await this.emitOwn({ type: "thinking_level_update", level, previousLevel });
842
+ }
843
+ catch (error) {
844
+ throw normalizeHarnessError(error, "session");
845
+ }
846
+ }
847
+ getTools() {
848
+ return [...this.tools.values()];
849
+ }
850
+ async setTools(tools, activeToolNames) {
851
+ try {
852
+ this.validateUniqueNames(tools.map((tool) => tool.name), "Duplicate tool name(s)");
853
+ const nextTools = new Map(tools.map((tool) => [tool.name, tool]));
854
+ const nextActiveToolNames = activeToolNames ? [...activeToolNames] : this.activeToolNames;
855
+ this.validateToolNames(nextActiveToolNames, nextTools);
856
+ const previousToolNames = [...this.tools.keys()];
857
+ const previousActiveToolNames = [...this.activeToolNames];
858
+ if (this.phase === "idle") {
859
+ await this.session.appendActiveToolsChange(nextActiveToolNames);
860
+ }
861
+ else {
862
+ this.pendingSessionWrites.push({ type: "active_tools_change", activeToolNames: [...nextActiveToolNames] });
863
+ }
864
+ this.tools = nextTools;
865
+ this.activeToolNames = [...nextActiveToolNames];
866
+ await this.emitOwn({
867
+ type: "tools_update",
868
+ toolNames: [...this.tools.keys()],
869
+ previousToolNames,
870
+ activeToolNames: [...this.activeToolNames],
871
+ previousActiveToolNames,
872
+ source: "set",
873
+ });
874
+ }
875
+ catch (error) {
876
+ throw normalizeHarnessError(error, "invalid_argument");
877
+ }
878
+ }
879
+ getActiveTools() {
880
+ return this.activeToolNames.map((name) => this.tools.get(name));
881
+ }
882
+ async setActiveTools(toolNames) {
883
+ try {
884
+ this.validateToolNames(toolNames);
885
+ const previousToolNames = [...this.tools.keys()];
886
+ const previousActiveToolNames = [...this.activeToolNames];
887
+ if (this.phase === "idle") {
888
+ await this.session.appendActiveToolsChange(toolNames);
889
+ }
890
+ else {
891
+ this.pendingSessionWrites.push({ type: "active_tools_change", activeToolNames: [...toolNames] });
892
+ }
893
+ this.activeToolNames = [...toolNames];
894
+ await this.emitOwn({
895
+ type: "tools_update",
896
+ toolNames: [...this.tools.keys()],
897
+ previousToolNames,
898
+ activeToolNames: [...this.activeToolNames],
899
+ previousActiveToolNames,
900
+ source: "set",
901
+ });
902
+ }
903
+ catch (error) {
904
+ throw normalizeHarnessError(error, "invalid_argument");
905
+ }
906
+ }
907
+ getSteeringMode() {
908
+ return this.steeringQueueMode;
909
+ }
910
+ async setSteeringMode(mode) {
911
+ this.steeringQueueMode = mode;
912
+ }
913
+ getFollowUpMode() {
914
+ return this.followUpQueueMode;
915
+ }
916
+ async setFollowUpMode(mode) {
917
+ this.followUpQueueMode = mode;
918
+ }
919
+ getResources() {
920
+ return {
921
+ skills: this.resources.skills?.slice(),
922
+ promptTemplates: this.resources.promptTemplates?.slice(),
923
+ };
924
+ }
925
+ async setResources(resources) {
926
+ const previousResources = this.getResources();
927
+ this.resources = {
928
+ skills: resources.skills?.slice(),
929
+ promptTemplates: resources.promptTemplates?.slice(),
930
+ };
931
+ await this.emitOwn({ type: "resources_update", resources: this.getResources(), previousResources });
932
+ }
933
+ getStreamOptions() {
934
+ return cloneStreamOptions(this.streamOptions);
935
+ }
936
+ async setStreamOptions(streamOptions) {
937
+ this.streamOptions = cloneStreamOptions(streamOptions);
938
+ }
939
+ async abort() {
940
+ const clearedSteer = [...this.steerQueue];
941
+ const clearedFollowUp = [...this.followUpQueue];
942
+ this.steerQueue = [];
943
+ this.followUpQueue = [];
944
+ this.runAbortController?.abort();
945
+ const errors = [];
946
+ try {
947
+ await this.emitQueueUpdate();
948
+ }
949
+ catch (error) {
950
+ errors.push(toError(error));
951
+ }
952
+ try {
953
+ await this.waitForIdle();
954
+ }
955
+ catch (error) {
956
+ errors.push(toError(error));
957
+ }
958
+ try {
959
+ await this.emitOwn({ type: "abort", clearedSteer, clearedFollowUp });
960
+ }
961
+ catch (error) {
962
+ errors.push(toError(error));
963
+ }
964
+ if (errors.length > 0) {
965
+ const cause = errors.length === 1 ? errors[0] : new AggregateError(errors, "Abort completed with errors");
966
+ throw normalizeHarnessError(cause, "hook");
967
+ }
968
+ return { clearedSteer, clearedFollowUp };
969
+ }
970
+ async waitForIdle() {
971
+ // Runs can chain: a settled listener may start the next prompt() before the
972
+ // previous run's promise resolves, so keep waiting until no run is active.
973
+ while (this.runPromise) {
974
+ await this.runPromise;
975
+ }
976
+ }
977
+ subscribe(listener) {
978
+ let handlers = this.handlers.get(SUBSCRIBER_EVENT_TYPE);
979
+ if (!handlers) {
980
+ handlers = new Set();
981
+ this.handlers.set(SUBSCRIBER_EVENT_TYPE, handlers);
982
+ }
983
+ handlers.add(listener);
984
+ return () => handlers.delete(listener);
985
+ }
986
+ on(type, handler) {
987
+ let handlers = this.handlers.get(type);
988
+ if (!handlers) {
989
+ handlers = new Set();
990
+ this.handlers.set(type, handlers);
991
+ }
992
+ handlers.add(handler);
993
+ return () => handlers.delete(handler);
994
+ }
995
+ }
996
+ //# sourceMappingURL=agent-harness.js.map