@botbotgo/agent-harness 0.0.268 → 0.0.270
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 +3 -2
- package/README.zh.md +3 -2
- package/dist/acp.js +2 -2
- package/dist/api.d.ts +6 -11
- package/dist/api.js +20 -23
- package/dist/contracts/runtime.d.ts +42 -70
- package/dist/flow/build-flow-graph.js +29 -45
- package/dist/flow/types.d.ts +0 -6
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.js +24 -17
- package/dist/persistence/sqlite-run-context-store.js +2 -2
- package/dist/persistence/sqlite-store.js +18 -16
- package/dist/protocol/a2a/http.js +69 -46
- package/dist/protocol/ag-ui/http.js +9 -9
- package/dist/runtime/adapter/invocation-result.js +2 -2
- package/dist/runtime/adapter/tool/tool-hitl.d.ts +3 -1
- package/dist/runtime/adapter/tool/tool-hitl.js +75 -6
- package/dist/runtime/harness/events/listener-runtime.d.ts +2 -2
- package/dist/runtime/harness/events/streaming.d.ts +8 -8
- package/dist/runtime/harness/events/streaming.js +10 -10
- package/dist/runtime/harness/events/timeline.js +4 -4
- package/dist/runtime/harness/run/governance.js +33 -4
- package/dist/runtime/harness/run/helpers.js +2 -2
- package/dist/runtime/harness/run/operator-overview.js +6 -0
- package/dist/runtime/harness/run/recovery.js +20 -20
- package/dist/runtime/harness/run/resume.js +3 -3
- package/dist/runtime/harness/run/run-lifecycle.js +5 -5
- package/dist/runtime/harness/run/run-operations.d.ts +2 -2
- package/dist/runtime/harness/run/run-operations.js +21 -21
- package/dist/runtime/harness/run/start-run.d.ts +3 -3
- package/dist/runtime/harness/run/start-run.js +3 -3
- package/dist/runtime/harness/run/startup-runtime.js +1 -1
- package/dist/runtime/harness/run/stream-run.js +37 -27
- package/dist/runtime/harness/run/thread-records.js +12 -33
- package/dist/runtime/harness/system/mem0-ingestion-sync.js +2 -2
- package/dist/runtime/harness/system/runtime-memory-manager.js +4 -4
- package/dist/runtime/harness/system/runtime-memory-records.js +6 -6
- package/dist/runtime/harness/system/runtime-memory-sync.js +6 -4
- package/dist/runtime/harness/system/thread-memory-sync.js +7 -5
- package/dist/runtime/harness.d.ts +2 -2
- package/dist/runtime/harness.js +161 -156
- package/dist/runtime/support/harness-support.js +4 -4
- package/package.json +1 -1
|
@@ -11,6 +11,10 @@ function writeJson(response, statusCode, payload) {
|
|
|
11
11
|
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
12
12
|
response.end(JSON.stringify(payload));
|
|
13
13
|
}
|
|
14
|
+
function writeA2aDiscoveryHeaders(response) {
|
|
15
|
+
response.setHeader("A2A-Version", "1.0");
|
|
16
|
+
response.setHeader("A2A-Supported-Versions", SUPPORTED_A2A_VERSIONS.join(", "));
|
|
17
|
+
}
|
|
14
18
|
function writeSseHeaders(response) {
|
|
15
19
|
response.statusCode = 200;
|
|
16
20
|
response.setHeader("content-type", "text/event-stream; charset=utf-8");
|
|
@@ -356,18 +360,19 @@ function toSessionRecord(session) {
|
|
|
356
360
|
if (!session) {
|
|
357
361
|
return null;
|
|
358
362
|
}
|
|
363
|
+
const typed = session;
|
|
359
364
|
return {
|
|
360
|
-
sessionId:
|
|
361
|
-
entryAgentId:
|
|
362
|
-
currentAgentId:
|
|
363
|
-
currentState:
|
|
364
|
-
latestRequestId:
|
|
365
|
-
createdAt:
|
|
366
|
-
updatedAt:
|
|
367
|
-
messages:
|
|
368
|
-
requests:
|
|
369
|
-
requestId: run.
|
|
370
|
-
sessionId: run.
|
|
365
|
+
sessionId: typed.sessionId ?? typed.threadId ?? "",
|
|
366
|
+
entryAgentId: typed.entryAgentId,
|
|
367
|
+
currentAgentId: typed.currentAgentId,
|
|
368
|
+
currentState: typed.currentState,
|
|
369
|
+
latestRequestId: typed.latestRequestId ?? typed.latestRunId ?? "",
|
|
370
|
+
createdAt: typed.createdAt,
|
|
371
|
+
updatedAt: typed.updatedAt,
|
|
372
|
+
messages: typed.messages,
|
|
373
|
+
requests: (typed.requests ?? typed.runs ?? []).map((run) => ({
|
|
374
|
+
requestId: run.requestId,
|
|
375
|
+
sessionId: run.sessionId,
|
|
371
376
|
agentId: run.agentId,
|
|
372
377
|
executionMode: run.executionMode,
|
|
373
378
|
adapterKind: run.adapterKind,
|
|
@@ -392,25 +397,26 @@ function toRequestRecord(request) {
|
|
|
392
397
|
if (!request) {
|
|
393
398
|
return null;
|
|
394
399
|
}
|
|
400
|
+
const typed = request;
|
|
395
401
|
return {
|
|
396
|
-
requestId:
|
|
397
|
-
sessionId:
|
|
398
|
-
agentId:
|
|
399
|
-
executionMode:
|
|
400
|
-
adapterKind:
|
|
401
|
-
createdAt:
|
|
402
|
-
updatedAt:
|
|
403
|
-
state:
|
|
404
|
-
checkpointRef:
|
|
405
|
-
resumable:
|
|
406
|
-
startedAt:
|
|
407
|
-
endedAt:
|
|
408
|
-
lastActivityAt:
|
|
409
|
-
currentAgentId:
|
|
410
|
-
delegationChain:
|
|
411
|
-
runtimeSnapshot:
|
|
412
|
-
traceItems:
|
|
413
|
-
runtimeTimeline:
|
|
402
|
+
requestId: typed.requestId ?? typed.runId ?? "",
|
|
403
|
+
sessionId: typed.sessionId ?? typed.threadId ?? "",
|
|
404
|
+
agentId: typed.agentId,
|
|
405
|
+
executionMode: typed.executionMode,
|
|
406
|
+
adapterKind: typed.adapterKind,
|
|
407
|
+
createdAt: typed.createdAt,
|
|
408
|
+
updatedAt: typed.updatedAt,
|
|
409
|
+
state: typed.state,
|
|
410
|
+
checkpointRef: typed.checkpointRef,
|
|
411
|
+
resumable: typed.resumable,
|
|
412
|
+
startedAt: typed.startedAt,
|
|
413
|
+
endedAt: typed.endedAt,
|
|
414
|
+
lastActivityAt: typed.lastActivityAt,
|
|
415
|
+
currentAgentId: typed.currentAgentId,
|
|
416
|
+
delegationChain: typed.delegationChain,
|
|
417
|
+
runtimeSnapshot: typed.runtimeSnapshot,
|
|
418
|
+
traceItems: typed.traceItems,
|
|
419
|
+
runtimeTimeline: typed.runtimeTimeline,
|
|
414
420
|
};
|
|
415
421
|
}
|
|
416
422
|
function buildTaskFromSessionAndRequest(session, request, approvals, output, failureMessage) {
|
|
@@ -473,8 +479,8 @@ async function buildTaskFromRuntime(runtime, requestId) {
|
|
|
473
479
|
if (!request) {
|
|
474
480
|
return null;
|
|
475
481
|
}
|
|
476
|
-
const session = await runtime.getThread(request.
|
|
477
|
-
const approvals = await runtime.listApprovals({
|
|
482
|
+
const session = await runtime.getThread(request.sessionId);
|
|
483
|
+
const approvals = await runtime.listApprovals({ sessionId: request.sessionId, requestId: request.requestId });
|
|
478
484
|
return buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(request), approvals);
|
|
479
485
|
}
|
|
480
486
|
async function hydrateTaskStateFromEvents(runtime, task) {
|
|
@@ -508,7 +514,7 @@ async function listTasksFromRuntime(runtime, params) {
|
|
|
508
514
|
});
|
|
509
515
|
const startIndex = params.cursor ? Number.parseInt(Buffer.from(params.cursor, "base64url").toString("utf8"), 10) || 0 : 0;
|
|
510
516
|
const page = runs.slice(startIndex, startIndex + params.limit);
|
|
511
|
-
const tasks = (await Promise.all(page.map((run) => buildTaskFromRuntime(runtime, run.
|
|
517
|
+
const tasks = (await Promise.all(page.map((run) => buildTaskFromRuntime(runtime, run.requestId)))).filter((task) => Boolean(task));
|
|
512
518
|
const nextIndex = startIndex + page.length;
|
|
513
519
|
const nextPageToken = nextIndex < runs.length ? Buffer.from(String(nextIndex), "utf8").toString("base64url") : "";
|
|
514
520
|
return {
|
|
@@ -718,12 +724,12 @@ async function* streamSendMessageTaskUpdates(runtime, options, method) {
|
|
|
718
724
|
let emittedInitialTask = false;
|
|
719
725
|
for await (const item of runtime.streamEvents(options)) {
|
|
720
726
|
if (item.type === "event") {
|
|
721
|
-
runId = runId ?? item.event.
|
|
727
|
+
runId = runId ?? item.event.requestId;
|
|
722
728
|
if (item.event.eventType === "output.delta" && typeof item.event.payload.content === "string") {
|
|
723
729
|
streamedText += item.event.payload.content;
|
|
724
730
|
}
|
|
725
731
|
if (item.event.eventType === "run.created" || item.event.eventType === "run.state.changed" || item.event.eventType === "output.delta" || item.event.eventType === "approval.requested") {
|
|
726
|
-
const task = withStreamingStatusText(await buildTaskFromRuntime(runtime, item.event.
|
|
732
|
+
const task = withStreamingStatusText(await buildTaskFromRuntime(runtime, item.event.requestId), streamedText || undefined);
|
|
727
733
|
if (task) {
|
|
728
734
|
yield { task, mode: emittedInitialTask ? "status-update" : "task" };
|
|
729
735
|
emittedInitialTask = true;
|
|
@@ -732,8 +738,8 @@ async function* streamSendMessageTaskUpdates(runtime, options, method) {
|
|
|
732
738
|
continue;
|
|
733
739
|
}
|
|
734
740
|
if (item.type === "result") {
|
|
735
|
-
runId = item.result.
|
|
736
|
-
const task = withRunResult(await buildTaskFromRuntime(runtime, item.result.
|
|
741
|
+
runId = item.result.requestId;
|
|
742
|
+
const task = withRunResult(await buildTaskFromRuntime(runtime, item.result.requestId), item.result, streamedText || item.result.output);
|
|
737
743
|
yield { task, mode: emittedInitialTask ? "status-update" : "task" };
|
|
738
744
|
return;
|
|
739
745
|
}
|
|
@@ -810,7 +816,7 @@ async function streamExistingTaskUpdates(runtime, response, id, method, taskId)
|
|
|
810
816
|
}
|
|
811
817
|
};
|
|
812
818
|
const unsubscribe = runtime.subscribe((event) => {
|
|
813
|
-
if (event.
|
|
819
|
+
if (event.requestId !== taskId) {
|
|
814
820
|
return;
|
|
815
821
|
}
|
|
816
822
|
if (event.eventType !== "run.state.changed"
|
|
@@ -893,10 +899,10 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
893
899
|
};
|
|
894
900
|
const pushRelevantEventTypes = new Set(["run.state.changed", "approval.requested", "approval.resolved"]);
|
|
895
901
|
const unsubscribePushNotifications = runtime.subscribe((event) => {
|
|
896
|
-
if (!pushRelevantEventTypes.has(event.eventType) || !pushConfigsByTask.has(event.
|
|
902
|
+
if (!pushRelevantEventTypes.has(event.eventType) || !pushConfigsByTask.has(event.requestId)) {
|
|
897
903
|
return;
|
|
898
904
|
}
|
|
899
|
-
void dispatchPushNotificationsForTask(event.
|
|
905
|
+
void dispatchPushNotificationsForTask(event.requestId);
|
|
900
906
|
});
|
|
901
907
|
const httpServer = createServer(async (request, response) => {
|
|
902
908
|
try {
|
|
@@ -911,6 +917,19 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
911
917
|
});
|
|
912
918
|
return;
|
|
913
919
|
}
|
|
920
|
+
writeA2aDiscoveryHeaders(response);
|
|
921
|
+
if (request.method === "OPTIONS" && (requestUrl.pathname === rpcPath || requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
|
|
922
|
+
response.statusCode = 204;
|
|
923
|
+
response.setHeader("Allow", "GET,HEAD,POST,OPTIONS");
|
|
924
|
+
response.end();
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
if (request.method === "HEAD" && (requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
|
|
928
|
+
response.statusCode = 200;
|
|
929
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
930
|
+
response.end();
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
914
933
|
if (request.method === "GET" && (requestUrl.pathname === agentCardPath || requestUrl.pathname === "/.well-known/agent.json")) {
|
|
915
934
|
writeJson(response, 200, buildAgentCard(runtime, buildCardOptions()));
|
|
916
935
|
return;
|
|
@@ -934,12 +953,12 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
934
953
|
const result = await runtime.run({
|
|
935
954
|
agentId: parsed.agentId ?? options.defaultAgentId,
|
|
936
955
|
input: parsed.input,
|
|
937
|
-
...(parsed.sessionId ? {
|
|
956
|
+
...(parsed.sessionId ? { sessionId: parsed.sessionId } : {}),
|
|
938
957
|
invocation: attachServiceParameters(parsed.invocation, serviceParameters),
|
|
939
958
|
});
|
|
940
|
-
const session = await runtime.getThread(result.
|
|
941
|
-
const requestRecord = await runtime.getRun(result.
|
|
942
|
-
const approvals = await runtime.listApprovals({
|
|
959
|
+
const session = await runtime.getThread(result.sessionId);
|
|
960
|
+
const requestRecord = await runtime.getRun(result.requestId);
|
|
961
|
+
const approvals = await runtime.listApprovals({ sessionId: result.sessionId, requestId: result.requestId });
|
|
943
962
|
const task = buildTaskFromSessionAndRequest(toSessionRecord(session), toRequestRecord(requestRecord), approvals, result.output);
|
|
944
963
|
if (task && parsed.pushNotificationConfig) {
|
|
945
964
|
registerPushNotificationConfig(task.id, parsed.pushNotificationConfig);
|
|
@@ -948,6 +967,10 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
948
967
|
writeJson(response, 200, toSuccess(payload.id ?? null, toSendMessageResult(payload.method, task)));
|
|
949
968
|
return;
|
|
950
969
|
}
|
|
970
|
+
if (payload.method === "GetAgentCard") {
|
|
971
|
+
writeJson(response, 200, toSuccess(payload.id ?? null, buildAgentCard(runtime, buildCardOptions())));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
951
974
|
if (payload.method === "message/stream" || payload.method === "SendStreamingMessage") {
|
|
952
975
|
if (!acceptsSse(request)) {
|
|
953
976
|
writeJson(response, 406, toError(payload.id ?? null, -32004, "A2A streaming requires `Accept: text/event-stream`."));
|
|
@@ -958,7 +981,7 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
958
981
|
const sourceIterator = streamSendMessageTaskUpdates(runtime, {
|
|
959
982
|
agentId: parsed.agentId ?? options.defaultAgentId,
|
|
960
983
|
input: parsed.input,
|
|
961
|
-
...(parsed.sessionId ? {
|
|
984
|
+
...(parsed.sessionId ? { sessionId: parsed.sessionId } : {}),
|
|
962
985
|
invocation: attachServiceParameters(parsed.invocation, serviceParameters),
|
|
963
986
|
}, payload.method);
|
|
964
987
|
const iterator = (async function* () {
|
|
@@ -991,8 +1014,8 @@ export async function serveA2aOverHttp(runtime, options = {}) {
|
|
|
991
1014
|
}
|
|
992
1015
|
if (payload.method === "tasks/cancel" || payload.method === "CancelTask") {
|
|
993
1016
|
const { taskId } = parseTaskLocatorParams(payload.params);
|
|
994
|
-
const result = await runtime.cancelRun({
|
|
995
|
-
const task = await buildTaskFromRuntime(runtime, result.
|
|
1017
|
+
const result = await runtime.cancelRun({ requestId: taskId, reason: "Cancelled via A2A bridge." });
|
|
1018
|
+
const task = await buildTaskFromRuntime(runtime, result.requestId);
|
|
996
1019
|
writeJson(response, 200, toSuccess(payload.id ?? null, task));
|
|
997
1020
|
return;
|
|
998
1021
|
}
|
|
@@ -51,8 +51,8 @@ function toRunStarted(event, input) {
|
|
|
51
51
|
return {
|
|
52
52
|
type: "RUN_STARTED",
|
|
53
53
|
timestamp: createTimestamp(),
|
|
54
|
-
threadId: event.
|
|
55
|
-
runId: event.
|
|
54
|
+
threadId: event.sessionId,
|
|
55
|
+
runId: event.requestId,
|
|
56
56
|
input,
|
|
57
57
|
};
|
|
58
58
|
}
|
|
@@ -187,19 +187,19 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
|
|
|
187
187
|
const result = await runtime.run({
|
|
188
188
|
agentId: input.agentId,
|
|
189
189
|
input: input.input,
|
|
190
|
-
...(input.sessionId ? {
|
|
190
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
191
191
|
...(input.invocation ? { invocation: input.invocation } : {}),
|
|
192
192
|
listeners: {
|
|
193
193
|
onEvent: async (event) => {
|
|
194
194
|
if (event.eventType === "run.created") {
|
|
195
|
-
runId = event.
|
|
196
|
-
threadId = event.
|
|
195
|
+
runId = event.requestId;
|
|
196
|
+
threadId = event.sessionId;
|
|
197
197
|
await writeSseEvent(response, toRunStarted(event, input.input));
|
|
198
198
|
return;
|
|
199
199
|
}
|
|
200
200
|
if (event.eventType === "output.delta") {
|
|
201
|
-
runId = runId ?? event.
|
|
202
|
-
threadId = threadId ?? event.
|
|
201
|
+
runId = runId ?? event.requestId;
|
|
202
|
+
threadId = threadId ?? event.sessionId;
|
|
203
203
|
const delta = typeof event.payload.content === "string" ? event.payload.content : "";
|
|
204
204
|
if (!delta) {
|
|
205
205
|
return;
|
|
@@ -301,8 +301,8 @@ export async function serveAgUiOverHttp(runtime, options = {}) {
|
|
|
301
301
|
},
|
|
302
302
|
},
|
|
303
303
|
});
|
|
304
|
-
runId = runId ?? result.
|
|
305
|
-
threadId = threadId ?? result.
|
|
304
|
+
runId = runId ?? result.requestId;
|
|
305
|
+
threadId = threadId ?? result.sessionId;
|
|
306
306
|
if (!textMessageStarted && result.output) {
|
|
307
307
|
await ensureTextStart();
|
|
308
308
|
await writeSseEvent(response, {
|
|
@@ -20,8 +20,8 @@ export function finalizeInvocationResult(params) {
|
|
|
20
20
|
const stateSnapshot = buildStateSnapshot(result);
|
|
21
21
|
const memoryCandidates = executedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
|
|
22
22
|
return {
|
|
23
|
-
threadId,
|
|
24
|
-
runId,
|
|
23
|
+
sessionId: threadId,
|
|
24
|
+
requestId: runId,
|
|
25
25
|
agentId: bindingAgentId,
|
|
26
26
|
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? "waiting_for_approval" : "completed",
|
|
27
27
|
interruptContent,
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { CompiledTool } from "../../../contracts/types.js";
|
|
2
2
|
import type { CompiledAgentBinding } from "../../../contracts/types.js";
|
|
3
|
+
import type { RuntimeGovernanceDecisionMode } from "../../../contracts/types.js";
|
|
3
4
|
type InterruptFn = <I = unknown, R = unknown>(value: I) => R;
|
|
5
|
+
export declare function resolveToolApprovalDecisionMode(compiledTool: CompiledTool, binding?: CompiledAgentBinding): RuntimeGovernanceDecisionMode;
|
|
4
6
|
export declare function toolRequiresRuntimeApproval(compiledTool: CompiledTool): boolean;
|
|
5
|
-
export declare function wrapToolForHumanInTheLoop<T>(resolvedTool: T, compiledTool: CompiledTool, interruptFn?: InterruptFn): T;
|
|
7
|
+
export declare function wrapToolForHumanInTheLoop<T>(resolvedTool: T, compiledTool: CompiledTool, binding?: CompiledAgentBinding, interruptFn?: InterruptFn): T;
|
|
6
8
|
export declare function wrapToolForExecution<T>(resolvedTool: T, compiledTool: CompiledTool, binding?: CompiledAgentBinding, interruptFn?: InterruptFn): T;
|
|
7
9
|
export {};
|
|
@@ -14,6 +14,16 @@ function asRecord(value) {
|
|
|
14
14
|
function asString(value) {
|
|
15
15
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
16
16
|
}
|
|
17
|
+
function readApprovalDecisionMode(value) {
|
|
18
|
+
if (value === "none" ||
|
|
19
|
+
value === "manual" ||
|
|
20
|
+
value === "auto-approve" ||
|
|
21
|
+
value === "auto-reject" ||
|
|
22
|
+
value === "deny-and-continue") {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
17
27
|
function readApprovalOverride(compiledTool) {
|
|
18
28
|
if (compiledTool.config?.requireApproval === true) {
|
|
19
29
|
return true;
|
|
@@ -59,11 +69,62 @@ function requiresApprovalForHighRiskMcpWrite(compiledTool) {
|
|
|
59
69
|
.join(" ");
|
|
60
70
|
return WRITE_LIKE_REMOTE_TOOL_PATTERN.test(targetText);
|
|
61
71
|
}
|
|
62
|
-
|
|
72
|
+
function matchesGovernanceToolPolicy(rule, compiledTool) {
|
|
73
|
+
const match = asRecord(rule.match) ?? rule;
|
|
74
|
+
const toolName = asString(match.toolName);
|
|
75
|
+
const category = asString(match.category);
|
|
76
|
+
const toolType = asString(match.toolType);
|
|
77
|
+
const compiledCategory = compiledTool.type === "mcp"
|
|
78
|
+
? "mcp"
|
|
79
|
+
: compiledTool.type === "backend"
|
|
80
|
+
? "backend"
|
|
81
|
+
: compiledTool.type === "provider"
|
|
82
|
+
? "provider-native"
|
|
83
|
+
: "local";
|
|
84
|
+
return (!toolName || toolName === compiledTool.name)
|
|
85
|
+
&& (!category || category === compiledCategory)
|
|
86
|
+
&& (!toolType || toolType === compiledTool.type);
|
|
87
|
+
}
|
|
88
|
+
function readToolApprovalModeFromGovernance(compiledTool, binding) {
|
|
89
|
+
const governance = asRecord(binding?.harnessRuntime.governance);
|
|
90
|
+
const overrides = Array.isArray(governance?.toolPolicies) ? governance.toolPolicies : [];
|
|
91
|
+
for (const rule of overrides) {
|
|
92
|
+
const typedRule = asRecord(rule);
|
|
93
|
+
if (!typedRule || !matchesGovernanceToolPolicy(typedRule, compiledTool)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const overrideMode = readApprovalDecisionMode(typedRule.decisionMode ?? typedRule.approvalMode);
|
|
97
|
+
if (overrideMode) {
|
|
98
|
+
return overrideMode;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
function readToolApprovalModeFromConfig(compiledTool) {
|
|
104
|
+
const approval = asRecord(compiledTool.config?.approval);
|
|
105
|
+
return readApprovalDecisionMode(approval?.mode ??
|
|
106
|
+
approval?.decisionMode ??
|
|
107
|
+
compiledTool.config?.approvalMode);
|
|
108
|
+
}
|
|
109
|
+
export function resolveToolApprovalDecisionMode(compiledTool, binding) {
|
|
63
110
|
if (compiledTool.hitl?.enabled === true) {
|
|
64
|
-
return
|
|
111
|
+
return "manual";
|
|
112
|
+
}
|
|
113
|
+
const governanceMode = readToolApprovalModeFromGovernance(compiledTool, binding);
|
|
114
|
+
if (governanceMode) {
|
|
115
|
+
return governanceMode;
|
|
116
|
+
}
|
|
117
|
+
const configuredMode = readToolApprovalModeFromConfig(compiledTool);
|
|
118
|
+
if (configuredMode) {
|
|
119
|
+
return configuredMode;
|
|
65
120
|
}
|
|
66
|
-
|
|
121
|
+
if (readApprovalOverride(compiledTool) || requiresApprovalForSensitiveMemoryWrite(compiledTool) || requiresApprovalForHighRiskMcpWrite(compiledTool)) {
|
|
122
|
+
return "manual";
|
|
123
|
+
}
|
|
124
|
+
return "none";
|
|
125
|
+
}
|
|
126
|
+
export function toolRequiresRuntimeApproval(compiledTool) {
|
|
127
|
+
return resolveToolApprovalDecisionMode(compiledTool) === "manual";
|
|
67
128
|
}
|
|
68
129
|
function toolApprovalReason(compiledTool) {
|
|
69
130
|
if (compiledTool.hitl?.enabled === true) {
|
|
@@ -101,17 +162,25 @@ function resolveApprovedInput(originalInput, resumeValue) {
|
|
|
101
162
|
}
|
|
102
163
|
return originalInput;
|
|
103
164
|
}
|
|
104
|
-
export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, interruptFn = interrupt) {
|
|
105
|
-
|
|
165
|
+
export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, binding, interruptFn = interrupt) {
|
|
166
|
+
const decisionMode = resolveToolApprovalDecisionMode(compiledTool, binding);
|
|
167
|
+
if (decisionMode === "none" || typeof resolvedTool !== "object" || resolvedTool === null) {
|
|
106
168
|
return resolvedTool;
|
|
107
169
|
}
|
|
108
170
|
const target = resolvedTool;
|
|
109
171
|
const runWithApproval = async (input, config, invokeOriginal) => {
|
|
172
|
+
if (decisionMode === "auto-approve") {
|
|
173
|
+
return invokeOriginal(input, config);
|
|
174
|
+
}
|
|
175
|
+
if (decisionMode === "auto-reject" || decisionMode === "deny-and-continue") {
|
|
176
|
+
return "Tool execution denied by runtime policy.";
|
|
177
|
+
}
|
|
110
178
|
const resumed = interruptFn({
|
|
111
179
|
toolName: compiledTool.name,
|
|
112
180
|
toolId: compiledTool.id,
|
|
113
181
|
allowedDecisions: compiledTool.hitl?.allow ?? ["approve", "edit", "reject"],
|
|
114
182
|
inputPreview: toInputPreview(input),
|
|
183
|
+
decisionMode,
|
|
115
184
|
...(toolApprovalReason(compiledTool) ? { approvalReason: toolApprovalReason(compiledTool) } : {}),
|
|
116
185
|
});
|
|
117
186
|
const approvedInput = resolveApprovedInput(input, resumed);
|
|
@@ -238,5 +307,5 @@ export function wrapToolForExecution(resolvedTool, compiledTool, binding, interr
|
|
|
238
307
|
},
|
|
239
308
|
})
|
|
240
309
|
: resolvedTool;
|
|
241
|
-
return wrapToolWithDedupe(wrapToolForHumanInTheLoop(guardedTool, compiledTool, interruptFn), compiledTool);
|
|
310
|
+
return wrapToolWithDedupe(wrapToolForHumanInTheLoop(guardedTool, compiledTool, binding, interruptFn), compiledTool);
|
|
242
311
|
}
|
|
@@ -4,10 +4,10 @@ export declare function createListenerDispatchRuntime(input: {
|
|
|
4
4
|
notifyListener: <T>(listener: ((value: T) => void | Promise<void>) | undefined, value: T) => Promise<void>;
|
|
5
5
|
getThread: (threadId: string) => Promise<{
|
|
6
6
|
currentState: RunResult["state"];
|
|
7
|
-
|
|
7
|
+
latestRequestId: string;
|
|
8
8
|
entryAgentId: string;
|
|
9
9
|
currentAgentId?: string;
|
|
10
|
-
|
|
10
|
+
requests: Array<{
|
|
11
11
|
agentId: string;
|
|
12
12
|
}>;
|
|
13
13
|
pendingDecision?: {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import type { HarnessEvent, HarnessStreamItem, RunListeners, RunResult } from "../../../contracts/types.js";
|
|
2
2
|
export type InternalHarnessStreamItem = HarnessStreamItem | {
|
|
3
3
|
type: "content";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
sessionId: string;
|
|
5
|
+
requestId: string;
|
|
6
6
|
agentId: string;
|
|
7
7
|
content: string;
|
|
8
8
|
} | {
|
|
9
9
|
type: "content-blocks";
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
sessionId: string;
|
|
11
|
+
requestId: string;
|
|
12
12
|
agentId: string;
|
|
13
13
|
contentBlocks: unknown[];
|
|
14
14
|
} | {
|
|
15
15
|
type: "tool-result";
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
sessionId: string;
|
|
17
|
+
requestId: string;
|
|
18
18
|
agentId: string;
|
|
19
19
|
toolName: string;
|
|
20
20
|
output: unknown;
|
|
@@ -27,10 +27,10 @@ export declare function dispatchRunListeners(stream: AsyncGenerator<InternalHarn
|
|
|
27
27
|
notifyListener: <T>(listener: ((value: T) => void | Promise<void>) | undefined, value: T) => Promise<void>;
|
|
28
28
|
getThread: (threadId: string) => Promise<{
|
|
29
29
|
currentState: RunResult["state"];
|
|
30
|
-
|
|
30
|
+
latestRequestId: string;
|
|
31
31
|
entryAgentId: string;
|
|
32
32
|
currentAgentId?: string;
|
|
33
|
-
|
|
33
|
+
requests: Array<{
|
|
34
34
|
agentId: string;
|
|
35
35
|
}>;
|
|
36
36
|
pendingDecision?: {
|
|
@@ -5,8 +5,8 @@ export async function emitOutputDeltaAndCreateItem(emit, threadId, runId, agentI
|
|
|
5
5
|
});
|
|
6
6
|
return {
|
|
7
7
|
type: "content",
|
|
8
|
-
threadId,
|
|
9
|
-
runId,
|
|
8
|
+
sessionId: threadId,
|
|
9
|
+
requestId: runId,
|
|
10
10
|
agentId,
|
|
11
11
|
content,
|
|
12
12
|
};
|
|
@@ -14,8 +14,8 @@ export async function emitOutputDeltaAndCreateItem(emit, threadId, runId, agentI
|
|
|
14
14
|
export function createContentBlocksItem(threadId, runId, agentId, contentBlocks) {
|
|
15
15
|
return {
|
|
16
16
|
type: "content-blocks",
|
|
17
|
-
threadId,
|
|
18
|
-
runId,
|
|
17
|
+
sessionId: threadId,
|
|
18
|
+
requestId: runId,
|
|
19
19
|
agentId,
|
|
20
20
|
contentBlocks,
|
|
21
21
|
};
|
|
@@ -44,8 +44,8 @@ export async function dispatchRunListeners(stream, listeners, options) {
|
|
|
44
44
|
await options.notifyListener(listeners.onUpstreamEvent, item.event);
|
|
45
45
|
if (item.surfaceItem) {
|
|
46
46
|
await options.notifyListener(listeners.onTraceItem, {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
sessionId: item.sessionId,
|
|
48
|
+
requestId: item.requestId,
|
|
49
49
|
surfaceItem: item.surfaceItem,
|
|
50
50
|
event: item.event,
|
|
51
51
|
});
|
|
@@ -67,16 +67,16 @@ export async function dispatchRunListeners(stream, listeners, options) {
|
|
|
67
67
|
if (latestResult) {
|
|
68
68
|
return mergeRunResultOutput(latestResult, output);
|
|
69
69
|
}
|
|
70
|
-
const thread = await options.getThread(latestEvent.
|
|
70
|
+
const thread = await options.getThread(latestEvent.sessionId);
|
|
71
71
|
if (!thread) {
|
|
72
|
-
throw new Error(`Unknown
|
|
72
|
+
throw new Error(`Unknown session ${latestEvent.sessionId}`);
|
|
73
73
|
}
|
|
74
74
|
return createFallbackRunResultFromLatestEvent({
|
|
75
75
|
latestEvent,
|
|
76
76
|
currentState: thread.currentState,
|
|
77
|
-
latestRunId: thread.
|
|
77
|
+
latestRunId: thread.latestRequestId,
|
|
78
78
|
entryAgentId: thread.entryAgentId,
|
|
79
|
-
latestAgentId: thread.currentAgentId ?? thread.
|
|
79
|
+
latestAgentId: thread.currentAgentId ?? thread.requests[0]?.agentId,
|
|
80
80
|
approvalId: thread.pendingDecision?.approvalId,
|
|
81
81
|
pendingActionId: thread.pendingDecision?.pendingActionId,
|
|
82
82
|
output,
|
|
@@ -19,10 +19,10 @@ function classifyTimelineItem(event) {
|
|
|
19
19
|
}
|
|
20
20
|
export function projectRuntimeTimeline(events, options = {}) {
|
|
21
21
|
const filtered = events.filter((event) => {
|
|
22
|
-
if (options.
|
|
22
|
+
if (options.sessionId && event.sessionId !== options.sessionId) {
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
|
-
if (options.
|
|
25
|
+
if (options.requestId && event.requestId !== options.requestId) {
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
28
|
return true;
|
|
@@ -40,8 +40,8 @@ export function projectRuntimeTimeline(events, options = {}) {
|
|
|
40
40
|
})
|
|
41
41
|
.map((event) => ({
|
|
42
42
|
eventId: event.eventId,
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
sessionId: event.sessionId,
|
|
44
|
+
requestId: event.requestId,
|
|
45
45
|
eventType: event.eventType,
|
|
46
46
|
timestamp: event.timestamp,
|
|
47
47
|
sequence: event.sequence,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getBindingPrimaryTools } from "../../support/compiled-binding.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveToolApprovalDecisionMode } from "../../adapter/tool/tool-hitl.js";
|
|
3
3
|
import { compiledToolHasInputSchema } from "../tool-schema.js";
|
|
4
4
|
const WRITE_LIKE_PATTERN = /\b(write|edit|delete|create|update|append|insert|push|commit|publish|send|post|apply|merge|sync|upload|save)\b/i;
|
|
5
5
|
function inputHints(binding, tool) {
|
|
@@ -64,6 +64,11 @@ function readRisk(value) {
|
|
|
64
64
|
function readApprovalPolicy(value) {
|
|
65
65
|
return value === "explicit-hitl" || value === "runtime-default" || value === "none" ? value : undefined;
|
|
66
66
|
}
|
|
67
|
+
function readDecisionMode(value) {
|
|
68
|
+
return value === "none" || value === "manual" || value === "auto-approve" || value === "auto-reject" || value === "deny-and-continue"
|
|
69
|
+
? value
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
67
72
|
function normalizeServerRef(value) {
|
|
68
73
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
69
74
|
return undefined;
|
|
@@ -123,18 +128,32 @@ function applyGovernanceOverrides(binding, policies) {
|
|
|
123
128
|
const overrideRisk = readRisk(typedRule.risk);
|
|
124
129
|
const overrideApprovalPolicy = readApprovalPolicy(typedRule.approvalPolicy);
|
|
125
130
|
const overrideRequiresApproval = typeof typedRule.requiresApproval === "boolean" ? typedRule.requiresApproval : undefined;
|
|
131
|
+
const overrideDecisionMode = readDecisionMode(typedRule.decisionMode ?? typedRule.approvalMode);
|
|
126
132
|
if (overrideRisk) {
|
|
127
133
|
merged.risk = overrideRisk;
|
|
128
134
|
}
|
|
129
135
|
if (overrideRequiresApproval !== undefined) {
|
|
130
136
|
merged.requiresApproval = overrideRequiresApproval;
|
|
131
137
|
}
|
|
138
|
+
if (overrideDecisionMode) {
|
|
139
|
+
merged.decisionMode = overrideDecisionMode;
|
|
140
|
+
merged.requiresApproval = overrideDecisionMode === "manual";
|
|
141
|
+
merged.approvalPolicy =
|
|
142
|
+
overrideDecisionMode === "none"
|
|
143
|
+
? "none"
|
|
144
|
+
: merged.approvalPolicy === "explicit-hitl"
|
|
145
|
+
? "explicit-hitl"
|
|
146
|
+
: "runtime-default";
|
|
147
|
+
}
|
|
132
148
|
if (overrideApprovalPolicy) {
|
|
133
149
|
merged.approvalPolicy = overrideApprovalPolicy;
|
|
134
150
|
}
|
|
135
151
|
else if (overrideRequiresApproval === true && merged.approvalPolicy === "none") {
|
|
136
152
|
merged.approvalPolicy = "runtime-default";
|
|
137
153
|
}
|
|
154
|
+
else if (overrideRequiresApproval === false && !overrideDecisionMode) {
|
|
155
|
+
merged.approvalPolicy = "none";
|
|
156
|
+
}
|
|
138
157
|
const extraHints = readStringArray(typedRule.inputRiskHints);
|
|
139
158
|
if (extraHints.length > 0) {
|
|
140
159
|
merged.inputRiskHints = Array.from(new Set([...merged.inputRiskHints, ...extraHints]));
|
|
@@ -160,6 +179,9 @@ function applyRemoteMcpGovernance(binding, policies) {
|
|
|
160
179
|
const transport = merged.mcpTransport;
|
|
161
180
|
if (transport && requireApprovalTransports.has(transport)) {
|
|
162
181
|
merged.requiresApproval = true;
|
|
182
|
+
if (merged.decisionMode === "none") {
|
|
183
|
+
merged.decisionMode = "manual";
|
|
184
|
+
}
|
|
163
185
|
if (merged.approvalPolicy === "none") {
|
|
164
186
|
merged.approvalPolicy = "runtime-default";
|
|
165
187
|
}
|
|
@@ -179,11 +201,15 @@ export function buildRuntimeGovernanceBundles(binding) {
|
|
|
179
201
|
const toolPolicies = applyGovernanceOverrides(binding, applyRemoteMcpGovernance(binding, getBindingPrimaryTools(binding).map((tool) => {
|
|
180
202
|
const remoteMcp = readRemoteMcpMetadata(tool);
|
|
181
203
|
const writeLikeRemoteMcp = tool.type === "mcp" && (remoteMcp.access === "read-write" || WRITE_LIKE_PATTERN.test(`${tool.name} ${tool.description}`));
|
|
182
|
-
const
|
|
204
|
+
const derivedDecisionMode = resolveToolApprovalDecisionMode(tool, binding);
|
|
205
|
+
const requiresApproval = derivedDecisionMode === "manual" ||
|
|
183
206
|
remoteMcp.trustTier === "untrusted" ||
|
|
184
207
|
remoteMcp.approvalPolicy === "always" ||
|
|
185
208
|
(remoteMcp.approvalPolicy === "write" && writeLikeRemoteMcp) ||
|
|
186
209
|
remoteMcp.tenantScope === "cross-tenant";
|
|
210
|
+
const decisionMode = requiresApproval
|
|
211
|
+
? (derivedDecisionMode === "none" ? "manual" : derivedDecisionMode)
|
|
212
|
+
: derivedDecisionMode;
|
|
187
213
|
const approvalReason = remoteMcp.trustTier === "untrusted"
|
|
188
214
|
? "untrusted-mcp-server"
|
|
189
215
|
: remoteMcp.tenantScope === "cross-tenant"
|
|
@@ -230,7 +256,8 @@ export function buildRuntimeGovernanceBundles(binding) {
|
|
|
230
256
|
promptInjectionRisk: remoteMcp.promptInjectionRisk,
|
|
231
257
|
}),
|
|
232
258
|
requiresApproval,
|
|
233
|
-
approvalPolicy: tool.hitl?.enabled === true ? "explicit-hitl" :
|
|
259
|
+
approvalPolicy: tool.hitl?.enabled === true ? "explicit-hitl" : decisionMode === "none" ? "none" : "runtime-default",
|
|
260
|
+
decisionMode,
|
|
234
261
|
hasInputSchema: compiledToolHasInputSchema(tool),
|
|
235
262
|
inputRiskHints: Array.from(new Set(inputRiskHints)),
|
|
236
263
|
};
|
|
@@ -241,7 +268,9 @@ export function buildRuntimeGovernanceBundles(binding) {
|
|
|
241
268
|
return [{
|
|
242
269
|
bundleId: `governance/${binding.agent.id}`,
|
|
243
270
|
title: "Runtime tool governance",
|
|
244
|
-
summary: `${toolPolicies.filter((tool) => tool.requiresApproval).length} of ${toolPolicies.length} tool(s) require approval
|
|
271
|
+
summary: `${toolPolicies.filter((tool) => tool.requiresApproval).length} of ${toolPolicies.length} tool(s) require approval; ` +
|
|
272
|
+
`auto-approved=${toolPolicies.filter((tool) => tool.decisionMode === "auto-approve").length}; ` +
|
|
273
|
+
`auto-rejected=${toolPolicies.filter((tool) => tool.decisionMode === "auto-reject" || tool.decisionMode === "deny-and-continue").length}`,
|
|
245
274
|
toolPolicies,
|
|
246
275
|
}];
|
|
247
276
|
}
|
|
@@ -57,8 +57,8 @@ export function mergeRunResultOutput(result, streamedOutput) {
|
|
|
57
57
|
}
|
|
58
58
|
export function createFallbackRunResultFromLatestEvent(input) {
|
|
59
59
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
sessionId: input.latestEvent.sessionId,
|
|
61
|
+
requestId: input.latestRunId,
|
|
62
62
|
agentId: input.latestAgentId ?? input.entryAgentId,
|
|
63
63
|
state: input.currentState,
|
|
64
64
|
output: input.output,
|