@botbotgo/agent-harness 0.0.137 → 0.0.139
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/api.js +8 -0
- package/dist/contracts/runtime.d.ts +30 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +13 -1
- package/dist/persistence/file-store.js +56 -4
- package/dist/persistence/sqlite-store.d.ts +17 -1
- package/dist/persistence/sqlite-store.js +133 -16
- package/dist/persistence/types.d.ts +25 -1
- package/dist/runtime/harness/events/events.d.ts +1 -1
- package/dist/runtime/harness/events/events.js +5 -0
- package/dist/runtime/harness/events/listener-runtime.d.ts +1 -0
- package/dist/runtime/harness/events/streaming.d.ts +1 -0
- package/dist/runtime/harness/events/streaming.js +1 -1
- package/dist/runtime/harness/run/inspection.d.ts +11 -0
- package/dist/runtime/harness/run/inspection.js +97 -0
- package/dist/runtime/harness/run/start-run.d.ts +8 -0
- package/dist/runtime/harness/run/start-run.js +13 -0
- package/dist/runtime/harness/run/stream-run.d.ts +8 -1
- package/dist/runtime/harness/run/stream-run.js +20 -5
- package/dist/runtime/harness/run/thread-records.js +1 -0
- package/dist/runtime/harness.js +1 -0
- package/dist/runtime/maintenance/runtime-record-maintenance.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -135,6 +135,7 @@ Boundary documents live in:
|
|
|
135
135
|
- `docs/acp-support-plan.md`
|
|
136
136
|
- `docs/recovery-policy-matrix.md`
|
|
137
137
|
- `docs/operator-timeline.md`
|
|
138
|
+
- `docs/runtime-inspection-contract.md`
|
|
138
139
|
- `docs/queue-concurrency-inspection.md`
|
|
139
140
|
- `docs/tool-execution-policy.md`
|
|
140
141
|
- `docs/feature-checklist.md`
|
package/README.zh.md
CHANGED
|
@@ -135,6 +135,7 @@ AI 让 agent 逻辑、工具调用和工作流代码更容易生成,真正变
|
|
|
135
135
|
- `docs/acp-support-plan.md`
|
|
136
136
|
- `docs/recovery-policy-matrix.md`
|
|
137
137
|
- `docs/operator-timeline.md`
|
|
138
|
+
- `docs/runtime-inspection-contract.md`
|
|
138
139
|
- `docs/queue-concurrency-inspection.md`
|
|
139
140
|
- `docs/tool-execution-policy.md`
|
|
140
141
|
- `docs/feature-checklist.md`
|
package/dist/api.js
CHANGED
|
@@ -10,6 +10,7 @@ function toSessionSummary(summary) {
|
|
|
10
10
|
createdAt: summary.createdAt,
|
|
11
11
|
updatedAt: summary.updatedAt,
|
|
12
12
|
status: summary.status,
|
|
13
|
+
currentAgentId: summary.currentAgentId,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
function toRequestSummary(summary) {
|
|
@@ -24,12 +25,19 @@ function toRequestSummary(summary) {
|
|
|
24
25
|
state: summary.state,
|
|
25
26
|
checkpointRef: summary.checkpointRef,
|
|
26
27
|
resumable: summary.resumable,
|
|
28
|
+
startedAt: summary.startedAt,
|
|
29
|
+
endedAt: summary.endedAt,
|
|
30
|
+
lastActivityAt: summary.lastActivityAt,
|
|
31
|
+
currentAgentId: summary.currentAgentId,
|
|
32
|
+
delegationChain: summary.delegationChain,
|
|
33
|
+
runtimeSnapshot: summary.runtimeSnapshot,
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
36
|
function toSessionRecord(record) {
|
|
30
37
|
return {
|
|
31
38
|
sessionId: record.threadId,
|
|
32
39
|
entryAgentId: record.entryAgentId,
|
|
40
|
+
currentAgentId: record.currentAgentId,
|
|
33
41
|
currentState: record.currentState,
|
|
34
42
|
latestRequestId: record.latestRunId,
|
|
35
43
|
createdAt: record.createdAt,
|
|
@@ -12,6 +12,7 @@ export type ThreadSummary = {
|
|
|
12
12
|
createdAt: string;
|
|
13
13
|
updatedAt: string;
|
|
14
14
|
status: RunState;
|
|
15
|
+
currentAgentId?: string;
|
|
15
16
|
};
|
|
16
17
|
export type SessionSummary = Omit<ThreadSummary, "threadId" | "latestRunId"> & {
|
|
17
18
|
sessionId: string;
|
|
@@ -74,6 +75,28 @@ export type RuntimeToolExecutionToolPolicy = {
|
|
|
74
75
|
hasInputSchema: boolean;
|
|
75
76
|
requiresApproval: boolean;
|
|
76
77
|
};
|
|
78
|
+
export type RuntimeSnapshotModel = {
|
|
79
|
+
id: string;
|
|
80
|
+
provider: string;
|
|
81
|
+
model: string;
|
|
82
|
+
baseUrl?: string;
|
|
83
|
+
};
|
|
84
|
+
export type RuntimeSnapshotTool = {
|
|
85
|
+
name: string;
|
|
86
|
+
description: string;
|
|
87
|
+
};
|
|
88
|
+
export type RuntimeSnapshotSkill = {
|
|
89
|
+
name: string;
|
|
90
|
+
path: string;
|
|
91
|
+
description?: string;
|
|
92
|
+
};
|
|
93
|
+
export type RuntimeSnapshot = {
|
|
94
|
+
agentId: string;
|
|
95
|
+
model?: RuntimeSnapshotModel;
|
|
96
|
+
tools: RuntimeSnapshotTool[];
|
|
97
|
+
skills: RuntimeSnapshotSkill[];
|
|
98
|
+
memory: string[];
|
|
99
|
+
};
|
|
77
100
|
/**
|
|
78
101
|
* Operator-facing projection of tool execution policy already compiled into a binding.
|
|
79
102
|
* This summarizes existing timeout, retry, validation, and retry-safety hints without
|
|
@@ -228,6 +251,12 @@ export type ThreadRunRecord = {
|
|
|
228
251
|
state: RunState;
|
|
229
252
|
checkpointRef: string | null;
|
|
230
253
|
resumable: boolean;
|
|
254
|
+
startedAt: string;
|
|
255
|
+
endedAt?: string;
|
|
256
|
+
lastActivityAt: string;
|
|
257
|
+
currentAgentId?: string;
|
|
258
|
+
delegationChain: string[];
|
|
259
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
231
260
|
};
|
|
232
261
|
/**
|
|
233
262
|
* Persisted run summary projected from upstream execution state plus runtime lifecycle metadata.
|
|
@@ -246,6 +275,7 @@ export type RequestRecord = RequestSummary;
|
|
|
246
275
|
export type ThreadRecord = {
|
|
247
276
|
threadId: string;
|
|
248
277
|
entryAgentId: string;
|
|
278
|
+
currentAgentId?: string;
|
|
249
279
|
currentState: RunState;
|
|
250
280
|
latestRunId: string;
|
|
251
281
|
createdAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.138";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.138";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, RunSummary, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
|
|
2
|
-
import type { ApprovalFilter, PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, RunSummaryFilter, ThreadSummaryFilter, PersistenceThreadMeta as ThreadMeta } from "./types.js";
|
|
2
|
+
import type { ApprovalFilter, PersistedRunRequest, PersistedRunControlRecord, PersistedRunInspection, PersistedRunQueueRecord, PersistenceLifecycle as Lifecycle, RuntimePersistence, RecoveryIntent, PersistenceRunMeta as RunMeta, RunSummaryFilter, ThreadSummaryFilter, PersistenceThreadMeta as ThreadMeta } from "./types.js";
|
|
3
3
|
type RunIndexRecord = {
|
|
4
4
|
runId: string;
|
|
5
5
|
threadId: string;
|
|
@@ -32,6 +32,10 @@ export declare class FilePersistence implements RuntimePersistence {
|
|
|
32
32
|
executionMode: string;
|
|
33
33
|
adapterKind?: string;
|
|
34
34
|
createdAt: string;
|
|
35
|
+
startedAt?: string;
|
|
36
|
+
currentAgentId?: string;
|
|
37
|
+
delegationChain?: string[];
|
|
38
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
35
39
|
}): Promise<void>;
|
|
36
40
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
37
41
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -49,6 +53,14 @@ export declare class FilePersistence implements RuntimePersistence {
|
|
|
49
53
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
50
54
|
getRunMeta(threadId: string, runId: string): Promise<RunMeta>;
|
|
51
55
|
getRunLifecycle(threadId: string, runId: string): Promise<Lifecycle>;
|
|
56
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
57
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
58
|
+
endedAt?: string | null;
|
|
59
|
+
lastActivityAt?: string;
|
|
60
|
+
currentAgentId?: string | null;
|
|
61
|
+
delegationChain?: string[];
|
|
62
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"] | null;
|
|
63
|
+
}): Promise<void>;
|
|
52
64
|
deleteThread(threadId: string): Promise<boolean>;
|
|
53
65
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
54
66
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -84,9 +84,18 @@ export class FilePersistence {
|
|
|
84
84
|
resumable: false,
|
|
85
85
|
checkpointRef: null,
|
|
86
86
|
};
|
|
87
|
+
const inspection = {
|
|
88
|
+
startedAt: input.startedAt ?? input.createdAt,
|
|
89
|
+
endedAt: null,
|
|
90
|
+
lastActivityAt: input.createdAt,
|
|
91
|
+
currentAgentId: input.currentAgentId ?? input.agentId,
|
|
92
|
+
delegationChain: input.delegationChain ?? [input.currentAgentId ?? input.agentId],
|
|
93
|
+
runtimeSnapshot: input.runtimeSnapshot ?? null,
|
|
94
|
+
};
|
|
87
95
|
await Promise.all([
|
|
88
96
|
writeJson(path.join(runDir, "meta.json"), meta),
|
|
89
97
|
writeJson(path.join(runDir, "lifecycle.json"), lifecycle),
|
|
98
|
+
writeJson(path.join(runDir, "inspection.json"), inspection),
|
|
90
99
|
writeJson(path.join(runDir, "checkpoint-ref.json"), {
|
|
91
100
|
threadId: input.threadId,
|
|
92
101
|
runId: input.runId,
|
|
@@ -124,9 +133,11 @@ export class FilePersistence {
|
|
|
124
133
|
async setRunState(threadId, runId, state, checkpointRef) {
|
|
125
134
|
const lifecyclePath = path.join(this.runDir(threadId, runId), "lifecycle.json");
|
|
126
135
|
const runMetaPath = path.join(this.runDir(threadId, runId), "meta.json");
|
|
127
|
-
const
|
|
136
|
+
const inspectionPath = path.join(this.runDir(threadId, runId), "inspection.json");
|
|
137
|
+
const [lifecycle, runMeta, inspection] = await Promise.all([
|
|
128
138
|
readJson(lifecyclePath),
|
|
129
139
|
readJson(runMetaPath),
|
|
140
|
+
readJson(inspectionPath),
|
|
130
141
|
]);
|
|
131
142
|
const now = nowIso();
|
|
132
143
|
const next = {
|
|
@@ -143,6 +154,11 @@ export class FilePersistence {
|
|
|
143
154
|
...runMeta,
|
|
144
155
|
updatedAt: now,
|
|
145
156
|
}),
|
|
157
|
+
writeJson(inspectionPath, {
|
|
158
|
+
...inspection,
|
|
159
|
+
endedAt: state === "completed" || state === "failed" || state === "cancelled" ? now : inspection.endedAt,
|
|
160
|
+
lastActivityAt: now,
|
|
161
|
+
}),
|
|
146
162
|
]);
|
|
147
163
|
if (checkpointRef !== undefined) {
|
|
148
164
|
await writeJson(path.join(this.runDir(threadId, runId), "checkpoint-ref.json"), {
|
|
@@ -175,7 +191,15 @@ export class FilePersistence {
|
|
|
175
191
|
}
|
|
176
192
|
async appendEvent(event) {
|
|
177
193
|
const sequenceId = String(event.sequence).padStart(6, "0");
|
|
178
|
-
|
|
194
|
+
const inspectionPath = path.join(this.runDir(event.threadId, event.runId), "inspection.json");
|
|
195
|
+
const inspection = await readJson(inspectionPath);
|
|
196
|
+
await Promise.all([
|
|
197
|
+
writeJson(path.join(this.runDir(event.threadId, event.runId), "events", `${sequenceId}.json`), event),
|
|
198
|
+
writeJson(inspectionPath, {
|
|
199
|
+
...inspection,
|
|
200
|
+
lastActivityAt: event.timestamp,
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
179
203
|
}
|
|
180
204
|
async listSessions(filter = {}) {
|
|
181
205
|
const threadIndexDir = path.join(this.runRoot, "indexes", "threads");
|
|
@@ -185,7 +209,10 @@ export class FilePersistence {
|
|
|
185
209
|
const entries = await readdir(threadIndexDir);
|
|
186
210
|
const records = await Promise.all(entries.map(async (entry) => {
|
|
187
211
|
const index = await readJson(path.join(threadIndexDir, entry));
|
|
188
|
-
const meta = await
|
|
212
|
+
const [meta, runInspection] = await Promise.all([
|
|
213
|
+
readJson(path.join(this.threadDir(index.threadId), "meta.json")),
|
|
214
|
+
readJson(path.join(this.runDir(index.threadId, index.latestRunId), "inspection.json")).catch(() => null),
|
|
215
|
+
]);
|
|
189
216
|
return {
|
|
190
217
|
agentId: meta.entryAgentId,
|
|
191
218
|
threadId: index.threadId,
|
|
@@ -193,6 +220,7 @@ export class FilePersistence {
|
|
|
193
220
|
createdAt: meta.createdAt,
|
|
194
221
|
updatedAt: index.updatedAt,
|
|
195
222
|
status: index.status,
|
|
223
|
+
currentAgentId: runInspection?.currentAgentId ?? undefined,
|
|
196
224
|
};
|
|
197
225
|
}));
|
|
198
226
|
return records
|
|
@@ -209,9 +237,10 @@ export class FilePersistence {
|
|
|
209
237
|
}
|
|
210
238
|
async readRunSummary(threadId, runId) {
|
|
211
239
|
const runDir = this.runDir(threadId, runId);
|
|
212
|
-
const [meta, lifecycle] = await Promise.all([
|
|
240
|
+
const [meta, lifecycle, inspection] = await Promise.all([
|
|
213
241
|
readJson(path.join(runDir, "meta.json")),
|
|
214
242
|
readJson(path.join(runDir, "lifecycle.json")),
|
|
243
|
+
readJson(path.join(runDir, "inspection.json")),
|
|
215
244
|
]);
|
|
216
245
|
return {
|
|
217
246
|
runId: meta.runId,
|
|
@@ -224,6 +253,12 @@ export class FilePersistence {
|
|
|
224
253
|
state: lifecycle.state,
|
|
225
254
|
checkpointRef: lifecycle.checkpointRef,
|
|
226
255
|
resumable: lifecycle.resumable,
|
|
256
|
+
startedAt: inspection.startedAt,
|
|
257
|
+
...(inspection.endedAt ? { endedAt: inspection.endedAt } : {}),
|
|
258
|
+
lastActivityAt: inspection.lastActivityAt,
|
|
259
|
+
...(inspection.currentAgentId ? { currentAgentId: inspection.currentAgentId } : {}),
|
|
260
|
+
delegationChain: inspection.delegationChain,
|
|
261
|
+
...(inspection.runtimeSnapshot ? { runtimeSnapshot: inspection.runtimeSnapshot } : {}),
|
|
227
262
|
};
|
|
228
263
|
}
|
|
229
264
|
async listRuns(filter = {}) {
|
|
@@ -261,6 +296,7 @@ export class FilePersistence {
|
|
|
261
296
|
readJson(filePath),
|
|
262
297
|
readJson(path.join(this.threadDir(threadId), "meta.json")),
|
|
263
298
|
]);
|
|
299
|
+
const runInspection = await readJson(path.join(this.runDir(threadId, index.latestRunId), "inspection.json")).catch(() => null);
|
|
264
300
|
return {
|
|
265
301
|
agentId: meta.entryAgentId,
|
|
266
302
|
threadId,
|
|
@@ -268,6 +304,7 @@ export class FilePersistence {
|
|
|
268
304
|
createdAt: meta.createdAt,
|
|
269
305
|
updatedAt: index.updatedAt,
|
|
270
306
|
status: index.status,
|
|
307
|
+
currentAgentId: runInspection?.currentAgentId ?? undefined,
|
|
271
308
|
};
|
|
272
309
|
}
|
|
273
310
|
async getThreadMeta(threadId) {
|
|
@@ -335,6 +372,21 @@ export class FilePersistence {
|
|
|
335
372
|
async getRunLifecycle(threadId, runId) {
|
|
336
373
|
return readJson(path.join(this.runDir(threadId, runId), "lifecycle.json"));
|
|
337
374
|
}
|
|
375
|
+
async getRunInspection(threadId, runId) {
|
|
376
|
+
return readJson(path.join(this.runDir(threadId, runId), "inspection.json"));
|
|
377
|
+
}
|
|
378
|
+
async updateRunInspection(threadId, runId, patch) {
|
|
379
|
+
const inspectionPath = path.join(this.runDir(threadId, runId), "inspection.json");
|
|
380
|
+
const current = await readJson(inspectionPath);
|
|
381
|
+
await writeJson(inspectionPath, {
|
|
382
|
+
...current,
|
|
383
|
+
...(patch.endedAt !== undefined ? { endedAt: patch.endedAt } : {}),
|
|
384
|
+
...(patch.lastActivityAt ? { lastActivityAt: patch.lastActivityAt } : {}),
|
|
385
|
+
...(patch.currentAgentId !== undefined ? { currentAgentId: patch.currentAgentId } : {}),
|
|
386
|
+
...(patch.delegationChain ? { delegationChain: patch.delegationChain } : {}),
|
|
387
|
+
...(patch.runtimeSnapshot !== undefined ? { runtimeSnapshot: patch.runtimeSnapshot } : {}),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
338
390
|
async deleteThread(threadId) {
|
|
339
391
|
const threadDir = this.threadDir(threadId);
|
|
340
392
|
const threadIndexPath = this.threadIndexPath(threadId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
2
|
-
import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence, ApprovalFilter, RunSummaryFilter, ThreadSummaryFilter } from "./types.js";
|
|
2
|
+
import type { PersistedRunRequest, PersistedRunControlRecord, PersistedRunInspection, PersistedRunQueueRecord, PersistenceLifecycle, PersistenceRunMeta, PersistenceThreadMeta, RecoveryIntent, RuntimePersistence, ApprovalFilter, RunSummaryFilter, ThreadSummaryFilter } from "./types.js";
|
|
3
3
|
export declare function listProtectedCheckpointThreadIds(dbPath: string): Promise<Set<string>>;
|
|
4
4
|
export declare class SqlitePersistence implements RuntimePersistence {
|
|
5
5
|
private readonly runRoot;
|
|
@@ -43,6 +43,10 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
43
43
|
userMessage: TranscriptMessage;
|
|
44
44
|
runRequest: PersistedRunRequest;
|
|
45
45
|
createThread: boolean;
|
|
46
|
+
startedAt?: string;
|
|
47
|
+
currentAgentId?: string;
|
|
48
|
+
delegationChain?: string[];
|
|
49
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
46
50
|
}): Promise<void>;
|
|
47
51
|
createRun(input: {
|
|
48
52
|
threadId: string;
|
|
@@ -51,6 +55,10 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
51
55
|
executionMode: string;
|
|
52
56
|
adapterKind?: string;
|
|
53
57
|
createdAt: string;
|
|
58
|
+
startedAt?: string;
|
|
59
|
+
currentAgentId?: string;
|
|
60
|
+
delegationChain?: string[];
|
|
61
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"];
|
|
54
62
|
}): Promise<void>;
|
|
55
63
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
56
64
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -66,6 +74,14 @@ export declare class SqlitePersistence implements RuntimePersistence {
|
|
|
66
74
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
67
75
|
getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
|
|
68
76
|
getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
|
|
77
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
78
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
79
|
+
endedAt?: string | null;
|
|
80
|
+
lastActivityAt?: string;
|
|
81
|
+
currentAgentId?: string | null;
|
|
82
|
+
delegationChain?: string[];
|
|
83
|
+
runtimeSnapshot?: RunSummary["runtimeSnapshot"] | null;
|
|
84
|
+
}): Promise<void>;
|
|
69
85
|
deleteThread(threadId: string): Promise<boolean>;
|
|
70
86
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
71
87
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -4,7 +4,7 @@ import { createClient } from "@libsql/client";
|
|
|
4
4
|
import { fileExists, readJson, writeJson } from "../utils/fs.js";
|
|
5
5
|
import { SqliteRunContextStore } from "./sqlite-run-context-store.js";
|
|
6
6
|
import { SqliteRunQueueStore } from "./sqlite-run-queue-store.js";
|
|
7
|
-
const RUNTIME_SQLITE_SCHEMA_VERSION =
|
|
7
|
+
const RUNTIME_SQLITE_SCHEMA_VERSION = 3;
|
|
8
8
|
const RUNTIME_SQLITE_SCHEMA_FAMILY = "agent-harness-runtime";
|
|
9
9
|
function asRow(value) {
|
|
10
10
|
return value;
|
|
@@ -143,10 +143,25 @@ export class SqlitePersistence {
|
|
|
143
143
|
checkpoint_ref TEXT,
|
|
144
144
|
FOREIGN KEY (thread_id) REFERENCES threads(thread_id)
|
|
145
145
|
)
|
|
146
|
+
`);
|
|
147
|
+
await this.rawExecute(`
|
|
148
|
+
CREATE TABLE IF NOT EXISTS run_inspection (
|
|
149
|
+
run_id TEXT PRIMARY KEY,
|
|
150
|
+
thread_id TEXT NOT NULL,
|
|
151
|
+
started_at TEXT NOT NULL,
|
|
152
|
+
ended_at TEXT,
|
|
153
|
+
last_activity_at TEXT NOT NULL,
|
|
154
|
+
current_agent_id TEXT,
|
|
155
|
+
delegation_chain_json TEXT NOT NULL,
|
|
156
|
+
runtime_snapshot_json TEXT,
|
|
157
|
+
FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
|
|
158
|
+
FOREIGN KEY (run_id) REFERENCES runs(run_id)
|
|
159
|
+
)
|
|
146
160
|
`);
|
|
147
161
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_thread_updated_idx ON runs(thread_id, updated_at DESC)");
|
|
148
162
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_state_updated_idx ON runs(state, updated_at DESC)");
|
|
149
163
|
await this.rawExecute("CREATE INDEX IF NOT EXISTS runs_agent_updated_idx ON runs(agent_id, updated_at DESC)");
|
|
164
|
+
await this.rawExecute("CREATE INDEX IF NOT EXISTS run_inspection_thread_activity_idx ON run_inspection(thread_id, last_activity_at DESC)");
|
|
150
165
|
await this.rawExecute(`
|
|
151
166
|
CREATE TABLE IF NOT EXISTS thread_messages (
|
|
152
167
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -323,9 +338,11 @@ export class SqlitePersistence {
|
|
|
323
338
|
createdAt: asString(row.created_at),
|
|
324
339
|
updatedAt: asString(row.updated_at),
|
|
325
340
|
status: asString(row.status),
|
|
341
|
+
currentAgentId: asNullableString(row.current_agent_id) ?? undefined,
|
|
326
342
|
};
|
|
327
343
|
}
|
|
328
344
|
mapRunSummary(row) {
|
|
345
|
+
const runtimeSnapshot = row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null;
|
|
329
346
|
return {
|
|
330
347
|
runId: asString(row.run_id),
|
|
331
348
|
threadId: asString(row.thread_id),
|
|
@@ -337,6 +354,12 @@ export class SqlitePersistence {
|
|
|
337
354
|
state: asString(row.state),
|
|
338
355
|
checkpointRef: asNullableString(row.checkpoint_ref),
|
|
339
356
|
resumable: asBoolean(row.resumable),
|
|
357
|
+
startedAt: asNullableString(row.started_at) ?? asString(row.created_at),
|
|
358
|
+
endedAt: asNullableString(row.ended_at) ?? undefined,
|
|
359
|
+
lastActivityAt: asNullableString(row.last_activity_at) ?? asString(row.updated_at),
|
|
360
|
+
currentAgentId: asNullableString(row.current_agent_id) ?? undefined,
|
|
361
|
+
delegationChain: row.delegation_chain_json ? parseJson(row.delegation_chain_json) : [asString(row.agent_id)],
|
|
362
|
+
...(runtimeSnapshot ? { runtimeSnapshot } : {}),
|
|
340
363
|
};
|
|
341
364
|
}
|
|
342
365
|
mapApproval(row) {
|
|
@@ -372,7 +395,27 @@ export class SqlitePersistence {
|
|
|
372
395
|
if (family !== RUNTIME_SQLITE_SCHEMA_FAMILY) {
|
|
373
396
|
throw new Error(`Unsupported runtime sqlite schema family ${JSON.stringify(family)} in ${this.dbPath}. Expected ${JSON.stringify(RUNTIME_SQLITE_SCHEMA_FAMILY)}.`);
|
|
374
397
|
}
|
|
375
|
-
if (version === "1") {
|
|
398
|
+
if (version === "1" || version === "2") {
|
|
399
|
+
await this.rawExecute(`
|
|
400
|
+
CREATE TABLE IF NOT EXISTS run_inspection (
|
|
401
|
+
run_id TEXT PRIMARY KEY,
|
|
402
|
+
thread_id TEXT NOT NULL,
|
|
403
|
+
started_at TEXT NOT NULL,
|
|
404
|
+
ended_at TEXT,
|
|
405
|
+
last_activity_at TEXT NOT NULL,
|
|
406
|
+
current_agent_id TEXT,
|
|
407
|
+
delegation_chain_json TEXT NOT NULL,
|
|
408
|
+
runtime_snapshot_json TEXT,
|
|
409
|
+
FOREIGN KEY (thread_id) REFERENCES threads(thread_id),
|
|
410
|
+
FOREIGN KEY (run_id) REFERENCES runs(run_id)
|
|
411
|
+
)
|
|
412
|
+
`);
|
|
413
|
+
await this.rawExecute(`
|
|
414
|
+
INSERT OR IGNORE INTO run_inspection
|
|
415
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
416
|
+
SELECT run_id, thread_id, created_at, NULL, updated_at, agent_id, json_array(agent_id), NULL
|
|
417
|
+
FROM runs
|
|
418
|
+
`);
|
|
376
419
|
await this.rawExecute("INSERT OR REPLACE INTO runtime_metadata (key, value) VALUES (?, ?)", ["schema_version", String(RUNTIME_SQLITE_SCHEMA_VERSION)]);
|
|
377
420
|
return;
|
|
378
421
|
}
|
|
@@ -422,6 +465,19 @@ export class SqlitePersistence {
|
|
|
422
465
|
(run_id, cancel_requested, cancel_reason, cancel_requested_at, heartbeat_at, worker_id, worker_started_at)
|
|
423
466
|
VALUES (?, 0, NULL, NULL, NULL, NULL, NULL)`,
|
|
424
467
|
args: [input.runId],
|
|
468
|
+
}, {
|
|
469
|
+
sql: `INSERT OR REPLACE INTO run_inspection
|
|
470
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
471
|
+
VALUES (?, ?, ?, NULL, ?, ?, ?, ?)`,
|
|
472
|
+
args: [
|
|
473
|
+
input.runId,
|
|
474
|
+
input.threadId,
|
|
475
|
+
input.startedAt ?? input.createdAt,
|
|
476
|
+
input.createdAt,
|
|
477
|
+
input.currentAgentId ?? input.agentId,
|
|
478
|
+
JSON.stringify(input.delegationChain ?? [input.currentAgentId ?? input.agentId]),
|
|
479
|
+
input.runtimeSnapshot ? JSON.stringify(input.runtimeSnapshot) : null,
|
|
480
|
+
],
|
|
425
481
|
}, {
|
|
426
482
|
sql: `INSERT INTO thread_messages
|
|
427
483
|
(thread_id, role, content_json, run_id, created_at)
|
|
@@ -463,6 +519,17 @@ export class SqlitePersistence {
|
|
|
463
519
|
await this.execute(`INSERT OR REPLACE INTO run_control
|
|
464
520
|
(run_id, cancel_requested, cancel_reason, cancel_requested_at, heartbeat_at, worker_id, worker_started_at)
|
|
465
521
|
VALUES (?, 0, NULL, NULL, NULL, NULL, NULL)`, [input.runId]);
|
|
522
|
+
await this.execute(`INSERT OR REPLACE INTO run_inspection
|
|
523
|
+
(run_id, thread_id, started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json)
|
|
524
|
+
VALUES (?, ?, ?, NULL, ?, ?, ?, ?)`, [
|
|
525
|
+
input.runId,
|
|
526
|
+
input.threadId,
|
|
527
|
+
input.startedAt ?? input.createdAt,
|
|
528
|
+
input.createdAt,
|
|
529
|
+
input.currentAgentId ?? input.agentId,
|
|
530
|
+
JSON.stringify(input.delegationChain ?? [input.currentAgentId ?? input.agentId]),
|
|
531
|
+
input.runtimeSnapshot ? JSON.stringify(input.runtimeSnapshot) : null,
|
|
532
|
+
]);
|
|
466
533
|
}
|
|
467
534
|
async setRunState(threadId, runId, state, checkpointRef) {
|
|
468
535
|
const current = await this.getRunLifecycle(threadId, runId);
|
|
@@ -475,42 +542,59 @@ export class SqlitePersistence {
|
|
|
475
542
|
await this.execute(`UPDATE threads
|
|
476
543
|
SET status = ?, latest_run_id = ?, updated_at = ?
|
|
477
544
|
WHERE thread_id = ?`, [state, runId, now, threadId]);
|
|
545
|
+
await this.execute(`UPDATE run_inspection
|
|
546
|
+
SET last_activity_at = ?, ended_at = CASE
|
|
547
|
+
WHEN ? IN ('completed', 'failed', 'cancelled') THEN ?
|
|
548
|
+
ELSE ended_at
|
|
549
|
+
END
|
|
550
|
+
WHERE run_id = ? AND thread_id = ?`, [now, state, now, runId, threadId]);
|
|
478
551
|
}
|
|
479
552
|
async appendEvent(event) {
|
|
480
553
|
await this.execute(`INSERT OR REPLACE INTO events
|
|
481
554
|
(thread_id, run_id, sequence, event_json, created_at)
|
|
482
555
|
VALUES (?, ?, ?, ?, ?)`, [event.threadId, event.runId, event.sequence, JSON.stringify(event), event.timestamp]);
|
|
556
|
+
await this.execute(`UPDATE run_inspection
|
|
557
|
+
SET last_activity_at = ?
|
|
558
|
+
WHERE run_id = ? AND thread_id = ?`, [event.timestamp, event.runId, event.threadId]);
|
|
483
559
|
}
|
|
484
560
|
async listSessions(filter = {}) {
|
|
485
561
|
const { clause, args } = buildWhereClause([
|
|
486
|
-
["entry_agent_id = ?", filter.agentId],
|
|
562
|
+
["threads.entry_agent_id = ?", filter.agentId],
|
|
487
563
|
]);
|
|
488
|
-
const rows = await this.selectAll(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
|
|
489
|
-
FROM threads
|
|
564
|
+
const rows = await this.selectAll(`SELECT threads.thread_id, threads.entry_agent_id, threads.latest_run_id, threads.created_at, threads.updated_at, threads.status, run_inspection.current_agent_id
|
|
565
|
+
FROM threads
|
|
566
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = threads.latest_run_id
|
|
567
|
+
${clause}
|
|
490
568
|
ORDER BY updated_at DESC`, args);
|
|
491
569
|
return rows.map((row) => this.mapThreadSummary(row));
|
|
492
570
|
}
|
|
493
571
|
async listRuns(filter = {}) {
|
|
494
572
|
const { clause, args } = buildWhereClause([
|
|
495
|
-
["agent_id = ?", filter.agentId],
|
|
496
|
-
["thread_id = ?", filter.threadId],
|
|
497
|
-
["state = ?", filter.state],
|
|
573
|
+
["runs.agent_id = ?", filter.agentId],
|
|
574
|
+
["runs.thread_id = ?", filter.threadId],
|
|
575
|
+
["runs.state = ?", filter.state],
|
|
498
576
|
]);
|
|
499
|
-
const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
500
|
-
|
|
577
|
+
const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
578
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
579
|
+
FROM runs
|
|
580
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
581
|
+
${clause}
|
|
501
582
|
ORDER BY updated_at DESC`, args);
|
|
502
583
|
return rows.map((row) => this.mapRunSummary(row));
|
|
503
584
|
}
|
|
504
585
|
async getRun(runId) {
|
|
505
|
-
const row = await this.selectOne(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
586
|
+
const row = await this.selectOne(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
587
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
506
588
|
FROM runs
|
|
507
|
-
|
|
589
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
590
|
+
WHERE runs.run_id = ?`, [runId]);
|
|
508
591
|
return row ? this.mapRunSummary(row) : null;
|
|
509
592
|
}
|
|
510
593
|
async getSession(threadId) {
|
|
511
|
-
const row = await this.selectOne(`SELECT thread_id, entry_agent_id, latest_run_id, created_at, updated_at, status
|
|
594
|
+
const row = await this.selectOne(`SELECT threads.thread_id, threads.entry_agent_id, threads.latest_run_id, threads.created_at, threads.updated_at, threads.status, run_inspection.current_agent_id
|
|
512
595
|
FROM threads
|
|
513
|
-
|
|
596
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = threads.latest_run_id
|
|
597
|
+
WHERE threads.thread_id = ?`, [threadId]);
|
|
514
598
|
return row ? this.mapThreadSummary(row) : null;
|
|
515
599
|
}
|
|
516
600
|
async getThreadMeta(threadId) {
|
|
@@ -529,9 +613,11 @@ export class SqlitePersistence {
|
|
|
529
613
|
};
|
|
530
614
|
}
|
|
531
615
|
async listThreadRuns(threadId) {
|
|
532
|
-
const rows = await this.selectAll(`SELECT run_id, thread_id, agent_id, execution_mode, adapter_kind, created_at, updated_at, state, checkpoint_ref, resumable
|
|
616
|
+
const rows = await this.selectAll(`SELECT runs.run_id, runs.thread_id, runs.agent_id, runs.execution_mode, runs.adapter_kind, runs.created_at, runs.updated_at, runs.state, runs.checkpoint_ref, runs.resumable,
|
|
617
|
+
run_inspection.started_at, run_inspection.ended_at, run_inspection.last_activity_at, run_inspection.current_agent_id, run_inspection.delegation_chain_json, run_inspection.runtime_snapshot_json
|
|
533
618
|
FROM runs
|
|
534
|
-
|
|
619
|
+
LEFT JOIN run_inspection ON run_inspection.run_id = runs.run_id
|
|
620
|
+
WHERE runs.thread_id = ?
|
|
535
621
|
ORDER BY created_at DESC`, [threadId]);
|
|
536
622
|
return rows.map((row) => this.mapRunSummary(row));
|
|
537
623
|
}
|
|
@@ -592,6 +678,36 @@ export class SqlitePersistence {
|
|
|
592
678
|
checkpointRef: asNullableString(row.checkpoint_ref),
|
|
593
679
|
};
|
|
594
680
|
}
|
|
681
|
+
async getRunInspection(threadId, runId) {
|
|
682
|
+
const row = await this.selectOne(`SELECT started_at, ended_at, last_activity_at, current_agent_id, delegation_chain_json, runtime_snapshot_json
|
|
683
|
+
FROM run_inspection
|
|
684
|
+
WHERE thread_id = ? AND run_id = ?`, [threadId, runId]);
|
|
685
|
+
if (!row) {
|
|
686
|
+
throw new Error(`Missing run inspection ${runId} for thread ${threadId}`);
|
|
687
|
+
}
|
|
688
|
+
return {
|
|
689
|
+
startedAt: asString(row.started_at),
|
|
690
|
+
endedAt: asNullableString(row.ended_at),
|
|
691
|
+
lastActivityAt: asString(row.last_activity_at),
|
|
692
|
+
currentAgentId: asNullableString(row.current_agent_id),
|
|
693
|
+
delegationChain: parseJson(row.delegation_chain_json),
|
|
694
|
+
runtimeSnapshot: row.runtime_snapshot_json ? parseJson(row.runtime_snapshot_json) : null,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
async updateRunInspection(threadId, runId, patch) {
|
|
698
|
+
const current = await this.getRunInspection(threadId, runId);
|
|
699
|
+
await this.execute(`UPDATE run_inspection
|
|
700
|
+
SET ended_at = ?, last_activity_at = ?, current_agent_id = ?, delegation_chain_json = ?, runtime_snapshot_json = ?
|
|
701
|
+
WHERE run_id = ? AND thread_id = ?`, [
|
|
702
|
+
patch.endedAt === undefined ? current.endedAt : patch.endedAt,
|
|
703
|
+
patch.lastActivityAt ?? current.lastActivityAt,
|
|
704
|
+
patch.currentAgentId === undefined ? current.currentAgentId : patch.currentAgentId,
|
|
705
|
+
JSON.stringify(patch.delegationChain ?? current.delegationChain),
|
|
706
|
+
JSON.stringify(patch.runtimeSnapshot === undefined ? current.runtimeSnapshot : patch.runtimeSnapshot),
|
|
707
|
+
runId,
|
|
708
|
+
threadId,
|
|
709
|
+
]);
|
|
710
|
+
}
|
|
595
711
|
async deleteThread(threadId) {
|
|
596
712
|
const exists = await this.getSession(threadId);
|
|
597
713
|
if (!exists) {
|
|
@@ -600,6 +716,7 @@ export class SqlitePersistence {
|
|
|
600
716
|
await this.execute("DELETE FROM artifacts WHERE thread_id = ?", [threadId]);
|
|
601
717
|
await this.execute("DELETE FROM approvals WHERE thread_id = ?", [threadId]);
|
|
602
718
|
await this.execute("DELETE FROM events WHERE thread_id = ?", [threadId]);
|
|
719
|
+
await this.execute("DELETE FROM run_inspection WHERE thread_id = ?", [threadId]);
|
|
603
720
|
await this.execute("DELETE FROM run_queue WHERE thread_id = ?", [threadId]);
|
|
604
721
|
await this.execute("DELETE FROM run_requests WHERE thread_id = ?", [threadId]);
|
|
605
722
|
await this.execute("DELETE FROM recovery_intents WHERE thread_id = ?", [threadId]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
1
|
+
import type { ArtifactListing, ArtifactRecord, HarnessEvent, InternalApprovalRecord, InvocationEnvelope, MessageContent, RuntimeSnapshot, RunState, RunSummary, ThreadRunRecord, ThreadSummary, TranscriptMessage } from "../contracts/types.js";
|
|
2
2
|
export type PersistenceThreadMeta = {
|
|
3
3
|
threadId: string;
|
|
4
4
|
workspaceId: string;
|
|
@@ -25,6 +25,14 @@ export type PersistenceLifecycle = {
|
|
|
25
25
|
resumable: boolean;
|
|
26
26
|
checkpointRef: string | null;
|
|
27
27
|
};
|
|
28
|
+
export type PersistedRunInspection = {
|
|
29
|
+
startedAt: string;
|
|
30
|
+
endedAt: string | null;
|
|
31
|
+
lastActivityAt: string;
|
|
32
|
+
currentAgentId: string | null;
|
|
33
|
+
delegationChain: string[];
|
|
34
|
+
runtimeSnapshot: RuntimeSnapshot | null;
|
|
35
|
+
};
|
|
28
36
|
export type PersistedRunRequest = {
|
|
29
37
|
input: MessageContent;
|
|
30
38
|
priority?: number;
|
|
@@ -86,6 +94,10 @@ export interface RuntimePersistence {
|
|
|
86
94
|
userMessage: TranscriptMessage;
|
|
87
95
|
runRequest: PersistedRunRequest;
|
|
88
96
|
createThread: boolean;
|
|
97
|
+
startedAt?: string;
|
|
98
|
+
currentAgentId?: string;
|
|
99
|
+
delegationChain?: string[];
|
|
100
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
89
101
|
}): Promise<void>;
|
|
90
102
|
createThread(input: {
|
|
91
103
|
threadId: string;
|
|
@@ -101,6 +113,10 @@ export interface RuntimePersistence {
|
|
|
101
113
|
executionMode: string;
|
|
102
114
|
adapterKind?: string;
|
|
103
115
|
createdAt: string;
|
|
116
|
+
startedAt?: string;
|
|
117
|
+
currentAgentId?: string;
|
|
118
|
+
delegationChain?: string[];
|
|
119
|
+
runtimeSnapshot?: RuntimeSnapshot;
|
|
104
120
|
}): Promise<void>;
|
|
105
121
|
setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
|
|
106
122
|
appendEvent(event: HarnessEvent): Promise<void>;
|
|
@@ -115,6 +131,14 @@ export interface RuntimePersistence {
|
|
|
115
131
|
getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
|
|
116
132
|
getRunMeta(threadId: string, runId: string): Promise<PersistenceRunMeta>;
|
|
117
133
|
getRunLifecycle(threadId: string, runId: string): Promise<PersistenceLifecycle>;
|
|
134
|
+
getRunInspection(threadId: string, runId: string): Promise<PersistedRunInspection>;
|
|
135
|
+
updateRunInspection(threadId: string, runId: string, patch: {
|
|
136
|
+
endedAt?: string | null;
|
|
137
|
+
lastActivityAt?: string;
|
|
138
|
+
currentAgentId?: string | null;
|
|
139
|
+
delegationChain?: string[];
|
|
140
|
+
runtimeSnapshot?: RuntimeSnapshot | null;
|
|
141
|
+
}): Promise<void>;
|
|
118
142
|
deleteThread(threadId: string): Promise<boolean>;
|
|
119
143
|
saveRunRequest(threadId: string, runId: string, request: PersistedRunRequest): Promise<void>;
|
|
120
144
|
getRunRequest(threadId: string, runId: string): Promise<PersistedRunRequest | null>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HarnessEvent, InternalApprovalRecord, MessageContent, RunResult } from "../../../contracts/types.js";
|
|
2
2
|
import type { RuntimePersistence } from "../../../persistence/types.js";
|
|
3
|
-
type EventPersistence = Pick<RuntimePersistence, "appendEvent" | "setRunState" | "createApproval" | "createArtifact">;
|
|
3
|
+
type EventPersistence = Pick<RuntimePersistence, "appendEvent" | "setRunState" | "createApproval" | "createArtifact" | "updateRunInspection">;
|
|
4
4
|
type EventRuntime = {
|
|
5
5
|
persistence: EventPersistence;
|
|
6
6
|
publishEvent: (event: HarnessEvent) => void;
|
|
@@ -18,6 +18,11 @@ export async function emitRunCreatedEvent(runtime, threadId, runId, payload) {
|
|
|
18
18
|
}
|
|
19
19
|
export async function setRunStateAndEmitEvent(runtime, threadId, runId, sequence, state, options) {
|
|
20
20
|
await runtime.persistence.setRunState(threadId, runId, state, options.checkpointRef ?? null);
|
|
21
|
+
const now = new Date(Date.now()).toISOString();
|
|
22
|
+
await runtime.persistence.updateRunInspection(threadId, runId, {
|
|
23
|
+
lastActivityAt: now,
|
|
24
|
+
...(state === "completed" || state === "failed" || state === "cancelled" ? { endedAt: now } : {}),
|
|
25
|
+
});
|
|
21
26
|
return emitHarnessEvent(runtime, threadId, runId, sequence, "run.state.changed", {
|
|
22
27
|
previousState: options.previousState,
|
|
23
28
|
state,
|
|
@@ -68,7 +68,7 @@ export async function dispatchRunListeners(stream, listeners, options) {
|
|
|
68
68
|
currentState: thread.currentState,
|
|
69
69
|
latestRunId: thread.latestRunId,
|
|
70
70
|
entryAgentId: thread.entryAgentId,
|
|
71
|
-
latestAgentId: thread.runs[0]?.agentId,
|
|
71
|
+
latestAgentId: thread.currentAgentId ?? thread.runs[0]?.agentId,
|
|
72
72
|
approvalId: thread.pendingDecision?.approvalId,
|
|
73
73
|
pendingActionId: thread.pendingDecision?.pendingActionId,
|
|
74
74
|
output,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CompiledAgentBinding, RuntimeSnapshot } from "../../../contracts/types.js";
|
|
2
|
+
export declare function buildRunRuntimeSnapshot(binding: CompiledAgentBinding): RuntimeSnapshot;
|
|
3
|
+
export declare function consumeRunInspectionUpstreamEvent(input: {
|
|
4
|
+
event: unknown;
|
|
5
|
+
currentAgentId: string;
|
|
6
|
+
delegationChain: string[];
|
|
7
|
+
binding: CompiledAgentBinding;
|
|
8
|
+
}): {
|
|
9
|
+
currentAgentId: string;
|
|
10
|
+
delegationChain: string[];
|
|
11
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readSkillMetadata } from "../../support/skill-metadata.js";
|
|
2
|
+
import { getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, } from "../../support/compiled-binding.js";
|
|
3
|
+
function asObject(value) {
|
|
4
|
+
return typeof value === "object" && value !== null ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function readStringArray(value) {
|
|
7
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
8
|
+
}
|
|
9
|
+
export function buildRunRuntimeSnapshot(binding) {
|
|
10
|
+
const model = getBindingPrimaryModel(binding);
|
|
11
|
+
const init = typeof model?.init === "object" && model.init ? model.init : {};
|
|
12
|
+
const baseUrl = typeof init.baseUrl === "string" && init.baseUrl.trim().length > 0
|
|
13
|
+
? init.baseUrl.trim()
|
|
14
|
+
: typeof init.baseURL === "string" && init.baseURL.trim().length > 0
|
|
15
|
+
? init.baseURL.trim()
|
|
16
|
+
: undefined;
|
|
17
|
+
return {
|
|
18
|
+
agentId: binding.agent.id,
|
|
19
|
+
...(model ? {
|
|
20
|
+
model: {
|
|
21
|
+
id: model.id,
|
|
22
|
+
provider: model.provider,
|
|
23
|
+
model: model.model,
|
|
24
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
25
|
+
},
|
|
26
|
+
} : {}),
|
|
27
|
+
tools: getBindingPrimaryTools(binding).map((tool) => ({
|
|
28
|
+
name: tool.name,
|
|
29
|
+
description: tool.description,
|
|
30
|
+
})),
|
|
31
|
+
skills: getBindingSkills(binding).map((skillPath) => {
|
|
32
|
+
const metadata = readSkillMetadata(skillPath);
|
|
33
|
+
return {
|
|
34
|
+
name: metadata.name,
|
|
35
|
+
path: skillPath,
|
|
36
|
+
...(metadata.description ? { description: metadata.description } : {}),
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
memory: getBindingMemorySources(binding),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function maybeAppendAgent(chain, agentId) {
|
|
43
|
+
if (!agentId) {
|
|
44
|
+
return chain;
|
|
45
|
+
}
|
|
46
|
+
if (chain[chain.length - 1] === agentId) {
|
|
47
|
+
return chain;
|
|
48
|
+
}
|
|
49
|
+
if (chain.includes(agentId)) {
|
|
50
|
+
return [...chain.filter((entry) => entry !== agentId), agentId];
|
|
51
|
+
}
|
|
52
|
+
return [...chain, agentId];
|
|
53
|
+
}
|
|
54
|
+
function extractSubagentFromTaskToolEvent(event) {
|
|
55
|
+
const eventName = typeof event.event === "string" ? event.event : "";
|
|
56
|
+
const toolName = typeof event.name === "string" ? event.name : "";
|
|
57
|
+
if (toolName !== "task" || (eventName !== "on_tool_start" && !(eventName === "on_chain_start" && event.run_type === "tool"))) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const data = asObject(event.data);
|
|
61
|
+
const input = asObject(data?.input);
|
|
62
|
+
const subagentType = typeof input?.subagent_type === "string"
|
|
63
|
+
? input.subagent_type
|
|
64
|
+
: typeof input?.subagentType === "string"
|
|
65
|
+
? input.subagentType
|
|
66
|
+
: null;
|
|
67
|
+
return subagentType && subagentType.trim().length > 0 ? subagentType.trim() : null;
|
|
68
|
+
}
|
|
69
|
+
function extractKnownSubagentFromEvent(event, knownSubagentIds) {
|
|
70
|
+
const names = [
|
|
71
|
+
typeof event.name === "string" ? event.name : "",
|
|
72
|
+
...readStringArray(event.tags),
|
|
73
|
+
...readStringArray(event.ns),
|
|
74
|
+
];
|
|
75
|
+
return names.find((name) => knownSubagentIds.has(name)) ?? null;
|
|
76
|
+
}
|
|
77
|
+
export function consumeRunInspectionUpstreamEvent(input) {
|
|
78
|
+
const typed = asObject(input.event);
|
|
79
|
+
if (!typed) {
|
|
80
|
+
return {
|
|
81
|
+
currentAgentId: input.currentAgentId,
|
|
82
|
+
delegationChain: input.delegationChain,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const knownSubagentIds = new Set(getBindingSubagents(input.binding).map((subagent) => subagent.name));
|
|
86
|
+
const delegatedAgentId = extractSubagentFromTaskToolEvent(typed) ?? extractKnownSubagentFromEvent(typed, knownSubagentIds);
|
|
87
|
+
if (!delegatedAgentId) {
|
|
88
|
+
return {
|
|
89
|
+
currentAgentId: input.currentAgentId,
|
|
90
|
+
delegationChain: input.delegationChain,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
currentAgentId: delegatedAgentId,
|
|
95
|
+
delegationChain: maybeAppendAgent(input.delegationChain, delegatedAgentId),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -20,6 +20,10 @@ type EnsureThreadStartedRuntime = {
|
|
|
20
20
|
};
|
|
21
21
|
runRequest: PersistedRunRequest;
|
|
22
22
|
createThread: boolean;
|
|
23
|
+
startedAt?: string;
|
|
24
|
+
currentAgentId?: string;
|
|
25
|
+
delegationChain?: string[];
|
|
26
|
+
runtimeSnapshot?: import("../../../contracts/types.js").RuntimeSnapshot;
|
|
23
27
|
}) => Promise<void>;
|
|
24
28
|
createThread: (input: {
|
|
25
29
|
threadId: string;
|
|
@@ -41,6 +45,10 @@ type EnsureThreadStartedRuntime = {
|
|
|
41
45
|
executionMode: string;
|
|
42
46
|
adapterKind: string;
|
|
43
47
|
createdAt: string;
|
|
48
|
+
startedAt?: string;
|
|
49
|
+
currentAgentId?: string;
|
|
50
|
+
delegationChain?: string[];
|
|
51
|
+
runtimeSnapshot?: import("../../../contracts/types.js").RuntimeSnapshot;
|
|
44
52
|
}) => Promise<void>;
|
|
45
53
|
saveRunRequest: (threadId: string, runId: string, runRequest: PersistedRunRequest) => Promise<void>;
|
|
46
54
|
};
|
|
@@ -4,12 +4,17 @@ import { normalizeMessageContent } from "../../../utils/message-content.js";
|
|
|
4
4
|
import { buildPersistedRunRequest, normalizeRunPriority } from "./helpers.js";
|
|
5
5
|
import { getRequiredWorkspaceBinding } from "../bindings.js";
|
|
6
6
|
import { getBindingAdapterKind, getBindingRuntimeExecutionMode } from "../../support/compiled-binding.js";
|
|
7
|
+
import { buildRunRuntimeSnapshot } from "./inspection.js";
|
|
7
8
|
export async function ensureThreadStarted(runtime, input) {
|
|
8
9
|
const { selectedAgentId, binding, message, runRequest, existingThreadId } = input;
|
|
9
10
|
const threadId = existingThreadId ?? createPersistentId();
|
|
10
11
|
const runId = createPersistentId();
|
|
11
12
|
const createdAt = new Date().toISOString();
|
|
12
13
|
const isNewThread = !existingThreadId;
|
|
14
|
+
const startedAt = createdAt;
|
|
15
|
+
const currentAgentId = selectedAgentId;
|
|
16
|
+
const delegationChain = [selectedAgentId];
|
|
17
|
+
const runtimeSnapshot = buildRunRuntimeSnapshot(binding);
|
|
13
18
|
const userMessage = {
|
|
14
19
|
role: "user",
|
|
15
20
|
content: normalizeMessageContent(message),
|
|
@@ -28,6 +33,10 @@ export async function ensureThreadStarted(runtime, input) {
|
|
|
28
33
|
userMessage,
|
|
29
34
|
runRequest,
|
|
30
35
|
createThread: isNewThread,
|
|
36
|
+
startedAt,
|
|
37
|
+
currentAgentId,
|
|
38
|
+
delegationChain,
|
|
39
|
+
runtimeSnapshot,
|
|
31
40
|
});
|
|
32
41
|
}
|
|
33
42
|
else {
|
|
@@ -49,6 +58,10 @@ export async function ensureThreadStarted(runtime, input) {
|
|
|
49
58
|
executionMode: getBindingRuntimeExecutionMode(binding),
|
|
50
59
|
adapterKind: getBindingAdapterKind(binding),
|
|
51
60
|
createdAt,
|
|
61
|
+
startedAt,
|
|
62
|
+
currentAgentId,
|
|
63
|
+
delegationChain,
|
|
64
|
+
runtimeSnapshot,
|
|
52
65
|
}),
|
|
53
66
|
runtime.persistence.saveRunRequest(threadId, runId, runRequest),
|
|
54
67
|
]);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompiledAgentBinding, HarnessEvent, MessageContent, RunResult, TranscriptMessage } from "../../../contracts/types.js";
|
|
1
|
+
import type { CompiledAgentBinding, HarnessEvent, MessageContent, RunResult, RuntimeSnapshot, TranscriptMessage } from "../../../contracts/types.js";
|
|
2
2
|
import { type InternalHarnessStreamItem } from "../events/streaming.js";
|
|
3
3
|
type RuntimeStreamChunk = string | {
|
|
4
4
|
kind: "content" | "interrupt" | "reasoning" | "step" | "tool-result" | "upstream-event";
|
|
@@ -45,6 +45,13 @@ type StreamRunOptions = {
|
|
|
45
45
|
}>;
|
|
46
46
|
appendAssistantMessage: (threadId: string, runId: string, content?: string) => Promise<void>;
|
|
47
47
|
clearRunRequest: (threadId: string, runId: string) => Promise<void>;
|
|
48
|
+
updateRunInspection: (threadId: string, runId: string, patch: {
|
|
49
|
+
endedAt?: string | null;
|
|
50
|
+
lastActivityAt?: string;
|
|
51
|
+
currentAgentId?: string | null;
|
|
52
|
+
delegationChain?: string[];
|
|
53
|
+
runtimeSnapshot?: RuntimeSnapshot | null;
|
|
54
|
+
}) => Promise<void>;
|
|
48
55
|
emitSyntheticFallback: (threadId: string, runId: string, selectedAgentId: string, error: unknown) => Promise<void>;
|
|
49
56
|
};
|
|
50
57
|
export declare function streamHarnessRun(options: StreamRunOptions): AsyncGenerator<InternalHarnessStreamItem>;
|
|
@@ -2,6 +2,7 @@ import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from ".
|
|
|
2
2
|
import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
|
|
3
3
|
import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
|
|
4
4
|
import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
|
|
5
|
+
import { consumeRunInspectionUpstreamEvent } from "./inspection.js";
|
|
5
6
|
function normalizeStreamChunk(chunk) {
|
|
6
7
|
if (typeof chunk === "string") {
|
|
7
8
|
if (chunk.startsWith(AGENT_INTERRUPT_SENTINEL_PREFIX)) {
|
|
@@ -43,6 +44,8 @@ export async function* streamHarnessRun(options) {
|
|
|
43
44
|
let emitted = false;
|
|
44
45
|
let streamActivityObserved = false;
|
|
45
46
|
let nonUpstreamStreamActivityObserved = false;
|
|
47
|
+
let currentAgentId = options.selectedAgentId;
|
|
48
|
+
let delegationChain = [options.selectedAgentId];
|
|
46
49
|
try {
|
|
47
50
|
const [priorHistory, acquiredReleaseRunSlot] = await Promise.all([
|
|
48
51
|
priorHistoryPromise,
|
|
@@ -64,6 +67,18 @@ export async function* streamHarnessRun(options) {
|
|
|
64
67
|
streamActivityObserved = true;
|
|
65
68
|
const normalizedChunk = normalizeStreamChunk(rawChunk);
|
|
66
69
|
if (normalizedChunk.kind === "upstream-event") {
|
|
70
|
+
const inspection = consumeRunInspectionUpstreamEvent({
|
|
71
|
+
event: normalizedChunk.event,
|
|
72
|
+
currentAgentId,
|
|
73
|
+
delegationChain,
|
|
74
|
+
binding: options.binding,
|
|
75
|
+
});
|
|
76
|
+
currentAgentId = inspection.currentAgentId;
|
|
77
|
+
delegationChain = inspection.delegationChain;
|
|
78
|
+
await options.updateRunInspection(options.threadId, options.runId, {
|
|
79
|
+
currentAgentId,
|
|
80
|
+
delegationChain,
|
|
81
|
+
});
|
|
67
82
|
yield {
|
|
68
83
|
type: "upstream-event",
|
|
69
84
|
threadId: options.threadId,
|
|
@@ -88,7 +103,7 @@ export async function* streamHarnessRun(options) {
|
|
|
88
103
|
result: {
|
|
89
104
|
threadId: options.threadId,
|
|
90
105
|
runId: options.runId,
|
|
91
|
-
agentId:
|
|
106
|
+
agentId: currentAgentId,
|
|
92
107
|
state: "waiting_for_approval",
|
|
93
108
|
output: assistantOutput,
|
|
94
109
|
finalMessageText: assistantOutput,
|
|
@@ -145,7 +160,7 @@ export async function* streamHarnessRun(options) {
|
|
|
145
160
|
result: {
|
|
146
161
|
threadId: options.threadId,
|
|
147
162
|
runId: options.runId,
|
|
148
|
-
agentId:
|
|
163
|
+
agentId: currentAgentId,
|
|
149
164
|
state: "completed",
|
|
150
165
|
output: assistantOutput,
|
|
151
166
|
finalMessageText: assistantOutput,
|
|
@@ -183,7 +198,7 @@ export async function* streamHarnessRun(options) {
|
|
|
183
198
|
result: {
|
|
184
199
|
threadId: options.threadId,
|
|
185
200
|
runId: options.runId,
|
|
186
|
-
agentId:
|
|
201
|
+
agentId: currentAgentId,
|
|
187
202
|
state: "failed",
|
|
188
203
|
output: runtimeFailure,
|
|
189
204
|
finalMessageText: runtimeFailure,
|
|
@@ -232,7 +247,7 @@ export async function* streamHarnessRun(options) {
|
|
|
232
247
|
...actual,
|
|
233
248
|
threadId: options.threadId,
|
|
234
249
|
runId: options.runId,
|
|
235
|
-
agentId:
|
|
250
|
+
agentId: currentAgentId,
|
|
236
251
|
},
|
|
237
252
|
};
|
|
238
253
|
yield {
|
|
@@ -265,7 +280,7 @@ export async function* streamHarnessRun(options) {
|
|
|
265
280
|
result: {
|
|
266
281
|
threadId: options.threadId,
|
|
267
282
|
runId: options.runId,
|
|
268
|
-
agentId:
|
|
283
|
+
agentId: currentAgentId,
|
|
269
284
|
state: "failed",
|
|
270
285
|
output: runtimeFailure,
|
|
271
286
|
finalMessageText: runtimeFailure,
|
|
@@ -20,6 +20,7 @@ export async function buildThreadInspectionRecord(input, threadId) {
|
|
|
20
20
|
return {
|
|
21
21
|
threadId,
|
|
22
22
|
entryAgentId: meta.entryAgentId,
|
|
23
|
+
currentAgentId: threadSummary.currentAgentId,
|
|
23
24
|
currentState: threadSummary.status,
|
|
24
25
|
latestRunId,
|
|
25
26
|
createdAt: meta.createdAt,
|
package/dist/runtime/harness.js
CHANGED
|
@@ -517,6 +517,7 @@ export class AgentHarnessRuntime {
|
|
|
517
517
|
requestApprovalAndEmit: (threadId, runId, input, interruptContent, checkpointRef, sequence) => this.requestApprovalAndEmit(threadId, runId, input, interruptContent, checkpointRef, sequence),
|
|
518
518
|
appendAssistantMessage: (threadId, runId, content) => appendLifecycleAssistantMessage(this.persistence, threadId, runId, content),
|
|
519
519
|
clearRunRequest: (threadId, runId) => this.persistence.clearRunRequest(threadId, runId),
|
|
520
|
+
updateRunInspection: (threadId, runId, patch) => this.persistence.updateRunInspection(threadId, runId, patch),
|
|
520
521
|
emitSyntheticFallback: (threadId, runId, selectedAgentId, error) => this.runtimeEventOperations.emitSyntheticFallback(threadId, runId, selectedAgentId, error),
|
|
521
522
|
});
|
|
522
523
|
for await (const item of stream) {
|
|
@@ -91,6 +91,7 @@ export async function maintainSqliteRuntimeRecords(dbPath, config, nowMs = Date.
|
|
|
91
91
|
"DELETE FROM artifacts WHERE thread_id = ?",
|
|
92
92
|
"DELETE FROM approvals WHERE thread_id = ?",
|
|
93
93
|
"DELETE FROM events WHERE thread_id = ?",
|
|
94
|
+
"DELETE FROM run_inspection WHERE thread_id = ?",
|
|
94
95
|
"DELETE FROM run_queue WHERE thread_id = ?",
|
|
95
96
|
"DELETE FROM run_requests WHERE thread_id = ?",
|
|
96
97
|
"DELETE FROM recovery_intents WHERE thread_id = ?",
|