@harness-kernel/core 0.1.0-beta.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/NOTICE +3 -0
- package/README.md +12 -0
- package/dist/agent/context.d.ts +5 -0
- package/dist/agent/context.js +11 -0
- package/dist/agent/context.js.map +1 -0
- package/dist/agent/event.d.ts +3 -0
- package/dist/agent/event.js +50 -0
- package/dist/agent/event.js.map +1 -0
- package/dist/agent/hook.d.ts +6 -0
- package/dist/agent/hook.js +7 -0
- package/dist/agent/hook.js.map +1 -0
- package/dist/agent/mode.d.ts +5 -0
- package/dist/agent/mode.js +7 -0
- package/dist/agent/mode.js.map +1 -0
- package/dist/agent/role.d.ts +1 -0
- package/dist/agent/role.js +27 -0
- package/dist/agent/role.js.map +1 -0
- package/dist/agent/session.d.ts +6 -0
- package/dist/agent/session.js +13 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/tool.d.ts +5 -0
- package/dist/agent/tool.js +7 -0
- package/dist/agent/tool.js.map +1 -0
- package/dist/agent.d.ts +12 -0
- package/dist/agent.js +7 -0
- package/dist/agent.js.map +1 -0
- package/dist/approval-DfvjpbFs.d.ts +247 -0
- package/dist/chunk-4A2P4QU5.js +179 -0
- package/dist/chunk-4A2P4QU5.js.map +1 -0
- package/dist/chunk-4SYLFKIX.js +207 -0
- package/dist/chunk-4SYLFKIX.js.map +1 -0
- package/dist/chunk-4WWSQAWA.js +3778 -0
- package/dist/chunk-4WWSQAWA.js.map +1 -0
- package/dist/chunk-AD3BCYWU.js +37 -0
- package/dist/chunk-AD3BCYWU.js.map +1 -0
- package/dist/chunk-AZVA22HW.js +135 -0
- package/dist/chunk-AZVA22HW.js.map +1 -0
- package/dist/chunk-OBKS4AJR.js +529 -0
- package/dist/chunk-OBKS4AJR.js.map +1 -0
- package/dist/chunk-Q44U2CMM.js +239 -0
- package/dist/chunk-Q44U2CMM.js.map +1 -0
- package/dist/chunk-Q73WH5D7.js +54 -0
- package/dist/chunk-Q73WH5D7.js.map +1 -0
- package/dist/chunk-RRWJUHJG.js +9 -0
- package/dist/chunk-RRWJUHJG.js.map +1 -0
- package/dist/context-75mlon5x.d.ts +394 -0
- package/dist/event-CKV4EeZ3.d.ts +230 -0
- package/dist/events-D4xcDi53.d.ts +69 -0
- package/dist/hook-DMb9fw9Z.d.ts +20 -0
- package/dist/index.d.ts +164 -0
- package/dist/index.js +174 -0
- package/dist/index.js.map +1 -0
- package/dist/model-provider-BrZ2RRmS.d.ts +130 -0
- package/dist/role-BN6KhQxx.d.ts +68 -0
- package/dist/runner/approval.d.ts +9 -0
- package/dist/runner/approval.js +1 -0
- package/dist/runner/approval.js.map +1 -0
- package/dist/runner/event.d.ts +3 -0
- package/dist/runner/event.js +50 -0
- package/dist/runner/event.js.map +1 -0
- package/dist/runner/logging.d.ts +31 -0
- package/dist/runner/logging.js +15 -0
- package/dist/runner/logging.js.map +1 -0
- package/dist/runner/model-provider.d.ts +8 -0
- package/dist/runner/model-provider.js +11 -0
- package/dist/runner/model-provider.js.map +1 -0
- package/dist/runner/sandbox.d.ts +47 -0
- package/dist/runner/sandbox.js +13 -0
- package/dist/runner/sandbox.js.map +1 -0
- package/dist/runner/storage.d.ts +6 -0
- package/dist/runner/storage.js +17 -0
- package/dist/runner/storage.js.map +1 -0
- package/dist/runner-Dxo7ALtp.d.ts +87 -0
- package/dist/runner.d.ts +10 -0
- package/dist/runner.js +18 -0
- package/dist/runner.js.map +1 -0
- package/dist/schema.d.ts +146 -0
- package/dist/schema.js +39 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage-BmOEwW-p.d.ts +118 -0
- package/dist/tool-errors-CygY1Nba.d.ts +27 -0
- package/dist/types-BPmsw-mF.d.ts +80 -0
- package/package.json +114 -0
|
@@ -0,0 +1,3778 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HarnessModelProviderRegistry,
|
|
3
|
+
modelProviderId
|
|
4
|
+
} from "./chunk-Q73WH5D7.js";
|
|
5
|
+
import {
|
|
6
|
+
AgentDebugLog,
|
|
7
|
+
AgentErrorLog,
|
|
8
|
+
AgentInfoLog,
|
|
9
|
+
AgentWarnLog,
|
|
10
|
+
ConsoleLogSink,
|
|
11
|
+
HarnessLog,
|
|
12
|
+
normalizeHarnessLog,
|
|
13
|
+
randomId,
|
|
14
|
+
shouldWriteLog,
|
|
15
|
+
summarizeValue
|
|
16
|
+
} from "./chunk-Q44U2CMM.js";
|
|
17
|
+
import {
|
|
18
|
+
HarnessSandboxSession,
|
|
19
|
+
NoopSandbox
|
|
20
|
+
} from "./chunk-AD3BCYWU.js";
|
|
21
|
+
import {
|
|
22
|
+
NoopRunStorage
|
|
23
|
+
} from "./chunk-AZVA22HW.js";
|
|
24
|
+
import {
|
|
25
|
+
ContextReadyEvent,
|
|
26
|
+
ErrorEvent,
|
|
27
|
+
MessageDeltaEvent,
|
|
28
|
+
MessageEndEvent,
|
|
29
|
+
MessageStartEvent,
|
|
30
|
+
ModeChangedEvent,
|
|
31
|
+
ModelAfterEvent,
|
|
32
|
+
ModelBeforeEvent,
|
|
33
|
+
RunEndEvent,
|
|
34
|
+
RunStartEvent,
|
|
35
|
+
SnapshotCreatedEvent,
|
|
36
|
+
SnapshotDeletedEvent,
|
|
37
|
+
SnapshotRestoredEvent,
|
|
38
|
+
ToolApprovalRequestedEvent,
|
|
39
|
+
ToolApprovalResolvedEvent,
|
|
40
|
+
ToolEndEvent,
|
|
41
|
+
ToolStartEvent,
|
|
42
|
+
TranscriptCursorChangedEvent,
|
|
43
|
+
TurnEndEvent,
|
|
44
|
+
TurnStartEvent,
|
|
45
|
+
runtimeEventClasses
|
|
46
|
+
} from "./chunk-4SYLFKIX.js";
|
|
47
|
+
import {
|
|
48
|
+
normalizeSchema
|
|
49
|
+
} from "./chunk-OBKS4AJR.js";
|
|
50
|
+
import {
|
|
51
|
+
HarnessContextProvider,
|
|
52
|
+
HarnessEvent,
|
|
53
|
+
HarnessHook,
|
|
54
|
+
HarnessMode,
|
|
55
|
+
HarnessRole,
|
|
56
|
+
HarnessTool,
|
|
57
|
+
assistantRole,
|
|
58
|
+
systemRole,
|
|
59
|
+
toolRole,
|
|
60
|
+
userRole
|
|
61
|
+
} from "./chunk-4A2P4QU5.js";
|
|
62
|
+
|
|
63
|
+
// src/logging/tool-errors.ts
|
|
64
|
+
function createToolErrorPayload(input) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: {
|
|
68
|
+
code: input.code,
|
|
69
|
+
message: input.message,
|
|
70
|
+
toolName: input.toolName,
|
|
71
|
+
invalidFields: input.invalidFields,
|
|
72
|
+
metadata: input.metadata
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/logging/runtime-logs.ts
|
|
78
|
+
var SessionCreatedLog = class extends HarnessLog {
|
|
79
|
+
level = "info";
|
|
80
|
+
category = "session";
|
|
81
|
+
message() {
|
|
82
|
+
return "session.created";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var RunStartedLog = class extends HarnessLog {
|
|
86
|
+
level = "info";
|
|
87
|
+
category = "run";
|
|
88
|
+
message(fields) {
|
|
89
|
+
return `run.started mode=${fields.modeId} model=${fields.model}`;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var RunCompletedLog = class extends HarnessLog {
|
|
93
|
+
level = "info";
|
|
94
|
+
category = "run";
|
|
95
|
+
message(fields) {
|
|
96
|
+
return `run.completed durationMs=${fields.durationMs} messages=${fields.messageCount} events=${fields.eventCount}`;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var RunFailedLog = class extends HarnessLog {
|
|
100
|
+
level = "error";
|
|
101
|
+
category = "run";
|
|
102
|
+
message() {
|
|
103
|
+
return "run.failed";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var TurnStartedLog = class extends HarnessLog {
|
|
107
|
+
level = "info";
|
|
108
|
+
category = "turn";
|
|
109
|
+
message(fields) {
|
|
110
|
+
return `turn.started ${fields.turnId}`;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var TurnCompletedLog = class extends HarnessLog {
|
|
114
|
+
level = "info";
|
|
115
|
+
category = "turn";
|
|
116
|
+
message() {
|
|
117
|
+
return "turn.completed";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var ContextBuildStartedLog = class extends HarnessLog {
|
|
121
|
+
level = "debug";
|
|
122
|
+
category = "context";
|
|
123
|
+
message(fields) {
|
|
124
|
+
return `context.started providers=${fields.providerCount}`;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var ContextBuildCompletedLog = class extends HarnessLog {
|
|
128
|
+
level = "info";
|
|
129
|
+
category = "context";
|
|
130
|
+
message(fields) {
|
|
131
|
+
return `context.ready providers=${fields.providerCount} contributions=${fields.contributionCount} durationMs=${fields.durationMs}`;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
var ContextProviderFailedLog = class extends HarnessLog {
|
|
135
|
+
level = "error";
|
|
136
|
+
category = "context";
|
|
137
|
+
message(fields) {
|
|
138
|
+
return `context.provider.failed ${fields.providerType}`;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var ModelCallStartedLog = class extends HarnessLog {
|
|
142
|
+
level = "info";
|
|
143
|
+
category = "model";
|
|
144
|
+
message(fields) {
|
|
145
|
+
return `model.started model=${fields.model} messages=${fields.messageCount}`;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var ModelCallCompletedLog = class extends HarnessLog {
|
|
149
|
+
level = "info";
|
|
150
|
+
category = "model";
|
|
151
|
+
message(fields) {
|
|
152
|
+
return `model.completed model=${fields.model} durationMs=${fields.durationMs}${fields.finishReason ? ` finish=${fields.finishReason}` : ""}`;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var ModelCallFailedLog = class extends HarnessLog {
|
|
156
|
+
level = "error";
|
|
157
|
+
category = "model";
|
|
158
|
+
message(fields) {
|
|
159
|
+
return `model.failed model=${fields.model}`;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
var ModelDeltaLog = class extends HarnessLog {
|
|
163
|
+
level = "info";
|
|
164
|
+
category = "model";
|
|
165
|
+
message(fields) {
|
|
166
|
+
return `model.delta length=${fields.length}`;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var ToolStartedLog = class extends HarnessLog {
|
|
170
|
+
level = "info";
|
|
171
|
+
category = "tool";
|
|
172
|
+
message(fields) {
|
|
173
|
+
return `tool.started ${fields.toolName}`;
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
var ToolArgsEmptyLog = class extends HarnessLog {
|
|
177
|
+
level = "warn";
|
|
178
|
+
category = "tool";
|
|
179
|
+
message(fields) {
|
|
180
|
+
return `tool.args.empty ${fields.toolName}`;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var ToolInvalidSchemaLog = class extends HarnessLog {
|
|
184
|
+
level = "error";
|
|
185
|
+
category = "tool";
|
|
186
|
+
message(fields) {
|
|
187
|
+
return `tool.args.invalid_schema ${fields.toolName}`;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
var ToolCompletedLog = class extends HarnessLog {
|
|
191
|
+
level = "info";
|
|
192
|
+
category = "tool";
|
|
193
|
+
message(fields) {
|
|
194
|
+
return `tool.completed ${fields.toolName} durationMs=${fields.durationMs} isError=${fields.isError}`;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var ToolFailedLog = class extends HarnessLog {
|
|
198
|
+
level = "error";
|
|
199
|
+
category = "tool";
|
|
200
|
+
message(fields) {
|
|
201
|
+
return `tool.failed ${fields.toolName}`;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var ToolApprovalRequestedLog = class extends HarnessLog {
|
|
205
|
+
level = "warn";
|
|
206
|
+
category = "approval";
|
|
207
|
+
message(fields) {
|
|
208
|
+
return `tool.approval.requested ${fields.toolName}`;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var ToolApprovalResolvedLog = class extends HarnessLog {
|
|
212
|
+
level = "info";
|
|
213
|
+
category = "approval";
|
|
214
|
+
levelFor(fields) {
|
|
215
|
+
return fields.approved ? "info" : "warn";
|
|
216
|
+
}
|
|
217
|
+
message(fields) {
|
|
218
|
+
return fields.approved ? `tool.approval.approved ${fields.toolName}` : `tool.approval.denied ${fields.toolName}`;
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var SandboxOpenedLog = class extends HarnessLog {
|
|
222
|
+
level = "info";
|
|
223
|
+
category = "tool";
|
|
224
|
+
message(fields) {
|
|
225
|
+
return `sandbox.opened ${fields.sandboxId}`;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var SandboxExecStartedLog = class extends HarnessLog {
|
|
229
|
+
level = "debug";
|
|
230
|
+
category = "tool";
|
|
231
|
+
message(fields) {
|
|
232
|
+
return `sandbox.exec.started ${fields.sandboxId}`;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var SandboxExecCompletedLog = class extends HarnessLog {
|
|
236
|
+
level = "debug";
|
|
237
|
+
category = "tool";
|
|
238
|
+
message(fields) {
|
|
239
|
+
return `sandbox.exec.completed ${fields.sandboxId} exitCode=${fields.exitCode ?? "null"} durationMs=${fields.durationMs}`;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var SandboxExecFailedLog = class extends HarnessLog {
|
|
243
|
+
level = "error";
|
|
244
|
+
category = "tool";
|
|
245
|
+
message(fields) {
|
|
246
|
+
return `sandbox.exec.failed ${fields.sandboxId}`;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
var SandboxClosedLog = class extends HarnessLog {
|
|
250
|
+
level = "info";
|
|
251
|
+
category = "tool";
|
|
252
|
+
message(fields) {
|
|
253
|
+
return `sandbox.closed ${fields.sandboxId}`;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var RunStorageOpenedLog = class extends HarnessLog {
|
|
257
|
+
level = "debug";
|
|
258
|
+
category = "storage";
|
|
259
|
+
message(fields) {
|
|
260
|
+
return `storage.opened ${fields.storageId} run=${fields.runId}`;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
var SnapshotCreatedLog = class extends HarnessLog {
|
|
264
|
+
level = "info";
|
|
265
|
+
category = "snapshot";
|
|
266
|
+
message() {
|
|
267
|
+
return "snapshot.created";
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var SnapshotRestoredLog = class extends HarnessLog {
|
|
271
|
+
level = "info";
|
|
272
|
+
category = "snapshot";
|
|
273
|
+
message() {
|
|
274
|
+
return "snapshot.restored";
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var SnapshotRestoreRejectedLog = class extends HarnessLog {
|
|
278
|
+
level = "warn";
|
|
279
|
+
category = "snapshot";
|
|
280
|
+
message() {
|
|
281
|
+
return "snapshot.restore_rejected";
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var SnapshotDeletedLog = class extends HarnessLog {
|
|
285
|
+
level = "info";
|
|
286
|
+
category = "snapshot";
|
|
287
|
+
message() {
|
|
288
|
+
return "snapshot.deleted";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var TranscriptCursorChangedLog = class extends HarnessLog {
|
|
292
|
+
level = "debug";
|
|
293
|
+
category = "transcript";
|
|
294
|
+
message() {
|
|
295
|
+
return "transcript.cursor_changed";
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var StorageWriteFailedLog = class extends HarnessLog {
|
|
299
|
+
level = "error";
|
|
300
|
+
category = "storage";
|
|
301
|
+
message(fields) {
|
|
302
|
+
return `storage.write_failed ${fields.operation}`;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// src/runtime/constructs.ts
|
|
307
|
+
function constructClass(value) {
|
|
308
|
+
return typeof value === "function" ? value : value.constructor;
|
|
309
|
+
}
|
|
310
|
+
function stripConstructSuffix(name) {
|
|
311
|
+
return name.replace(/(ContextProvider|Provider|Tool|Mode|Hook|Role|Event)$/u, "");
|
|
312
|
+
}
|
|
313
|
+
function wordsFromName(name) {
|
|
314
|
+
const spaced = name.replace(/[_:-]+/gu, " ").replace(/([a-z0-9])([A-Z])/gu, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/gu, "$1 $2").trim();
|
|
315
|
+
return spaced ? spaced.split(/\s+/u) : [name];
|
|
316
|
+
}
|
|
317
|
+
function snakeFromType(type) {
|
|
318
|
+
return wordsFromName(stripConstructSuffix(type)).map((word) => word.toLowerCase()).join("_");
|
|
319
|
+
}
|
|
320
|
+
function labelFromType(type) {
|
|
321
|
+
return wordsFromName(stripConstructSuffix(type)).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
322
|
+
}
|
|
323
|
+
function getConstructType(constructOrClass) {
|
|
324
|
+
const cls = constructClass(constructOrClass);
|
|
325
|
+
return cls.type ?? cls.name ?? "AnonymousConstruct";
|
|
326
|
+
}
|
|
327
|
+
function getConstructLabel(constructOrClass) {
|
|
328
|
+
const instanceLabel = typeof constructOrClass === "object" && "label" in constructOrClass && typeof constructOrClass.label === "string" ? constructOrClass.label : void 0;
|
|
329
|
+
if (instanceLabel) return instanceLabel;
|
|
330
|
+
const cls = constructClass(constructOrClass);
|
|
331
|
+
return cls.label ?? labelFromType(getConstructType(constructOrClass));
|
|
332
|
+
}
|
|
333
|
+
function getToolName(toolOrClass) {
|
|
334
|
+
if (typeof toolOrClass === "object" && typeof toolOrClass.name === "string") return toolOrClass.name;
|
|
335
|
+
const cls = constructClass(toolOrClass);
|
|
336
|
+
return cls.name ?? snakeFromType(getConstructType(toolOrClass));
|
|
337
|
+
}
|
|
338
|
+
function getRoleName(role) {
|
|
339
|
+
return role.name ?? role.nativeRole ?? snakeFromType(getConstructType(role));
|
|
340
|
+
}
|
|
341
|
+
function modeSummary(mode) {
|
|
342
|
+
return {
|
|
343
|
+
type: getConstructType(mode),
|
|
344
|
+
label: getConstructLabel(mode)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function contextProviderSummary(provider, options) {
|
|
348
|
+
return {
|
|
349
|
+
type: getConstructType(provider),
|
|
350
|
+
label: getConstructLabel(provider),
|
|
351
|
+
options
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function isConstructInstance(value, BaseClass) {
|
|
355
|
+
return value instanceof BaseClass;
|
|
356
|
+
}
|
|
357
|
+
function isModeInstance(value) {
|
|
358
|
+
return isConstructInstance(value, HarnessMode);
|
|
359
|
+
}
|
|
360
|
+
function isToolInstance(value) {
|
|
361
|
+
return isConstructInstance(value, HarnessTool);
|
|
362
|
+
}
|
|
363
|
+
function isContextProviderInstance(value) {
|
|
364
|
+
return isConstructInstance(value, HarnessContextProvider);
|
|
365
|
+
}
|
|
366
|
+
function isRoleInstance(value) {
|
|
367
|
+
return isConstructInstance(value, HarnessRole);
|
|
368
|
+
}
|
|
369
|
+
function isHookInstance(value) {
|
|
370
|
+
return isConstructInstance(value, HarnessHook);
|
|
371
|
+
}
|
|
372
|
+
function isClassLike(value) {
|
|
373
|
+
return typeof value === "function" && typeof value.prototype === "object";
|
|
374
|
+
}
|
|
375
|
+
function isModeClass(value) {
|
|
376
|
+
return isClassLike(value) && HarnessMode.prototype.isPrototypeOf(value.prototype);
|
|
377
|
+
}
|
|
378
|
+
function isToolClass(value) {
|
|
379
|
+
return isClassLike(value) && HarnessTool.prototype.isPrototypeOf(value.prototype);
|
|
380
|
+
}
|
|
381
|
+
function isRoleClass(value) {
|
|
382
|
+
return isClassLike(value) && HarnessRole.prototype.isPrototypeOf(value.prototype);
|
|
383
|
+
}
|
|
384
|
+
function isContextProviderClass(value) {
|
|
385
|
+
return isClassLike(value) && HarnessContextProvider.prototype.isPrototypeOf(value.prototype);
|
|
386
|
+
}
|
|
387
|
+
function modeMatchesSelector(mode, selector) {
|
|
388
|
+
if (typeof selector === "string") return getConstructType(mode) === selector;
|
|
389
|
+
if (isModeClass(selector)) return mode instanceof selector || getConstructType(mode) === getConstructType(selector);
|
|
390
|
+
return mode === selector || mode.constructor === selector.constructor || getConstructType(mode) === getConstructType(selector);
|
|
391
|
+
}
|
|
392
|
+
function toolMatchesSelector(tool, selector) {
|
|
393
|
+
if (typeof selector === "string") return getToolName(tool) === selector || getConstructType(tool) === selector;
|
|
394
|
+
if (isToolClass(selector)) return tool instanceof selector || getConstructType(tool) === getConstructType(selector);
|
|
395
|
+
return tool === selector || tool.constructor === selector.constructor || getConstructType(tool) === getConstructType(selector);
|
|
396
|
+
}
|
|
397
|
+
function roleMatchesSelector(role, selector) {
|
|
398
|
+
if (typeof selector === "string") {
|
|
399
|
+
return getRoleName(role) === selector || getConstructType(role) === selector || role.nativeRole === selector;
|
|
400
|
+
}
|
|
401
|
+
if (isRoleClass(selector)) return role instanceof selector || getConstructType(role) === getConstructType(selector);
|
|
402
|
+
return role === selector || role.constructor === selector.constructor || getConstructType(role) === getConstructType(selector);
|
|
403
|
+
}
|
|
404
|
+
function contextProviderMatchesSelector(provider, selector) {
|
|
405
|
+
if (typeof selector === "string") return getConstructType(provider) === selector;
|
|
406
|
+
if (isContextProviderClass(selector)) {
|
|
407
|
+
return provider instanceof selector || getConstructType(provider) === getConstructType(selector);
|
|
408
|
+
}
|
|
409
|
+
return provider === selector || provider.constructor === selector.constructor || getConstructType(provider) === getConstructType(selector);
|
|
410
|
+
}
|
|
411
|
+
function hookEventClass(hook) {
|
|
412
|
+
const eventClass = hook.eventClass;
|
|
413
|
+
if (eventClass) return eventClass;
|
|
414
|
+
const legacyEvents = hook.events;
|
|
415
|
+
if (legacyEvents !== void 0) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
`Hook '${getConstructType(hook)}' uses the old events array. Extend HarnessHook.for(EventClass) instead.`
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return void 0;
|
|
421
|
+
}
|
|
422
|
+
function eventType(eventClass) {
|
|
423
|
+
return getConstructType(eventClass);
|
|
424
|
+
}
|
|
425
|
+
function isContextProviderBinding(value) {
|
|
426
|
+
return typeof value === "object" && value !== null && "provider" in value && (isContextProviderInstance(value.provider) || isContextProviderClass(value.provider));
|
|
427
|
+
}
|
|
428
|
+
function isContextProviderReference(value) {
|
|
429
|
+
return isContextProviderInstance(value) || isContextProviderClass(value) || isContextProviderBinding(value);
|
|
430
|
+
}
|
|
431
|
+
function assertNoAuthorId(construct, kind) {
|
|
432
|
+
if (Object.prototype.hasOwnProperty.call(construct, "id")) {
|
|
433
|
+
throw new Error(`${kind} '${getConstructType(construct)}' declares an author id. Use the class type instead.`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/runtime/context-registry.ts
|
|
438
|
+
function nowIso() {
|
|
439
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
440
|
+
}
|
|
441
|
+
function cloneJSON(value) {
|
|
442
|
+
return JSON.parse(JSON.stringify(value));
|
|
443
|
+
}
|
|
444
|
+
var ContextRegistry = class {
|
|
445
|
+
snapshots = [];
|
|
446
|
+
currentSnapshot;
|
|
447
|
+
entries = [];
|
|
448
|
+
get current() {
|
|
449
|
+
return this.currentSnapshot;
|
|
450
|
+
}
|
|
451
|
+
get allEntries() {
|
|
452
|
+
return this.entries;
|
|
453
|
+
}
|
|
454
|
+
restore(input) {
|
|
455
|
+
this.entries = cloneJSON(input.entries);
|
|
456
|
+
this.currentSnapshot = input.snapshot ? cloneJSON(input.snapshot) : void 0;
|
|
457
|
+
}
|
|
458
|
+
recordSnapshot(snapshot) {
|
|
459
|
+
this.currentSnapshot = snapshot;
|
|
460
|
+
this.snapshots.push(snapshot);
|
|
461
|
+
}
|
|
462
|
+
loadSnapshots(snapshots) {
|
|
463
|
+
this.snapshots = cloneJSON(snapshots);
|
|
464
|
+
this.currentSnapshot = this.snapshots.at(-1);
|
|
465
|
+
}
|
|
466
|
+
filter(filter) {
|
|
467
|
+
let entries = [...this.entries];
|
|
468
|
+
if (filter?.id) entries = entries.filter((entry) => entry.id === filter.id);
|
|
469
|
+
if (filter?.scope) entries = entries.filter((entry) => entry.scope === filter.scope);
|
|
470
|
+
if (filter?.consume) entries = entries.filter((entry) => entry.consume === filter.consume);
|
|
471
|
+
if (filter?.providerId) entries = entries.filter((entry) => entry.contribution.providerId === filter.providerId);
|
|
472
|
+
if (filter?.role) entries = entries.filter((entry) => entry.contribution.authorRole === filter.role || entry.contribution.role === filter.role);
|
|
473
|
+
if (filter?.on) {
|
|
474
|
+
const on = eventType(filter.on);
|
|
475
|
+
entries = entries.filter((entry) => entry.on === on);
|
|
476
|
+
}
|
|
477
|
+
return entries;
|
|
478
|
+
}
|
|
479
|
+
entriesFor(eventClass, runId, turnId) {
|
|
480
|
+
const on = eventType(eventClass);
|
|
481
|
+
return this.entries.filter(
|
|
482
|
+
(entry) => this.isActive(entry, runId, turnId) && (entry.on === on || entry.activatedAt !== void 0)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
activateFor(eventClass, record, runId, turnId) {
|
|
486
|
+
const on = eventType(eventClass);
|
|
487
|
+
this.entries = this.entries.map((entry) => {
|
|
488
|
+
if (entry.on !== on || entry.activatedAt !== void 0 || !this.isActive(entry, runId, turnId)) return entry;
|
|
489
|
+
return {
|
|
490
|
+
...entry,
|
|
491
|
+
activatedAt: record.at,
|
|
492
|
+
activatedByEventId: record.id,
|
|
493
|
+
activatedByEventType: record.type
|
|
494
|
+
};
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
consume(entries) {
|
|
498
|
+
const consumed = new Set(entries.filter((entry) => entry.consume === "once" /* Once */).map((entry) => entry.id));
|
|
499
|
+
if (!consumed.size) return;
|
|
500
|
+
this.entries = this.entries.filter((entry) => !consumed.has(entry.id));
|
|
501
|
+
}
|
|
502
|
+
expireScope(scope, turnId) {
|
|
503
|
+
this.entries = this.entries.filter((entry) => {
|
|
504
|
+
if (entry.scope !== scope) return true;
|
|
505
|
+
if (scope === "turn" /* Turn */ && turnId) return entry.turnId !== turnId;
|
|
506
|
+
return false;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
addContribution(input) {
|
|
510
|
+
const options = input.options ?? {};
|
|
511
|
+
const entry = {
|
|
512
|
+
id: options.id ?? randomId(),
|
|
513
|
+
scope: options.scope ?? "run" /* Run */,
|
|
514
|
+
on: eventType(options.on ?? ModelBeforeEvent),
|
|
515
|
+
consume: options.consume ?? "once" /* Once */,
|
|
516
|
+
createdAt: nowIso(),
|
|
517
|
+
runId: input.runId,
|
|
518
|
+
turnId: input.turnId,
|
|
519
|
+
modeId: input.modeId,
|
|
520
|
+
contribution: input.contribution,
|
|
521
|
+
metadata: options.metadata
|
|
522
|
+
};
|
|
523
|
+
if (options.replace) {
|
|
524
|
+
this.entries = this.entries.filter((candidate) => candidate.id !== entry.id);
|
|
525
|
+
} else if (this.entries.some((candidate) => candidate.id === entry.id)) {
|
|
526
|
+
throw new Error(`Context entry '${entry.id}' already exists. Use replace: true to overwrite it.`);
|
|
527
|
+
}
|
|
528
|
+
this.entries.push(entry);
|
|
529
|
+
return cloneJSON(entry);
|
|
530
|
+
}
|
|
531
|
+
remove(id) {
|
|
532
|
+
const before = this.entries.length;
|
|
533
|
+
this.entries = this.entries.filter((entry) => entry.id !== id);
|
|
534
|
+
return this.entries.length !== before;
|
|
535
|
+
}
|
|
536
|
+
clear(filter) {
|
|
537
|
+
const targets = new Set(this.filter(filter).map((entry) => entry.id));
|
|
538
|
+
this.entries = this.entries.filter((entry) => !targets.has(entry.id));
|
|
539
|
+
return targets.size;
|
|
540
|
+
}
|
|
541
|
+
normalizeProviderInput(input, options, provider, normalize, runId, turnId, modeId) {
|
|
542
|
+
return this.addContribution({
|
|
543
|
+
contribution: normalize(input, {
|
|
544
|
+
providerId: provider?.type,
|
|
545
|
+
providerLabel: provider?.label
|
|
546
|
+
}),
|
|
547
|
+
options,
|
|
548
|
+
runId,
|
|
549
|
+
turnId,
|
|
550
|
+
modeId
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
isActive(entry, runId, turnId) {
|
|
554
|
+
if (entry.scope === "session" /* Session */) return true;
|
|
555
|
+
if (entry.scope === "run" /* Run */) return entry.runId === runId;
|
|
556
|
+
if (entry.scope === "turn" /* Turn */) return entry.turnId === turnId;
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// src/runtime/sandbox-manager.ts
|
|
562
|
+
var SandboxManager = class {
|
|
563
|
+
constructor(input) {
|
|
564
|
+
this.input = input;
|
|
565
|
+
}
|
|
566
|
+
input;
|
|
567
|
+
session;
|
|
568
|
+
get current() {
|
|
569
|
+
return this.session;
|
|
570
|
+
}
|
|
571
|
+
async ensureOpen() {
|
|
572
|
+
if (this.session) return this.session;
|
|
573
|
+
const opened = await this.input.sandbox.open({
|
|
574
|
+
sessionId: this.input.sessionId,
|
|
575
|
+
runId: this.input.getRunId(),
|
|
576
|
+
agentKey: this.input.agentKey,
|
|
577
|
+
workDir: this.input.workDir,
|
|
578
|
+
outputDir: this.input.getOutputDir(),
|
|
579
|
+
services: this.input.services
|
|
580
|
+
});
|
|
581
|
+
this.session = this.wrap(opened);
|
|
582
|
+
this.input.logOpened({ sandboxId: opened.id, workDir: opened.workDir });
|
|
583
|
+
return this.session;
|
|
584
|
+
}
|
|
585
|
+
async close() {
|
|
586
|
+
const sandbox = this.session;
|
|
587
|
+
this.session = void 0;
|
|
588
|
+
if (!sandbox) return;
|
|
589
|
+
await sandbox.close?.();
|
|
590
|
+
this.input.logClosed({ sandboxId: sandbox.id });
|
|
591
|
+
}
|
|
592
|
+
wrap(base) {
|
|
593
|
+
const input = this.input;
|
|
594
|
+
return new class LoggedHarnessSandboxSession extends HarnessSandboxSession {
|
|
595
|
+
id = base.id;
|
|
596
|
+
workDir = base.workDir;
|
|
597
|
+
async exec(commandInput) {
|
|
598
|
+
const started = performance.now();
|
|
599
|
+
input.logExecStarted({
|
|
600
|
+
sandboxId: base.id,
|
|
601
|
+
command: summarizeValue(commandInput.command),
|
|
602
|
+
cwd: commandInput.cwd,
|
|
603
|
+
timeoutMs: commandInput.timeoutMs
|
|
604
|
+
});
|
|
605
|
+
try {
|
|
606
|
+
const result = await base.exec(commandInput);
|
|
607
|
+
input.logExecCompleted({
|
|
608
|
+
sandboxId: base.id,
|
|
609
|
+
exitCode: result.exitCode,
|
|
610
|
+
signal: result.signal,
|
|
611
|
+
timedOut: result.timedOut,
|
|
612
|
+
durationMs: result.durationMs
|
|
613
|
+
});
|
|
614
|
+
return result;
|
|
615
|
+
} catch (error) {
|
|
616
|
+
input.logExecFailed({
|
|
617
|
+
sandboxId: base.id,
|
|
618
|
+
error,
|
|
619
|
+
durationMs: Math.round(performance.now() - started)
|
|
620
|
+
});
|
|
621
|
+
throw error;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async close() {
|
|
625
|
+
await base.close?.();
|
|
626
|
+
}
|
|
627
|
+
}();
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// src/runtime/storage-coordinator.ts
|
|
632
|
+
var RunStorageCoordinator = class {
|
|
633
|
+
constructor(input) {
|
|
634
|
+
this.input = input;
|
|
635
|
+
this.runIdValue = input.runId;
|
|
636
|
+
this.storePromise = this.createStore(this.runIdValue);
|
|
637
|
+
}
|
|
638
|
+
input;
|
|
639
|
+
runIdValue;
|
|
640
|
+
store;
|
|
641
|
+
storePromise;
|
|
642
|
+
initialized = false;
|
|
643
|
+
runtimeLoaded = false;
|
|
644
|
+
get runDir() {
|
|
645
|
+
return this.store?.runDir;
|
|
646
|
+
}
|
|
647
|
+
async ensureInitialized() {
|
|
648
|
+
if (this.initialized) return;
|
|
649
|
+
await this.write("init", (store) => store.init());
|
|
650
|
+
this.initialized = true;
|
|
651
|
+
}
|
|
652
|
+
async loadRuntimeState() {
|
|
653
|
+
if (this.runtimeLoaded) return void 0;
|
|
654
|
+
this.runtimeLoaded = true;
|
|
655
|
+
return {
|
|
656
|
+
snapshots: await this.write("load_snapshots", (store) => store.loadSnapshots()),
|
|
657
|
+
contextSnapshots: await this.write("load_context_snapshots", (store) => store.loadContextSnapshots()),
|
|
658
|
+
transcript: await this.write("load_transcript", (store) => store.loadTranscript()),
|
|
659
|
+
events: await this.write("load_events", (store) => store.loadEvents()),
|
|
660
|
+
cursors: await this.write("load_cursors", (store) => store.loadCursors())
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
async saveTranscript(messages) {
|
|
664
|
+
await this.write("save_transcript", (store) => store.saveTranscript(messages));
|
|
665
|
+
}
|
|
666
|
+
async saveMetrics(metrics) {
|
|
667
|
+
await this.write("save_metrics", (store) => store.saveMetrics(metrics));
|
|
668
|
+
}
|
|
669
|
+
async saveSnapshot(snapshot) {
|
|
670
|
+
await this.write("save_snapshot", (store) => store.saveSnapshot(snapshot));
|
|
671
|
+
}
|
|
672
|
+
async deleteSnapshot(id) {
|
|
673
|
+
await this.write("delete_snapshot", (store) => store.deleteSnapshot(id));
|
|
674
|
+
}
|
|
675
|
+
async saveContextSnapshot(snapshot) {
|
|
676
|
+
await this.write("save_context_snapshot", (store) => store.saveContextSnapshot(snapshot));
|
|
677
|
+
}
|
|
678
|
+
async recordEvent(event) {
|
|
679
|
+
await this.write("record_event", (store) => store.recordEvent(event));
|
|
680
|
+
}
|
|
681
|
+
async saveCursors(cursors) {
|
|
682
|
+
await this.write("save_cursors", (store) => store.saveCursors(cursors));
|
|
683
|
+
}
|
|
684
|
+
async beginRun(runId) {
|
|
685
|
+
await this.close();
|
|
686
|
+
this.runIdValue = runId;
|
|
687
|
+
this.store = void 0;
|
|
688
|
+
this.storePromise = this.createStore(runId);
|
|
689
|
+
this.initialized = false;
|
|
690
|
+
this.runtimeLoaded = false;
|
|
691
|
+
}
|
|
692
|
+
async close() {
|
|
693
|
+
const store = this.store ?? await this.storePromise;
|
|
694
|
+
await store.close?.();
|
|
695
|
+
}
|
|
696
|
+
async createStore(runId) {
|
|
697
|
+
try {
|
|
698
|
+
const store = await this.input.storage.openRun({
|
|
699
|
+
runId,
|
|
700
|
+
sessionId: this.input.sessionId,
|
|
701
|
+
agentKey: this.input.agentKey,
|
|
702
|
+
outputDir: this.input.outputDir
|
|
703
|
+
});
|
|
704
|
+
this.input.logOpened({ storageId: this.input.storage.id, runId, runDir: store.runDir });
|
|
705
|
+
return store;
|
|
706
|
+
} catch (error) {
|
|
707
|
+
this.input.logFailed({ operation: "open_run", error });
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async getStore() {
|
|
712
|
+
if (this.store) return this.store;
|
|
713
|
+
this.store = await this.storePromise;
|
|
714
|
+
return this.store;
|
|
715
|
+
}
|
|
716
|
+
async write(operation, write) {
|
|
717
|
+
try {
|
|
718
|
+
return await write(await this.getStore());
|
|
719
|
+
} catch (error) {
|
|
720
|
+
this.input.logFailed({ operation, error });
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// src/runtime/event-recorder.ts
|
|
727
|
+
function nowIso2() {
|
|
728
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
729
|
+
}
|
|
730
|
+
var RehydratedHarnessEvent = class extends HarnessEvent {
|
|
731
|
+
};
|
|
732
|
+
var EventRecorder = class {
|
|
733
|
+
events = [];
|
|
734
|
+
eventSeq = 0;
|
|
735
|
+
listeners = /* @__PURE__ */ new Set();
|
|
736
|
+
get count() {
|
|
737
|
+
return this.events.length;
|
|
738
|
+
}
|
|
739
|
+
subscribe(listener) {
|
|
740
|
+
this.listeners.add(listener);
|
|
741
|
+
return () => this.listeners.delete(listener);
|
|
742
|
+
}
|
|
743
|
+
load(records) {
|
|
744
|
+
if (records.length === 0) return;
|
|
745
|
+
this.events = records.map((record) => new RehydratedHarnessEvent(record));
|
|
746
|
+
this.eventSeq = Math.max(0, ...records.map((event) => event.seq));
|
|
747
|
+
}
|
|
748
|
+
has(id) {
|
|
749
|
+
return this.events.some((event) => event.id === id);
|
|
750
|
+
}
|
|
751
|
+
latestForBranch(branchId) {
|
|
752
|
+
let latest;
|
|
753
|
+
for (const event of this.events) {
|
|
754
|
+
if (event.record.branchId !== branchId) continue;
|
|
755
|
+
if (!latest || event.record.seq > latest.record.seq) latest = event;
|
|
756
|
+
}
|
|
757
|
+
return latest;
|
|
758
|
+
}
|
|
759
|
+
record(input) {
|
|
760
|
+
const record = {
|
|
761
|
+
id: randomId(),
|
|
762
|
+
seq: ++this.eventSeq,
|
|
763
|
+
branchId: input.branchId,
|
|
764
|
+
type: input.type,
|
|
765
|
+
eventClassId: input.type,
|
|
766
|
+
at: nowIso2(),
|
|
767
|
+
source: input.source,
|
|
768
|
+
payload: input.payload,
|
|
769
|
+
runId: input.runId,
|
|
770
|
+
turnId: input.turnId,
|
|
771
|
+
modeId: input.modeId,
|
|
772
|
+
correlationId: input.correlationId,
|
|
773
|
+
causationId: input.causationId,
|
|
774
|
+
hidden: true,
|
|
775
|
+
metadata: input.metadata
|
|
776
|
+
};
|
|
777
|
+
const event = new input.eventClass(record);
|
|
778
|
+
this.events.push(event);
|
|
779
|
+
return event;
|
|
780
|
+
}
|
|
781
|
+
query(filter, activeSegments) {
|
|
782
|
+
let events = this.events;
|
|
783
|
+
if (!filter?.includeInactive) {
|
|
784
|
+
events = events.filter((event) => {
|
|
785
|
+
const maxSeq = activeSegments.get(event.record.branchId);
|
|
786
|
+
return maxSeq !== void 0 && event.record.seq <= maxSeq;
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
if (filter?.event) events = events.filter((event) => event.type === eventType(filter.event));
|
|
790
|
+
if (filter?.type) events = events.filter((event) => event.type === filter.type);
|
|
791
|
+
if (filter?.sourceKind) events = events.filter((event) => event.record.source.kind === filter.sourceKind);
|
|
792
|
+
if (filter?.since) events = events.filter((event) => event.at >= filter.since);
|
|
793
|
+
if (filter?.until) events = events.filter((event) => event.at <= filter.until);
|
|
794
|
+
if (typeof filter?.limit === "number" && filter.limit > 0) events = events.slice(-filter.limit);
|
|
795
|
+
return events;
|
|
796
|
+
}
|
|
797
|
+
async notify(record) {
|
|
798
|
+
for (const listener of this.listeners) await listener(record);
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
// src/runtime/transcript-manager.ts
|
|
803
|
+
var rootBranchId = "main";
|
|
804
|
+
function nowIso3() {
|
|
805
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
806
|
+
}
|
|
807
|
+
function cloneJSON2(value) {
|
|
808
|
+
return JSON.parse(JSON.stringify(value));
|
|
809
|
+
}
|
|
810
|
+
function createTranscriptCursor(input) {
|
|
811
|
+
return {
|
|
812
|
+
id: randomId(),
|
|
813
|
+
branchId: input?.branchId ?? rootBranchId,
|
|
814
|
+
headMessageId: input?.headMessageId,
|
|
815
|
+
seq: input?.seq ?? 0,
|
|
816
|
+
updatedAt: nowIso3()
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
function createEventCursor(input) {
|
|
820
|
+
return {
|
|
821
|
+
id: randomId(),
|
|
822
|
+
branchId: input?.branchId ?? rootBranchId,
|
|
823
|
+
headEventId: input?.headEventId,
|
|
824
|
+
seq: input?.seq ?? 0,
|
|
825
|
+
updatedAt: nowIso3()
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
var TranscriptManager = class {
|
|
829
|
+
messages = [];
|
|
830
|
+
messageSeq = 0;
|
|
831
|
+
transcriptCursor = createTranscriptCursor();
|
|
832
|
+
eventCursor = createEventCursor();
|
|
833
|
+
branches = /* @__PURE__ */ new Map([
|
|
834
|
+
[rootBranchId, { id: rootBranchId, createdAt: nowIso3() }]
|
|
835
|
+
]);
|
|
836
|
+
get count() {
|
|
837
|
+
return this.messages.length;
|
|
838
|
+
}
|
|
839
|
+
get activeTranscriptCursor() {
|
|
840
|
+
return this.transcriptCursor;
|
|
841
|
+
}
|
|
842
|
+
get activeEventCursor() {
|
|
843
|
+
return this.eventCursor;
|
|
844
|
+
}
|
|
845
|
+
get allMessages() {
|
|
846
|
+
return this.messages;
|
|
847
|
+
}
|
|
848
|
+
get allBranches() {
|
|
849
|
+
return [...this.branches.values()];
|
|
850
|
+
}
|
|
851
|
+
addBranches(branches) {
|
|
852
|
+
for (const branch of branches) {
|
|
853
|
+
if (!this.branches.has(branch.id)) this.branches.set(branch.id, cloneJSON2(branch));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
loadTranscript(messages) {
|
|
857
|
+
if (messages.length === 0) return;
|
|
858
|
+
this.messages = cloneJSON2(messages);
|
|
859
|
+
this.messageSeq = Math.max(0, ...messages.map((message) => message.seq));
|
|
860
|
+
}
|
|
861
|
+
loadCursors(input) {
|
|
862
|
+
this.addBranches(input.branches ?? []);
|
|
863
|
+
const transcriptHead = input.transcriptCursor.headMessageId;
|
|
864
|
+
const eventHead = input.eventCursor.headEventId;
|
|
865
|
+
if (!transcriptHead || this.messages.some((message) => message.id === transcriptHead)) {
|
|
866
|
+
this.transcriptCursor = cloneJSON2(input.transcriptCursor);
|
|
867
|
+
}
|
|
868
|
+
if (!eventHead || input.eventExists(eventHead)) {
|
|
869
|
+
this.eventCursor = cloneJSON2(input.eventCursor);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
restoreCursors(input) {
|
|
873
|
+
this.addBranches(input.branches ?? []);
|
|
874
|
+
this.transcriptCursor = cloneJSON2(input.transcriptCursor);
|
|
875
|
+
this.eventCursor = cloneJSON2(input.eventCursor);
|
|
876
|
+
}
|
|
877
|
+
appendMessage(input) {
|
|
878
|
+
const branchId = input.branchId ?? this.transcriptCursor.branchId;
|
|
879
|
+
const message = {
|
|
880
|
+
id: input.id ?? randomId(),
|
|
881
|
+
seq: ++this.messageSeq,
|
|
882
|
+
branchId,
|
|
883
|
+
parentMessageId: this.transcriptCursor.headMessageId,
|
|
884
|
+
role: input.role,
|
|
885
|
+
authorRole: input.roleInfo?.authorRole,
|
|
886
|
+
roleType: input.roleInfo?.roleType,
|
|
887
|
+
content: input.content,
|
|
888
|
+
createdAt: input.createdAt ?? nowIso3(),
|
|
889
|
+
modeId: input.modeId,
|
|
890
|
+
turnId: input.turnId,
|
|
891
|
+
hidden: input.hidden,
|
|
892
|
+
eventCursor: cloneJSON2(this.eventCursor),
|
|
893
|
+
metadata: input.metadata
|
|
894
|
+
};
|
|
895
|
+
this.messages.push(message);
|
|
896
|
+
this.transcriptCursor = createTranscriptCursor({
|
|
897
|
+
branchId,
|
|
898
|
+
headMessageId: message.id,
|
|
899
|
+
seq: message.seq
|
|
900
|
+
});
|
|
901
|
+
return message;
|
|
902
|
+
}
|
|
903
|
+
markMessageEventCursor(messageId) {
|
|
904
|
+
const message = this.messages.find((candidate) => candidate.id === messageId);
|
|
905
|
+
if (!message) return false;
|
|
906
|
+
message.eventCursor = cloneJSON2(this.eventCursor);
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
ensureBranchForAppend() {
|
|
910
|
+
const cursor = this.transcriptCursor;
|
|
911
|
+
const latest = this.branchLatestMessage(cursor.branchId);
|
|
912
|
+
if (!latest || latest.id === cursor.headMessageId) return;
|
|
913
|
+
const branchId = randomId();
|
|
914
|
+
const branch = {
|
|
915
|
+
id: branchId,
|
|
916
|
+
createdAt: nowIso3(),
|
|
917
|
+
parentBranchId: cursor.branchId,
|
|
918
|
+
parentMessageId: cursor.headMessageId,
|
|
919
|
+
parentMessageSeq: cursor.seq,
|
|
920
|
+
parentEventSeq: this.eventCursor.seq
|
|
921
|
+
};
|
|
922
|
+
this.branches.set(branchId, branch);
|
|
923
|
+
this.transcriptCursor = createTranscriptCursor({
|
|
924
|
+
branchId,
|
|
925
|
+
headMessageId: cursor.headMessageId,
|
|
926
|
+
seq: cursor.seq
|
|
927
|
+
});
|
|
928
|
+
this.eventCursor = createEventCursor({
|
|
929
|
+
branchId,
|
|
930
|
+
headEventId: this.eventCursor.headEventId,
|
|
931
|
+
seq: this.eventCursor.seq
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
ensureBranchForEventAppend(latestEvent) {
|
|
935
|
+
if (!latestEvent || latestEvent.record.seq <= this.eventCursor.seq) return;
|
|
936
|
+
const branchId = randomId();
|
|
937
|
+
const branch = {
|
|
938
|
+
id: branchId,
|
|
939
|
+
createdAt: nowIso3(),
|
|
940
|
+
parentBranchId: this.eventCursor.branchId,
|
|
941
|
+
parentMessageId: this.transcriptCursor.headMessageId,
|
|
942
|
+
parentMessageSeq: this.transcriptCursor.seq,
|
|
943
|
+
parentEventSeq: this.eventCursor.seq
|
|
944
|
+
};
|
|
945
|
+
this.branches.set(branchId, branch);
|
|
946
|
+
this.transcriptCursor = createTranscriptCursor({
|
|
947
|
+
branchId,
|
|
948
|
+
headMessageId: this.transcriptCursor.headMessageId,
|
|
949
|
+
seq: this.transcriptCursor.seq
|
|
950
|
+
});
|
|
951
|
+
this.eventCursor = createEventCursor({
|
|
952
|
+
branchId,
|
|
953
|
+
headEventId: this.eventCursor.headEventId,
|
|
954
|
+
seq: this.eventCursor.seq
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
advanceEventCursor(record) {
|
|
958
|
+
this.eventCursor = createEventCursor({
|
|
959
|
+
branchId: record.branchId,
|
|
960
|
+
headEventId: record.id,
|
|
961
|
+
seq: record.seq
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
filter(options, currentTurnId) {
|
|
965
|
+
const activeIds = options?.includeInactive ? void 0 : this.activeMessageIds();
|
|
966
|
+
let messages = activeIds ? this.messages.filter((message) => activeIds.has(message.id)) : [...this.messages];
|
|
967
|
+
if (!options?.includeHidden) messages = messages.filter((message) => !message.hidden);
|
|
968
|
+
if (options?.beforeCurrentTurn && currentTurnId) {
|
|
969
|
+
messages = messages.filter((message) => message.turnId !== currentTurnId);
|
|
970
|
+
}
|
|
971
|
+
if (options?.roles?.length) {
|
|
972
|
+
const allowed = new Set(options.roles);
|
|
973
|
+
messages = messages.filter((message) => allowed.has(message.role));
|
|
974
|
+
}
|
|
975
|
+
messages.sort((a, b) => a.seq - b.seq);
|
|
976
|
+
if (typeof options?.limit === "number" && options.limit > 0) messages = messages.slice(-options.limit);
|
|
977
|
+
return messages;
|
|
978
|
+
}
|
|
979
|
+
resolveSeekTarget(target) {
|
|
980
|
+
if (target === "start") {
|
|
981
|
+
return {
|
|
982
|
+
transcriptCursor: createTranscriptCursor({ branchId: rootBranchId, seq: 0 }),
|
|
983
|
+
eventCursor: createEventCursor({ branchId: rootBranchId, seq: 0 })
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
if (target === "latest") {
|
|
987
|
+
const transcriptCursor = this.latestCursorForBranch(this.transcriptCursor.branchId);
|
|
988
|
+
const head2 = transcriptCursor.headMessageId ? this.messages.find((message) => message.id === transcriptCursor.headMessageId) : void 0;
|
|
989
|
+
return {
|
|
990
|
+
transcriptCursor,
|
|
991
|
+
eventCursor: head2 ? this.eventCursorForMessage(head2) : this.eventCursor
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
if ("messageId" in target) {
|
|
995
|
+
const message = this.messages.find((candidate) => candidate.id === target.messageId);
|
|
996
|
+
if (!message) throw new Error(`Transcript message '${target.messageId}' was not found.`);
|
|
997
|
+
return {
|
|
998
|
+
transcriptCursor: createTranscriptCursor({
|
|
999
|
+
branchId: message.branchId,
|
|
1000
|
+
headMessageId: message.id,
|
|
1001
|
+
seq: message.seq
|
|
1002
|
+
}),
|
|
1003
|
+
eventCursor: this.eventCursorForMessage(message)
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
const head = target.cursor.headMessageId ? this.messages.find((message) => message.id === target.cursor.headMessageId) : void 0;
|
|
1007
|
+
return {
|
|
1008
|
+
transcriptCursor: cloneJSON2(target.cursor),
|
|
1009
|
+
eventCursor: head ? this.eventCursorForMessage(head) : createEventCursor({
|
|
1010
|
+
branchId: target.cursor.branchId,
|
|
1011
|
+
seq: 0
|
|
1012
|
+
})
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
applyResolvedSeek(resolved) {
|
|
1016
|
+
this.transcriptCursor = resolved.transcriptCursor;
|
|
1017
|
+
this.eventCursor = resolved.eventCursor;
|
|
1018
|
+
}
|
|
1019
|
+
activeEventSegments(cursor = this.eventCursor) {
|
|
1020
|
+
const segments = /* @__PURE__ */ new Map();
|
|
1021
|
+
let branchId = cursor.branchId;
|
|
1022
|
+
let maxSeq = cursor.seq;
|
|
1023
|
+
while (branchId) {
|
|
1024
|
+
const existing = segments.get(branchId);
|
|
1025
|
+
segments.set(branchId, existing === void 0 ? maxSeq : Math.max(existing, maxSeq));
|
|
1026
|
+
const branch = this.branches.get(branchId);
|
|
1027
|
+
if (!branch?.parentBranchId) break;
|
|
1028
|
+
branchId = branch.parentBranchId;
|
|
1029
|
+
maxSeq = branch.parentEventSeq ?? 0;
|
|
1030
|
+
}
|
|
1031
|
+
return segments;
|
|
1032
|
+
}
|
|
1033
|
+
activeMessageIds(cursor = this.transcriptCursor) {
|
|
1034
|
+
const byId = new Map(this.messages.map((message) => [message.id, message]));
|
|
1035
|
+
const active = /* @__PURE__ */ new Set();
|
|
1036
|
+
let current = cursor.headMessageId ? byId.get(cursor.headMessageId) : void 0;
|
|
1037
|
+
while (current) {
|
|
1038
|
+
if (current.seq > cursor.seq) break;
|
|
1039
|
+
active.add(current.id);
|
|
1040
|
+
current = current.parentMessageId ? byId.get(current.parentMessageId) : void 0;
|
|
1041
|
+
}
|
|
1042
|
+
return active;
|
|
1043
|
+
}
|
|
1044
|
+
branchLatestMessage(branchId) {
|
|
1045
|
+
let latest;
|
|
1046
|
+
for (const message of this.messages) {
|
|
1047
|
+
if (message.branchId !== branchId) continue;
|
|
1048
|
+
if (!latest || message.seq > latest.seq) latest = message;
|
|
1049
|
+
}
|
|
1050
|
+
return latest;
|
|
1051
|
+
}
|
|
1052
|
+
eventCursorForMessage(message) {
|
|
1053
|
+
return message.eventCursor ? cloneJSON2(message.eventCursor) : createEventCursor({
|
|
1054
|
+
branchId: message.branchId,
|
|
1055
|
+
seq: this.eventCursor.seq,
|
|
1056
|
+
headEventId: this.eventCursor.headEventId
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
latestCursorForBranch(branchId) {
|
|
1060
|
+
const latest = this.branchLatestMessage(branchId);
|
|
1061
|
+
const branch = this.branches.get(branchId);
|
|
1062
|
+
return createTranscriptCursor({
|
|
1063
|
+
branchId,
|
|
1064
|
+
headMessageId: latest?.id ?? branch?.parentMessageId,
|
|
1065
|
+
seq: latest?.seq ?? branch?.parentMessageSeq ?? 0
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// src/runtime/role-resolver.ts
|
|
1071
|
+
var RoleResolver = class {
|
|
1072
|
+
constructor(roles, input) {
|
|
1073
|
+
this.roles = roles;
|
|
1074
|
+
this.input = input;
|
|
1075
|
+
}
|
|
1076
|
+
roles;
|
|
1077
|
+
input;
|
|
1078
|
+
resolve(selector) {
|
|
1079
|
+
const definition = this.definitionFromSelector(selector);
|
|
1080
|
+
if (!definition) {
|
|
1081
|
+
if (typeof selector !== "string") throw new Error(`Unknown role '${getConstructType(selector)}'.`);
|
|
1082
|
+
return {
|
|
1083
|
+
role: selector,
|
|
1084
|
+
authorRole: selector,
|
|
1085
|
+
roleType: selector,
|
|
1086
|
+
target: selector === "system" /* System */ ? "system" /* System */ : "messages" /* Messages */
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
role: definition.nativeRole ?? getRoleName(definition),
|
|
1091
|
+
authorRole: getRoleName(definition),
|
|
1092
|
+
roleType: getConstructType(definition),
|
|
1093
|
+
target: definition.target,
|
|
1094
|
+
definition
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
assertModelProviderSupportsMessages(messages) {
|
|
1098
|
+
for (const message of messages) {
|
|
1099
|
+
if (message.hidden || message.role === "event") continue;
|
|
1100
|
+
this.assertModelProviderSupportsRole(message.role);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
summary(role) {
|
|
1104
|
+
return {
|
|
1105
|
+
type: getConstructType(role),
|
|
1106
|
+
name: getRoleName(role),
|
|
1107
|
+
label: getConstructLabel(role),
|
|
1108
|
+
target: role.target,
|
|
1109
|
+
nativeRole: role.nativeRole,
|
|
1110
|
+
default: role.default,
|
|
1111
|
+
description: role.description
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
definitionFromSelector(selector) {
|
|
1115
|
+
const registered = this.roles.find((role) => roleMatchesSelector(role, selector));
|
|
1116
|
+
if (registered) return registered;
|
|
1117
|
+
if (typeof selector === "string") return void 0;
|
|
1118
|
+
if (isRoleInstance(selector)) return selector;
|
|
1119
|
+
if (isRoleClass(selector)) {
|
|
1120
|
+
try {
|
|
1121
|
+
return new selector();
|
|
1122
|
+
} catch {
|
|
1123
|
+
throw new Error(`Role '${getConstructType(selector)}' must be registered as an instance or have a zero-argument constructor.`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return void 0;
|
|
1127
|
+
}
|
|
1128
|
+
assertModelProviderSupportsRole(role) {
|
|
1129
|
+
const supportsRole = this.input.supportsRole;
|
|
1130
|
+
if (supportsRole && !supportsRole(role)) {
|
|
1131
|
+
throw new Error(`Model provider '${this.input.modelProviderId}' does not support native role '${role}'.`);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// src/runtime/tool-executor.ts
|
|
1137
|
+
function invalidFieldsFromIssues(issues) {
|
|
1138
|
+
return issues.map((entry) => ({
|
|
1139
|
+
path: entry.path,
|
|
1140
|
+
message: entry.message,
|
|
1141
|
+
code: entry.code,
|
|
1142
|
+
expected: entry.expected,
|
|
1143
|
+
received: entry.received
|
|
1144
|
+
}));
|
|
1145
|
+
}
|
|
1146
|
+
function safeParseWithSchema(schema, payload2) {
|
|
1147
|
+
const normalized = normalizeSchema(schema);
|
|
1148
|
+
const result = normalized.safeParse(payload2);
|
|
1149
|
+
if (result.success) return { ok: true, data: result.data };
|
|
1150
|
+
const issues = normalized.issuesFromError(result.error);
|
|
1151
|
+
return { ok: false, error: result.error, issues, invalidFields: invalidFieldsFromIssues(issues) };
|
|
1152
|
+
}
|
|
1153
|
+
function isEmptyToolArgs(args) {
|
|
1154
|
+
return args == null || typeof args === "object" && !Array.isArray(args) && Object.keys(args).length === 0;
|
|
1155
|
+
}
|
|
1156
|
+
function ensureToolMetric(metrics, name) {
|
|
1157
|
+
const existing = metrics.tools[name];
|
|
1158
|
+
if (existing) return existing;
|
|
1159
|
+
const metric = { name, count: 0, errorCount: 0, totalDurationMs: 0 };
|
|
1160
|
+
metrics.tools[name] = metric;
|
|
1161
|
+
return metric;
|
|
1162
|
+
}
|
|
1163
|
+
var ToolExecutor = class {
|
|
1164
|
+
constructor(input) {
|
|
1165
|
+
this.input = input;
|
|
1166
|
+
}
|
|
1167
|
+
input;
|
|
1168
|
+
depth = 0;
|
|
1169
|
+
async execute(input) {
|
|
1170
|
+
if (this.depth > 8) throw new Error("Tool invocation depth exceeded.");
|
|
1171
|
+
const source = input.source ?? { kind: "runtime" };
|
|
1172
|
+
const id = input.callId ?? randomId();
|
|
1173
|
+
const toolSource = { kind: "tool", id, name: input.tool.name };
|
|
1174
|
+
const start = performance.now();
|
|
1175
|
+
const metrics = this.input.getMetrics();
|
|
1176
|
+
const metric = ensureToolMetric(metrics, input.tool.name);
|
|
1177
|
+
metric.count++;
|
|
1178
|
+
metrics.toolCallCount++;
|
|
1179
|
+
await this.input.addToolCallMessage(input.tool, input.args, id, source);
|
|
1180
|
+
this.input.log(
|
|
1181
|
+
ToolStartedLog,
|
|
1182
|
+
{ toolName: input.tool.name, args: summarizeValue(input.args), risk: input.tool.risk },
|
|
1183
|
+
toolSource,
|
|
1184
|
+
id,
|
|
1185
|
+
input.parentCausationId ?? input.parentCorrelationId
|
|
1186
|
+
);
|
|
1187
|
+
if (isEmptyToolArgs(input.args)) {
|
|
1188
|
+
this.input.log(ToolArgsEmptyLog, { toolName: input.tool.name }, toolSource, id, input.parentCausationId ?? input.parentCorrelationId);
|
|
1189
|
+
}
|
|
1190
|
+
const startEvent = await this.input.emitInternal(ToolStartEvent, { id, name: input.tool.name, args: input.args }, {
|
|
1191
|
+
source,
|
|
1192
|
+
correlationId: id,
|
|
1193
|
+
causationId: input.parentCausationId ?? input.parentCorrelationId
|
|
1194
|
+
});
|
|
1195
|
+
const toolEventOptions = {
|
|
1196
|
+
source: toolSource,
|
|
1197
|
+
correlationId: id,
|
|
1198
|
+
causationId: startEvent.id
|
|
1199
|
+
};
|
|
1200
|
+
const callerEventOptions = {
|
|
1201
|
+
source,
|
|
1202
|
+
correlationId: id,
|
|
1203
|
+
causationId: startEvent.id
|
|
1204
|
+
};
|
|
1205
|
+
const parsedArgs = safeParseWithSchema(input.tool.inputSchema, input.args);
|
|
1206
|
+
if (!parsedArgs.ok) {
|
|
1207
|
+
const result = this.structuredToolError({
|
|
1208
|
+
toolName: input.tool.name,
|
|
1209
|
+
code: "tool.args.invalid_schema",
|
|
1210
|
+
message: "Tool arguments did not match schema.",
|
|
1211
|
+
invalidFields: parsedArgs.invalidFields
|
|
1212
|
+
});
|
|
1213
|
+
const durationMs = Math.round(performance.now() - start);
|
|
1214
|
+
metric.errorCount++;
|
|
1215
|
+
metric.totalDurationMs += durationMs;
|
|
1216
|
+
metrics.errors.push(result.content);
|
|
1217
|
+
this.input.log(
|
|
1218
|
+
ToolInvalidSchemaLog,
|
|
1219
|
+
{ toolName: input.tool.name, issues: parsedArgs.issues },
|
|
1220
|
+
toolSource,
|
|
1221
|
+
id,
|
|
1222
|
+
startEvent.id,
|
|
1223
|
+
{ durationMs }
|
|
1224
|
+
);
|
|
1225
|
+
await this.input.addToolResultMessage(input.tool, result, id);
|
|
1226
|
+
await this.input.emitInternal(ToolEndEvent, { id, name: input.tool.name, durationMs, result }, callerEventOptions);
|
|
1227
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1228
|
+
return result;
|
|
1229
|
+
}
|
|
1230
|
+
const approved = await this.approveTool(
|
|
1231
|
+
{ id, name: input.tool.name, args: parsedArgs.data, modeId: this.input.getCurrentMode(), risk: input.tool.risk, permissions: input.tool.permissions },
|
|
1232
|
+
input.tool,
|
|
1233
|
+
parsedArgs.data,
|
|
1234
|
+
callerEventOptions
|
|
1235
|
+
);
|
|
1236
|
+
if (!approved) {
|
|
1237
|
+
const denied = this.structuredToolError({
|
|
1238
|
+
toolName: input.tool.name,
|
|
1239
|
+
code: "tool.approval.denied",
|
|
1240
|
+
message: `Tool '${input.tool.name}' was denied by runner policy.`,
|
|
1241
|
+
metadata: { denied: true }
|
|
1242
|
+
});
|
|
1243
|
+
metric.errorCount++;
|
|
1244
|
+
const durationMs = Math.round(performance.now() - start);
|
|
1245
|
+
metric.totalDurationMs += durationMs;
|
|
1246
|
+
this.input.log(ToolFailedLog, {
|
|
1247
|
+
toolName: input.tool.name,
|
|
1248
|
+
durationMs,
|
|
1249
|
+
result: summarizeValue(denied)
|
|
1250
|
+
}, toolSource, id, startEvent.id, { durationMs });
|
|
1251
|
+
await this.input.addToolResultMessage(input.tool, denied, id);
|
|
1252
|
+
await this.input.emitInternal(ToolEndEvent, { id, name: input.tool.name, durationMs, result: denied }, callerEventOptions);
|
|
1253
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1254
|
+
return denied;
|
|
1255
|
+
}
|
|
1256
|
+
this.depth++;
|
|
1257
|
+
try {
|
|
1258
|
+
await this.input.ensureSandboxOpen();
|
|
1259
|
+
const executed = await input.tool.execute(parsedArgs.data, this.input.buildActionSession({ id, name: input.tool.name }, toolSource, id, startEvent.id));
|
|
1260
|
+
const result = this.ensureStructuredToolErrorResult(input.tool, executed);
|
|
1261
|
+
const durationMs = Math.round(performance.now() - start);
|
|
1262
|
+
metric.totalDurationMs += durationMs;
|
|
1263
|
+
if (result.isError) metric.errorCount++;
|
|
1264
|
+
if (result.isError) {
|
|
1265
|
+
this.input.log(ToolFailedLog, {
|
|
1266
|
+
toolName: input.tool.name,
|
|
1267
|
+
durationMs,
|
|
1268
|
+
result: summarizeValue(result)
|
|
1269
|
+
}, toolSource, id, startEvent.id, { durationMs });
|
|
1270
|
+
} else {
|
|
1271
|
+
this.input.log(ToolCompletedLog, {
|
|
1272
|
+
toolName: input.tool.name,
|
|
1273
|
+
durationMs,
|
|
1274
|
+
isError: false,
|
|
1275
|
+
result: summarizeValue(result)
|
|
1276
|
+
}, toolSource, id, startEvent.id, { durationMs });
|
|
1277
|
+
}
|
|
1278
|
+
await this.input.addToolResultMessage(input.tool, result, id);
|
|
1279
|
+
await this.input.emitInternal(ToolEndEvent, { id, name: input.tool.name, durationMs, result }, callerEventOptions);
|
|
1280
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1281
|
+
return result;
|
|
1282
|
+
} catch (error) {
|
|
1283
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1284
|
+
const result = this.structuredToolError({
|
|
1285
|
+
toolName: input.tool.name,
|
|
1286
|
+
code: "tool.failed",
|
|
1287
|
+
message: "Tool execution failed.",
|
|
1288
|
+
metadata: { error: true }
|
|
1289
|
+
});
|
|
1290
|
+
const durationMs = Math.round(performance.now() - start);
|
|
1291
|
+
metric.errorCount++;
|
|
1292
|
+
metric.totalDurationMs += durationMs;
|
|
1293
|
+
metrics.errors.push(message);
|
|
1294
|
+
this.input.log(ToolFailedLog, {
|
|
1295
|
+
toolName: input.tool.name,
|
|
1296
|
+
durationMs,
|
|
1297
|
+
error
|
|
1298
|
+
}, toolSource, id, startEvent.id, { durationMs });
|
|
1299
|
+
await this.input.addToolResultMessage(input.tool, result, id);
|
|
1300
|
+
await this.input.emitInternal(ErrorEvent, { message, details: error }, toolEventOptions);
|
|
1301
|
+
await this.input.emitInternal(ToolEndEvent, { id, name: input.tool.name, durationMs, result }, callerEventOptions);
|
|
1302
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1303
|
+
return result;
|
|
1304
|
+
} finally {
|
|
1305
|
+
this.depth--;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
structuredToolError(input) {
|
|
1309
|
+
return {
|
|
1310
|
+
content: input.message,
|
|
1311
|
+
data: createToolErrorPayload({
|
|
1312
|
+
code: input.code,
|
|
1313
|
+
message: input.message,
|
|
1314
|
+
toolName: input.toolName,
|
|
1315
|
+
invalidFields: input.invalidFields
|
|
1316
|
+
}),
|
|
1317
|
+
isError: true,
|
|
1318
|
+
metadata: {
|
|
1319
|
+
errorCode: input.code,
|
|
1320
|
+
...input.invalidFields ? { invalidFields: input.invalidFields } : {},
|
|
1321
|
+
...input.metadata ?? {}
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
ensureStructuredToolErrorResult(tool, result) {
|
|
1326
|
+
if (!result.isError || result.data !== void 0) return result;
|
|
1327
|
+
return {
|
|
1328
|
+
...result,
|
|
1329
|
+
data: createToolErrorPayload({
|
|
1330
|
+
code: "tool.failed",
|
|
1331
|
+
message: result.content || "Tool execution failed.",
|
|
1332
|
+
toolName: tool.name
|
|
1333
|
+
}),
|
|
1334
|
+
metadata: {
|
|
1335
|
+
...result.metadata,
|
|
1336
|
+
errorCode: result.metadata?.errorCode ?? "tool.failed"
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
async approveTool(request, tool, args, eventOptions) {
|
|
1341
|
+
const policy = this.input.getToolApprovalMode() ?? "tool-default";
|
|
1342
|
+
if (policy === "auto") return this.recordApprovalResolution(request, "approved", eventOptions);
|
|
1343
|
+
if (policy === "deny") return this.recordApprovalResolution(request, "denied", eventOptions);
|
|
1344
|
+
const requiresApproval = await this.toolRequiresApproval(tool, args, request.id, eventOptions.causationId);
|
|
1345
|
+
if (policy === "tool-default" && !requiresApproval) return true;
|
|
1346
|
+
await this.input.emitInternal(
|
|
1347
|
+
ToolApprovalRequestedEvent,
|
|
1348
|
+
{
|
|
1349
|
+
id: request.id,
|
|
1350
|
+
name: request.name,
|
|
1351
|
+
args: request.args,
|
|
1352
|
+
modeId: request.modeId,
|
|
1353
|
+
risk: request.risk,
|
|
1354
|
+
permissions: request.permissions
|
|
1355
|
+
},
|
|
1356
|
+
eventOptions
|
|
1357
|
+
);
|
|
1358
|
+
this.input.log(
|
|
1359
|
+
ToolApprovalRequestedLog,
|
|
1360
|
+
{ toolName: request.name, risk: request.risk, permissions: request.permissions },
|
|
1361
|
+
eventOptions.source,
|
|
1362
|
+
eventOptions.correlationId,
|
|
1363
|
+
eventOptions.causationId
|
|
1364
|
+
);
|
|
1365
|
+
if (!this.input.approveTool) return this.recordApprovalResolution(request, "denied", eventOptions);
|
|
1366
|
+
const decision = await this.input.approveTool(request);
|
|
1367
|
+
return this.recordApprovalResolution(request, decision === true || decision === "approved" ? "approved" : "denied", eventOptions);
|
|
1368
|
+
}
|
|
1369
|
+
async toolRequiresApproval(tool, args, toolCallId, causationId) {
|
|
1370
|
+
if (typeof tool.requiresApproval === "function") {
|
|
1371
|
+
await this.input.ensureSandboxOpen();
|
|
1372
|
+
return tool.requiresApproval(
|
|
1373
|
+
args,
|
|
1374
|
+
this.input.buildActionSession(
|
|
1375
|
+
{ id: toolCallId, name: tool.name },
|
|
1376
|
+
{ kind: "tool", id: toolCallId, name: tool.name },
|
|
1377
|
+
toolCallId,
|
|
1378
|
+
causationId
|
|
1379
|
+
)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
return tool.requiresApproval === true;
|
|
1383
|
+
}
|
|
1384
|
+
async recordApprovalResolution(request, decision, eventOptions) {
|
|
1385
|
+
await this.input.emitInternal(
|
|
1386
|
+
ToolApprovalResolvedEvent,
|
|
1387
|
+
{ id: request.id, name: request.name, args: request.args, decision, modeId: request.modeId },
|
|
1388
|
+
eventOptions
|
|
1389
|
+
);
|
|
1390
|
+
this.input.log(
|
|
1391
|
+
ToolApprovalResolvedLog,
|
|
1392
|
+
{ toolName: request.name, approved: decision === "approved" },
|
|
1393
|
+
eventOptions.source,
|
|
1394
|
+
eventOptions.correlationId,
|
|
1395
|
+
eventOptions.causationId
|
|
1396
|
+
);
|
|
1397
|
+
return decision === "approved";
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/runtime/snapshot-manager.ts
|
|
1402
|
+
function cloneJSON3(value) {
|
|
1403
|
+
return JSON.parse(JSON.stringify(value));
|
|
1404
|
+
}
|
|
1405
|
+
var SnapshotManager = class {
|
|
1406
|
+
constructor(input) {
|
|
1407
|
+
this.input = input;
|
|
1408
|
+
}
|
|
1409
|
+
input;
|
|
1410
|
+
snapshots = [];
|
|
1411
|
+
load(snapshot) {
|
|
1412
|
+
if (!this.snapshots.some((candidate) => candidate.id === snapshot.id)) {
|
|
1413
|
+
this.snapshots.push(cloneJSON3(snapshot));
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
add(snapshot) {
|
|
1417
|
+
this.snapshots.push(snapshot);
|
|
1418
|
+
}
|
|
1419
|
+
get(id) {
|
|
1420
|
+
const snapshot = this.snapshots.find((candidate) => candidate.id === id);
|
|
1421
|
+
return snapshot ? cloneJSON3(snapshot) : void 0;
|
|
1422
|
+
}
|
|
1423
|
+
getRaw(id) {
|
|
1424
|
+
return this.snapshots.find((candidate) => candidate.id === id);
|
|
1425
|
+
}
|
|
1426
|
+
async create(input = {}, eventOptions) {
|
|
1427
|
+
const manager = this.requireInput();
|
|
1428
|
+
await manager.ensureStoreInitialized();
|
|
1429
|
+
const snapshot = {
|
|
1430
|
+
id: randomId(),
|
|
1431
|
+
label: input.label,
|
|
1432
|
+
createdAt: manager.now(),
|
|
1433
|
+
...cloneJSON3(manager.readState()),
|
|
1434
|
+
metadata: input.metadata
|
|
1435
|
+
};
|
|
1436
|
+
this.add(snapshot);
|
|
1437
|
+
await manager.saveSnapshot(snapshot);
|
|
1438
|
+
const summary = this.summary(snapshot);
|
|
1439
|
+
await manager.emitCreated(summary, eventOptions);
|
|
1440
|
+
manager.logCreated(summary, eventOptions);
|
|
1441
|
+
return cloneJSON3(snapshot);
|
|
1442
|
+
}
|
|
1443
|
+
async restore(id, eventOptions) {
|
|
1444
|
+
const manager = this.requireInput();
|
|
1445
|
+
await manager.ensureStoreInitialized();
|
|
1446
|
+
const snapshot = this.getRaw(id);
|
|
1447
|
+
if (!snapshot) {
|
|
1448
|
+
manager.logRestoreRejected(id, "not_found", eventOptions);
|
|
1449
|
+
throw new Error(`Snapshot '${id}' was not found.`);
|
|
1450
|
+
}
|
|
1451
|
+
manager.restoreState(snapshot);
|
|
1452
|
+
const summary = this.summary(snapshot);
|
|
1453
|
+
await manager.emitRestored(summary, eventOptions);
|
|
1454
|
+
manager.logRestored(summary, eventOptions);
|
|
1455
|
+
await manager.persistCursors();
|
|
1456
|
+
return cloneJSON3(snapshot);
|
|
1457
|
+
}
|
|
1458
|
+
list() {
|
|
1459
|
+
return cloneJSON3(this.snapshots.map((snapshot) => this.summary(snapshot)));
|
|
1460
|
+
}
|
|
1461
|
+
delete(id) {
|
|
1462
|
+
const snapshot = this.snapshots.find((candidate) => candidate.id === id);
|
|
1463
|
+
if (!snapshot) return void 0;
|
|
1464
|
+
this.snapshots = this.snapshots.filter((candidate) => candidate.id !== id);
|
|
1465
|
+
return snapshot;
|
|
1466
|
+
}
|
|
1467
|
+
async deletePersisted(id, eventOptions) {
|
|
1468
|
+
const manager = this.requireInput();
|
|
1469
|
+
await manager.ensureStoreInitialized();
|
|
1470
|
+
const snapshot = this.delete(id);
|
|
1471
|
+
if (!snapshot) return false;
|
|
1472
|
+
await manager.deleteSnapshot(id);
|
|
1473
|
+
const summary = this.summary(snapshot);
|
|
1474
|
+
await manager.emitDeleted(summary, eventOptions);
|
|
1475
|
+
manager.logDeleted(summary, eventOptions);
|
|
1476
|
+
return true;
|
|
1477
|
+
}
|
|
1478
|
+
summary(snapshot) {
|
|
1479
|
+
return {
|
|
1480
|
+
id: snapshot.id,
|
|
1481
|
+
label: snapshot.label,
|
|
1482
|
+
createdAt: snapshot.createdAt,
|
|
1483
|
+
agentKey: snapshot.agentKey,
|
|
1484
|
+
runId: snapshot.runId,
|
|
1485
|
+
turnId: snapshot.turnId,
|
|
1486
|
+
modeId: snapshot.modeId,
|
|
1487
|
+
model: snapshot.model,
|
|
1488
|
+
transcriptCursor: cloneJSON3(snapshot.transcriptCursor),
|
|
1489
|
+
eventCursor: cloneJSON3(snapshot.eventCursor),
|
|
1490
|
+
metadata: snapshot.metadata
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
requireInput() {
|
|
1494
|
+
if (!this.input) throw new Error("SnapshotManager operation requires runtime dependencies.");
|
|
1495
|
+
return this.input;
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
// src/runtime/model-pipeline.ts
|
|
1500
|
+
var ModelPipeline = class {
|
|
1501
|
+
constructor(input) {
|
|
1502
|
+
this.input = input;
|
|
1503
|
+
}
|
|
1504
|
+
input;
|
|
1505
|
+
async run(input) {
|
|
1506
|
+
const prepared = await this.prepareContext(input.mode, input.userMessage);
|
|
1507
|
+
const systemPrompt = prepared.systemPrompt;
|
|
1508
|
+
const messages = prepared.messages;
|
|
1509
|
+
const model = this.input.getModel();
|
|
1510
|
+
const resolved = this.input.resolveModelProvider(model);
|
|
1511
|
+
const source = this.modelProviderSource(resolved);
|
|
1512
|
+
try {
|
|
1513
|
+
const modelStart = performance.now();
|
|
1514
|
+
this.input.log(ModelCallStartedLog, { model, messageCount: messages.length }, source);
|
|
1515
|
+
const result = await resolved.provider.run({
|
|
1516
|
+
runId: this.input.getRunId(),
|
|
1517
|
+
turnId: this.input.getTurnId(),
|
|
1518
|
+
modeId: this.input.getModeId(),
|
|
1519
|
+
modelRef: resolved.modelRef,
|
|
1520
|
+
provider: resolved.namespace,
|
|
1521
|
+
model: resolved.modelId,
|
|
1522
|
+
systemPrompt,
|
|
1523
|
+
messages,
|
|
1524
|
+
roles: this.input.roles,
|
|
1525
|
+
tools: input.tools,
|
|
1526
|
+
maxTurns: input.mode.maxTurns ?? 20,
|
|
1527
|
+
signal: input.options.signal,
|
|
1528
|
+
prepareContext: () => this.prepareContext(input.mode, input.userMessage),
|
|
1529
|
+
emit: async (eventClass, payload2, options) => {
|
|
1530
|
+
const event = await this.input.emit(
|
|
1531
|
+
eventClass,
|
|
1532
|
+
payload2,
|
|
1533
|
+
this.input.withEmitDefaults(source, void 0, void 0, options)
|
|
1534
|
+
);
|
|
1535
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1536
|
+
return event;
|
|
1537
|
+
},
|
|
1538
|
+
executeTool: (tool, args, callId) => this.input.executeTool(tool, args, callId, source)
|
|
1539
|
+
});
|
|
1540
|
+
const assistantMessage = await this.input.addAssistantMessage(result.content, {
|
|
1541
|
+
usage: result.usage,
|
|
1542
|
+
finishReason: result.finishReason
|
|
1543
|
+
});
|
|
1544
|
+
this.input.setFinalAnswer(result.content);
|
|
1545
|
+
this.input.getMetrics().usage = result.usage;
|
|
1546
|
+
await this.input.emitInternal(MessageEndEvent, { message: assistantMessage });
|
|
1547
|
+
await this.input.markMessageEventCursor(assistantMessage.id);
|
|
1548
|
+
this.input.log(ModelCallCompletedLog, {
|
|
1549
|
+
model,
|
|
1550
|
+
durationMs: Math.round(performance.now() - modelStart),
|
|
1551
|
+
finishReason: result.finishReason
|
|
1552
|
+
}, source);
|
|
1553
|
+
await this.input.emitInternal(ModelAfterEvent, {
|
|
1554
|
+
model,
|
|
1555
|
+
content: result.content,
|
|
1556
|
+
usage: result.usage,
|
|
1557
|
+
finishReason: result.finishReason
|
|
1558
|
+
});
|
|
1559
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1560
|
+
} catch (error) {
|
|
1561
|
+
if (this.input.isTurnHandoffSignal(error)) return;
|
|
1562
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1563
|
+
this.input.getMetrics().errors.push(message);
|
|
1564
|
+
this.input.log(ModelCallFailedLog, { model, error }, source);
|
|
1565
|
+
await this.input.emitInternal(ErrorEvent, { message, details: error });
|
|
1566
|
+
throw error;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
modelProviderSource(resolved) {
|
|
1570
|
+
const id = modelProviderId(resolved.provider);
|
|
1571
|
+
return { kind: "model_provider", id, name: id };
|
|
1572
|
+
}
|
|
1573
|
+
async resolveModePrompt(mode) {
|
|
1574
|
+
const source = { kind: "mode", id: getConstructType(mode), name: getConstructLabel(mode) };
|
|
1575
|
+
if (typeof mode.getPrompt === "function") return mode.getPrompt(this.input.buildReadSession(source));
|
|
1576
|
+
if (typeof mode.prompt === "function") return mode.prompt(this.input.buildReadSession(source));
|
|
1577
|
+
return mode.prompt ?? "";
|
|
1578
|
+
}
|
|
1579
|
+
async prepareContext(mode, userMessage) {
|
|
1580
|
+
const contextSnapshot = await this.input.buildContextSnapshot(ModelBeforeEvent);
|
|
1581
|
+
const prompt = await this.resolveModePrompt(mode);
|
|
1582
|
+
const systemPrompt = [contextSnapshot.systemPrompt, prompt].filter(Boolean).join("\n\n---\n\n");
|
|
1583
|
+
const messages = [...contextSnapshot.messages, userMessage];
|
|
1584
|
+
this.input.assertModelProviderSupportsMessages(messages);
|
|
1585
|
+
await this.input.emitInternal(ContextReadyEvent, {
|
|
1586
|
+
snapshotId: contextSnapshot.id,
|
|
1587
|
+
providerCount: contextSnapshot.providers.length,
|
|
1588
|
+
contributionCount: contextSnapshot.contributions.length
|
|
1589
|
+
});
|
|
1590
|
+
await this.input.emitInternal(ModelBeforeEvent, { model: this.input.getModel(), messageCount: messages.length });
|
|
1591
|
+
this.input.throwIfTurnHandoffRequested();
|
|
1592
|
+
return {
|
|
1593
|
+
systemPrompt,
|
|
1594
|
+
messages,
|
|
1595
|
+
snapshot: contextSnapshot
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1600
|
+
// src/runtime/runner.ts
|
|
1601
|
+
function nowIso4() {
|
|
1602
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1603
|
+
}
|
|
1604
|
+
function cloneJSON4(value) {
|
|
1605
|
+
return JSON.parse(JSON.stringify(value));
|
|
1606
|
+
}
|
|
1607
|
+
var TurnHandoffSignal = class extends Error {
|
|
1608
|
+
constructor() {
|
|
1609
|
+
super("Turn handoff requested.");
|
|
1610
|
+
this.name = "TurnHandoffSignal";
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
function defaultRoles() {
|
|
1614
|
+
return [systemRole, userRole, assistantRole, toolRole];
|
|
1615
|
+
}
|
|
1616
|
+
function emptyMetrics() {
|
|
1617
|
+
return {
|
|
1618
|
+
startedAt: nowIso4(),
|
|
1619
|
+
durationMs: 0,
|
|
1620
|
+
turnCount: 0,
|
|
1621
|
+
messageCount: 0,
|
|
1622
|
+
eventCount: 0,
|
|
1623
|
+
toolCallCount: 0,
|
|
1624
|
+
tools: {},
|
|
1625
|
+
errors: []
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
function stringifyContent(content) {
|
|
1629
|
+
if (typeof content === "string") return content;
|
|
1630
|
+
if (content == null) return "";
|
|
1631
|
+
return JSON.stringify(content, null, 2);
|
|
1632
|
+
}
|
|
1633
|
+
function normalizeAgentMessageInput(input) {
|
|
1634
|
+
return typeof input === "string" ? { content: input } : input;
|
|
1635
|
+
}
|
|
1636
|
+
var noopAgentLogSession = {
|
|
1637
|
+
debug: () => void 0,
|
|
1638
|
+
info: () => void 0,
|
|
1639
|
+
warn: () => void 0,
|
|
1640
|
+
error: () => void 0,
|
|
1641
|
+
emit: () => void 0
|
|
1642
|
+
};
|
|
1643
|
+
function keyFromLabel(label) {
|
|
1644
|
+
const key = label.trim().replace(/([a-z0-9])([A-Z])/gu, "$1-$2").replace(/[^a-zA-Z0-9]+/gu, "-").replace(/^-|-$/gu, "").toLowerCase();
|
|
1645
|
+
return key || "agent";
|
|
1646
|
+
}
|
|
1647
|
+
function ensureNoLegacyAgentShape(agent) {
|
|
1648
|
+
const candidate = agent;
|
|
1649
|
+
if ("id" in candidate) throw new Error("Agent definitions no longer declare id. Use optional key for packaging metadata.");
|
|
1650
|
+
if ("events" in candidate) throw new Error("Agent definitions no longer declare events. Emit and query event classes directly.");
|
|
1651
|
+
if ("contextProviders" in candidate) {
|
|
1652
|
+
throw new Error("Agent definitions no longer declare contextProviders. Attach providers to modes directly.");
|
|
1653
|
+
}
|
|
1654
|
+
if (!Array.isArray(candidate.modes)) {
|
|
1655
|
+
throw new Error("Agent modes must be an array of HarnessMode instances. Object-literal mode maps are not supported.");
|
|
1656
|
+
}
|
|
1657
|
+
if (typeof candidate.initialMode === "string") {
|
|
1658
|
+
throw new Error("Agent initialMode must be a HarnessMode class or instance, not a string id.");
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function validateMode(mode) {
|
|
1662
|
+
if (!isModeInstance(mode)) throw new Error("Agent modes must extend HarnessMode.");
|
|
1663
|
+
assertNoAuthorId(mode, "Mode");
|
|
1664
|
+
if ("context" in mode) {
|
|
1665
|
+
throw new Error(`Mode '${getConstructType(mode)}' uses context. Declare providers directly on the mode class.`);
|
|
1666
|
+
}
|
|
1667
|
+
if ("lifecycle" in mode) {
|
|
1668
|
+
throw new Error(`Mode '${getConstructType(mode)}' uses lifecycle. Put onEnter/onExit methods directly on the class.`);
|
|
1669
|
+
}
|
|
1670
|
+
if (mode.prompt === void 0 && typeof mode.getPrompt !== "function") {
|
|
1671
|
+
throw new Error(`Mode '${getConstructType(mode)}' must define prompt or getPrompt(ctx).`);
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
function validateRoles(roles) {
|
|
1675
|
+
if (!roles) return void 0;
|
|
1676
|
+
for (const role of roles) {
|
|
1677
|
+
if (!isRoleInstance(role)) throw new Error("Agent roles must extend HarnessRole.");
|
|
1678
|
+
assertNoAuthorId(role, "Role");
|
|
1679
|
+
}
|
|
1680
|
+
return roles;
|
|
1681
|
+
}
|
|
1682
|
+
function validateHooks(hooks) {
|
|
1683
|
+
if (!hooks) return void 0;
|
|
1684
|
+
for (const hook of hooks) {
|
|
1685
|
+
if (!isHookInstance(hook)) throw new Error("Agent hooks must extend HarnessHook.");
|
|
1686
|
+
assertNoAuthorId(hook, "Hook");
|
|
1687
|
+
if (!hookEventClass(hook)) {
|
|
1688
|
+
throw new Error(`Hook '${getConstructType(hook)}' must extend HarnessHook.for(EventClass).`);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return hooks;
|
|
1692
|
+
}
|
|
1693
|
+
function validateDeclaredEvents(events) {
|
|
1694
|
+
if (!events) return void 0;
|
|
1695
|
+
for (const eventClass of events) eventType(eventClass);
|
|
1696
|
+
return events;
|
|
1697
|
+
}
|
|
1698
|
+
function resolveInitialModeType(selector, modes) {
|
|
1699
|
+
for (const mode of Object.values(modes)) {
|
|
1700
|
+
if (modeMatchesSelector(mode, selector)) return getConstructType(mode);
|
|
1701
|
+
}
|
|
1702
|
+
throw new Error(`Unknown initial mode '${getConstructType(selector)}'.`);
|
|
1703
|
+
}
|
|
1704
|
+
function normalizeAgent(definition) {
|
|
1705
|
+
ensureNoLegacyAgentShape(definition);
|
|
1706
|
+
const modes = {};
|
|
1707
|
+
for (const mode of definition.modes) {
|
|
1708
|
+
validateMode(mode);
|
|
1709
|
+
const type = getConstructType(mode);
|
|
1710
|
+
if (modes[type]) throw new Error(`Duplicate mode type '${type}'.`);
|
|
1711
|
+
modes[type] = mode;
|
|
1712
|
+
}
|
|
1713
|
+
const initialMode = resolveInitialModeType(definition.initialMode, modes);
|
|
1714
|
+
const key = definition.key ?? keyFromLabel(definition.label);
|
|
1715
|
+
return {
|
|
1716
|
+
key,
|
|
1717
|
+
label: definition.label,
|
|
1718
|
+
initialMode,
|
|
1719
|
+
modes,
|
|
1720
|
+
sharedState: definition.sharedState,
|
|
1721
|
+
roles: validateRoles(definition.roles),
|
|
1722
|
+
hooks: validateHooks(definition.hooks),
|
|
1723
|
+
declaredEvents: validateDeclaredEvents(definition.declaredEvents)
|
|
1724
|
+
};
|
|
1725
|
+
}
|
|
1726
|
+
var AgentSessionRunner = class {
|
|
1727
|
+
constructor(options) {
|
|
1728
|
+
this.options = options;
|
|
1729
|
+
this.agent = normalizeAgent(options.agent);
|
|
1730
|
+
this.sessionIdValue = options.sessionId ?? randomId();
|
|
1731
|
+
this.workDir = options.workDir ?? ".";
|
|
1732
|
+
this.outputDir = options.outputDir ?? ".harness-kernel/runs";
|
|
1733
|
+
const storage = options.storage ?? new NoopRunStorage();
|
|
1734
|
+
const sandbox = options.sandbox ?? new NoopSandbox();
|
|
1735
|
+
this.providerRegistry = new HarnessModelProviderRegistry(options.providers);
|
|
1736
|
+
this.currentMode = options.initialMode ? this.resolveModeType(options.initialMode) : this.agent.initialMode;
|
|
1737
|
+
this.services = options.services ?? {};
|
|
1738
|
+
this.roles = validateRoles(options.roles) ?? this.agent.roles ?? defaultRoles();
|
|
1739
|
+
const initialProvider = this.resolveModelProvider(options.defaultModel).provider;
|
|
1740
|
+
this.roleResolver = new RoleResolver(this.roles, {
|
|
1741
|
+
modelProviderId: modelProviderId(initialProvider),
|
|
1742
|
+
supportsRole: (roleId) => this.resolveModelProvider(this.getActiveModel()).provider.supportsRole?.(roleId) ?? true
|
|
1743
|
+
});
|
|
1744
|
+
this.defaultModel = options.defaultModel;
|
|
1745
|
+
this.storageCoordinator = new RunStorageCoordinator({
|
|
1746
|
+
storage,
|
|
1747
|
+
runId: this.runIdValue,
|
|
1748
|
+
sessionId: this.sessionIdValue,
|
|
1749
|
+
agentKey: this.agent.key,
|
|
1750
|
+
outputDir: this.outputDir,
|
|
1751
|
+
logOpened: (fields) => this.log(RunStorageOpenedLog, fields),
|
|
1752
|
+
logFailed: (fields) => this.log(StorageWriteFailedLog, fields)
|
|
1753
|
+
});
|
|
1754
|
+
this.snapshotManager = new SnapshotManager({
|
|
1755
|
+
now: () => nowIso4(),
|
|
1756
|
+
ensureStoreInitialized: () => this.ensureStoreInitialized(),
|
|
1757
|
+
readState: () => ({
|
|
1758
|
+
agentKey: this.agent.key,
|
|
1759
|
+
runId: this.started ? this.runId : void 0,
|
|
1760
|
+
turnId: this.currentTurnId,
|
|
1761
|
+
modeId: this.currentMode,
|
|
1762
|
+
model: this.getActiveModel(),
|
|
1763
|
+
transcriptCursor: cloneJSON4(this.transcriptManager.activeTranscriptCursor),
|
|
1764
|
+
eventCursor: cloneJSON4(this.transcriptManager.activeEventCursor),
|
|
1765
|
+
state: cloneJSON4(this.state),
|
|
1766
|
+
contextEntries: cloneJSON4(this.contextRegistry.allEntries),
|
|
1767
|
+
contextSnapshot: this.contextRegistry.current ? cloneJSON4(this.contextRegistry.current) : void 0,
|
|
1768
|
+
branches: cloneJSON4(this.transcriptManager.allBranches)
|
|
1769
|
+
}),
|
|
1770
|
+
restoreState: (snapshot) => {
|
|
1771
|
+
this.currentMode = snapshot.modeId;
|
|
1772
|
+
this.modelOverride = snapshot.model;
|
|
1773
|
+
this.state = cloneJSON4(snapshot.state);
|
|
1774
|
+
this.contextRegistry.restore({
|
|
1775
|
+
entries: snapshot.contextEntries,
|
|
1776
|
+
snapshot: snapshot.contextSnapshot
|
|
1777
|
+
});
|
|
1778
|
+
this.transcriptManager.restoreCursors({
|
|
1779
|
+
transcriptCursor: snapshot.transcriptCursor,
|
|
1780
|
+
eventCursor: snapshot.eventCursor,
|
|
1781
|
+
branches: snapshot.branches
|
|
1782
|
+
});
|
|
1783
|
+
},
|
|
1784
|
+
persistCursors: () => this.persistCursors(),
|
|
1785
|
+
saveSnapshot: (snapshot) => this.storageCoordinator.saveSnapshot(snapshot),
|
|
1786
|
+
deleteSnapshot: (id) => this.storageCoordinator.deleteSnapshot(id),
|
|
1787
|
+
emitCreated: async (summary, eventOptions) => {
|
|
1788
|
+
await this.emitInternal(SnapshotCreatedEvent, { snapshot: summary }, {
|
|
1789
|
+
...eventOptions,
|
|
1790
|
+
hiddenTranscript: false
|
|
1791
|
+
});
|
|
1792
|
+
},
|
|
1793
|
+
emitRestored: async (summary, eventOptions) => {
|
|
1794
|
+
await this.emitInternal(SnapshotRestoredEvent, { snapshot: summary }, {
|
|
1795
|
+
...eventOptions,
|
|
1796
|
+
hiddenTranscript: false
|
|
1797
|
+
});
|
|
1798
|
+
},
|
|
1799
|
+
emitDeleted: async (summary, eventOptions) => {
|
|
1800
|
+
await this.emitInternal(SnapshotDeletedEvent, { snapshot: summary }, {
|
|
1801
|
+
...eventOptions,
|
|
1802
|
+
hiddenTranscript: false
|
|
1803
|
+
});
|
|
1804
|
+
},
|
|
1805
|
+
logCreated: (summary, eventOptions) => this.log(
|
|
1806
|
+
SnapshotCreatedLog,
|
|
1807
|
+
{ snapshotId: summary.id, label: summary.label },
|
|
1808
|
+
eventOptions?.source,
|
|
1809
|
+
eventOptions?.correlationId,
|
|
1810
|
+
eventOptions?.causationId
|
|
1811
|
+
),
|
|
1812
|
+
logRestored: (summary, eventOptions) => this.log(
|
|
1813
|
+
SnapshotRestoredLog,
|
|
1814
|
+
{ snapshotId: summary.id, label: summary.label },
|
|
1815
|
+
eventOptions?.source,
|
|
1816
|
+
eventOptions?.correlationId,
|
|
1817
|
+
eventOptions?.causationId
|
|
1818
|
+
),
|
|
1819
|
+
logDeleted: (summary, eventOptions) => this.log(
|
|
1820
|
+
SnapshotDeletedLog,
|
|
1821
|
+
{ snapshotId: summary.id, label: summary.label },
|
|
1822
|
+
eventOptions?.source,
|
|
1823
|
+
eventOptions?.correlationId,
|
|
1824
|
+
eventOptions?.causationId
|
|
1825
|
+
),
|
|
1826
|
+
logRestoreRejected: (snapshotId, reason, eventOptions) => this.log(
|
|
1827
|
+
SnapshotRestoreRejectedLog,
|
|
1828
|
+
{ snapshotId, reason },
|
|
1829
|
+
eventOptions?.source,
|
|
1830
|
+
eventOptions?.correlationId,
|
|
1831
|
+
eventOptions?.causationId
|
|
1832
|
+
)
|
|
1833
|
+
});
|
|
1834
|
+
this.sandboxManager = new SandboxManager({
|
|
1835
|
+
sandbox,
|
|
1836
|
+
sessionId: this.sessionIdValue,
|
|
1837
|
+
agentKey: this.agent.key,
|
|
1838
|
+
workDir: this.workDir,
|
|
1839
|
+
services: this.services,
|
|
1840
|
+
getRunId: () => this.runId,
|
|
1841
|
+
getOutputDir: () => this.storeRunDir(),
|
|
1842
|
+
logOpened: (fields) => this.log(SandboxOpenedLog, fields),
|
|
1843
|
+
logClosed: (fields) => this.log(SandboxClosedLog, fields),
|
|
1844
|
+
logExecStarted: (fields) => this.log(SandboxExecStartedLog, fields),
|
|
1845
|
+
logExecCompleted: (fields) => this.log(SandboxExecCompletedLog, fields),
|
|
1846
|
+
logExecFailed: (fields) => this.log(SandboxExecFailedLog, fields)
|
|
1847
|
+
});
|
|
1848
|
+
this.toolExecutor = new ToolExecutor({
|
|
1849
|
+
getMetrics: () => this.metrics,
|
|
1850
|
+
getCurrentMode: () => this.currentMode,
|
|
1851
|
+
getToolApprovalMode: () => this.options.toolApproval ?? this.getModeDefinition(this.currentMode).toolApproval,
|
|
1852
|
+
approveTool: this.options.approveTool,
|
|
1853
|
+
ensureSandboxOpen: () => this.ensureSandboxOpen(),
|
|
1854
|
+
buildActionSession: (tool, source, correlationId, causationId) => this.buildActionSession(tool, source, correlationId, causationId),
|
|
1855
|
+
addToolCallMessage: (tool, args, toolCallId, source) => this.addToolCallMessage(tool, args, toolCallId, source),
|
|
1856
|
+
addToolResultMessage: (tool, result, toolCallId) => this.addToolResultMessage(tool, result, toolCallId),
|
|
1857
|
+
emitInternal: (eventClass, payload2, options2) => this.emitInternal(eventClass, payload2, options2),
|
|
1858
|
+
log: (logClass, fields, source, correlationId, causationId, overrides) => this.log(logClass, fields, source, correlationId, causationId, overrides),
|
|
1859
|
+
throwIfTurnHandoffRequested: () => this.throwIfTurnHandoffRequested()
|
|
1860
|
+
});
|
|
1861
|
+
this.modelPipeline = new ModelPipeline({
|
|
1862
|
+
resolveModelProvider: (model) => this.resolveModelProvider(model),
|
|
1863
|
+
roles: this.roles,
|
|
1864
|
+
getRunId: () => this.runId,
|
|
1865
|
+
getTurnId: () => this.currentTurnId,
|
|
1866
|
+
getModeId: () => this.currentMode,
|
|
1867
|
+
getModel: () => this.getActiveModel(),
|
|
1868
|
+
getMetrics: () => this.metrics,
|
|
1869
|
+
setFinalAnswer: (answer) => {
|
|
1870
|
+
this.finalAnswer = answer;
|
|
1871
|
+
},
|
|
1872
|
+
buildReadSession: (source) => this.buildReadSession(source),
|
|
1873
|
+
buildContextSnapshot: (trigger) => this.buildContextSnapshot(trigger),
|
|
1874
|
+
assertModelProviderSupportsMessages: (messages) => this.assertModelProviderSupportsMessages(messages),
|
|
1875
|
+
addAssistantMessage: (content, metadata) => this.addMessage("assistant", content, metadata),
|
|
1876
|
+
markMessageEventCursor: (messageId) => this.markMessageEventCursor(messageId),
|
|
1877
|
+
executeTool: (tool, args, callId, source) => this.executeTool(tool, args, callId, source),
|
|
1878
|
+
emitInternal: (eventClass, payload2, eventOptions) => this.emitInternal(eventClass, payload2, eventOptions),
|
|
1879
|
+
emit: (eventClass, payload2, eventOptions) => this.emit(eventClass, payload2, eventOptions),
|
|
1880
|
+
withEmitDefaults: (source, correlationId, causationId, eventOptions) => this.withEmitDefaults(source, correlationId, causationId, eventOptions),
|
|
1881
|
+
log: (logClass, fields, source, correlationId, causationId, overrides) => this.log(logClass, fields, source, correlationId, causationId, overrides),
|
|
1882
|
+
throwIfTurnHandoffRequested: () => this.throwIfTurnHandoffRequested(),
|
|
1883
|
+
isTurnHandoffSignal: (error) => error instanceof TurnHandoffSignal
|
|
1884
|
+
});
|
|
1885
|
+
this.state = this.createInitialState(this.agent);
|
|
1886
|
+
for (const eventClass of runtimeEventClasses) eventType(eventClass);
|
|
1887
|
+
}
|
|
1888
|
+
options;
|
|
1889
|
+
agent;
|
|
1890
|
+
workDir;
|
|
1891
|
+
outputDir;
|
|
1892
|
+
sessionIdValue;
|
|
1893
|
+
storageCoordinator;
|
|
1894
|
+
sandboxManager;
|
|
1895
|
+
runIdValue = randomId();
|
|
1896
|
+
transcriptManager = new TranscriptManager();
|
|
1897
|
+
eventRecorder = new EventRecorder();
|
|
1898
|
+
contextRegistry = new ContextRegistry();
|
|
1899
|
+
snapshotManager;
|
|
1900
|
+
toolExecutor;
|
|
1901
|
+
modelPipeline;
|
|
1902
|
+
services;
|
|
1903
|
+
roles;
|
|
1904
|
+
roleResolver;
|
|
1905
|
+
providerRegistry;
|
|
1906
|
+
defaultModel;
|
|
1907
|
+
modelOverride;
|
|
1908
|
+
runModelOverride;
|
|
1909
|
+
currentMode;
|
|
1910
|
+
state;
|
|
1911
|
+
metrics = emptyMetrics();
|
|
1912
|
+
startedAtPerf = 0;
|
|
1913
|
+
started = false;
|
|
1914
|
+
pendingInputs = [];
|
|
1915
|
+
finalAnswer = "";
|
|
1916
|
+
currentTurnId;
|
|
1917
|
+
providerStack = [];
|
|
1918
|
+
hookDepth = 0;
|
|
1919
|
+
turnHandoffRequested = false;
|
|
1920
|
+
logSource(source) {
|
|
1921
|
+
if (!source) return { kind: "runtime" };
|
|
1922
|
+
return {
|
|
1923
|
+
kind: source.kind,
|
|
1924
|
+
type: source.id,
|
|
1925
|
+
name: source.name,
|
|
1926
|
+
label: source.name
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
logContext(source, correlationId, causationId, overrides = {}) {
|
|
1930
|
+
return {
|
|
1931
|
+
sessionId: this.sessionIdValue,
|
|
1932
|
+
runId: this.runId,
|
|
1933
|
+
turnId: this.currentTurnId,
|
|
1934
|
+
modeId: this.currentMode,
|
|
1935
|
+
branchId: this.transcriptManager.activeTranscriptCursor.branchId,
|
|
1936
|
+
source: this.logSource(source),
|
|
1937
|
+
correlationId,
|
|
1938
|
+
causationId,
|
|
1939
|
+
...overrides
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
log(logClass, fields, source, correlationId, causationId, overrides) {
|
|
1943
|
+
this.options.logger?.emit(logClass, fields, this.logContext(source, correlationId, causationId, overrides));
|
|
1944
|
+
}
|
|
1945
|
+
logModelDelta(payload2, options) {
|
|
1946
|
+
const policy = this.options.logger?.modelDeltas ?? "none";
|
|
1947
|
+
if (policy === "none") return;
|
|
1948
|
+
const text = payload2 && typeof payload2 === "object" && "text" in payload2 ? String(payload2.text ?? "") : "";
|
|
1949
|
+
this.log(
|
|
1950
|
+
ModelDeltaLog,
|
|
1951
|
+
{
|
|
1952
|
+
length: text.length,
|
|
1953
|
+
...policy === "full" ? { text } : {}
|
|
1954
|
+
},
|
|
1955
|
+
options?.source ?? this.modelProviderSource(),
|
|
1956
|
+
options?.correlationId,
|
|
1957
|
+
options?.causationId
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
get mode() {
|
|
1961
|
+
return this.currentMode;
|
|
1962
|
+
}
|
|
1963
|
+
get runId() {
|
|
1964
|
+
return this.runIdValue;
|
|
1965
|
+
}
|
|
1966
|
+
subscribe(listener) {
|
|
1967
|
+
return this.eventRecorder.subscribe(listener);
|
|
1968
|
+
}
|
|
1969
|
+
requestTurnHandoff() {
|
|
1970
|
+
this.turnHandoffRequested = true;
|
|
1971
|
+
}
|
|
1972
|
+
storeRunDir() {
|
|
1973
|
+
return this.storageCoordinator.runDir;
|
|
1974
|
+
}
|
|
1975
|
+
async ensureStoreInitialized() {
|
|
1976
|
+
await this.storageCoordinator.ensureInitialized();
|
|
1977
|
+
const stored = await this.storageCoordinator.loadRuntimeState();
|
|
1978
|
+
if (stored) this.applyStoredRuntimeState(stored);
|
|
1979
|
+
}
|
|
1980
|
+
async ensureSandboxOpen() {
|
|
1981
|
+
await this.sandboxManager.ensureOpen();
|
|
1982
|
+
}
|
|
1983
|
+
async closeSandbox() {
|
|
1984
|
+
await this.sandboxManager.close();
|
|
1985
|
+
}
|
|
1986
|
+
async closeStore() {
|
|
1987
|
+
await this.storageCoordinator.close();
|
|
1988
|
+
}
|
|
1989
|
+
applyStoredRuntimeState(stored) {
|
|
1990
|
+
this.contextRegistry.loadSnapshots(stored.contextSnapshots);
|
|
1991
|
+
for (const snapshot of stored.snapshots) {
|
|
1992
|
+
this.snapshotManager.load(snapshot);
|
|
1993
|
+
this.transcriptManager.addBranches(snapshot.branches);
|
|
1994
|
+
}
|
|
1995
|
+
this.transcriptManager.loadTranscript(stored.transcript);
|
|
1996
|
+
this.metrics.messageCount = this.transcriptManager.count;
|
|
1997
|
+
this.eventRecorder.load(stored.events);
|
|
1998
|
+
this.metrics.eventCount = this.eventRecorder.count;
|
|
1999
|
+
if (stored.cursors) {
|
|
2000
|
+
this.transcriptManager.loadCursors({
|
|
2001
|
+
...stored.cursors,
|
|
2002
|
+
eventExists: (eventId) => this.eventRecorder.has(eventId)
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
async persistCursors() {
|
|
2007
|
+
await this.ensureStoreInitialized();
|
|
2008
|
+
await this.storageCoordinator.saveCursors({
|
|
2009
|
+
transcriptCursor: cloneJSON4(this.transcriptManager.activeTranscriptCursor),
|
|
2010
|
+
eventCursor: cloneJSON4(this.transcriptManager.activeEventCursor),
|
|
2011
|
+
branches: cloneJSON4(this.transcriptManager.allBranches)
|
|
2012
|
+
});
|
|
2013
|
+
}
|
|
2014
|
+
async beginNewRun() {
|
|
2015
|
+
if (this.started) {
|
|
2016
|
+
await this.closeSandbox();
|
|
2017
|
+
this.runIdValue = randomId();
|
|
2018
|
+
await this.storageCoordinator.beginRun(this.runIdValue);
|
|
2019
|
+
this.started = false;
|
|
2020
|
+
}
|
|
2021
|
+
this.startedAtPerf = 0;
|
|
2022
|
+
this.metrics = emptyMetrics();
|
|
2023
|
+
this.finalAnswer = "";
|
|
2024
|
+
this.turnHandoffRequested = false;
|
|
2025
|
+
}
|
|
2026
|
+
async run(message, options = {}) {
|
|
2027
|
+
if (options.signal?.aborted) throw new Error("Run aborted.");
|
|
2028
|
+
await this.beginNewRun();
|
|
2029
|
+
this.runModelOverride = void 0;
|
|
2030
|
+
this.runModelOverride = this.normalizeModelOverride(options.model);
|
|
2031
|
+
let runStarted = false;
|
|
2032
|
+
try {
|
|
2033
|
+
await this.start();
|
|
2034
|
+
runStarted = true;
|
|
2035
|
+
this.pendingInputs.push({
|
|
2036
|
+
id: options.userInputId,
|
|
2037
|
+
content: message,
|
|
2038
|
+
metadata: options.userMetadata,
|
|
2039
|
+
role: options.userRole,
|
|
2040
|
+
external: true
|
|
2041
|
+
});
|
|
2042
|
+
const maxRunnerTurns = this.options.maxTurns ?? 5;
|
|
2043
|
+
while (this.pendingInputs.length > 0 && this.metrics.turnCount < maxRunnerTurns) {
|
|
2044
|
+
if (options.signal?.aborted) throw new Error("Run aborted.");
|
|
2045
|
+
const input = this.pendingInputs.shift();
|
|
2046
|
+
await this.runTurn(input, options);
|
|
2047
|
+
}
|
|
2048
|
+
const completedAt = nowIso4();
|
|
2049
|
+
this.metrics.completedAt = completedAt;
|
|
2050
|
+
this.metrics.durationMs = Math.round(performance.now() - this.startedAtPerf);
|
|
2051
|
+
this.metrics.finalMode = this.currentMode;
|
|
2052
|
+
await this.storageCoordinator.saveTranscript(this.transcriptManager.allMessages);
|
|
2053
|
+
await this.emitInternal(RunEndEvent, {
|
|
2054
|
+
metrics: cloneJSON4({ ...this.metrics, eventCount: this.eventRecorder.count + 1 }),
|
|
2055
|
+
finalAnswer: this.finalAnswer
|
|
2056
|
+
});
|
|
2057
|
+
this.contextRegistry.expireScope("run" /* Run */);
|
|
2058
|
+
this.metrics.durationMs = Math.round(performance.now() - this.startedAtPerf);
|
|
2059
|
+
await this.storageCoordinator.saveMetrics(this.metrics);
|
|
2060
|
+
this.log(RunCompletedLog, {
|
|
2061
|
+
durationMs: this.metrics.durationMs,
|
|
2062
|
+
messageCount: this.metrics.messageCount,
|
|
2063
|
+
eventCount: this.metrics.eventCount
|
|
2064
|
+
});
|
|
2065
|
+
return {
|
|
2066
|
+
runId: this.runId,
|
|
2067
|
+
agentKey: this.agent.key,
|
|
2068
|
+
finalAnswer: this.finalAnswer,
|
|
2069
|
+
transcript: cloneJSON4(this.filterTranscript()),
|
|
2070
|
+
events: this.queryEvents().map((event) => cloneJSON4(event.record)),
|
|
2071
|
+
metrics: cloneJSON4(this.metrics),
|
|
2072
|
+
outputDir: this.storeRunDir()
|
|
2073
|
+
};
|
|
2074
|
+
} catch (error) {
|
|
2075
|
+
this.log(RunFailedLog, { error });
|
|
2076
|
+
throw error;
|
|
2077
|
+
} finally {
|
|
2078
|
+
if (runStarted) await this.closeSandbox();
|
|
2079
|
+
this.runModelOverride = void 0;
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
async prompt(message, options = {}) {
|
|
2083
|
+
return this.run(message, options);
|
|
2084
|
+
}
|
|
2085
|
+
async close() {
|
|
2086
|
+
await this.closeSandbox();
|
|
2087
|
+
await this.closeStore();
|
|
2088
|
+
}
|
|
2089
|
+
getTranscript(options) {
|
|
2090
|
+
return cloneJSON4(this.filterTranscript(options));
|
|
2091
|
+
}
|
|
2092
|
+
getMetrics() {
|
|
2093
|
+
return cloneJSON4({
|
|
2094
|
+
...this.metrics,
|
|
2095
|
+
durationMs: this.startedAtPerf > 0 ? Math.round(performance.now() - this.startedAtPerf) : 0,
|
|
2096
|
+
finalMode: this.currentMode
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
getRunInfo() {
|
|
2100
|
+
return {
|
|
2101
|
+
runId: this.runId,
|
|
2102
|
+
agentKey: this.agent.key,
|
|
2103
|
+
workDir: this.workDir,
|
|
2104
|
+
outputDir: this.storeRunDir(),
|
|
2105
|
+
started: this.started,
|
|
2106
|
+
startedAt: this.metrics.startedAt
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
getModel() {
|
|
2110
|
+
return this.getActiveModel();
|
|
2111
|
+
}
|
|
2112
|
+
setModel(model) {
|
|
2113
|
+
this.modelOverride = this.normalizeModelOverride(model);
|
|
2114
|
+
}
|
|
2115
|
+
clearModelOverride() {
|
|
2116
|
+
this.modelOverride = void 0;
|
|
2117
|
+
}
|
|
2118
|
+
getModelProviderInfo() {
|
|
2119
|
+
const resolved = this.resolveModelProvider(this.getActiveModel());
|
|
2120
|
+
return resolved.provider.getInfo?.() ?? {
|
|
2121
|
+
id: modelProviderId(resolved.provider),
|
|
2122
|
+
provider: resolved.namespace
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
getAvailableModels() {
|
|
2126
|
+
return this.providerRegistry.list().flatMap((provider) => {
|
|
2127
|
+
const models = provider.getModels?.() ?? [];
|
|
2128
|
+
return models.map((model) => ({
|
|
2129
|
+
...model,
|
|
2130
|
+
id: model.id.includes("/") ? model.id : `${provider.namespace}/${model.id}`,
|
|
2131
|
+
provider: model.provider ?? provider.namespace
|
|
2132
|
+
}));
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
getAgentManifest() {
|
|
2136
|
+
return {
|
|
2137
|
+
key: this.agent.key,
|
|
2138
|
+
label: this.agent.label,
|
|
2139
|
+
initialMode: this.agent.initialMode,
|
|
2140
|
+
currentMode: this.currentMode,
|
|
2141
|
+
modes: Object.values(this.agent.modes).map((mode) => modeSummary(mode)),
|
|
2142
|
+
roles: this.roles.map((role) => this.roleResolver.summary(role)),
|
|
2143
|
+
hooks: (this.agent.hooks ?? []).map((hook) => this.hookSummary(hook)),
|
|
2144
|
+
contextProviders: this.getContextProviders().map((provider) => contextProviderSummary(provider)),
|
|
2145
|
+
tools: this.getAllTools().map((tool) => this.toToolCatalogEntry(tool)),
|
|
2146
|
+
events: this.eventSummaries()
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
getEvents(filter) {
|
|
2150
|
+
return this.queryEvents(filter).map((event) => cloneJSON4(event.record));
|
|
2151
|
+
}
|
|
2152
|
+
getTranscriptCursor() {
|
|
2153
|
+
return cloneJSON4(this.transcriptManager.activeTranscriptCursor);
|
|
2154
|
+
}
|
|
2155
|
+
async seekTranscript(target) {
|
|
2156
|
+
return this.applyTranscriptSeek(target);
|
|
2157
|
+
}
|
|
2158
|
+
async latestTranscript() {
|
|
2159
|
+
return this.applyTranscriptSeek("latest");
|
|
2160
|
+
}
|
|
2161
|
+
getState() {
|
|
2162
|
+
return cloneJSON4(this.state);
|
|
2163
|
+
}
|
|
2164
|
+
updateState(patch) {
|
|
2165
|
+
if (patch && typeof patch === "object" && !Array.isArray(patch)) {
|
|
2166
|
+
Object.assign(this.state, patch);
|
|
2167
|
+
} else {
|
|
2168
|
+
this.state.value = patch;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
replaceState(next) {
|
|
2172
|
+
this.state = cloneJSON4(next);
|
|
2173
|
+
}
|
|
2174
|
+
async createSnapshot(input, eventOptions) {
|
|
2175
|
+
return this.snapshotManager.create(input, eventOptions);
|
|
2176
|
+
}
|
|
2177
|
+
listSnapshots() {
|
|
2178
|
+
return this.snapshotManager.list();
|
|
2179
|
+
}
|
|
2180
|
+
getSnapshot(id) {
|
|
2181
|
+
return this.snapshotManager.get(id);
|
|
2182
|
+
}
|
|
2183
|
+
async restoreSnapshot(id, eventOptions) {
|
|
2184
|
+
return this.snapshotManager.restore(id, eventOptions);
|
|
2185
|
+
}
|
|
2186
|
+
async deleteSnapshot(id, eventOptions) {
|
|
2187
|
+
return this.snapshotManager.deletePersisted(id, eventOptions);
|
|
2188
|
+
}
|
|
2189
|
+
getContextSnapshot() {
|
|
2190
|
+
return this.contextRegistry.current ? cloneJSON4(this.contextRegistry.current) : void 0;
|
|
2191
|
+
}
|
|
2192
|
+
getContextEntries(filter) {
|
|
2193
|
+
return cloneJSON4(this.contextRegistry.filter(filter));
|
|
2194
|
+
}
|
|
2195
|
+
async switchMode(mode, input) {
|
|
2196
|
+
const modeId = this.resolveModeType(mode);
|
|
2197
|
+
const previousModeId = this.currentMode;
|
|
2198
|
+
const previous = this.getModeDefinition(previousModeId);
|
|
2199
|
+
if (previousModeId !== modeId) {
|
|
2200
|
+
await this.ensureSandboxOpen();
|
|
2201
|
+
await previous.onExit?.(
|
|
2202
|
+
this.buildActionSession(
|
|
2203
|
+
void 0,
|
|
2204
|
+
{ kind: "mode", id: previousModeId, name: getConstructLabel(previous) }
|
|
2205
|
+
),
|
|
2206
|
+
modeSummary(this.getModeDefinition(modeId))
|
|
2207
|
+
);
|
|
2208
|
+
this.currentMode = modeId;
|
|
2209
|
+
const next = this.getModeDefinition(modeId);
|
|
2210
|
+
await next.onEnter?.(
|
|
2211
|
+
this.buildActionSession(
|
|
2212
|
+
void 0,
|
|
2213
|
+
{ kind: "mode", id: modeId, name: getConstructLabel(next) }
|
|
2214
|
+
),
|
|
2215
|
+
input
|
|
2216
|
+
);
|
|
2217
|
+
await this.emitInternal(ModeChangedEvent, {
|
|
2218
|
+
previousMode: previousModeId,
|
|
2219
|
+
mode: modeId,
|
|
2220
|
+
input
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
if (input !== void 0) {
|
|
2224
|
+
this.pendingInputs.push({
|
|
2225
|
+
content: typeof input === "string" ? input : JSON.stringify(input, null, 2),
|
|
2226
|
+
external: false
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
async start() {
|
|
2231
|
+
if (this.started) return;
|
|
2232
|
+
await this.ensureStoreInitialized();
|
|
2233
|
+
await this.ensureSandboxOpen();
|
|
2234
|
+
this.started = true;
|
|
2235
|
+
this.startedAtPerf = performance.now();
|
|
2236
|
+
this.log(RunStartedLog, { modeId: this.currentMode, model: this.getActiveModel() });
|
|
2237
|
+
await this.emitInternal(RunStartEvent, {
|
|
2238
|
+
agentKey: this.agent.key,
|
|
2239
|
+
modeId: this.currentMode,
|
|
2240
|
+
workDir: this.workDir,
|
|
2241
|
+
outputDir: this.storeRunDir()
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
async runTurn(input, options) {
|
|
2245
|
+
if (input.external !== false) {
|
|
2246
|
+
await this.createSnapshot({
|
|
2247
|
+
label: "Before user message",
|
|
2248
|
+
metadata: {
|
|
2249
|
+
kind: "before_user_message",
|
|
2250
|
+
automatic: true,
|
|
2251
|
+
userInputId: input.id
|
|
2252
|
+
}
|
|
2253
|
+
}, { hiddenTranscript: false });
|
|
2254
|
+
this.transcriptManager.ensureBranchForAppend();
|
|
2255
|
+
}
|
|
2256
|
+
this.metrics.turnCount++;
|
|
2257
|
+
this.currentTurnId = randomId();
|
|
2258
|
+
const turnStart = performance.now();
|
|
2259
|
+
this.log(TurnStartedLog, { turnId: this.currentTurnId });
|
|
2260
|
+
await this.emitInternal(TurnStartEvent, { turnId: this.currentTurnId, input: input.content });
|
|
2261
|
+
const inputRole = this.resolveRole(input.role ?? userRole);
|
|
2262
|
+
await this.emitInternal(MessageStartEvent, { role: inputRole.role });
|
|
2263
|
+
const userMessage = await this.addMessage(inputRole.role, input.content, input.metadata, inputRole, input.id);
|
|
2264
|
+
await this.emitInternal(MessageEndEvent, { message: userMessage });
|
|
2265
|
+
await this.markMessageEventCursor(userMessage.id);
|
|
2266
|
+
const mode = this.getModeDefinition(this.currentMode);
|
|
2267
|
+
const tools = await this.resolveTools();
|
|
2268
|
+
await this.emitInternal(MessageStartEvent, { role: "assistant" });
|
|
2269
|
+
try {
|
|
2270
|
+
await this.modelPipeline.run({
|
|
2271
|
+
mode,
|
|
2272
|
+
userMessage,
|
|
2273
|
+
tools,
|
|
2274
|
+
options
|
|
2275
|
+
});
|
|
2276
|
+
} catch (error) {
|
|
2277
|
+
if (error instanceof TurnHandoffSignal) return;
|
|
2278
|
+
throw error;
|
|
2279
|
+
} finally {
|
|
2280
|
+
this.log(TurnCompletedLog, {
|
|
2281
|
+
turnId: this.currentTurnId,
|
|
2282
|
+
durationMs: Math.round(performance.now() - turnStart)
|
|
2283
|
+
});
|
|
2284
|
+
await this.emitInternal(TurnEndEvent, { turnId: this.currentTurnId, finalAnswer: this.finalAnswer });
|
|
2285
|
+
this.contextRegistry.expireScope("turn" /* Turn */, this.currentTurnId);
|
|
2286
|
+
this.currentTurnId = void 0;
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
async addMessage(role, content, metadata, roleInfo, id) {
|
|
2290
|
+
const message = this.transcriptManager.appendMessage({
|
|
2291
|
+
id,
|
|
2292
|
+
role,
|
|
2293
|
+
content,
|
|
2294
|
+
modeId: this.currentMode,
|
|
2295
|
+
turnId: this.currentTurnId,
|
|
2296
|
+
metadata,
|
|
2297
|
+
roleInfo
|
|
2298
|
+
});
|
|
2299
|
+
this.metrics.messageCount = this.transcriptManager.count;
|
|
2300
|
+
await this.storageCoordinator.saveTranscript(this.transcriptManager.allMessages);
|
|
2301
|
+
await this.persistCursors();
|
|
2302
|
+
return message;
|
|
2303
|
+
}
|
|
2304
|
+
async addHiddenEventMessage(record) {
|
|
2305
|
+
this.transcriptManager.appendMessage({
|
|
2306
|
+
branchId: record.branchId,
|
|
2307
|
+
role: "event",
|
|
2308
|
+
content: record,
|
|
2309
|
+
createdAt: record.at,
|
|
2310
|
+
modeId: record.modeId,
|
|
2311
|
+
turnId: record.turnId,
|
|
2312
|
+
hidden: true,
|
|
2313
|
+
metadata: { eventId: record.id, eventType: record.type }
|
|
2314
|
+
});
|
|
2315
|
+
this.metrics.messageCount = this.transcriptManager.count;
|
|
2316
|
+
await this.storageCoordinator.saveTranscript(this.transcriptManager.allMessages);
|
|
2317
|
+
await this.persistCursors();
|
|
2318
|
+
}
|
|
2319
|
+
async addToolCallMessage(tool, args, toolCallId, source) {
|
|
2320
|
+
return this.addMessage("assistant", [{
|
|
2321
|
+
type: "tool-call",
|
|
2322
|
+
toolCallId,
|
|
2323
|
+
toolName: tool.name,
|
|
2324
|
+
input: args
|
|
2325
|
+
}], {
|
|
2326
|
+
toolCallId,
|
|
2327
|
+
toolName: tool.name,
|
|
2328
|
+
source
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
async addToolResultMessage(tool, result, toolCallId) {
|
|
2332
|
+
await this.addMessage("tool", [{
|
|
2333
|
+
type: "tool-result",
|
|
2334
|
+
toolCallId,
|
|
2335
|
+
toolName: tool.name,
|
|
2336
|
+
output: result.data ?? result.content
|
|
2337
|
+
}], {
|
|
2338
|
+
toolCallId,
|
|
2339
|
+
toolName: tool.name,
|
|
2340
|
+
isError: result.isError,
|
|
2341
|
+
...result.metadata
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
async markMessageEventCursor(messageId) {
|
|
2345
|
+
if (!this.transcriptManager.markMessageEventCursor(messageId)) return;
|
|
2346
|
+
await this.storageCoordinator.saveTranscript(this.transcriptManager.allMessages);
|
|
2347
|
+
}
|
|
2348
|
+
createInitialState(agent) {
|
|
2349
|
+
const initial = agent.sharedState?.initial;
|
|
2350
|
+
if (typeof initial === "function") return cloneJSON4(initial());
|
|
2351
|
+
if (initial && typeof initial === "object") return cloneJSON4(initial);
|
|
2352
|
+
return {};
|
|
2353
|
+
}
|
|
2354
|
+
getModeDefinition(modeId) {
|
|
2355
|
+
const mode = this.agent.modes[modeId];
|
|
2356
|
+
if (!mode) throw new Error(`Unknown mode '${modeId}' for agent '${this.agent.key}'.`);
|
|
2357
|
+
return mode;
|
|
2358
|
+
}
|
|
2359
|
+
resolveModeType(selector) {
|
|
2360
|
+
for (const mode of Object.values(this.agent.modes)) {
|
|
2361
|
+
if (modeMatchesSelector(mode, selector)) return getConstructType(mode);
|
|
2362
|
+
}
|
|
2363
|
+
throw new Error(`Unknown mode '${getConstructType(selector)}' for agent '${this.agent.key}'.`);
|
|
2364
|
+
}
|
|
2365
|
+
buildReadSession(source = { kind: "runtime" }, correlationId, causationId) {
|
|
2366
|
+
const log = this.options.logger?.agent(this.logContext(source, correlationId, causationId)) ?? noopAgentLogSession;
|
|
2367
|
+
return {
|
|
2368
|
+
runId: this.runId,
|
|
2369
|
+
turnId: this.currentTurnId,
|
|
2370
|
+
agentKey: this.agent.key,
|
|
2371
|
+
workDir: this.workDir,
|
|
2372
|
+
outputDir: this.storeRunDir(),
|
|
2373
|
+
services: this.services,
|
|
2374
|
+
state: {
|
|
2375
|
+
get: () => cloneJSON4(this.state)
|
|
2376
|
+
},
|
|
2377
|
+
history: {
|
|
2378
|
+
get: (options) => this.getTranscript({ includeHidden: false, ...options })
|
|
2379
|
+
},
|
|
2380
|
+
events: {
|
|
2381
|
+
query: (filter) => this.queryEvents(filter).map((event) => cloneJSON4(event.record))
|
|
2382
|
+
},
|
|
2383
|
+
mode: {
|
|
2384
|
+
current: () => modeSummary(this.getModeDefinition(this.currentMode))
|
|
2385
|
+
},
|
|
2386
|
+
context: {
|
|
2387
|
+
get: (filter) => this.getContextEntries(filter),
|
|
2388
|
+
snapshot: () => this.getContextSnapshot()
|
|
2389
|
+
},
|
|
2390
|
+
log
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
buildActionSession(tool, source = tool ? { kind: "tool", id: tool.id, name: tool.name } : { kind: "runtime" }, correlationId, causationId) {
|
|
2394
|
+
const readSession = this.buildReadSession(source, correlationId, causationId);
|
|
2395
|
+
const sandbox = this.sandboxManager.current;
|
|
2396
|
+
if (!sandbox) throw new Error("Sandbox session has not been opened.");
|
|
2397
|
+
const applyEmitDefaults = (options) => this.withEmitDefaults(source, correlationId, causationId, options);
|
|
2398
|
+
return {
|
|
2399
|
+
...readSession,
|
|
2400
|
+
sandbox,
|
|
2401
|
+
state: {
|
|
2402
|
+
get: () => cloneJSON4(this.state),
|
|
2403
|
+
update: (patch) => this.updateState(patch),
|
|
2404
|
+
set: (next) => this.replaceState(next)
|
|
2405
|
+
},
|
|
2406
|
+
events: {
|
|
2407
|
+
...readSession.events,
|
|
2408
|
+
emit: (eventClass, payload2, options) => this.emit(eventClass, payload2, applyEmitDefaults(options))
|
|
2409
|
+
},
|
|
2410
|
+
mode: {
|
|
2411
|
+
...readSession.mode,
|
|
2412
|
+
switch: async (mode, input) => this.switchMode(mode, input)
|
|
2413
|
+
},
|
|
2414
|
+
tools: {
|
|
2415
|
+
invoke: (tool2, args) => this.invokeTool(tool2, args, source, correlationId, causationId)
|
|
2416
|
+
},
|
|
2417
|
+
context: {
|
|
2418
|
+
...readSession.context,
|
|
2419
|
+
add: (input, options) => Promise.resolve(this.addDynamicContext(input, options)),
|
|
2420
|
+
render: (binding, options) => this.renderDynamicContext(binding, options),
|
|
2421
|
+
remove: (id) => Promise.resolve(this.removeDynamicContext(id)),
|
|
2422
|
+
clear: (filter) => Promise.resolve(this.clearDynamicContext(filter))
|
|
2423
|
+
},
|
|
2424
|
+
messages: {
|
|
2425
|
+
enqueue: async (input, options) => {
|
|
2426
|
+
const message = normalizeAgentMessageInput(input);
|
|
2427
|
+
this.pendingInputs.push({
|
|
2428
|
+
id: message.id,
|
|
2429
|
+
content: message.content,
|
|
2430
|
+
metadata: { ...message.metadata, ...options?.metadata },
|
|
2431
|
+
role: message.role,
|
|
2432
|
+
external: false
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2435
|
+
},
|
|
2436
|
+
snapshots: {
|
|
2437
|
+
create: (input) => this.createSnapshot(input, applyEmitDefaults({ hiddenTranscript: false }))
|
|
2438
|
+
},
|
|
2439
|
+
toolCall: tool
|
|
2440
|
+
};
|
|
2441
|
+
}
|
|
2442
|
+
withEmitDefaults(source, correlationId, causationId, options) {
|
|
2443
|
+
return {
|
|
2444
|
+
...options,
|
|
2445
|
+
source: options?.source ?? source,
|
|
2446
|
+
correlationId: options?.correlationId ?? correlationId,
|
|
2447
|
+
causationId: options?.causationId ?? causationId
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
throwIfTurnHandoffRequested() {
|
|
2451
|
+
if (!this.turnHandoffRequested) return;
|
|
2452
|
+
this.turnHandoffRequested = false;
|
|
2453
|
+
throw new TurnHandoffSignal();
|
|
2454
|
+
}
|
|
2455
|
+
normalizeModelOverride(model) {
|
|
2456
|
+
if (model === void 0) return void 0;
|
|
2457
|
+
const next = model.trim();
|
|
2458
|
+
if (!next) throw new Error("Model must not be empty.");
|
|
2459
|
+
this.resolveModelProvider(next);
|
|
2460
|
+
return next;
|
|
2461
|
+
}
|
|
2462
|
+
getActiveModel() {
|
|
2463
|
+
return this.runModelOverride ?? this.modelOverride ?? this.getModeDefinition(this.currentMode).model ?? this.defaultModel;
|
|
2464
|
+
}
|
|
2465
|
+
resolveModelProvider(model) {
|
|
2466
|
+
return this.providerRegistry.resolve(model);
|
|
2467
|
+
}
|
|
2468
|
+
modelProviderSource() {
|
|
2469
|
+
const resolved = this.resolveModelProvider(this.getActiveModel());
|
|
2470
|
+
const id = modelProviderId(resolved.provider);
|
|
2471
|
+
return { kind: "model_provider", id, name: id };
|
|
2472
|
+
}
|
|
2473
|
+
filterTranscript(options) {
|
|
2474
|
+
return this.transcriptManager.filter(options, this.currentTurnId);
|
|
2475
|
+
}
|
|
2476
|
+
getActiveContextProviderBindings() {
|
|
2477
|
+
const mode = this.getModeDefinition(this.currentMode);
|
|
2478
|
+
if (!mode.providers) return [];
|
|
2479
|
+
if (mode.providers === "all") {
|
|
2480
|
+
return this.getContextProviders().filter((provider) => !mode.excludeProviders?.includes(getConstructType(provider))).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
2481
|
+
}
|
|
2482
|
+
return mode.providers.filter((binding) => !mode.excludeProviders?.includes(this.bindingToSummary(binding).type));
|
|
2483
|
+
}
|
|
2484
|
+
getContextProviders() {
|
|
2485
|
+
const providers = /* @__PURE__ */ new Map();
|
|
2486
|
+
for (const mode of Object.values(this.agent.modes)) {
|
|
2487
|
+
if (!mode.providers || mode.providers === "all") continue;
|
|
2488
|
+
for (const binding of mode.providers) {
|
|
2489
|
+
const provider = this.providerFromReference(binding, false).provider;
|
|
2490
|
+
providers.set(getConstructType(provider), provider);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return [...providers.values()];
|
|
2494
|
+
}
|
|
2495
|
+
providerFromReference(reference, allowClassSelector) {
|
|
2496
|
+
if (!isContextProviderReference(reference)) {
|
|
2497
|
+
throw new Error("Invalid context provider reference. Use a provider instance or provider.with(options).");
|
|
2498
|
+
}
|
|
2499
|
+
const selector = isContextProviderBinding(reference) ? reference.provider : reference;
|
|
2500
|
+
const options = isContextProviderBinding(reference) ? reference.options : void 0;
|
|
2501
|
+
if (isContextProviderInstance(selector)) {
|
|
2502
|
+
assertNoAuthorId(selector, "Context provider");
|
|
2503
|
+
return { provider: selector, options };
|
|
2504
|
+
}
|
|
2505
|
+
if (isContextProviderClass(selector)) {
|
|
2506
|
+
if (!allowClassSelector) {
|
|
2507
|
+
throw new Error(
|
|
2508
|
+
`Context provider '${getConstructType(selector)}' is a class selector. Register an instance in mode.providers.`
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
const provider = this.getContextProviders().find((candidate) => contextProviderMatchesSelector(candidate, selector));
|
|
2512
|
+
if (!provider) throw new Error(`Unknown context provider '${getConstructType(selector)}'.`);
|
|
2513
|
+
return { provider, options };
|
|
2514
|
+
}
|
|
2515
|
+
throw new Error("Invalid context provider reference. Provider ids and strings are not supported.");
|
|
2516
|
+
}
|
|
2517
|
+
bindingToSummary(binding) {
|
|
2518
|
+
const { provider, options } = this.providerFromReference(binding, true);
|
|
2519
|
+
return contextProviderSummary(provider, options);
|
|
2520
|
+
}
|
|
2521
|
+
dynamicContextEntriesFor(eventClass) {
|
|
2522
|
+
return this.contextRegistry.entriesFor(eventClass, this.runId, this.currentTurnId);
|
|
2523
|
+
}
|
|
2524
|
+
activateDynamicContextFor(eventClass, record) {
|
|
2525
|
+
this.contextRegistry.activateFor(eventClass, record, this.runId, this.currentTurnId);
|
|
2526
|
+
}
|
|
2527
|
+
consumeDynamicContext(entries) {
|
|
2528
|
+
this.contextRegistry.consume(entries);
|
|
2529
|
+
}
|
|
2530
|
+
addDynamicContext(input, options = {}, provider) {
|
|
2531
|
+
const contribution = this.normalizeContextContribution(
|
|
2532
|
+
input,
|
|
2533
|
+
{
|
|
2534
|
+
providerId: provider?.type,
|
|
2535
|
+
providerLabel: provider?.label
|
|
2536
|
+
}
|
|
2537
|
+
);
|
|
2538
|
+
return this.addDynamicContextContribution(contribution, options);
|
|
2539
|
+
}
|
|
2540
|
+
addDynamicContextContribution(contribution, options = {}) {
|
|
2541
|
+
return this.contextRegistry.addContribution({
|
|
2542
|
+
contribution,
|
|
2543
|
+
options,
|
|
2544
|
+
runId: this.runId,
|
|
2545
|
+
turnId: this.currentTurnId,
|
|
2546
|
+
modeId: this.currentMode
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
async renderDynamicContext(binding, options) {
|
|
2550
|
+
const rendered = await this.loadContextProvider(binding);
|
|
2551
|
+
return rendered.contributions.map((contribution, index) => this.addDynamicContextContribution(
|
|
2552
|
+
contribution,
|
|
2553
|
+
{
|
|
2554
|
+
...options,
|
|
2555
|
+
id: options?.id && rendered.contributions.length === 1 ? options.id : options?.id ? `${options.id}:${index}` : void 0
|
|
2556
|
+
}
|
|
2557
|
+
));
|
|
2558
|
+
}
|
|
2559
|
+
removeDynamicContext(id) {
|
|
2560
|
+
return this.contextRegistry.remove(id);
|
|
2561
|
+
}
|
|
2562
|
+
clearDynamicContext(filter) {
|
|
2563
|
+
return this.contextRegistry.clear(filter);
|
|
2564
|
+
}
|
|
2565
|
+
async buildContextSnapshot(trigger = ModelBeforeEvent) {
|
|
2566
|
+
const started = performance.now();
|
|
2567
|
+
const activeBindings = this.getActiveContextProviderBindings();
|
|
2568
|
+
this.log(ContextBuildStartedLog, { providerCount: activeBindings.length }, { kind: "runtime" });
|
|
2569
|
+
const providers = [];
|
|
2570
|
+
for (const binding of activeBindings) {
|
|
2571
|
+
providers.push(await this.loadContextProvider(binding));
|
|
2572
|
+
}
|
|
2573
|
+
const dynamicEntries = this.dynamicContextEntriesFor(trigger);
|
|
2574
|
+
const contributions = [
|
|
2575
|
+
...providers.flatMap((provider) => provider.contributions),
|
|
2576
|
+
...dynamicEntries.map((entry) => entry.contribution)
|
|
2577
|
+
];
|
|
2578
|
+
const systemContributions = contributions.filter((contribution) => this.resolveRole(contribution.role).target === "system" /* System */);
|
|
2579
|
+
const messageContributions = contributions.filter((contribution) => this.resolveRole(contribution.role).target === "messages" /* Messages */);
|
|
2580
|
+
const systemPrompt = this.renderSystemContributions(systemContributions);
|
|
2581
|
+
const messages = messageContributions.map((contribution) => this.contributionToMessage(contribution));
|
|
2582
|
+
const snapshot = {
|
|
2583
|
+
id: randomId(),
|
|
2584
|
+
turnId: this.currentTurnId,
|
|
2585
|
+
modeId: this.currentMode,
|
|
2586
|
+
createdAt: nowIso4(),
|
|
2587
|
+
providers,
|
|
2588
|
+
contributions,
|
|
2589
|
+
systemPrompt,
|
|
2590
|
+
messages
|
|
2591
|
+
};
|
|
2592
|
+
this.contextRegistry.recordSnapshot(snapshot);
|
|
2593
|
+
await this.storageCoordinator.saveContextSnapshot(snapshot);
|
|
2594
|
+
this.consumeDynamicContext(dynamicEntries);
|
|
2595
|
+
this.log(ContextBuildCompletedLog, {
|
|
2596
|
+
providerCount: providers.length,
|
|
2597
|
+
contributionCount: contributions.length,
|
|
2598
|
+
durationMs: Math.round(performance.now() - started)
|
|
2599
|
+
}, { kind: "runtime" });
|
|
2600
|
+
return snapshot;
|
|
2601
|
+
}
|
|
2602
|
+
async loadContextProvider(binding) {
|
|
2603
|
+
const { provider, options } = this.providerFromReference(binding, true);
|
|
2604
|
+
const summary = contextProviderSummary(provider, options);
|
|
2605
|
+
if (this.providerStack.includes(summary.type)) {
|
|
2606
|
+
throw new Error(`Context provider cycle detected: ${[...this.providerStack, summary.type].join(" -> ")}`);
|
|
2607
|
+
}
|
|
2608
|
+
this.providerStack.push(summary.type);
|
|
2609
|
+
try {
|
|
2610
|
+
const output = await provider.render(
|
|
2611
|
+
this.buildReadSession(
|
|
2612
|
+
{ kind: "context_provider", id: summary.type, name: summary.label },
|
|
2613
|
+
this.currentTurnId
|
|
2614
|
+
),
|
|
2615
|
+
options
|
|
2616
|
+
);
|
|
2617
|
+
return {
|
|
2618
|
+
providerId: summary.type,
|
|
2619
|
+
providerLabel: summary.label,
|
|
2620
|
+
binding: summary,
|
|
2621
|
+
contributions: this.normalizeContextOutput(provider, output)
|
|
2622
|
+
};
|
|
2623
|
+
} catch (error) {
|
|
2624
|
+
this.log(
|
|
2625
|
+
ContextProviderFailedLog,
|
|
2626
|
+
{ providerType: summary.type, error },
|
|
2627
|
+
{ kind: "context_provider", id: summary.type, name: summary.label },
|
|
2628
|
+
this.currentTurnId
|
|
2629
|
+
);
|
|
2630
|
+
throw error;
|
|
2631
|
+
} finally {
|
|
2632
|
+
this.providerStack.pop();
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
normalizeContextContribution(input, context = {}) {
|
|
2636
|
+
if (typeof input === "string") {
|
|
2637
|
+
const role2 = this.resolveRole(context.defaultRole ?? systemRole);
|
|
2638
|
+
return {
|
|
2639
|
+
providerId: context.providerId,
|
|
2640
|
+
providerLabel: context.providerLabel,
|
|
2641
|
+
role: role2.authorRole,
|
|
2642
|
+
authorRole: role2.authorRole,
|
|
2643
|
+
roleType: role2.roleType,
|
|
2644
|
+
content: input
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
const role = this.resolveRole(input.role ?? context.defaultRole ?? systemRole);
|
|
2648
|
+
return {
|
|
2649
|
+
providerId: context.providerId,
|
|
2650
|
+
providerLabel: context.providerLabel,
|
|
2651
|
+
role: role.authorRole,
|
|
2652
|
+
authorRole: role.authorRole,
|
|
2653
|
+
roleType: role.roleType,
|
|
2654
|
+
content: input.content,
|
|
2655
|
+
metadata: input.metadata
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
normalizeContextOutput(provider, output) {
|
|
2659
|
+
const summary = contextProviderSummary(provider);
|
|
2660
|
+
if (output == null) return [];
|
|
2661
|
+
const entries = Array.isArray(output) ? output : [output];
|
|
2662
|
+
return entries.map((entry) => {
|
|
2663
|
+
if (typeof entry === "string" && !entry.trim()) return void 0;
|
|
2664
|
+
if (typeof entry !== "string" && (entry.content == null || typeof entry.content === "string" && !entry.content.trim())) {
|
|
2665
|
+
return void 0;
|
|
2666
|
+
}
|
|
2667
|
+
return this.normalizeContextContribution(entry, {
|
|
2668
|
+
providerId: summary.type,
|
|
2669
|
+
providerLabel: summary.label,
|
|
2670
|
+
defaultRole: provider.role
|
|
2671
|
+
});
|
|
2672
|
+
}).filter(Boolean);
|
|
2673
|
+
}
|
|
2674
|
+
resolveRole(selector) {
|
|
2675
|
+
return this.roleResolver.resolve(selector);
|
|
2676
|
+
}
|
|
2677
|
+
assertModelProviderSupportsMessages(messages) {
|
|
2678
|
+
this.roleResolver.assertModelProviderSupportsMessages(messages);
|
|
2679
|
+
}
|
|
2680
|
+
renderSystemContributions(contributions) {
|
|
2681
|
+
if (!contributions.length) return "";
|
|
2682
|
+
const rendered = contributions.map((contribution) => {
|
|
2683
|
+
const label = contribution.providerLabel ?? contribution.providerId ?? contribution.role;
|
|
2684
|
+
return `## ${label}
|
|
2685
|
+
|
|
2686
|
+
${stringifyContent(contribution.content)}`;
|
|
2687
|
+
});
|
|
2688
|
+
return `# Runtime Context
|
|
2689
|
+
|
|
2690
|
+
${rendered.join("\n\n")}`;
|
|
2691
|
+
}
|
|
2692
|
+
contributionToMessage(contribution) {
|
|
2693
|
+
const role = this.resolveRole(contribution.role);
|
|
2694
|
+
return {
|
|
2695
|
+
id: randomId(),
|
|
2696
|
+
seq: 0,
|
|
2697
|
+
branchId: this.transcriptManager.activeTranscriptCursor.branchId,
|
|
2698
|
+
role: role.role,
|
|
2699
|
+
authorRole: contribution.authorRole ?? role.authorRole,
|
|
2700
|
+
roleType: contribution.roleType ?? role.roleType,
|
|
2701
|
+
content: contribution.content,
|
|
2702
|
+
createdAt: nowIso4(),
|
|
2703
|
+
modeId: this.currentMode,
|
|
2704
|
+
turnId: this.currentTurnId,
|
|
2705
|
+
eventCursor: cloneJSON4(this.transcriptManager.activeEventCursor),
|
|
2706
|
+
metadata: {
|
|
2707
|
+
contextProviderId: contribution.providerId,
|
|
2708
|
+
contextRole: contribution.authorRole ?? contribution.role,
|
|
2709
|
+
contextRoleType: contribution.roleType,
|
|
2710
|
+
...contribution.metadata
|
|
2711
|
+
}
|
|
2712
|
+
};
|
|
2713
|
+
}
|
|
2714
|
+
async resolveTools() {
|
|
2715
|
+
const mode = this.getModeDefinition(this.currentMode);
|
|
2716
|
+
const tools = [];
|
|
2717
|
+
for (const source of mode.tools ?? []) {
|
|
2718
|
+
if (!isToolInstance(source)) throw new Error("Mode tools must extend HarnessTool. Tool factories and object literals are not supported.");
|
|
2719
|
+
assertNoAuthorId(source, "Tool");
|
|
2720
|
+
tools.push(source);
|
|
2721
|
+
}
|
|
2722
|
+
const names = /* @__PURE__ */ new Set();
|
|
2723
|
+
for (const tool of tools) {
|
|
2724
|
+
if (names.has(tool.name)) throw new Error(`Duplicate toolName '${tool.name}' in mode '${this.currentMode}'.`);
|
|
2725
|
+
names.add(tool.name);
|
|
2726
|
+
}
|
|
2727
|
+
return tools;
|
|
2728
|
+
}
|
|
2729
|
+
getAllTools() {
|
|
2730
|
+
const tools = /* @__PURE__ */ new Map();
|
|
2731
|
+
for (const mode of Object.values(this.agent.modes)) {
|
|
2732
|
+
for (const source of mode.tools ?? []) {
|
|
2733
|
+
if (!isToolInstance(source)) continue;
|
|
2734
|
+
tools.set(source.name, source);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
return [...tools.values()];
|
|
2738
|
+
}
|
|
2739
|
+
toToolCatalogEntry(tool) {
|
|
2740
|
+
return {
|
|
2741
|
+
name: tool.name,
|
|
2742
|
+
description: tool.description,
|
|
2743
|
+
risk: tool.risk,
|
|
2744
|
+
permissions: tool.permissions,
|
|
2745
|
+
requiresApproval: typeof tool.requiresApproval === "boolean" ? tool.requiresApproval : void 0
|
|
2746
|
+
};
|
|
2747
|
+
}
|
|
2748
|
+
hookSummary(hook) {
|
|
2749
|
+
const eventClass = hookEventClass(hook);
|
|
2750
|
+
const type = eventType(eventClass);
|
|
2751
|
+
return {
|
|
2752
|
+
type: getConstructType(hook),
|
|
2753
|
+
label: getConstructLabel(hook),
|
|
2754
|
+
eventType: type,
|
|
2755
|
+
eventClassId: type
|
|
2756
|
+
};
|
|
2757
|
+
}
|
|
2758
|
+
eventSummaries() {
|
|
2759
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
2760
|
+
const add = (eventClass, builtIn = false) => {
|
|
2761
|
+
const type = eventType(eventClass);
|
|
2762
|
+
summaries.set(type, {
|
|
2763
|
+
type,
|
|
2764
|
+
label: getConstructLabel(eventClass),
|
|
2765
|
+
className: getConstructType(eventClass),
|
|
2766
|
+
builtIn
|
|
2767
|
+
});
|
|
2768
|
+
};
|
|
2769
|
+
for (const eventClass of runtimeEventClasses) add(eventClass, true);
|
|
2770
|
+
for (const hook of this.agent.hooks ?? []) add(hookEventClass(hook));
|
|
2771
|
+
for (const eventClass of this.agent.declaredEvents ?? []) add(eventClass);
|
|
2772
|
+
return [...summaries.values()];
|
|
2773
|
+
}
|
|
2774
|
+
async invokeTool(selector, args, source = { kind: "runtime" }, parentCorrelationId, parentCausationId) {
|
|
2775
|
+
const tool = (await this.resolveTools()).find((candidate) => toolMatchesSelector(candidate, selector));
|
|
2776
|
+
if (!tool) {
|
|
2777
|
+
const label = getConstructType(selector);
|
|
2778
|
+
throw new Error(`Unknown tool '${label}' in mode '${this.currentMode}'.`);
|
|
2779
|
+
}
|
|
2780
|
+
return this.executeTool(tool, args, void 0, source, parentCorrelationId, parentCausationId);
|
|
2781
|
+
}
|
|
2782
|
+
async executeTool(tool, args, callId, source = { kind: "runtime" }, parentCorrelationId, parentCausationId) {
|
|
2783
|
+
return this.toolExecutor.execute({
|
|
2784
|
+
tool,
|
|
2785
|
+
args,
|
|
2786
|
+
callId,
|
|
2787
|
+
source,
|
|
2788
|
+
parentCorrelationId,
|
|
2789
|
+
parentCausationId
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
async applyTranscriptSeek(target) {
|
|
2793
|
+
const previousCursor = cloneJSON4(this.transcriptManager.activeTranscriptCursor);
|
|
2794
|
+
const resolved = this.transcriptManager.resolveSeekTarget(target);
|
|
2795
|
+
this.transcriptManager.applyResolvedSeek(resolved);
|
|
2796
|
+
await this.emitInternal(TranscriptCursorChangedEvent, {
|
|
2797
|
+
previousCursor,
|
|
2798
|
+
cursor: this.transcriptManager.activeTranscriptCursor
|
|
2799
|
+
}, { hiddenTranscript: false });
|
|
2800
|
+
this.log(TranscriptCursorChangedLog, { cursorId: this.transcriptManager.activeTranscriptCursor.id });
|
|
2801
|
+
this.transcriptManager.applyResolvedSeek(resolved);
|
|
2802
|
+
await this.persistCursors();
|
|
2803
|
+
return cloneJSON4(this.transcriptManager.activeTranscriptCursor);
|
|
2804
|
+
}
|
|
2805
|
+
async emitInternal(eventClass, payload2, options) {
|
|
2806
|
+
return this.recordHarnessEvent(eventClass, payload2, {
|
|
2807
|
+
source: { kind: "runtime" },
|
|
2808
|
+
...options
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
async emit(eventClass, payload2, options) {
|
|
2812
|
+
return this.recordHarnessEvent(eventClass, payload2, options);
|
|
2813
|
+
}
|
|
2814
|
+
async recordHarnessEvent(eventClass, payload2, options) {
|
|
2815
|
+
const type = eventType(eventClass);
|
|
2816
|
+
const parsedPayload = normalizeSchema(eventClass.schema).parse(payload2);
|
|
2817
|
+
if (type === MessageDeltaEvent.type) this.logModelDelta(parsedPayload, options);
|
|
2818
|
+
this.transcriptManager.ensureBranchForEventAppend(
|
|
2819
|
+
this.eventRecorder.latestForBranch(this.transcriptManager.activeEventCursor.branchId)
|
|
2820
|
+
);
|
|
2821
|
+
const branchId = this.transcriptManager.activeTranscriptCursor.branchId;
|
|
2822
|
+
const event = this.eventRecorder.record({
|
|
2823
|
+
eventClass,
|
|
2824
|
+
branchId,
|
|
2825
|
+
type,
|
|
2826
|
+
source: options?.source ?? { kind: "custom" },
|
|
2827
|
+
payload: parsedPayload,
|
|
2828
|
+
runId: this.runId,
|
|
2829
|
+
turnId: this.currentTurnId,
|
|
2830
|
+
modeId: this.currentMode,
|
|
2831
|
+
correlationId: options?.correlationId,
|
|
2832
|
+
causationId: options?.causationId,
|
|
2833
|
+
metadata: options?.metadata
|
|
2834
|
+
});
|
|
2835
|
+
const record = event.record;
|
|
2836
|
+
this.transcriptManager.advanceEventCursor(record);
|
|
2837
|
+
this.metrics.eventCount = this.eventRecorder.count;
|
|
2838
|
+
await this.ensureStoreInitialized();
|
|
2839
|
+
await this.storageCoordinator.recordEvent(record);
|
|
2840
|
+
await this.persistCursors();
|
|
2841
|
+
this.activateDynamicContextFor(eventClass, record);
|
|
2842
|
+
if (options?.hiddenTranscript !== false) await this.addHiddenEventMessage(record);
|
|
2843
|
+
await this.eventRecorder.notify(record);
|
|
2844
|
+
if (!options?.skipHooks) await this.dispatchHooks(event);
|
|
2845
|
+
return event;
|
|
2846
|
+
}
|
|
2847
|
+
queryEvents(filter) {
|
|
2848
|
+
return this.eventRecorder.query(filter, this.transcriptManager.activeEventSegments());
|
|
2849
|
+
}
|
|
2850
|
+
async dispatchHooks(event) {
|
|
2851
|
+
if (this.hookDepth > 25) throw new Error("Hook dispatch depth exceeded.");
|
|
2852
|
+
const hooks = (this.agent.hooks ?? []).filter((hook) => {
|
|
2853
|
+
const eventClass = hookEventClass(hook);
|
|
2854
|
+
return eventClass ? eventType(eventClass) === event.type : false;
|
|
2855
|
+
});
|
|
2856
|
+
if (!hooks.length) return;
|
|
2857
|
+
await this.ensureSandboxOpen();
|
|
2858
|
+
this.hookDepth++;
|
|
2859
|
+
try {
|
|
2860
|
+
for (const hook of hooks) {
|
|
2861
|
+
await hook.onActive(
|
|
2862
|
+
this.buildActionSession(
|
|
2863
|
+
void 0,
|
|
2864
|
+
{ kind: "hook", id: getConstructType(hook), name: getConstructLabel(hook) },
|
|
2865
|
+
event.record.correlationId,
|
|
2866
|
+
event.id
|
|
2867
|
+
),
|
|
2868
|
+
event
|
|
2869
|
+
);
|
|
2870
|
+
}
|
|
2871
|
+
} finally {
|
|
2872
|
+
this.hookDepth--;
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
|
|
2877
|
+
// src/session/types.ts
|
|
2878
|
+
var HarnessSessionPhase = /* @__PURE__ */ ((HarnessSessionPhase2) => {
|
|
2879
|
+
HarnessSessionPhase2["Idle"] = "idle";
|
|
2880
|
+
HarnessSessionPhase2["Queued"] = "queued";
|
|
2881
|
+
HarnessSessionPhase2["Starting"] = "starting";
|
|
2882
|
+
HarnessSessionPhase2["BuildingContext"] = "building_context";
|
|
2883
|
+
HarnessSessionPhase2["WaitingModel"] = "waiting_model";
|
|
2884
|
+
HarnessSessionPhase2["RunningTool"] = "running_tool";
|
|
2885
|
+
HarnessSessionPhase2["WaitingApproval"] = "waiting_approval";
|
|
2886
|
+
HarnessSessionPhase2["ClosingTurn"] = "closing_turn";
|
|
2887
|
+
HarnessSessionPhase2["Completed"] = "completed";
|
|
2888
|
+
HarnessSessionPhase2["Error"] = "error";
|
|
2889
|
+
HarnessSessionPhase2["Closed"] = "closed";
|
|
2890
|
+
return HarnessSessionPhase2;
|
|
2891
|
+
})(HarnessSessionPhase || {});
|
|
2892
|
+
|
|
2893
|
+
// src/session/approvals.ts
|
|
2894
|
+
function nowIso5() {
|
|
2895
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
2896
|
+
}
|
|
2897
|
+
function createToolApprovalHandle(input) {
|
|
2898
|
+
const createdAt = nowIso5();
|
|
2899
|
+
const expiresAt = input.timeoutMs > 0 ? new Date(Date.now() + input.timeoutMs).toISOString() : void 0;
|
|
2900
|
+
const id = randomId();
|
|
2901
|
+
return {
|
|
2902
|
+
id,
|
|
2903
|
+
sessionId: input.sessionId,
|
|
2904
|
+
runId: input.runId,
|
|
2905
|
+
toolCallId: input.request.id,
|
|
2906
|
+
name: input.request.name,
|
|
2907
|
+
args: input.request.args,
|
|
2908
|
+
modeId: input.request.modeId,
|
|
2909
|
+
risk: input.request.risk,
|
|
2910
|
+
permissions: input.request.permissions,
|
|
2911
|
+
createdAt,
|
|
2912
|
+
expiresAt,
|
|
2913
|
+
approve: () => input.approve(id),
|
|
2914
|
+
deny: (reason) => input.deny(id, reason)
|
|
2915
|
+
};
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
// src/session/approval-controller.ts
|
|
2919
|
+
var ApprovalController = class {
|
|
2920
|
+
constructor(input) {
|
|
2921
|
+
this.input = input;
|
|
2922
|
+
}
|
|
2923
|
+
input;
|
|
2924
|
+
pendingApprovals = /* @__PURE__ */ new Map();
|
|
2925
|
+
approvalIdsByToolCallId = /* @__PURE__ */ new Map();
|
|
2926
|
+
get count() {
|
|
2927
|
+
return this.pendingApprovals.size;
|
|
2928
|
+
}
|
|
2929
|
+
getPending() {
|
|
2930
|
+
return [...this.pendingApprovals.values()].map((approval) => approval.handle);
|
|
2931
|
+
}
|
|
2932
|
+
hydrateApprovalId(toolCallId) {
|
|
2933
|
+
const approvalId = this.approvalIdsByToolCallId.get(toolCallId) ?? toolCallId;
|
|
2934
|
+
this.approvalIdsByToolCallId.delete(toolCallId);
|
|
2935
|
+
return approvalId;
|
|
2936
|
+
}
|
|
2937
|
+
request(request) {
|
|
2938
|
+
return new Promise((resolve) => {
|
|
2939
|
+
const handle = createToolApprovalHandle({
|
|
2940
|
+
sessionId: this.input.sessionId,
|
|
2941
|
+
runId: this.input.getRunId(),
|
|
2942
|
+
request,
|
|
2943
|
+
timeoutMs: this.input.timeoutMs,
|
|
2944
|
+
approve: async (id) => this.resolve(id, true),
|
|
2945
|
+
deny: async (id, reason) => this.resolve(id, false, reason)
|
|
2946
|
+
});
|
|
2947
|
+
const timeout = setTimeout(() => {
|
|
2948
|
+
this.resolve(handle.id, false);
|
|
2949
|
+
}, this.input.timeoutMs);
|
|
2950
|
+
this.pendingApprovals.set(handle.id, {
|
|
2951
|
+
handle,
|
|
2952
|
+
timeout,
|
|
2953
|
+
resolve: (approved) => resolve(approved ? "approved" : "denied")
|
|
2954
|
+
});
|
|
2955
|
+
this.approvalIdsByToolCallId.set(handle.toolCallId, handle.id);
|
|
2956
|
+
this.input.notifyRequested(handle);
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
resolve(approvalId, approved, _reason) {
|
|
2960
|
+
const pending = this.pendingApprovals.get(approvalId);
|
|
2961
|
+
if (!pending) throw new Error(`Tool approval '${approvalId}' was not found.`);
|
|
2962
|
+
if (pending.timeout) clearTimeout(pending.timeout);
|
|
2963
|
+
this.pendingApprovals.delete(approvalId);
|
|
2964
|
+
pending.resolve(approved);
|
|
2965
|
+
}
|
|
2966
|
+
denyAll() {
|
|
2967
|
+
for (const approval of [...this.pendingApprovals.values()]) {
|
|
2968
|
+
this.resolve(approval.handle.id, false);
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2972
|
+
|
|
2973
|
+
// src/session/engine.ts
|
|
2974
|
+
async function resolveAgent(input) {
|
|
2975
|
+
return input.definition;
|
|
2976
|
+
}
|
|
2977
|
+
function resolveWorkDir(workDir) {
|
|
2978
|
+
return workDir ?? ".";
|
|
2979
|
+
}
|
|
2980
|
+
function resolveStorage(storage) {
|
|
2981
|
+
return storage ?? new NoopRunStorage();
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// src/session/event-hub.ts
|
|
2985
|
+
function payloadObject(record) {
|
|
2986
|
+
return record.payload && typeof record.payload === "object" ? record.payload : {};
|
|
2987
|
+
}
|
|
2988
|
+
var SessionEventHub = class {
|
|
2989
|
+
constructor(options) {
|
|
2990
|
+
this.options = options;
|
|
2991
|
+
}
|
|
2992
|
+
options;
|
|
2993
|
+
listeners = /* @__PURE__ */ new Set();
|
|
2994
|
+
on(listener) {
|
|
2995
|
+
this.listeners.add(listener);
|
|
2996
|
+
return () => this.listeners.delete(listener);
|
|
2997
|
+
}
|
|
2998
|
+
onEvent(eventClass, listener) {
|
|
2999
|
+
const targetType = eventType(eventClass);
|
|
3000
|
+
return this.on((event) => {
|
|
3001
|
+
if (event.type !== "event" || event.event.type !== targetType) return;
|
|
3002
|
+
void listener(new eventClass(event.event));
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
waitForEvent(eventClass, options = {}) {
|
|
3006
|
+
return new Promise((resolve, reject) => {
|
|
3007
|
+
let timeout;
|
|
3008
|
+
const cleanup = this.onEvent(eventClass, (event) => {
|
|
3009
|
+
if (timeout) clearTimeout(timeout);
|
|
3010
|
+
cleanup();
|
|
3011
|
+
resolve(event);
|
|
3012
|
+
});
|
|
3013
|
+
if (options.signal) {
|
|
3014
|
+
if (options.signal.aborted) {
|
|
3015
|
+
cleanup();
|
|
3016
|
+
reject(options.signal.reason ?? new Error("waitForEvent aborted."));
|
|
3017
|
+
return;
|
|
3018
|
+
}
|
|
3019
|
+
options.signal.addEventListener("abort", () => {
|
|
3020
|
+
if (timeout) clearTimeout(timeout);
|
|
3021
|
+
cleanup();
|
|
3022
|
+
reject(options.signal?.reason ?? new Error("waitForEvent aborted."));
|
|
3023
|
+
}, { once: true });
|
|
3024
|
+
}
|
|
3025
|
+
if (options.timeoutMs !== void 0) {
|
|
3026
|
+
timeout = setTimeout(() => {
|
|
3027
|
+
cleanup();
|
|
3028
|
+
reject(new Error(`Timed out waiting for event '${eventType(eventClass)}'.`));
|
|
3029
|
+
}, options.timeoutMs);
|
|
3030
|
+
}
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
notify(event) {
|
|
3034
|
+
for (const listener of this.listeners) void listener(event);
|
|
3035
|
+
}
|
|
3036
|
+
notifyRunnerRecord(record) {
|
|
3037
|
+
for (const event of this.hydrateRuntimeRecord(record)) this.notify(event);
|
|
3038
|
+
}
|
|
3039
|
+
hydrateRuntimeRecord(record) {
|
|
3040
|
+
const events = [];
|
|
3041
|
+
const payload2 = payloadObject(record);
|
|
3042
|
+
if (record.type === RunStartEvent.type) {
|
|
3043
|
+
events.push({
|
|
3044
|
+
type: "run.started",
|
|
3045
|
+
sessionId: this.options.sessionId,
|
|
3046
|
+
runId: record.runId,
|
|
3047
|
+
mode: String(payload2.modeId ?? record.modeId ?? "")
|
|
3048
|
+
});
|
|
3049
|
+
} else if (record.type === MessageDeltaEvent.type) {
|
|
3050
|
+
events.push({
|
|
3051
|
+
type: "assistant.delta",
|
|
3052
|
+
text: String(payload2.text ?? ""),
|
|
3053
|
+
event: record
|
|
3054
|
+
});
|
|
3055
|
+
} else if (record.type === MessageEndEvent.type) {
|
|
3056
|
+
const message = payload2.message;
|
|
3057
|
+
if (message?.role === "user") events.push({ type: "user.message", message });
|
|
3058
|
+
if (message?.role === "assistant") events.push({ type: "assistant.message", message });
|
|
3059
|
+
} else if (record.type === ToolStartEvent.type) {
|
|
3060
|
+
events.push({
|
|
3061
|
+
type: "tool.started",
|
|
3062
|
+
toolCallId: String(payload2.id ?? ""),
|
|
3063
|
+
name: String(payload2.name ?? ""),
|
|
3064
|
+
args: payload2.args
|
|
3065
|
+
});
|
|
3066
|
+
} else if (record.type === ToolApprovalResolvedEvent.type) {
|
|
3067
|
+
const approvalId = String(payload2.id ?? "");
|
|
3068
|
+
events.push({
|
|
3069
|
+
type: "tool.approval.resolved",
|
|
3070
|
+
approvalId: this.options.hydrateApprovalId?.(approvalId) ?? approvalId,
|
|
3071
|
+
approved: payload2.decision === "approved"
|
|
3072
|
+
});
|
|
3073
|
+
} else if (record.type === ToolEndEvent.type) {
|
|
3074
|
+
events.push({
|
|
3075
|
+
type: "tool.ended",
|
|
3076
|
+
toolCallId: String(payload2.id ?? ""),
|
|
3077
|
+
name: String(payload2.name ?? ""),
|
|
3078
|
+
result: payload2.result
|
|
3079
|
+
});
|
|
3080
|
+
} else if (record.type === ModeChangedEvent.type) {
|
|
3081
|
+
events.push({
|
|
3082
|
+
type: "mode.changed",
|
|
3083
|
+
previousMode: String(payload2.previousMode ?? ""),
|
|
3084
|
+
mode: String(payload2.mode ?? "")
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
events.push({ type: "event", event: record });
|
|
3088
|
+
return events;
|
|
3089
|
+
}
|
|
3090
|
+
clear() {
|
|
3091
|
+
this.listeners.clear();
|
|
3092
|
+
}
|
|
3093
|
+
};
|
|
3094
|
+
|
|
3095
|
+
// src/logging/logger.ts
|
|
3096
|
+
function normalizeModelDeltas(value) {
|
|
3097
|
+
if (value === true) return "full";
|
|
3098
|
+
if (value === false || value === void 0) return "none";
|
|
3099
|
+
return value;
|
|
3100
|
+
}
|
|
3101
|
+
function configuredSinks(config) {
|
|
3102
|
+
if (config.sinks?.length) return config.sinks;
|
|
3103
|
+
if (config.level && config.level !== "silent") {
|
|
3104
|
+
return [new ConsoleLogSink({ format: config.format ?? "pretty" })];
|
|
3105
|
+
}
|
|
3106
|
+
return [];
|
|
3107
|
+
}
|
|
3108
|
+
function messageFromUnknown(errorOrMessage) {
|
|
3109
|
+
if (errorOrMessage instanceof Error) return errorOrMessage.message;
|
|
3110
|
+
return String(errorOrMessage);
|
|
3111
|
+
}
|
|
3112
|
+
function payload(message, fields, error) {
|
|
3113
|
+
return {
|
|
3114
|
+
...fields ?? {},
|
|
3115
|
+
message,
|
|
3116
|
+
...error === void 0 ? {} : { error }
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
function createHarnessLogger(config = {}) {
|
|
3120
|
+
const level = config.level ?? "silent";
|
|
3121
|
+
const sinks = configuredSinks(config);
|
|
3122
|
+
const modelDeltas = normalizeModelDeltas(config.modelDeltas);
|
|
3123
|
+
const emit = (logClass, fields, context = {}) => {
|
|
3124
|
+
const record = normalizeHarnessLog(logClass, fields, context, config.redact);
|
|
3125
|
+
if (!shouldWriteLog(record.level, level)) return;
|
|
3126
|
+
for (const sink of sinks) {
|
|
3127
|
+
try {
|
|
3128
|
+
void Promise.resolve(sink.write(record)).catch(() => void 0);
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
};
|
|
3133
|
+
const logger = {
|
|
3134
|
+
modelDeltas,
|
|
3135
|
+
emit,
|
|
3136
|
+
debug(message, fields, context) {
|
|
3137
|
+
emit(AgentDebugLog, payload(message, fields), context);
|
|
3138
|
+
},
|
|
3139
|
+
info(message, fields, context) {
|
|
3140
|
+
emit(AgentInfoLog, payload(message, fields), context);
|
|
3141
|
+
},
|
|
3142
|
+
warn(message, fields, context) {
|
|
3143
|
+
emit(AgentWarnLog, payload(message, fields), context);
|
|
3144
|
+
},
|
|
3145
|
+
error(errorOrMessage, fields, context) {
|
|
3146
|
+
emit(AgentErrorLog, payload(messageFromUnknown(errorOrMessage), fields, errorOrMessage), context);
|
|
3147
|
+
},
|
|
3148
|
+
agent(context) {
|
|
3149
|
+
return {
|
|
3150
|
+
debug: (message, fields) => logger.debug(message, fields, context),
|
|
3151
|
+
info: (message, fields) => logger.info(message, fields, context),
|
|
3152
|
+
warn: (message, fields) => logger.warn(message, fields, context),
|
|
3153
|
+
error: (errorOrMessage, fields) => logger.error(errorOrMessage, fields, context),
|
|
3154
|
+
emit: (logClass, fields) => logger.emit(logClass, fields, context)
|
|
3155
|
+
};
|
|
3156
|
+
},
|
|
3157
|
+
async flush() {
|
|
3158
|
+
await Promise.all(sinks.map(async (sink) => {
|
|
3159
|
+
try {
|
|
3160
|
+
await sink.flush?.();
|
|
3161
|
+
} catch {
|
|
3162
|
+
}
|
|
3163
|
+
}));
|
|
3164
|
+
},
|
|
3165
|
+
async close() {
|
|
3166
|
+
await Promise.all(sinks.map(async (sink) => {
|
|
3167
|
+
try {
|
|
3168
|
+
await sink.close?.();
|
|
3169
|
+
} catch {
|
|
3170
|
+
}
|
|
3171
|
+
}));
|
|
3172
|
+
}
|
|
3173
|
+
};
|
|
3174
|
+
return logger;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
// src/session/queue.ts
|
|
3178
|
+
var SessionQueue = class {
|
|
3179
|
+
queue = Promise.resolve();
|
|
3180
|
+
pendingSendTriggers = [];
|
|
3181
|
+
enqueue(execute) {
|
|
3182
|
+
const previous = this.queue;
|
|
3183
|
+
const next = previous.then(execute, execute);
|
|
3184
|
+
this.queue = next.catch(() => void 0);
|
|
3185
|
+
return next;
|
|
3186
|
+
}
|
|
3187
|
+
addPendingSendTrigger(after) {
|
|
3188
|
+
this.pendingSendTriggers.push(eventType(after ?? ToolEndEvent));
|
|
3189
|
+
}
|
|
3190
|
+
applyPendingSendTrigger(record) {
|
|
3191
|
+
const targetType = this.pendingSendTriggers[0];
|
|
3192
|
+
if (!targetType) return "none";
|
|
3193
|
+
if (record.type === targetType) {
|
|
3194
|
+
this.pendingSendTriggers.shift();
|
|
3195
|
+
return record.type === RunEndEvent.type ? "cleared" : "handoff";
|
|
3196
|
+
}
|
|
3197
|
+
if (record.type === RunEndEvent.type) {
|
|
3198
|
+
this.pendingSendTriggers.shift();
|
|
3199
|
+
return "cleared";
|
|
3200
|
+
}
|
|
3201
|
+
return "none";
|
|
3202
|
+
}
|
|
3203
|
+
};
|
|
3204
|
+
|
|
3205
|
+
// src/session/stream.ts
|
|
3206
|
+
var AsyncEventQueue = class {
|
|
3207
|
+
values = [];
|
|
3208
|
+
waiters = [];
|
|
3209
|
+
closed = false;
|
|
3210
|
+
push(value) {
|
|
3211
|
+
if (this.closed) return;
|
|
3212
|
+
const waiter = this.waiters.shift();
|
|
3213
|
+
if (waiter) waiter({ value, done: false });
|
|
3214
|
+
else this.values.push(value);
|
|
3215
|
+
}
|
|
3216
|
+
close() {
|
|
3217
|
+
if (this.closed) return;
|
|
3218
|
+
this.closed = true;
|
|
3219
|
+
for (const waiter of this.waiters.splice(0)) waiter({ value: void 0, done: true });
|
|
3220
|
+
}
|
|
3221
|
+
[Symbol.asyncIterator]() {
|
|
3222
|
+
return {
|
|
3223
|
+
next: () => {
|
|
3224
|
+
const value = this.values.shift();
|
|
3225
|
+
if (value) return Promise.resolve({ value, done: false });
|
|
3226
|
+
if (this.closed) return Promise.resolve({ value: void 0, done: true });
|
|
3227
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
function createHarnessRunStream(start, cancel) {
|
|
3233
|
+
const queue = new AsyncEventQueue();
|
|
3234
|
+
const controller = {
|
|
3235
|
+
push: (event) => queue.push(event),
|
|
3236
|
+
close: () => queue.close()
|
|
3237
|
+
};
|
|
3238
|
+
const result = start(controller).finally(() => queue.close());
|
|
3239
|
+
return {
|
|
3240
|
+
id: randomId(),
|
|
3241
|
+
result,
|
|
3242
|
+
cancel,
|
|
3243
|
+
[Symbol.asyncIterator]: () => queue[Symbol.asyncIterator]()
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
// src/session/status.ts
|
|
3248
|
+
function payloadObject2(record) {
|
|
3249
|
+
return record.payload && typeof record.payload === "object" ? record.payload : {};
|
|
3250
|
+
}
|
|
3251
|
+
var SessionStatusTracker = class {
|
|
3252
|
+
runningValue = false;
|
|
3253
|
+
closedValue = false;
|
|
3254
|
+
phaseValue = "idle" /* Idle */;
|
|
3255
|
+
queuedInputCountValue = 0;
|
|
3256
|
+
currentTurnIdValue;
|
|
3257
|
+
activeToolValue;
|
|
3258
|
+
lastEventAtValue;
|
|
3259
|
+
lastErrorValue;
|
|
3260
|
+
get running() {
|
|
3261
|
+
return this.runningValue;
|
|
3262
|
+
}
|
|
3263
|
+
get queuedInputCount() {
|
|
3264
|
+
return this.queuedInputCountValue;
|
|
3265
|
+
}
|
|
3266
|
+
get activeTool() {
|
|
3267
|
+
return this.activeToolValue;
|
|
3268
|
+
}
|
|
3269
|
+
get phase() {
|
|
3270
|
+
return this.closedValue ? "closed" /* Closed */ : this.phaseValue;
|
|
3271
|
+
}
|
|
3272
|
+
enqueueSend() {
|
|
3273
|
+
this.queuedInputCountValue++;
|
|
3274
|
+
if (this.runningValue) this.phaseValue = "queued" /* Queued */;
|
|
3275
|
+
}
|
|
3276
|
+
beginRun() {
|
|
3277
|
+
this.runningValue = true;
|
|
3278
|
+
this.queuedInputCountValue = Math.max(0, this.queuedInputCountValue - 1);
|
|
3279
|
+
this.phaseValue = "starting" /* Starting */;
|
|
3280
|
+
}
|
|
3281
|
+
completeRun() {
|
|
3282
|
+
this.phaseValue = "completed" /* Completed */;
|
|
3283
|
+
}
|
|
3284
|
+
failRun(error) {
|
|
3285
|
+
this.lastErrorValue = error;
|
|
3286
|
+
this.phaseValue = "error" /* Error */;
|
|
3287
|
+
}
|
|
3288
|
+
finishRun() {
|
|
3289
|
+
this.runningValue = false;
|
|
3290
|
+
if (!this.closedValue && this.phaseValue !== "error" /* Error */) {
|
|
3291
|
+
this.phaseValue = this.queuedInputCountValue > 0 ? "queued" /* Queued */ : "idle" /* Idle */;
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
close() {
|
|
3295
|
+
this.closedValue = true;
|
|
3296
|
+
this.phaseValue = "closed" /* Closed */;
|
|
3297
|
+
}
|
|
3298
|
+
markClosingTurn() {
|
|
3299
|
+
this.phaseValue = "closing_turn" /* ClosingTurn */;
|
|
3300
|
+
}
|
|
3301
|
+
applyRunnerRecord(record) {
|
|
3302
|
+
this.lastEventAtValue = record.at;
|
|
3303
|
+
const payload2 = payloadObject2(record);
|
|
3304
|
+
if (record.type === RunStartEvent.type) {
|
|
3305
|
+
this.phaseValue = "starting" /* Starting */;
|
|
3306
|
+
this.currentTurnIdValue = void 0;
|
|
3307
|
+
this.activeToolValue = void 0;
|
|
3308
|
+
this.lastErrorValue = void 0;
|
|
3309
|
+
} else if (record.type === ContextReadyEvent.type) {
|
|
3310
|
+
this.phaseValue = "building_context" /* BuildingContext */;
|
|
3311
|
+
} else if (record.type === ModelBeforeEvent.type) {
|
|
3312
|
+
this.phaseValue = "waiting_model" /* WaitingModel */;
|
|
3313
|
+
} else if (record.type === ToolStartEvent.type) {
|
|
3314
|
+
this.phaseValue = "running_tool" /* RunningTool */;
|
|
3315
|
+
this.activeToolValue = {
|
|
3316
|
+
id: String(payload2.id ?? ""),
|
|
3317
|
+
name: String(payload2.name ?? "")
|
|
3318
|
+
};
|
|
3319
|
+
} else if (record.type === ToolApprovalRequestedEvent.type) {
|
|
3320
|
+
this.phaseValue = "waiting_approval" /* WaitingApproval */;
|
|
3321
|
+
} else if (record.type === ToolEndEvent.type || record.type === ToolApprovalResolvedEvent.type) {
|
|
3322
|
+
this.activeToolValue = void 0;
|
|
3323
|
+
this.phaseValue = "waiting_model" /* WaitingModel */;
|
|
3324
|
+
} else if (record.type === TurnEndEvent.type) {
|
|
3325
|
+
this.phaseValue = "closing_turn" /* ClosingTurn */;
|
|
3326
|
+
this.currentTurnIdValue = void 0;
|
|
3327
|
+
} else if (record.type === RunEndEvent.type) {
|
|
3328
|
+
this.phaseValue = "completed" /* Completed */;
|
|
3329
|
+
} else if (record.type === ErrorEvent.type) {
|
|
3330
|
+
this.phaseValue = "error" /* Error */;
|
|
3331
|
+
this.lastErrorValue = { message: String(payload2.message ?? "Unknown error") };
|
|
3332
|
+
}
|
|
3333
|
+
if (record.turnId) this.currentTurnIdValue = record.turnId;
|
|
3334
|
+
}
|
|
3335
|
+
snapshot() {
|
|
3336
|
+
return {
|
|
3337
|
+
running: this.runningValue,
|
|
3338
|
+
phase: this.phase,
|
|
3339
|
+
queuedInputCount: this.queuedInputCountValue,
|
|
3340
|
+
currentTurnId: this.currentTurnIdValue,
|
|
3341
|
+
activeTool: this.activeToolValue,
|
|
3342
|
+
lastEventAt: this.lastEventAtValue,
|
|
3343
|
+
lastError: this.lastErrorValue
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
|
|
3348
|
+
// src/session/session.ts
|
|
3349
|
+
var defaultApprovalTimeoutMs = 5 * 60 * 1e3;
|
|
3350
|
+
function nowIso6() {
|
|
3351
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
3352
|
+
}
|
|
3353
|
+
function normalizeInput(input) {
|
|
3354
|
+
return typeof input === "string" ? { content: input } : input;
|
|
3355
|
+
}
|
|
3356
|
+
function resolveInitialMode(agent, mode) {
|
|
3357
|
+
if (mode === void 0) return void 0;
|
|
3358
|
+
if (typeof mode !== "string") return mode;
|
|
3359
|
+
if (!Array.isArray(agent.modes)) return void 0;
|
|
3360
|
+
const resolved = agent.modes.find((candidate) => getConstructType(candidate) === mode);
|
|
3361
|
+
if (!resolved) throw new Error(`Unknown initial mode '${mode}'.`);
|
|
3362
|
+
return resolved;
|
|
3363
|
+
}
|
|
3364
|
+
function toErrorShape(error) {
|
|
3365
|
+
if (error instanceof Error) {
|
|
3366
|
+
return {
|
|
3367
|
+
name: error.name,
|
|
3368
|
+
message: error.message,
|
|
3369
|
+
stack: error.stack,
|
|
3370
|
+
cause: error.cause
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
return { message: String(error) };
|
|
3374
|
+
}
|
|
3375
|
+
var HarnessSessionImpl = class {
|
|
3376
|
+
constructor(config, input) {
|
|
3377
|
+
this.config = config;
|
|
3378
|
+
this.id = input.sessionId ?? randomId();
|
|
3379
|
+
this.events = new SessionEventHub({
|
|
3380
|
+
sessionId: this.id,
|
|
3381
|
+
hydrateApprovalId: (toolCallId) => this.approvals.hydrateApprovalId(toolCallId)
|
|
3382
|
+
});
|
|
3383
|
+
this.logger = createHarnessLogger(config.logging);
|
|
3384
|
+
this.approvals = new ApprovalController({
|
|
3385
|
+
sessionId: this.id,
|
|
3386
|
+
getRunId: () => this.runner.runId,
|
|
3387
|
+
timeoutMs: this.config.toolApprovalTimeoutMs ?? defaultApprovalTimeoutMs,
|
|
3388
|
+
notifyRequested: (approval) => this.notify({ type: "tool.approval.requested", approval })
|
|
3389
|
+
});
|
|
3390
|
+
const storage = resolveStorage(config.storage);
|
|
3391
|
+
this.runner = new AgentSessionRunner({
|
|
3392
|
+
sessionId: this.id,
|
|
3393
|
+
agent: input.agent,
|
|
3394
|
+
providers: config.providers,
|
|
3395
|
+
defaultModel: config.defaultModel,
|
|
3396
|
+
sandbox: config.sandbox,
|
|
3397
|
+
workDir: input.workDir,
|
|
3398
|
+
storage,
|
|
3399
|
+
initialMode: resolveInitialMode(input.agent, config.initialMode),
|
|
3400
|
+
toolApproval: config.toolApproval,
|
|
3401
|
+
maxTurns: config.maxTurns,
|
|
3402
|
+
services: config.services,
|
|
3403
|
+
approveTool: (request) => this.requestToolApproval(request),
|
|
3404
|
+
logger: this.logger
|
|
3405
|
+
});
|
|
3406
|
+
this.logger.emit(SessionCreatedLog, { sessionId: this.id }, { sessionId: this.id, source: { kind: "runtime" } });
|
|
3407
|
+
this.unsubscribeRunner = this.runner.subscribe((record) => {
|
|
3408
|
+
this.status.applyRunnerRecord(record);
|
|
3409
|
+
this.applyPendingSendTrigger(record);
|
|
3410
|
+
this.events.notifyRunnerRecord(record);
|
|
3411
|
+
this.notifyStatus();
|
|
3412
|
+
});
|
|
3413
|
+
this.transcript = {
|
|
3414
|
+
get: (options) => this.runner.getTranscript(options),
|
|
3415
|
+
getCursor: () => this.runner.getTranscriptCursor(),
|
|
3416
|
+
seek: (target) => this.runner.seekTranscript(target),
|
|
3417
|
+
latest: () => this.runner.latestTranscript()
|
|
3418
|
+
};
|
|
3419
|
+
this.snapshots = {
|
|
3420
|
+
create: (input2) => this.runner.createSnapshot(input2, { hiddenTranscript: false }),
|
|
3421
|
+
list: () => this.runner.listSnapshots(),
|
|
3422
|
+
get: (id) => this.runner.getSnapshot(id),
|
|
3423
|
+
restore: (id) => {
|
|
3424
|
+
this.assertSnapshotRestoreAllowed();
|
|
3425
|
+
return this.runner.restoreSnapshot(id, { hiddenTranscript: false });
|
|
3426
|
+
},
|
|
3427
|
+
delete: (id) => this.runner.deleteSnapshot(id, { hiddenTranscript: false })
|
|
3428
|
+
};
|
|
3429
|
+
}
|
|
3430
|
+
config;
|
|
3431
|
+
id;
|
|
3432
|
+
transcript;
|
|
3433
|
+
snapshots;
|
|
3434
|
+
runner;
|
|
3435
|
+
events;
|
|
3436
|
+
approvals;
|
|
3437
|
+
queue = new SessionQueue();
|
|
3438
|
+
status = new SessionStatusTracker();
|
|
3439
|
+
createdAt = nowIso6();
|
|
3440
|
+
logger;
|
|
3441
|
+
lastActiveAt = this.createdAt;
|
|
3442
|
+
closed = false;
|
|
3443
|
+
activeAbort;
|
|
3444
|
+
lastResult;
|
|
3445
|
+
unsubscribeRunner;
|
|
3446
|
+
async send(input, options) {
|
|
3447
|
+
const stream = this.stream(input, options);
|
|
3448
|
+
for await (const _event of stream) {
|
|
3449
|
+
}
|
|
3450
|
+
return stream.result;
|
|
3451
|
+
}
|
|
3452
|
+
stream(input, options = {}) {
|
|
3453
|
+
const userInput = normalizeInput(input);
|
|
3454
|
+
const externalSignal = options.signal;
|
|
3455
|
+
const controller = new AbortController();
|
|
3456
|
+
if (externalSignal) {
|
|
3457
|
+
if (externalSignal.aborted) controller.abort(externalSignal.reason);
|
|
3458
|
+
else externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), { once: true });
|
|
3459
|
+
}
|
|
3460
|
+
this.status.enqueueSend();
|
|
3461
|
+
if (this.status.running) this.queue.addPendingSendTrigger(options.after);
|
|
3462
|
+
this.notifyStatus();
|
|
3463
|
+
const run = async (streamController) => {
|
|
3464
|
+
const execute = async () => {
|
|
3465
|
+
if (this.closed) throw new Error(`Harness session '${this.id}' is closed.`);
|
|
3466
|
+
const unsubscribe = this.on((event) => streamController.push(event));
|
|
3467
|
+
this.status.beginRun();
|
|
3468
|
+
this.activeAbort = controller;
|
|
3469
|
+
this.lastActiveAt = nowIso6();
|
|
3470
|
+
this.notifyStatus();
|
|
3471
|
+
try {
|
|
3472
|
+
const result = await this.runner.run(userInput.content, {
|
|
3473
|
+
signal: controller.signal,
|
|
3474
|
+
model: options.model,
|
|
3475
|
+
userInputId: userInput.id,
|
|
3476
|
+
userMetadata: userInput.metadata,
|
|
3477
|
+
userRole: userInput.role
|
|
3478
|
+
});
|
|
3479
|
+
const sendResult = {
|
|
3480
|
+
sessionId: this.id,
|
|
3481
|
+
runId: result.runId,
|
|
3482
|
+
agentKey: result.agentKey,
|
|
3483
|
+
answer: result.finalAnswer,
|
|
3484
|
+
mode: this.runner.mode,
|
|
3485
|
+
outputDir: result.outputDir,
|
|
3486
|
+
metrics: result.metrics,
|
|
3487
|
+
transcript: result.transcript,
|
|
3488
|
+
events: result.events
|
|
3489
|
+
};
|
|
3490
|
+
this.lastResult = sendResult;
|
|
3491
|
+
this.status.completeRun();
|
|
3492
|
+
this.notify({ type: "run.completed", result: sendResult });
|
|
3493
|
+
this.notifyStatus();
|
|
3494
|
+
await this.logger.flush();
|
|
3495
|
+
return sendResult;
|
|
3496
|
+
} catch (error) {
|
|
3497
|
+
const shaped = toErrorShape(error);
|
|
3498
|
+
this.status.failRun(shaped);
|
|
3499
|
+
this.notify({ type: "error", error: shaped });
|
|
3500
|
+
this.notifyStatus();
|
|
3501
|
+
await this.logger.flush();
|
|
3502
|
+
throw error;
|
|
3503
|
+
} finally {
|
|
3504
|
+
unsubscribe();
|
|
3505
|
+
this.status.finishRun();
|
|
3506
|
+
if (this.activeAbort === controller) this.activeAbort = void 0;
|
|
3507
|
+
this.lastActiveAt = nowIso6();
|
|
3508
|
+
this.notifyStatus();
|
|
3509
|
+
}
|
|
3510
|
+
};
|
|
3511
|
+
return this.queue.enqueue(execute);
|
|
3512
|
+
};
|
|
3513
|
+
return createHarnessRunStream(run, async (reason) => {
|
|
3514
|
+
controller.abort(reason);
|
|
3515
|
+
await this.cancelActiveRun(reason);
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3518
|
+
getStatus() {
|
|
3519
|
+
const runInfo = this.runner.getRunInfo();
|
|
3520
|
+
const status = this.status.snapshot();
|
|
3521
|
+
return {
|
|
3522
|
+
sessionId: this.id,
|
|
3523
|
+
agentKey: this.runner.agent.key,
|
|
3524
|
+
mode: this.runner.mode,
|
|
3525
|
+
model: this.runner.getModel(),
|
|
3526
|
+
provider: this.runner.getModelProviderInfo(),
|
|
3527
|
+
createdAt: this.createdAt,
|
|
3528
|
+
lastActiveAt: this.lastActiveAt,
|
|
3529
|
+
running: status.running,
|
|
3530
|
+
phase: status.phase,
|
|
3531
|
+
queuedInputCount: status.queuedInputCount,
|
|
3532
|
+
currentTurnId: status.currentTurnId,
|
|
3533
|
+
activeTool: status.activeTool,
|
|
3534
|
+
lastEventAt: status.lastEventAt,
|
|
3535
|
+
lastError: status.lastError,
|
|
3536
|
+
pendingApprovalCount: this.approvals.count,
|
|
3537
|
+
runId: runInfo.runId,
|
|
3538
|
+
outputDir: this.lastResult?.outputDir ?? runInfo.outputDir,
|
|
3539
|
+
metrics: this.lastResult?.metrics ?? this.runner.getMetrics()
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
getMode() {
|
|
3543
|
+
return this.runner.mode;
|
|
3544
|
+
}
|
|
3545
|
+
getModel() {
|
|
3546
|
+
return this.runner.getModel();
|
|
3547
|
+
}
|
|
3548
|
+
setModel(model) {
|
|
3549
|
+
this.runner.setModel(model);
|
|
3550
|
+
this.lastActiveAt = nowIso6();
|
|
3551
|
+
this.notifyStatus();
|
|
3552
|
+
}
|
|
3553
|
+
clearModelOverride() {
|
|
3554
|
+
this.runner.clearModelOverride();
|
|
3555
|
+
this.lastActiveAt = nowIso6();
|
|
3556
|
+
this.notifyStatus();
|
|
3557
|
+
}
|
|
3558
|
+
async switchMode(mode, input) {
|
|
3559
|
+
await this.runner.switchMode(this.resolveModeSelector(mode), input);
|
|
3560
|
+
this.lastActiveAt = nowIso6();
|
|
3561
|
+
}
|
|
3562
|
+
getState() {
|
|
3563
|
+
return this.runner.getState();
|
|
3564
|
+
}
|
|
3565
|
+
updateState(patch) {
|
|
3566
|
+
this.runner.updateState(patch);
|
|
3567
|
+
this.lastActiveAt = nowIso6();
|
|
3568
|
+
}
|
|
3569
|
+
replaceState(next) {
|
|
3570
|
+
this.runner.replaceState(next);
|
|
3571
|
+
this.lastActiveAt = nowIso6();
|
|
3572
|
+
}
|
|
3573
|
+
getEvents(filter) {
|
|
3574
|
+
return this.runner.getEvents(filter);
|
|
3575
|
+
}
|
|
3576
|
+
getContextSnapshot() {
|
|
3577
|
+
return this.runner.getContextSnapshot();
|
|
3578
|
+
}
|
|
3579
|
+
getAgentManifest() {
|
|
3580
|
+
return this.runner.getAgentManifest();
|
|
3581
|
+
}
|
|
3582
|
+
getPendingApprovals() {
|
|
3583
|
+
return this.approvals.getPending();
|
|
3584
|
+
}
|
|
3585
|
+
async approveTool(approvalId) {
|
|
3586
|
+
this.approvals.resolve(approvalId, true);
|
|
3587
|
+
}
|
|
3588
|
+
async denyTool(approvalId, _reason) {
|
|
3589
|
+
this.approvals.resolve(approvalId, false);
|
|
3590
|
+
}
|
|
3591
|
+
on(listener) {
|
|
3592
|
+
return this.events.on(listener);
|
|
3593
|
+
}
|
|
3594
|
+
onEvent(eventClass, listener) {
|
|
3595
|
+
return this.events.onEvent(eventClass, listener);
|
|
3596
|
+
}
|
|
3597
|
+
waitForEvent(eventClass, options = {}) {
|
|
3598
|
+
return this.events.waitForEvent(eventClass, options);
|
|
3599
|
+
}
|
|
3600
|
+
async close() {
|
|
3601
|
+
if (this.closed) return;
|
|
3602
|
+
this.closed = true;
|
|
3603
|
+
this.status.close();
|
|
3604
|
+
await this.cancelActiveRun("Session closed.");
|
|
3605
|
+
this.unsubscribeRunner();
|
|
3606
|
+
await this.runner.close();
|
|
3607
|
+
this.notifyStatus();
|
|
3608
|
+
this.events.clear();
|
|
3609
|
+
await this.logger.close();
|
|
3610
|
+
}
|
|
3611
|
+
notify(event) {
|
|
3612
|
+
this.events.notify(event);
|
|
3613
|
+
}
|
|
3614
|
+
notifyStatus() {
|
|
3615
|
+
this.notify({ type: "session.status", status: this.getStatus() });
|
|
3616
|
+
}
|
|
3617
|
+
assertSnapshotRestoreAllowed() {
|
|
3618
|
+
const reject = (reason) => {
|
|
3619
|
+
this.logger.emit(
|
|
3620
|
+
SnapshotRestoreRejectedLog,
|
|
3621
|
+
{ reason },
|
|
3622
|
+
{ sessionId: this.id, runId: this.runner.runId, source: { kind: "runtime" } }
|
|
3623
|
+
);
|
|
3624
|
+
throw new Error(reason);
|
|
3625
|
+
};
|
|
3626
|
+
if (this.closed) reject(`Harness session '${this.id}' is closed.`);
|
|
3627
|
+
if (this.status.running || this.status.queuedInputCount > 0 || this.approvals.count > 0 || this.status.activeTool) {
|
|
3628
|
+
reject("Snapshots can only be restored while the session is idle and has no pending approvals.");
|
|
3629
|
+
}
|
|
3630
|
+
if (this.status.phase !== "idle" /* Idle */) {
|
|
3631
|
+
reject("Snapshots can only be restored while the session is idle and has no pending approvals.");
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
applyPendingSendTrigger(record) {
|
|
3635
|
+
if (this.queue.applyPendingSendTrigger(record) === "handoff") {
|
|
3636
|
+
this.status.markClosingTurn();
|
|
3637
|
+
this.runner.requestTurnHandoff();
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
resolveModeSelector(mode) {
|
|
3641
|
+
if (typeof mode !== "string") return mode;
|
|
3642
|
+
const resolved = Object.values(this.runner.agent.modes).find((candidate) => getConstructType(candidate) === mode);
|
|
3643
|
+
if (!resolved) throw new Error(`Unknown mode '${mode}'.`);
|
|
3644
|
+
return resolved;
|
|
3645
|
+
}
|
|
3646
|
+
requestToolApproval(request) {
|
|
3647
|
+
return this.approvals.request(request);
|
|
3648
|
+
}
|
|
3649
|
+
async cancelActiveRun(reason) {
|
|
3650
|
+
this.activeAbort?.abort(reason);
|
|
3651
|
+
this.approvals.denyAll();
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
async function createHarnessSession(config, options = {}) {
|
|
3655
|
+
const workDir = resolveWorkDir(config.workDir);
|
|
3656
|
+
const agent = await resolveAgent(config.agent);
|
|
3657
|
+
return new HarnessSessionImpl(config, {
|
|
3658
|
+
sessionId: options.sessionId,
|
|
3659
|
+
agent,
|
|
3660
|
+
workDir
|
|
3661
|
+
});
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
// src/session/store.ts
|
|
3665
|
+
function mergeConfig(base, overrides) {
|
|
3666
|
+
return {
|
|
3667
|
+
...base,
|
|
3668
|
+
...overrides,
|
|
3669
|
+
agent: overrides?.agent ?? base.agent,
|
|
3670
|
+
providers: overrides?.providers ?? base.providers,
|
|
3671
|
+
services: overrides?.services ?? base.services,
|
|
3672
|
+
logging: overrides?.logging ?? base.logging
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
var HarnessSessionStoreImpl = class {
|
|
3676
|
+
constructor(config) {
|
|
3677
|
+
this.config = config;
|
|
3678
|
+
}
|
|
3679
|
+
config;
|
|
3680
|
+
sessions = /* @__PURE__ */ new Map();
|
|
3681
|
+
unsubscriptions = /* @__PURE__ */ new Map();
|
|
3682
|
+
listeners = /* @__PURE__ */ new Set();
|
|
3683
|
+
closed = false;
|
|
3684
|
+
async getOrCreate(sessionId, overrides) {
|
|
3685
|
+
this.cleanupExpired();
|
|
3686
|
+
if (this.closed) throw new Error("Harness session store is closed.");
|
|
3687
|
+
const id = sessionId?.trim() || randomId();
|
|
3688
|
+
const existing = this.sessions.get(id);
|
|
3689
|
+
if (existing) return existing;
|
|
3690
|
+
const session = await createHarnessSession(mergeConfig(this.config, overrides), { sessionId: id });
|
|
3691
|
+
this.sessions.set(id, session);
|
|
3692
|
+
this.unsubscriptions.set(id, session.on((event) => this.notify({ type: "session.event", sessionId: id, event })));
|
|
3693
|
+
this.notify({ type: "session.created", sessionId: id, status: session.getStatus() });
|
|
3694
|
+
return session;
|
|
3695
|
+
}
|
|
3696
|
+
get(sessionId) {
|
|
3697
|
+
this.cleanupExpired();
|
|
3698
|
+
return this.sessions.get(sessionId);
|
|
3699
|
+
}
|
|
3700
|
+
list() {
|
|
3701
|
+
this.cleanupExpired();
|
|
3702
|
+
return [...this.sessions.values()].map((session) => session.getStatus());
|
|
3703
|
+
}
|
|
3704
|
+
async delete(sessionId) {
|
|
3705
|
+
const session = this.sessions.get(sessionId);
|
|
3706
|
+
if (!session) return false;
|
|
3707
|
+
this.sessions.delete(sessionId);
|
|
3708
|
+
this.unsubscriptions.get(sessionId)?.();
|
|
3709
|
+
this.unsubscriptions.delete(sessionId);
|
|
3710
|
+
await session.close();
|
|
3711
|
+
this.notify({ type: "session.deleted", sessionId });
|
|
3712
|
+
return true;
|
|
3713
|
+
}
|
|
3714
|
+
async clear() {
|
|
3715
|
+
for (const sessionId of [...this.sessions.keys()]) await this.delete(sessionId);
|
|
3716
|
+
this.notify({ type: "session.cleared" });
|
|
3717
|
+
}
|
|
3718
|
+
async send(sessionId, input, options) {
|
|
3719
|
+
const session = await this.getOrCreate(sessionId);
|
|
3720
|
+
return session.send(input, options);
|
|
3721
|
+
}
|
|
3722
|
+
async stream(sessionId, input, options) {
|
|
3723
|
+
const session = await this.getOrCreate(sessionId);
|
|
3724
|
+
return session.stream(input, options);
|
|
3725
|
+
}
|
|
3726
|
+
getPendingApprovals(sessionId) {
|
|
3727
|
+
if (sessionId) return this.sessions.get(sessionId)?.getPendingApprovals() ?? [];
|
|
3728
|
+
return [...this.sessions.values()].flatMap((session) => session.getPendingApprovals());
|
|
3729
|
+
}
|
|
3730
|
+
async approveTool(sessionId, approvalId) {
|
|
3731
|
+
const session = this.sessions.get(sessionId);
|
|
3732
|
+
if (!session) throw new Error(`Harness session '${sessionId}' was not found.`);
|
|
3733
|
+
await session.approveTool(approvalId);
|
|
3734
|
+
}
|
|
3735
|
+
async denyTool(sessionId, approvalId, reason) {
|
|
3736
|
+
const session = this.sessions.get(sessionId);
|
|
3737
|
+
if (!session) throw new Error(`Harness session '${sessionId}' was not found.`);
|
|
3738
|
+
await session.denyTool(approvalId, reason);
|
|
3739
|
+
}
|
|
3740
|
+
getAgentManifest(sessionId) {
|
|
3741
|
+
return this.sessions.get(sessionId)?.getAgentManifest();
|
|
3742
|
+
}
|
|
3743
|
+
on(listener) {
|
|
3744
|
+
this.listeners.add(listener);
|
|
3745
|
+
return () => this.listeners.delete(listener);
|
|
3746
|
+
}
|
|
3747
|
+
async close() {
|
|
3748
|
+
if (this.closed) return;
|
|
3749
|
+
this.closed = true;
|
|
3750
|
+
await this.clear();
|
|
3751
|
+
this.listeners.clear();
|
|
3752
|
+
}
|
|
3753
|
+
notify(event) {
|
|
3754
|
+
for (const listener of this.listeners) void listener(event);
|
|
3755
|
+
}
|
|
3756
|
+
cleanupExpired() {
|
|
3757
|
+
const ttl = this.config.sessionTtlMs;
|
|
3758
|
+
if (!ttl || ttl <= 0) return;
|
|
3759
|
+
const cutoff = Date.now() - ttl;
|
|
3760
|
+
for (const session of [...this.sessions.values()]) {
|
|
3761
|
+
if (Date.parse(session.getStatus().lastActiveAt) < cutoff) void this.delete(session.id);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
async function createHarnessSessionStore(config) {
|
|
3766
|
+
return new HarnessSessionStoreImpl(config);
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
export {
|
|
3770
|
+
createToolErrorPayload,
|
|
3771
|
+
AgentSessionRunner,
|
|
3772
|
+
HarnessSessionPhase,
|
|
3773
|
+
HarnessSessionImpl,
|
|
3774
|
+
createHarnessSession,
|
|
3775
|
+
HarnessSessionStoreImpl,
|
|
3776
|
+
createHarnessSessionStore
|
|
3777
|
+
};
|
|
3778
|
+
//# sourceMappingURL=chunk-4WWSQAWA.js.map
|