@sema-agent/core 5.8.0 → 5.9.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/CHANGELOG.md +42 -0
- package/dist/agents/cascade.js +24 -0
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +64 -14
- package/dist/brain/open-responses.d.ts +11 -0
- package/dist/brain/open-responses.js +721 -0
- package/dist/brain/request-params.d.ts +1 -0
- package/dist/brain/request-params.js +16 -0
- package/dist/core/fs-write-gate-policy.js +2 -2
- package/dist/core/lsp-diagnostics.d.ts +3 -2
- package/dist/core/lsp-diagnostics.js +20 -7
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +14 -7
- package/dist/core/runner/prepare-task.d.ts +1 -0
- package/dist/core/runner/prepare-task.js +7 -4
- package/dist/core/runner/runtask.js +28 -22
- package/dist/core/runner/session-rule-policy.d.ts +1 -0
- package/dist/core/runner/session-rule-policy.js +4 -3
- package/dist/core/task-registry-shared.d.ts +0 -1
- package/dist/core/tool-policy.d.ts +8 -0
- package/dist/core/tool-policy.js +11 -0
- package/dist/core/trace.d.ts +0 -2
- package/dist/core/types.d.ts +3 -0
- package/dist/engine/harness/agent-harness.d.ts +1 -0
- package/dist/engine/harness/agent-harness.js +3 -0
- package/dist/engine/harness/types.d.ts +1 -0
- package/dist/engine/llm/types.d.ts +2 -73
- package/dist/engine/loop/agent-loop.js +6 -6
- package/dist/engine/loop/types.d.ts +1 -0
- package/dist/engine/session/repo-utils.d.ts +1 -2
- package/dist/engine/session/repo-utils.js +0 -7
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/internal/llm.d.ts +1 -1
- package/dist/orchestration/workflow.js +14 -5
- package/dist/tools/web.js +20 -19
- package/package.json +4 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const OPENAI_RESERVED: ReadonlySet<string>;
|
|
2
2
|
export declare const ANTHROPIC_RESERVED: ReadonlySet<string>;
|
|
3
|
+
export declare const RESPONSES_RESERVED: ReadonlySet<string>;
|
|
3
4
|
export declare function reservedFor(api: string): ReadonlySet<string>;
|
|
4
5
|
export declare function applyExtraBody(body: Record<string, unknown>, extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): Record<string, unknown>;
|
|
5
6
|
export declare function stripAuthHeaders(headers: Record<string, string>): void;
|
|
@@ -25,7 +25,23 @@ export const ANTHROPIC_RESERVED = new Set([
|
|
|
25
25
|
"output_config",
|
|
26
26
|
"context_management",
|
|
27
27
|
]);
|
|
28
|
+
export const RESPONSES_RESERVED = new Set([
|
|
29
|
+
"model",
|
|
30
|
+
"input",
|
|
31
|
+
"stream",
|
|
32
|
+
"instructions",
|
|
33
|
+
"tools",
|
|
34
|
+
"temperature",
|
|
35
|
+
"max_output_tokens",
|
|
36
|
+
"reasoning",
|
|
37
|
+
"store",
|
|
38
|
+
"previous_response_id",
|
|
39
|
+
"conversation",
|
|
40
|
+
]);
|
|
41
|
+
const RESPONSES_APIS = new Set(["openai-responses", "azure-openai-responses", "openai-chatgpt-responses"]);
|
|
28
42
|
export function reservedFor(api) {
|
|
43
|
+
if (RESPONSES_APIS.has(api))
|
|
44
|
+
return RESPONSES_RESERVED;
|
|
29
45
|
return api === "anthropic-messages" ? ANTHROPIC_RESERVED : OPENAI_RESERVED;
|
|
30
46
|
}
|
|
31
47
|
export function applyExtraBody(body, extraBody, reserved) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { canonicalizeTarget, writeTargetPath } from "../tools/fs/safety.js";
|
|
2
|
-
import {
|
|
2
|
+
import { PATH_CONFINABLE_WRITE_TOOLS, isWithin } from "./runner/session-rule-policy.js";
|
|
3
3
|
const ask = (message) => ({ action: "ask", message, decisionReason: "rule" });
|
|
4
4
|
export function createFsWriteGatePolicy(opts) {
|
|
5
5
|
const { env, rootPath, defaultWrite } = opts;
|
|
6
|
-
const gated =
|
|
6
|
+
const gated = PATH_CONFINABLE_WRITE_TOOLS;
|
|
7
7
|
const acceptDirs = opts.acceptDirs && opts.acceptDirs.length > 0 ? opts.acceptDirs : undefined;
|
|
8
8
|
const exemptDirs = opts.exemptDirs && opts.exemptDirs.length > 0 ? opts.exemptDirs : undefined;
|
|
9
9
|
return {
|
|
@@ -22,9 +22,10 @@ export declare class LspDiagnosticsRegistry {
|
|
|
22
22
|
private readonly pending;
|
|
23
23
|
private readonly delivered;
|
|
24
24
|
publish(uri: string, diagnostics: LspDiagnostic[]): void;
|
|
25
|
-
fileEdited(uri: string): void;
|
|
25
|
+
fileEdited(runIdent: string, uri: string): void;
|
|
26
|
+
releaseRun(runIdent: string): void;
|
|
26
27
|
isEmpty(): boolean;
|
|
27
|
-
drain(): LspFileDiagnostics[];
|
|
28
|
+
drain(runIdent: string): LspFileDiagnostics[];
|
|
28
29
|
}
|
|
29
30
|
export declare function formatDiagnosticsSummary(files: LspFileDiagnostics[]): string;
|
|
30
31
|
export declare function formatDiagnosticsBlock(files: LspFileDiagnostics[]): string;
|
|
@@ -16,30 +16,39 @@ function diagnosticKey(uri, d) {
|
|
|
16
16
|
}
|
|
17
17
|
export class LspDiagnosticsRegistry {
|
|
18
18
|
pending = new Map();
|
|
19
|
-
delivered = new
|
|
19
|
+
delivered = new Map();
|
|
20
20
|
publish(uri, diagnostics) {
|
|
21
21
|
if (diagnostics.length === 0)
|
|
22
22
|
this.pending.delete(uri);
|
|
23
23
|
else
|
|
24
24
|
this.pending.set(uri, diagnostics);
|
|
25
25
|
}
|
|
26
|
-
fileEdited(uri) {
|
|
26
|
+
fileEdited(runIdent, uri) {
|
|
27
|
+
const forRun = this.delivered.get(runIdent);
|
|
28
|
+
if (forRun === undefined)
|
|
29
|
+
return;
|
|
27
30
|
const prefix = `${uri}|`;
|
|
28
|
-
for (const key of
|
|
31
|
+
for (const key of forRun) {
|
|
29
32
|
if (key.startsWith(prefix))
|
|
30
|
-
|
|
33
|
+
forRun.delete(key);
|
|
31
34
|
}
|
|
35
|
+
if (forRun.size === 0)
|
|
36
|
+
this.delivered.delete(runIdent);
|
|
37
|
+
}
|
|
38
|
+
releaseRun(runIdent) {
|
|
39
|
+
this.delivered.delete(runIdent);
|
|
32
40
|
}
|
|
33
41
|
isEmpty() {
|
|
34
42
|
return this.pending.size === 0;
|
|
35
43
|
}
|
|
36
|
-
drain() {
|
|
44
|
+
drain(runIdent) {
|
|
37
45
|
const out = [];
|
|
38
46
|
const requeued = new Map();
|
|
47
|
+
let delivered = this.delivered.get(runIdent);
|
|
39
48
|
let total = 0;
|
|
40
49
|
for (const [uri, all] of this.pending) {
|
|
41
50
|
const fresh = all
|
|
42
|
-
.filter((d) =>
|
|
51
|
+
.filter((d) => delivered?.has(diagnosticKey(uri, d)) !== true)
|
|
43
52
|
.sort((a, b) => (a.severity ?? 99) - (b.severity ?? 99));
|
|
44
53
|
if (fresh.length === 0)
|
|
45
54
|
continue;
|
|
@@ -49,8 +58,12 @@ export class LspDiagnosticsRegistry {
|
|
|
49
58
|
requeued.set(uri, fresh.slice(take.length));
|
|
50
59
|
if (take.length === 0)
|
|
51
60
|
continue;
|
|
61
|
+
if (delivered === undefined) {
|
|
62
|
+
delivered = new Set();
|
|
63
|
+
this.delivered.set(runIdent, delivered);
|
|
64
|
+
}
|
|
52
65
|
for (const d of take)
|
|
53
|
-
|
|
66
|
+
delivered.add(diagnosticKey(uri, d));
|
|
54
67
|
total += take.length;
|
|
55
68
|
out.push({ uri, diagnostics: take });
|
|
56
69
|
}
|
|
@@ -59,6 +59,7 @@ export interface ResultFlags {
|
|
|
59
59
|
unpricedSpend?: boolean;
|
|
60
60
|
rewindNotes?: TaskResult["rewindNotes"];
|
|
61
61
|
remoteEnvFailures?: TaskResult["remoteEnvFailures"];
|
|
62
|
+
retryAfterMs?: number;
|
|
62
63
|
abortedForTimeout: boolean;
|
|
63
64
|
abortedForTurns: boolean;
|
|
64
65
|
abortedLive?: boolean;
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
2
2
|
import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
3
|
+
const SALVAGE_ELIGIBLE_TERMINALS = new Set([
|
|
4
|
+
"output.degenerate",
|
|
5
|
+
"limits.max_tokens_exceeded",
|
|
6
|
+
"limits.max_cost_exceeded",
|
|
7
|
+
"limits.max_turns_exceeded",
|
|
8
|
+
"limits.max_walltime_exceeded",
|
|
9
|
+
"env.lifetime_expired",
|
|
10
|
+
"usage.window_exhausted",
|
|
11
|
+
]);
|
|
3
12
|
export function errorCodeOf(err) {
|
|
4
13
|
let cur = err;
|
|
5
14
|
let fallback;
|
|
@@ -63,7 +72,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
63
72
|
status = "failed";
|
|
64
73
|
errorCode = "output.degenerate";
|
|
65
74
|
errorMessage = final?.errorMessage;
|
|
66
|
-
salvagedOutput = text.trim() || undefined;
|
|
67
75
|
}
|
|
68
76
|
else if (flags.budgetHit) {
|
|
69
77
|
status = "failed";
|
|
@@ -73,7 +81,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
73
81
|
flags.budgetHit === "precall"
|
|
74
82
|
? `the estimated cost of the first call already exceeds ${axisName}; the task was not started`
|
|
75
83
|
: `cumulative usage exceeded ${axisName}`;
|
|
76
|
-
salvagedOutput = text.trim() || undefined;
|
|
77
84
|
}
|
|
78
85
|
else if (flags.suspendLoop) {
|
|
79
86
|
status = "failed";
|
|
@@ -90,8 +97,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
90
97
|
: flags.conflict
|
|
91
98
|
? "conflict"
|
|
92
99
|
: errorCodeOf(flags.threw);
|
|
93
|
-
if (flags.abortedForTimeout || flags.abortedForTurns)
|
|
94
|
-
salvagedOutput = text.trim() || undefined;
|
|
95
100
|
}
|
|
96
101
|
else if (flags.blockedReason) {
|
|
97
102
|
status = "blocked";
|
|
@@ -114,8 +119,6 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
114
119
|
status = "failed";
|
|
115
120
|
errorCode = flags.abortedForTimeout ? "limits.max_walltime_exceeded" : flags.abortedForTurns ? "limits.max_turns_exceeded" : undefined;
|
|
116
121
|
errorMessage = final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted");
|
|
117
|
-
if (flags.abortedForTimeout || flags.abortedForTurns)
|
|
118
|
-
salvagedOutput = text.trim() || undefined;
|
|
119
122
|
}
|
|
120
123
|
else if (!final) {
|
|
121
124
|
status = "failed";
|
|
@@ -139,9 +142,13 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
139
142
|
else {
|
|
140
143
|
status = "completed";
|
|
141
144
|
}
|
|
145
|
+
if (errorCode !== undefined && SALVAGE_ELIGIBLE_TERMINALS.has(errorCode)) {
|
|
146
|
+
salvagedOutput = text.trim() || undefined;
|
|
147
|
+
}
|
|
148
|
+
const retryAfterMs = errorCode === "usage.window_exhausted" ? flags.retryAfterMs : undefined;
|
|
142
149
|
const { compactionMicroUsd: _internalCompaction, ...publicStats } = stats;
|
|
143
150
|
void _internalCompaction;
|
|
144
151
|
if (flags.unpricedSpend)
|
|
145
152
|
delete publicStats.costMicroUsd;
|
|
146
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
|
|
153
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), stats: publicStats };
|
|
147
154
|
}
|
|
@@ -17,7 +17,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
|
|
|
17
17
|
import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
|
|
18
18
|
import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
|
|
19
19
|
import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
|
|
20
|
-
import { combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
20
|
+
import { askApproverIdentity, combinePolicies, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets } from "../tool-policy.js";
|
|
21
21
|
import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
|
|
22
22
|
import { CHANGED_FILES_MTIME_EPS_MS, fenceMcpServerInstructions, renderAgentListingDelta } from "./turn-attachments.js";
|
|
23
23
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
@@ -944,6 +944,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
944
944
|
model: harnessRef.current?.getModel(),
|
|
945
945
|
thinkingLevel: harnessRef.current?.getThinkingLevel(),
|
|
946
946
|
principal: spec.principal,
|
|
947
|
+
...(frozenOnAsk !== undefined ? { onAsk: frozenOnAsk } : {}),
|
|
947
948
|
oneShot: spec.oneShot,
|
|
948
949
|
clientContext: spec.clientContext,
|
|
949
950
|
excludeTools: toolFaceSnapshot.exclude,
|
|
@@ -1562,11 +1563,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1562
1563
|
const lspDiagnostics = spec.lspDiagnostics !== false && lspManager?.diagnostics !== undefined && handsEnabled && spec.handsReadOnly !== true
|
|
1563
1564
|
? lspManager.diagnostics
|
|
1564
1565
|
: undefined;
|
|
1566
|
+
const lspRunIdent = sessionId;
|
|
1565
1567
|
const nudgeLspOnEdit = lspDiagnostics
|
|
1566
1568
|
? (rawPath) => {
|
|
1567
1569
|
const baseDir = handsCwdRef?.current ?? taskRootPath;
|
|
1568
1570
|
const filePath = resolveLspPath(rawPath, baseDir);
|
|
1569
|
-
lspDiagnostics.fileEdited(pathToUri(filePath));
|
|
1571
|
+
lspDiagnostics.fileEdited(lspRunIdent, pathToUri(filePath));
|
|
1570
1572
|
void lspManager
|
|
1571
1573
|
.sessionFor(filePath, undefined, executionEnv)
|
|
1572
1574
|
.then((session) => session?.notifyFileChanged?.(filePath))
|
|
@@ -2203,6 +2205,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2203
2205
|
? { legDate: envFacts.date, today: () => formatLocalDate(new Date(), tzValid ? userTz : undefined) }
|
|
2204
2206
|
: undefined;
|
|
2205
2207
|
const harness = new AgentHarness({
|
|
2208
|
+
abortResultDetails: () => suspendRef.token !== undefined || reviewRef.token !== undefined ? { code: "gate.parked" } : undefined,
|
|
2206
2209
|
...(spec.limits?.maxOutputTokens !== undefined && spec.limits.maxOutputTokens > 0
|
|
2207
2210
|
? { maxOutputTokens: spec.limits.maxOutputTokens }
|
|
2208
2211
|
: {}),
|
|
@@ -2774,7 +2777,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2774
2777
|
}
|
|
2775
2778
|
if (decision.decisionReason === undefined || decision.decisionReason === "rule") {
|
|
2776
2779
|
const grant = inheritedAskGrants.get(req.toolCallId);
|
|
2777
|
-
if (grant !== undefined && grant.approver === onAsk && grant.argsJson === askGrantShapeOf(req.args)) {
|
|
2780
|
+
if (grant !== undefined && askApproverIdentity(grant.approver) === askApproverIdentity(onAsk) && grant.argsJson === askGrantShapeOf(req.args)) {
|
|
2778
2781
|
inheritedAskGrants.delete(req.toolCallId);
|
|
2779
2782
|
humanReviewRef.count += 1;
|
|
2780
2783
|
humanReviewRef.totalWaitMs += grant.waitMs;
|
|
@@ -3599,7 +3602,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3599
3602
|
: undefined;
|
|
3600
3603
|
overheadState.promptChars = systemPrompt.length;
|
|
3601
3604
|
const preparedHolder = {};
|
|
3602
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3605
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), ownedEnv, suspendRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3603
3606
|
const prepared = buildPrepared();
|
|
3604
3607
|
preparedHolder.current = prepared;
|
|
3605
3608
|
return prepared;
|
|
@@ -113,6 +113,16 @@ function resumeDecisionWasNegative(resume) {
|
|
|
113
113
|
}
|
|
114
114
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
115
115
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
116
|
+
function toolEndBodyFrom(result, isError) {
|
|
117
|
+
const o = toolOutputFrom(result);
|
|
118
|
+
const st = structuredFrom(result);
|
|
119
|
+
const code = isError ? result?.details?.code : undefined;
|
|
120
|
+
return {
|
|
121
|
+
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
122
|
+
...(st !== undefined ? { structured: st } : {}),
|
|
123
|
+
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
116
126
|
function deepJsonEqual(a, b) {
|
|
117
127
|
if (a === b)
|
|
118
128
|
return true;
|
|
@@ -380,7 +390,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
380
390
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "review.dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
381
391
|
}
|
|
382
392
|
if (prepared.lspDiagnostics && !prepared.lspDiagnostics.registry.isEmpty() && !prepared.abortController.signal.aborted) {
|
|
383
|
-
const files = prepared.lspDiagnostics.registry.drain();
|
|
393
|
+
const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
|
|
384
394
|
if (files.length > 0) {
|
|
385
395
|
queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
|
|
386
396
|
const block = formatDiagnosticsBlock(files);
|
|
@@ -1049,16 +1059,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1049
1059
|
toolName: event.toolName,
|
|
1050
1060
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1051
1061
|
isError: event.isError,
|
|
1052
|
-
...(
|
|
1053
|
-
const o = toolOutputFrom(event.result);
|
|
1054
|
-
return o !== undefined
|
|
1055
|
-
? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) }
|
|
1056
|
-
: {};
|
|
1057
|
-
})(),
|
|
1058
|
-
...(() => {
|
|
1059
|
-
const st = structuredFrom(event.result);
|
|
1060
|
-
return st !== undefined ? { structured: st } : {};
|
|
1061
|
-
})(),
|
|
1062
|
+
...toolEndBodyFrom(event.result, event.isError),
|
|
1062
1063
|
...ident(),
|
|
1063
1064
|
});
|
|
1064
1065
|
announceWorkspaceMove();
|
|
@@ -2446,7 +2447,7 @@ export class Runner {
|
|
|
2446
2447
|
if (resume) {
|
|
2447
2448
|
const walltimeExhaustedResume = effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0;
|
|
2448
2449
|
if (!walltimeExhaustedResume && resume.outcome.gate !== "wake") {
|
|
2449
|
-
await this.applyResumeDecision(prepared, resume, (e) =>
|
|
2450
|
+
await this.applyResumeDecision(prepared, resume, (e) => pushContent({ ...e, ...ident() }), emitCommitted, (toolName, details) => {
|
|
2450
2451
|
if (rs.attach.attachState === undefined)
|
|
2451
2452
|
return;
|
|
2452
2453
|
const family = writeFamilyOf(toolName);
|
|
@@ -2638,6 +2639,7 @@ export class Runner {
|
|
|
2638
2639
|
}
|
|
2639
2640
|
finally {
|
|
2640
2641
|
timeout.clear();
|
|
2642
|
+
prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
|
|
2641
2643
|
prepared.abortController.abort();
|
|
2642
2644
|
prepared.releaseSignal();
|
|
2643
2645
|
try {
|
|
@@ -2779,6 +2781,7 @@ export class Runner {
|
|
|
2779
2781
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2780
2782
|
rewindNotes: prepared.rewindNotes,
|
|
2781
2783
|
remoteEnvFailures: prepared.remoteEnvFailures,
|
|
2784
|
+
retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
|
|
2782
2785
|
abortedForTimeout: timeout.fired,
|
|
2783
2786
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2784
2787
|
abortedLive,
|
|
@@ -3366,7 +3369,7 @@ export class Runner {
|
|
|
3366
3369
|
}
|
|
3367
3370
|
else if (!completed.has(id)) {
|
|
3368
3371
|
const name = names.get(id) ?? "unknown";
|
|
3369
|
-
emit({ type: "tool_end", toolCallId: id, toolName: name, ...((() => { const l = prepared.tools.find((t) => t.name === name)?.label; return l !== undefined && l !== name ? { label: l } : {}; })()), isError: true });
|
|
3372
|
+
emit({ type: "tool_end", toolCallId: id, toolName: name, ...((() => { const l = prepared.tools.find((t) => t.name === name)?.label; return l !== undefined && l !== name ? { label: l } : {}; })()), isError: true, ...toolEndBodyFrom({ content: formatHookFeedback(DEFERRED_REISSUE) }, true) });
|
|
3370
3373
|
const eid = await prepared.session.appendMessage(toolResultMsg(id, name, formatHookFeedback(DEFERRED_REISSUE), true));
|
|
3371
3374
|
emitCommitted(eid, "toolResult", id);
|
|
3372
3375
|
}
|
|
@@ -3380,10 +3383,10 @@ export class Runner {
|
|
|
3380
3383
|
const resolvedArgs = pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME ? pendingAction.args : (outcome.updatedInput ?? pendingAction.args);
|
|
3381
3384
|
const pendingLabel = (() => { const l = prepared.tools.find((t) => t.name === pendingAction.toolName)?.label; return l !== undefined && l !== pendingAction.toolName ? { label: l } : {}; })();
|
|
3382
3385
|
emit({ type: "tool_start", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, args: resolvedArgs });
|
|
3383
|
-
const emitEnd = (isError) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError });
|
|
3386
|
+
const emitEnd = (isError, result) => emit({ type: "tool_end", toolCallId: pendingAction.toolCallId, toolName: pendingAction.toolName, ...pendingLabel, isError, ...toolEndBodyFrom(result, isError) });
|
|
3384
3387
|
if (outcome.decision === "deny") {
|
|
3385
|
-
emitEnd(true);
|
|
3386
3388
|
const reason = outcome.reason ? delimitUntrusted("reviewer note", outcome.reason) : `The pending tool call "${pendingAction.toolName}" was denied by an approver.`;
|
|
3389
|
+
emitEnd(true, { content: formatHookFeedback(reason) });
|
|
3387
3390
|
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, formatHookFeedback(reason), true));
|
|
3388
3391
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3389
3392
|
return;
|
|
@@ -3391,8 +3394,9 @@ export class Runner {
|
|
|
3391
3394
|
if (outcome.decision === "allow" && outcome.updatedInput !== undefined && prepared.basePolicyForResumeEdit) {
|
|
3392
3395
|
const rechecked = refuseOutOfContractDecision(await prepared.basePolicyForResumeEdit.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3393
3396
|
if (rechecked.action === "deny") {
|
|
3394
|
-
|
|
3395
|
-
|
|
3397
|
+
const editedDenial = formatHookFeedback(`The approver EDITED this call's input; the edited call is denied by the deployment's tool policy and was not executed${rechecked.message ? `: ${rechecked.message}` : ""}.`);
|
|
3398
|
+
emitEnd(true, { content: editedDenial });
|
|
3399
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, editedDenial, true));
|
|
3396
3400
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3397
3401
|
return;
|
|
3398
3402
|
}
|
|
@@ -3400,8 +3404,9 @@ export class Runner {
|
|
|
3400
3404
|
if (prepared.denyNarrowingPolicy) {
|
|
3401
3405
|
const narrowed = refuseOutOfContractDecision(await prepared.denyNarrowingPolicy.check({ toolName: pendingAction.toolName, args: resolvedArgs, toolCallId: pendingAction.toolCallId }, prepared.abortController.signal));
|
|
3402
3406
|
if (narrowed.action === "deny") {
|
|
3403
|
-
|
|
3404
|
-
|
|
3407
|
+
const narrowedDenial = formatHookFeedback(`The approved tool call "${pendingAction.toolName}" is now denied by a session rule and was not executed${narrowed.message ? `: ${narrowed.message}` : ""}.`);
|
|
3408
|
+
emitEnd(true, { content: narrowedDenial });
|
|
3409
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, narrowedDenial, true));
|
|
3405
3410
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3406
3411
|
return;
|
|
3407
3412
|
}
|
|
@@ -3419,13 +3424,14 @@ export class Runner {
|
|
|
3419
3424
|
res = await tool.execute(pendingAction.toolCallId, args, prepared.abortController.signal);
|
|
3420
3425
|
}
|
|
3421
3426
|
catch (err) {
|
|
3422
|
-
|
|
3423
|
-
|
|
3427
|
+
const execError = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
3428
|
+
emitEnd(true, { content: execError });
|
|
3429
|
+
const eid = await prepared.session.appendMessage(toolResultMsg(pendingAction.toolCallId, pendingAction.toolName, execError, true));
|
|
3424
3430
|
emitCommitted(eid, "toolResult", pendingAction.toolCallId);
|
|
3425
3431
|
return;
|
|
3426
3432
|
}
|
|
3427
3433
|
const executedIsError = res.isError === true;
|
|
3428
|
-
emitEnd(executedIsError);
|
|
3434
|
+
emitEnd(executedIsError, res);
|
|
3429
3435
|
onResolvedToolSuccess?.(pendingAction.toolName, executedIsError ? undefined : res.details);
|
|
3430
3436
|
const eid = await prepared.session.appendMessage({
|
|
3431
3437
|
role: "toolResult",
|
|
@@ -3,6 +3,7 @@ import type { ToolEffect } from "../types.js";
|
|
|
3
3
|
import { type NamedToolPolicy } from "../tool-policy.js";
|
|
4
4
|
import type { SessionPermissionRules } from "../session-policy-store.js";
|
|
5
5
|
export declare const PATH_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
|
+
export declare const PATH_CONFINABLE_WRITE_TOOLS: ReadonlySet<string>;
|
|
6
7
|
export declare function isWithin(root: string, p: string): boolean;
|
|
7
8
|
export declare function createSessionRulePolicy(rules: SessionPermissionRules, opts: {
|
|
8
9
|
env: ExecutionEnv;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { canonicalizeTarget,
|
|
1
|
+
import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { isWinFormPath } from "../../tools/fs/safety.js";
|
|
3
3
|
import { createCoarseCommandNamePolicy } from "../tool-policy.js";
|
|
4
4
|
export const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
|
|
5
|
+
export const PATH_CONFINABLE_WRITE_TOOLS = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
|
|
5
6
|
export function isWithin(root, p) {
|
|
6
7
|
if (!root)
|
|
7
8
|
return false;
|
|
@@ -46,14 +47,14 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
46
47
|
return d;
|
|
47
48
|
}
|
|
48
49
|
if (allowDirs) {
|
|
49
|
-
if (!
|
|
50
|
+
if (!PATH_CONFINABLE_WRITE_TOOLS.has(toolName)) {
|
|
50
51
|
const eff = toolEffects?.get(toolName) ?? "write";
|
|
51
52
|
if (eff !== "read") {
|
|
52
53
|
return deny(`write-capable tool "${req.toolName}" denied: session rule confines writes to allowDirs but this tool cannot be path-confined`);
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
else {
|
|
56
|
-
const path =
|
|
57
|
+
const path = writeTargetPath(toolName, req.args);
|
|
57
58
|
if (typeof path !== "string" || path.length === 0) {
|
|
58
59
|
return deny(`write tool "${req.toolName}" denied: session rule confines writes to allowDirs but the call has no resolvable path`);
|
|
59
60
|
}
|
|
@@ -70,6 +70,11 @@ export declare function createTranscriptIntegrityPolicy(opts?: {
|
|
|
70
70
|
readAllow?: readonly string[];
|
|
71
71
|
tools?: string[];
|
|
72
72
|
}): ToolPolicy;
|
|
73
|
+
export interface AskDelegationProvenance {
|
|
74
|
+
readonly parentToolCallId: string;
|
|
75
|
+
readonly depth: number;
|
|
76
|
+
readonly agentName?: string;
|
|
77
|
+
}
|
|
73
78
|
export interface AskRequest {
|
|
74
79
|
toolName: string;
|
|
75
80
|
toolCallId: string;
|
|
@@ -81,12 +86,15 @@ export interface AskRequest {
|
|
|
81
86
|
readonly fromSubagent?: true;
|
|
82
87
|
readonly sourceAgentName?: string;
|
|
83
88
|
readonly requiresRealApproval?: boolean;
|
|
89
|
+
readonly delegation?: AskDelegationProvenance;
|
|
84
90
|
}
|
|
85
91
|
export type OnAsk = "deny" | "allow" | ((req: AskRequest, signal?: AbortSignal) => AskOutcome | Promise<AskOutcome>);
|
|
86
92
|
export type AskOutcome = boolean | "unavailable" | {
|
|
87
93
|
allow: boolean;
|
|
88
94
|
updatedInput?: unknown;
|
|
89
95
|
};
|
|
96
|
+
export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDelegationProvenance): OnAsk;
|
|
97
|
+
export declare function askApproverIdentity(onAsk: unknown): unknown;
|
|
90
98
|
export type ResolvedAsk = PermissionResult & {
|
|
91
99
|
approverUnavailable?: true;
|
|
92
100
|
presentedInput?: unknown;
|
package/dist/core/tool-policy.js
CHANGED
|
@@ -516,6 +516,17 @@ export function createTranscriptIntegrityPolicy(opts) {
|
|
|
516
516
|
},
|
|
517
517
|
};
|
|
518
518
|
}
|
|
519
|
+
const delegatedApproverRoot = new WeakMap();
|
|
520
|
+
export function withDelegationProvenance(onAsk, delegation) {
|
|
521
|
+
if (typeof onAsk !== "function")
|
|
522
|
+
return onAsk;
|
|
523
|
+
const wrapper = (req, signal) => req.delegation !== undefined ? onAsk(req, signal) : onAsk({ ...req, delegation }, signal);
|
|
524
|
+
delegatedApproverRoot.set(wrapper, askApproverIdentity(onAsk));
|
|
525
|
+
return wrapper;
|
|
526
|
+
}
|
|
527
|
+
export function askApproverIdentity(onAsk) {
|
|
528
|
+
return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
|
|
529
|
+
}
|
|
519
530
|
function tryCloneArgs(v) {
|
|
520
531
|
try {
|
|
521
532
|
const value = structuredClone(v);
|
package/dist/core/trace.d.ts
CHANGED
package/dist/core/types.d.ts
CHANGED
|
@@ -98,6 +98,7 @@ export interface ToolExecuteContext {
|
|
|
98
98
|
model?: Model;
|
|
99
99
|
thinkingLevel?: ThinkingLevel;
|
|
100
100
|
principal?: string;
|
|
101
|
+
onAsk?: import("./tool-policy.js").OnAsk;
|
|
101
102
|
oneShot?: boolean;
|
|
102
103
|
clientContext?: TaskSpec["clientContext"];
|
|
103
104
|
excludeTools?: readonly string[];
|
|
@@ -410,6 +411,7 @@ export interface TaskResult {
|
|
|
410
411
|
remoteEnvFailures?: RemoteEnvFailureNote[];
|
|
411
412
|
errorMessage?: string;
|
|
412
413
|
errorCode?: string;
|
|
414
|
+
retryAfterMs?: number;
|
|
413
415
|
degraded?: {
|
|
414
416
|
from: string;
|
|
415
417
|
to: string;
|
|
@@ -523,6 +525,7 @@ export type TaskEvent = ({
|
|
|
523
525
|
isError: boolean;
|
|
524
526
|
output?: unknown;
|
|
525
527
|
structured?: unknown;
|
|
528
|
+
errorCode?: string;
|
|
526
529
|
truncated?: boolean;
|
|
527
530
|
totalChars?: number;
|
|
528
531
|
} & TaskEventIdentity) | ({
|
|
@@ -50,6 +50,7 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
|
|
|
50
50
|
private maxOutputTokens?;
|
|
51
51
|
private maxOutputTokensPerCall?;
|
|
52
52
|
private stallTimeoutsPerCall?;
|
|
53
|
+
private abortResultDetails?;
|
|
53
54
|
private loopTrace?;
|
|
54
55
|
private resilience?;
|
|
55
56
|
private maxToolConcurrency?;
|
|
@@ -186,6 +186,7 @@ export class AgentHarness {
|
|
|
186
186
|
maxOutputTokens;
|
|
187
187
|
maxOutputTokensPerCall;
|
|
188
188
|
stallTimeoutsPerCall;
|
|
189
|
+
abortResultDetails;
|
|
189
190
|
loopTrace;
|
|
190
191
|
resilience;
|
|
191
192
|
maxToolConcurrency;
|
|
@@ -216,6 +217,7 @@ export class AgentHarness {
|
|
|
216
217
|
this.maxOutputTokens = options.maxOutputTokens;
|
|
217
218
|
this.maxOutputTokensPerCall = options.maxOutputTokensPerCall;
|
|
218
219
|
this.stallTimeoutsPerCall = options.stallTimeoutsPerCall;
|
|
220
|
+
this.abortResultDetails = options.abortResultDetails;
|
|
219
221
|
this.loopTrace = options.loopTrace;
|
|
220
222
|
this.resilience = options.resilience;
|
|
221
223
|
this.maxToolConcurrency = options.maxToolConcurrency;
|
|
@@ -440,6 +442,7 @@ export class AgentHarness {
|
|
|
440
442
|
...(this.maxOutputTokens !== undefined ? { maxTokens: this.maxOutputTokens } : {}),
|
|
441
443
|
...(this.maxOutputTokensPerCall !== undefined ? { maxTokensPerCall: this.maxOutputTokensPerCall } : {}),
|
|
442
444
|
...(this.stallTimeoutsPerCall !== undefined ? { stallTimeoutsPerCall: this.stallTimeoutsPerCall } : {}),
|
|
445
|
+
...(this.abortResultDetails !== undefined ? { abortResultDetails: this.abortResultDetails } : {}),
|
|
443
446
|
...(this.resilience !== undefined ? { resilience: this.resilience } : {}),
|
|
444
447
|
...(this.maxToolConcurrency !== undefined ? { maxToolConcurrency: this.maxToolConcurrency } : {}),
|
|
445
448
|
...(this.streamingToolExecution === true && (this.getHandlers("tool_call")?.size ?? 0) === 0
|
|
@@ -542,6 +542,7 @@ export interface AgentHarnessOptions<TSkill extends Skill = Skill, TPromptTempla
|
|
|
542
542
|
maxOutputTokens?: number;
|
|
543
543
|
maxOutputTokensPerCall?: () => number | undefined;
|
|
544
544
|
stallTimeoutsPerCall?: () => import("../llm/types.js").StallTimeouts | undefined;
|
|
545
|
+
abortResultDetails?: () => Record<string, unknown> | undefined;
|
|
545
546
|
loopTrace?: (step: import("../loop/agent-loop.js").LoopStep) => void;
|
|
546
547
|
resilience?: import("../llm/types.js").ResilienceOptions;
|
|
547
548
|
maxToolConcurrency?: number;
|