@sema-agent/core 2.8.0 → 2.10.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/dist/agents/send-message-tool.js +37 -29
- package/dist/agents/subagent.js +91 -4
- package/dist/brain/circuit-breaker.js +18 -8
- package/dist/brain/retry.d.ts +1 -0
- package/dist/brain/retry.js +29 -7
- package/dist/brain/stream-engine.d.ts +1 -0
- package/dist/brain/stream-engine.js +74 -12
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/defaults.js +1 -0
- package/dist/core/auto-compaction.js +9 -1
- package/dist/core/background-agent-store.d.ts +2 -0
- package/dist/core/background-agent-store.js +20 -0
- package/dist/core/mcp.js +8 -5
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +2 -1
- package/dist/core/runner/prepare-task.js +61 -20
- package/dist/core/runner/runtask.js +38 -12
- package/dist/core/runner/tool-disclosure.d.ts +8 -3
- package/dist/core/runner/tool-disclosure.js +39 -10
- package/dist/core/skills-directory.d.ts +1 -1
- package/dist/core/skills-directory.js +257 -28
- package/dist/core/task-registry-agent.d.ts +2 -1
- package/dist/core/task-registry-agent.js +50 -54
- package/dist/core/task-registry.d.ts +1 -0
- package/dist/core/task-registry.js +1 -1
- package/dist/core/types.d.ts +9 -1
- package/dist/engine/compaction/compaction.js +71 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/harness-types.d.ts +1 -1
- package/dist/internal/harness.d.ts +1 -1
- package/dist/internal/harness.js +1 -1
- package/dist/orchestration/run-workflow-tool.d.ts +1 -0
- package/dist/orchestration/run-workflow-tool.js +4 -1
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/orchestration/workflow.js +20 -12
- package/dist/tools/fs/bash-readonly-classifier.js +83 -17
- package/dist/tools/fs/fs-bash.js +17 -11
- package/dist/tools/fs/fs-shared.d.ts +1 -0
- package/dist/tools/fs/fs-shared.js +44 -2
- package/dist/tools/web.d.ts +15 -0
- package/dist/tools/web.js +42 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, prepareCompaction, shouldCompact, } from "../internal/harness.js";
|
|
1
|
+
import { DEFAULT_CHARS_PER_TOKEN, DEFAULT_CLAMP_TOLERANCE, DEFAULT_COMPACTION_SETTINGS, CompactionError, compact, computeFileLists, dryRunSummarizationClamp, estimateContextTokens, estimateTokens, formatPersistedOutputRefs, prepareCompaction, shouldCompact, } from "../internal/harness.js";
|
|
2
2
|
import { fileArgPath } from "../tools/fs/safety.js";
|
|
3
3
|
import { contextEditFrontier } from "./context-edit.js";
|
|
4
4
|
import { selectCompactionEpoch } from "../prompt-assembly/epoch.js";
|
|
@@ -155,6 +155,14 @@ export async function maybeCompact(opts) {
|
|
|
155
155
|
if (prep.value.invokedSkills.length > 0) {
|
|
156
156
|
details.invokedSkills = prep.value.invokedSkills;
|
|
157
157
|
}
|
|
158
|
+
const reusedRefs = prep.value.persistedOutputRefs ?? [];
|
|
159
|
+
if (reusedRefs.length > 0) {
|
|
160
|
+
details.persistedOutputRefs = reusedRefs;
|
|
161
|
+
summary += formatPersistedOutputRefs(reusedRefs);
|
|
162
|
+
}
|
|
163
|
+
if (prep.value.elidedMessages !== undefined && prep.value.elidedMessages > 0) {
|
|
164
|
+
details.elidedMessages = prep.value.elidedMessages;
|
|
165
|
+
}
|
|
158
166
|
}
|
|
159
167
|
else {
|
|
160
168
|
let summaryModel = opts.compactionModel ?? opts.model;
|
|
@@ -46,6 +46,8 @@ export interface BackgroundAgentRecord {
|
|
|
46
46
|
usage?: BackgroundAgentUsage;
|
|
47
47
|
rev: number;
|
|
48
48
|
}
|
|
49
|
+
export declare const REVIVED_ROW_CLEARED_FIELDS: readonly ["settledAt", "stoppedBy", "completionId", "finalOutput", "finalOutputFull", "error", "errorCode", "errorRetryable", "errorKind", "resultIsPartial", "summary", "recentSteps", "editedFiles", "usage"];
|
|
50
|
+
export declare function clearRevivedRowTerminalPayload(record: BackgroundAgentRecord): void;
|
|
49
51
|
export interface BackgroundAgentRowSummary {
|
|
50
52
|
handle: string;
|
|
51
53
|
owner: string;
|
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { uuidv7 } from "../internal/harness.js";
|
|
2
|
+
export const REVIVED_ROW_CLEARED_FIELDS = [
|
|
3
|
+
"settledAt",
|
|
4
|
+
"stoppedBy",
|
|
5
|
+
"completionId",
|
|
6
|
+
"finalOutput",
|
|
7
|
+
"finalOutputFull",
|
|
8
|
+
"error",
|
|
9
|
+
"errorCode",
|
|
10
|
+
"errorRetryable",
|
|
11
|
+
"errorKind",
|
|
12
|
+
"resultIsPartial",
|
|
13
|
+
"summary",
|
|
14
|
+
"recentSteps",
|
|
15
|
+
"editedFiles",
|
|
16
|
+
"usage",
|
|
17
|
+
];
|
|
18
|
+
export function clearRevivedRowTerminalPayload(record) {
|
|
19
|
+
for (const field of REVIVED_ROW_CLEARED_FIELDS)
|
|
20
|
+
delete record[field];
|
|
21
|
+
}
|
|
2
22
|
export class BackgroundAgentStoreError extends Error {
|
|
3
23
|
code;
|
|
4
24
|
constructor(code, message) {
|
package/dist/core/mcp.js
CHANGED
|
@@ -145,6 +145,11 @@ const MCP_SPEC_ERROR_CODE_NAMES = new Map([
|
|
|
145
145
|
export function describeMcpSpecErrorCode(code) {
|
|
146
146
|
return typeof code === "number" ? MCP_SPEC_ERROR_CODE_NAMES.get(code) : undefined;
|
|
147
147
|
}
|
|
148
|
+
function namedMcpFailureText(err) {
|
|
149
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
150
|
+
const condition = err instanceof McpError ? describeMcpSpecErrorCode(err.code) : undefined;
|
|
151
|
+
return condition !== undefined ? `${condition}: ${detail}` : detail;
|
|
152
|
+
}
|
|
148
153
|
function isTransportLost(err) {
|
|
149
154
|
if (err instanceof McpError && err.code === ErrorCode.ConnectionClosed)
|
|
150
155
|
return true;
|
|
@@ -555,7 +560,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
555
560
|
}
|
|
556
561
|
else {
|
|
557
562
|
warnings.push(asServerWarning(spec, r.reason));
|
|
558
|
-
statuses.push({ name: spec.name, status: "failed", error:
|
|
563
|
+
statuses.push({ name: spec.name, status: "failed", error: namedMcpFailureText(r.reason) });
|
|
559
564
|
}
|
|
560
565
|
}
|
|
561
566
|
const resourceTools = buildResourceTools(resourceServers);
|
|
@@ -614,7 +619,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
614
619
|
toolCount: h.toolNames.length,
|
|
615
620
|
added: [],
|
|
616
621
|
removed: [],
|
|
617
|
-
error: inlineUntrusted(
|
|
622
|
+
error: inlineUntrusted(namedMcpFailureText(err), 240),
|
|
618
623
|
});
|
|
619
624
|
}
|
|
620
625
|
}
|
|
@@ -1155,9 +1160,7 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
1155
1160
|
return { serverTools, serverAxes, dropped };
|
|
1156
1161
|
}
|
|
1157
1162
|
function asServerWarning(spec, err) {
|
|
1158
|
-
const
|
|
1159
|
-
const condition = err instanceof McpError ? describeMcpSpecErrorCode(err.code) : undefined;
|
|
1160
|
-
const warning = new Error(`mcp: server "${spec.name}" failed to connect — skipped (${condition !== undefined ? `${condition}: ` : ""}${detail})`, { cause: err });
|
|
1163
|
+
const warning = new Error(`mcp: server "${spec.name}" failed to connect — skipped (${namedMcpFailureText(err)})`, { cause: err });
|
|
1161
1164
|
warning.code = "mcp.server_unavailable";
|
|
1162
1165
|
return warning;
|
|
1163
1166
|
}
|
|
@@ -143,5 +143,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
143
143
|
void _internalCompaction;
|
|
144
144
|
if (flags.unpricedSpend)
|
|
145
145
|
delete publicStats.costMicroUsd;
|
|
146
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, stats: publicStats };
|
|
146
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, checkpointToken, checkpointGate, ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), stats: publicStats };
|
|
147
147
|
}
|
|
@@ -17,7 +17,7 @@ import { type CwdRef } from "../../tools/fs/index.js";
|
|
|
17
17
|
import type { Runner } from "./runtask.js";
|
|
18
18
|
import { type CheckpointGate, type CheckpointState, type CheckpointToken, type ResourceLedger, type ResourceLimitReason } from "../checkpoint-store.js";
|
|
19
19
|
import type { AgentMessage, AgentTool, ExecutionEnv } from "../../internal/harness.js";
|
|
20
|
-
import type { RunnerDeps, TaskEvent, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
20
|
+
import type { RunnerDeps, TaskEvent, TaskResult, TaskSpec, ToolActivity, ToolEffect } from "../types.js";
|
|
21
21
|
import type { RepairBundle } from "../../agents/repair-loop.js";
|
|
22
22
|
export declare const DEFAULT_IRREVERSIBLE_SCOPE = "irreversible";
|
|
23
23
|
export declare function checkpointScopeOf(spec: {
|
|
@@ -48,6 +48,7 @@ export interface Prepared {
|
|
|
48
48
|
tasks: number;
|
|
49
49
|
costMicroUsd: number;
|
|
50
50
|
};
|
|
51
|
+
rewindNotes?: NonNullable<TaskResult["rewindNotes"]>;
|
|
51
52
|
cwdRef?: CwdRef;
|
|
52
53
|
worktreeSessionRef?: {
|
|
53
54
|
current?: {
|
|
@@ -616,8 +616,37 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
616
616
|
catch {
|
|
617
617
|
}
|
|
618
618
|
}
|
|
619
|
-
|
|
619
|
+
const rewindNotes = [];
|
|
620
|
+
const rewindCaptureRequested = spec.rewindFiles === true;
|
|
621
|
+
let rewindTarget = spec.resumeAt !== undefined ? (rewindCaptureRequested ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
620
622
|
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
623
|
+
if (spec.resumeAt !== undefined && !rewindCaptureRequested) {
|
|
624
|
+
rewindNotes.push({
|
|
625
|
+
code: "conversation_only",
|
|
626
|
+
message: `the conversation was branched at entry "${spec.resumeAt}" but the working tree was NOT rewound — this task set resumeAt without rewindFiles, so files remain at their current state`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
if (!deps.fileSnapshotStore) {
|
|
630
|
+
if (rewindTarget !== undefined) {
|
|
631
|
+
const e = new Error(`rewind-files: restoring the working tree to entry "${rewindTarget}" requires a snapshot backend, but this deployment wired no RunnerDeps.fileSnapshotStore — no snapshot was ever captured, so the files were NOT rewound`);
|
|
632
|
+
e.code = "rewind.store_unconfigured";
|
|
633
|
+
throw e;
|
|
634
|
+
}
|
|
635
|
+
if (rewindCaptureRequested) {
|
|
636
|
+
rewindNotes.push({
|
|
637
|
+
code: "snapshot_store_unconfigured",
|
|
638
|
+
message: "rewindFiles was requested but no RunnerDeps.fileSnapshotStore is wired — no working-tree snapshot was captured for this turn, so it cannot be rewound to later",
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else if (!handsEnabled) {
|
|
643
|
+
if (rewindTarget !== undefined || rewindCaptureRequested) {
|
|
644
|
+
rewindNotes.push({
|
|
645
|
+
code: "files_env_unsupported",
|
|
646
|
+
message: "the file side of rewind was inert: this deployment mounts no filesystem-capable ExecutionEnv, so no working-tree snapshot was captured or restored",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
}
|
|
621
650
|
if (rewindTarget !== undefined && deps.fileSnapshotStore && handsEnabled) {
|
|
622
651
|
if (rewindBefore) {
|
|
623
652
|
let anchor;
|
|
@@ -642,18 +671,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
642
671
|
const restoreSignal = spec.signal ? AbortSignal.any([abortController.signal, spec.signal]) : abortController.signal;
|
|
643
672
|
const restored = await deps.fileSnapshotStore.restore(sessionId, rewindTarget, executionEnv, restoreRoot, restoreSignal);
|
|
644
673
|
if (!restored.ok) {
|
|
645
|
-
if (restored.error.code === "not_found"
|
|
646
|
-
const e = new Error(
|
|
674
|
+
if (restored.error.code === "not_found") {
|
|
675
|
+
const e = new Error(rewindBefore
|
|
676
|
+
? `rewind-files: the resolved "before" snapshot anchor "${rewindTarget}" disappeared before restore — files were NOT rewound`
|
|
677
|
+
: `rewind-files: no file snapshot exists for entry "${rewindTarget}" on session "${sessionId}" — the working tree was NOT rewound (only a COMPLETED turn that ran with rewindFiles is snapshotted, and snapshots are keyed by that turn's END leaf; earlier turns that ran without rewindFiles, mid-turn entries, and reaped snapshots have none)`);
|
|
647
678
|
e.code = "rewind_snapshot.unresolvable";
|
|
648
679
|
throw e;
|
|
649
680
|
}
|
|
650
|
-
if (restored.error.code === "not_found") {
|
|
651
|
-
try {
|
|
652
|
-
deps.onError?.(new Error(`rewind-files: no snapshot for entry "${rewindTarget}" — files left unchanged`), { phase: "rewind", sessionId });
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
681
|
else {
|
|
658
682
|
const e = new Error(`rewind-files restore failed (${restored.error.code}): ${restored.error.message}`);
|
|
659
683
|
e.code = "rewind.restore_failed";
|
|
@@ -758,6 +782,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
758
782
|
clientContext: spec.clientContext,
|
|
759
783
|
excludeTools: toolFaceSnapshot.exclude,
|
|
760
784
|
deferTools: toolFaceSnapshot.defer,
|
|
785
|
+
alwaysLoadTools: toolFaceSnapshot.alwaysLoad,
|
|
761
786
|
promptProfile,
|
|
762
787
|
...(spec.additionalDirectories !== undefined ? { additionalDirectories: Object.freeze([...spec.additionalDirectories]) } : {}),
|
|
763
788
|
...(spec.envFacts !== undefined ? { envFacts: { ...spec.envFacts } } : {}),
|
|
@@ -885,6 +910,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
885
910
|
governanceBaseline: deps.workflowGovernanceBaseline,
|
|
886
911
|
parentExcludeTools: toolFaceSnapshot.exclude,
|
|
887
912
|
parentDeferTools: toolFaceSnapshot.defer,
|
|
913
|
+
parentAlwaysLoadTools: toolFaceSnapshot.alwaysLoad,
|
|
888
914
|
parentPromptProfile: promptProfile,
|
|
889
915
|
models: deps.models,
|
|
890
916
|
agents: deps.agents,
|
|
@@ -1796,7 +1822,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1796
1822
|
deferNames: (toolFaceSnapshot.defer ?? []).filter((n) => tools.some((t) => t.name === n)),
|
|
1797
1823
|
alwaysLoadNames: [
|
|
1798
1824
|
...(toolFaceSnapshot.alwaysLoad ?? []),
|
|
1799
|
-
...mcp.tools
|
|
1825
|
+
...mcp.tools
|
|
1826
|
+
.filter((t) => t.mcpAlwaysLoad === true && !(toolFaceSnapshot.defer ?? []).includes(t.name))
|
|
1827
|
+
.map((t) => t.name),
|
|
1800
1828
|
],
|
|
1801
1829
|
});
|
|
1802
1830
|
for (const n of [...deferred]) {
|
|
@@ -1831,21 +1859,33 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1831
1859
|
throw e;
|
|
1832
1860
|
}
|
|
1833
1861
|
const registry = buildDeferredRegistry(deferred, tools);
|
|
1834
|
-
const realByName = new Map(tools.map((t) => [t.name, t]));
|
|
1835
1862
|
const directCallFor = (name) => {
|
|
1836
1863
|
if (spec.deferSelfResolve === false)
|
|
1837
1864
|
return undefined;
|
|
1838
|
-
const
|
|
1839
|
-
if (real === undefined)
|
|
1840
|
-
return undefined;
|
|
1865
|
+
const executionMode = tools.find((t) => t.name === name)?.executionMode;
|
|
1841
1866
|
return {
|
|
1842
|
-
|
|
1843
|
-
|
|
1867
|
+
resolveReal: () => {
|
|
1868
|
+
const real = tools.find((t) => t.name === name);
|
|
1869
|
+
if (real === undefined)
|
|
1870
|
+
return undefined;
|
|
1871
|
+
return {
|
|
1872
|
+
parameters: real.parameters,
|
|
1873
|
+
invoke: (toolCallId, params, signal, onUpdate) => real.execute(toolCallId, params, signal, onUpdate),
|
|
1874
|
+
};
|
|
1875
|
+
},
|
|
1876
|
+
...(executionMode !== undefined ? { executionMode } : {}),
|
|
1844
1877
|
activate: async () => {
|
|
1845
1878
|
if (activeTools.has(name))
|
|
1846
|
-
return;
|
|
1879
|
+
return undefined;
|
|
1847
1880
|
activeTools.add(name);
|
|
1848
|
-
|
|
1881
|
+
try {
|
|
1882
|
+
await rematerialize(activeTools);
|
|
1883
|
+
}
|
|
1884
|
+
catch (e) {
|
|
1885
|
+
activeTools.delete(name);
|
|
1886
|
+
throw e;
|
|
1887
|
+
}
|
|
1888
|
+
return listingRideRef.current?.([name]);
|
|
1849
1889
|
},
|
|
1850
1890
|
};
|
|
1851
1891
|
};
|
|
@@ -1918,6 +1958,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1918
1958
|
rematerialize,
|
|
1919
1959
|
listingRide: (newly) => listingRideRef.current?.(newly),
|
|
1920
1960
|
mountedNames: () => new Set(buildToolList(activeTools).map((t) => t.name).filter((n) => !registry.has(n) || activeTools.has(n))),
|
|
1961
|
+
directCallEnabled: spec.deferSelfResolve !== false,
|
|
1921
1962
|
});
|
|
1922
1963
|
harnessTools = buildToolList(activeTools);
|
|
1923
1964
|
}
|
|
@@ -3341,7 +3382,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3341
3382
|
: undefined;
|
|
3342
3383
|
overheadState.promptChars = systemPrompt.length;
|
|
3343
3384
|
const preparedHolder = {};
|
|
3344
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, 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, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), 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 } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3385
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, 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, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), 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 } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3345
3386
|
const prepared = buildPrepared();
|
|
3346
3387
|
preparedHolder.current = prepared;
|
|
3347
3388
|
return prepared;
|
|
@@ -688,13 +688,6 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
688
688
|
queue.push({ type: "compaction_outcome", outcome, trigger: passTrigger, ...ident() });
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
|
-
if (comp.compacted && !forceManual) {
|
|
692
|
-
if (recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
|
|
693
|
-
compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
|
|
694
|
-
runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
|
|
695
|
-
queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
691
|
runnerHooks.recordCompactionReuse(prepared, comp);
|
|
699
692
|
if (comp.compacted) {
|
|
700
693
|
queue.push({
|
|
@@ -713,6 +706,11 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
713
706
|
...(comp.clampReason !== undefined ? { clampReason: comp.clampReason } : {}),
|
|
714
707
|
...ident(),
|
|
715
708
|
});
|
|
709
|
+
if (!forceManual && recordCompactionAndCheckRapidRefill(rapidRefill, stats.turns)) {
|
|
710
|
+
compactionBreaker.failures = MAX_CONSECUTIVE_COMPACTION_FAILURES;
|
|
711
|
+
queue.push({ type: "compaction_outcome", outcome: "disabled", trigger: passTrigger, reason: "rapid_refill: compaction disabled for the rest of this task", ...ident() });
|
|
712
|
+
runnerHooks.onError?.(new Error("compaction.rapid_refill: the context refilled within <3 turns of compaction 3 times in a row — compaction disabled for the rest of this task (thrash spiral; the transcript is dominated by incompressible content)"), { phase: "compaction", sessionId: prepared.sessionId });
|
|
713
|
+
}
|
|
716
714
|
if (comp.phaseDurations !== undefined && comp.durationMs !== undefined) {
|
|
717
715
|
const pd = comp.phaseDurations;
|
|
718
716
|
const pdDur = comp.durationMs;
|
|
@@ -2074,6 +2072,9 @@ export class Runner {
|
|
|
2074
2072
|
phase: s.phase,
|
|
2075
2073
|
...(s.detail !== undefined ? { detail: s.detail } : {}),
|
|
2076
2074
|
...(s.retryInSec !== undefined ? { retryInSec: s.retryInSec } : {}),
|
|
2075
|
+
...(s.retryInMs !== undefined ? { retryInMs: s.retryInMs } : {}),
|
|
2076
|
+
...(s.attempt !== undefined ? { attempt: s.attempt } : {}),
|
|
2077
|
+
...(s.maxRetries !== undefined ? { maxRetries: s.maxRetries } : {}),
|
|
2077
2078
|
...ident(),
|
|
2078
2079
|
});
|
|
2079
2080
|
};
|
|
@@ -2675,6 +2676,7 @@ export class Runner {
|
|
|
2675
2676
|
minTokens: rs.counters.compactionFloor,
|
|
2676
2677
|
brain: compactionBrain,
|
|
2677
2678
|
windowSafety: windowSafetyOptions(prepared.model),
|
|
2679
|
+
onCompactionFailed: (reason) => queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "auto", reason, ...ident() }),
|
|
2678
2680
|
});
|
|
2679
2681
|
try {
|
|
2680
2682
|
if (comp?.compacted) {
|
|
@@ -2762,6 +2764,7 @@ export class Runner {
|
|
|
2762
2764
|
threw,
|
|
2763
2765
|
model: prepared.model.id,
|
|
2764
2766
|
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2767
|
+
rewindNotes: prepared.rewindNotes,
|
|
2765
2768
|
abortedForTimeout: timeout.fired,
|
|
2766
2769
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2767
2770
|
abortedLive,
|
|
@@ -3043,10 +3046,19 @@ export class Runner {
|
|
|
3043
3046
|
if (!leafId)
|
|
3044
3047
|
return;
|
|
3045
3048
|
const root = prepared.taskRootPath;
|
|
3046
|
-
const
|
|
3047
|
-
if (
|
|
3048
|
-
if (Date.now() - refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS)
|
|
3049
|
+
const refusal = this.snapshotTooLargeRoots.get(root);
|
|
3050
|
+
if (refusal !== undefined) {
|
|
3051
|
+
if (Date.now() - refusal.refusedAt < SNAPSHOT_TOO_LARGE_TTL_MS) {
|
|
3052
|
+
if (!refusal.skipAnnounced) {
|
|
3053
|
+
refusal.skipAnnounced = true;
|
|
3054
|
+
try {
|
|
3055
|
+
this.deps.onError?.(new Error(`rewind-files snapshot skipped for this working-tree root: a too_large refusal is still inside its ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown (re-probed at ${new Date(refusal.refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}) — turns completed during the cooldown capture no snapshot and cannot be rewound to. Announced once per cooldown window.`), { phase: "rewind", sessionId: prepared.sessionId, classification: "too_large" });
|
|
3056
|
+
}
|
|
3057
|
+
catch {
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3049
3060
|
return;
|
|
3061
|
+
}
|
|
3050
3062
|
this.snapshotTooLargeRoots.delete(root);
|
|
3051
3063
|
}
|
|
3052
3064
|
const ac = new AbortController();
|
|
@@ -3069,11 +3081,14 @@ export class Runner {
|
|
|
3069
3081
|
}
|
|
3070
3082
|
if (!r.ok) {
|
|
3071
3083
|
const tooLarge = r.error.code === "too_large";
|
|
3084
|
+
const refusedAt = Date.now();
|
|
3072
3085
|
if (tooLarge)
|
|
3073
|
-
this.snapshotTooLargeRoots.set(root,
|
|
3086
|
+
this.snapshotTooLargeRoots.set(root, { refusedAt, skipAnnounced: false });
|
|
3074
3087
|
try {
|
|
3075
3088
|
this.deps.onError?.(new Error(`rewind-files snapshot failed (${r.error.code}): ${r.error.message}` +
|
|
3076
|
-
(tooLarge
|
|
3089
|
+
(tooLarge
|
|
3090
|
+
? ` — skipping further snapshots for this root (this process) during a ${SNAPSHOT_TOO_LARGE_TTL_MS / 60_000}-minute cooldown, until ${new Date(refusedAt + SNAPSHOT_TOO_LARGE_TTL_MS).toISOString()}, when the tree is re-probed automatically. Raise the store's snapshotBounds to accept a tree this size; shrinking the tree also re-enables rewind, but only at that re-probe — not immediately`
|
|
3091
|
+
: "")), { phase: "rewind", sessionId: prepared.sessionId, ...(tooLarge ? { classification: "too_large" } : {}) });
|
|
3077
3092
|
}
|
|
3078
3093
|
catch {
|
|
3079
3094
|
}
|
|
@@ -3516,6 +3531,17 @@ export class Runner {
|
|
|
3516
3531
|
comp = finishComp;
|
|
3517
3532
|
}
|
|
3518
3533
|
catch (err) {
|
|
3534
|
+
const failMsg = String(err instanceof Error ? err.message : err);
|
|
3535
|
+
const failReason = failMsg.length > 512 ? `${failMsg.slice(0, 512)}…` : failMsg;
|
|
3536
|
+
emitTrace(spec.tracer ?? this.deps.tracer, () => ({
|
|
3537
|
+
kind: "compaction.failed",
|
|
3538
|
+
version: 1,
|
|
3539
|
+
taskId: spec.taskId ?? prepared.sessionId,
|
|
3540
|
+
trigger: "auto",
|
|
3541
|
+
reason: failReason,
|
|
3542
|
+
ts: Date.now(),
|
|
3543
|
+
}));
|
|
3544
|
+
opts?.onCompactionFailed?.(failReason);
|
|
3519
3545
|
this.deps.onError?.(err, { phase: "compaction", sessionId: prepared.sessionId });
|
|
3520
3546
|
}
|
|
3521
3547
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TSchema } from "typebox";
|
|
2
|
-
import type { AgentMessage, AgentTool, AgentToolResult } from "../../internal/harness-types.js";
|
|
2
|
+
import type { AgentMessage, AgentTool, AgentToolResult, AgentToolUpdateCallback, ToolExecutionMode } from "../../internal/harness-types.js";
|
|
3
3
|
import type { Model } from "../../internal/llm.js";
|
|
4
4
|
import type { ToolSpec } from "../types.js";
|
|
5
5
|
export declare const TOOL_SEARCH_NAME = "ToolSearch";
|
|
@@ -29,9 +29,13 @@ export declare function buildDeferredRegistry(deferred: ReadonlySet<string>, too
|
|
|
29
29
|
description: string;
|
|
30
30
|
}>): Map<string, DeferredToolInfo>;
|
|
31
31
|
export interface PlaceholderDirectCall {
|
|
32
|
+
resolveReal: () => PlaceholderDirectTarget | undefined;
|
|
33
|
+
executionMode?: ToolExecutionMode;
|
|
34
|
+
activate: () => Promise<string | undefined>;
|
|
35
|
+
}
|
|
36
|
+
export interface PlaceholderDirectTarget {
|
|
32
37
|
parameters: TSchema;
|
|
33
|
-
invoke: (toolCallId: string, params: unknown, signal?: AbortSignal) => Promise<AgentToolResult<unknown>>;
|
|
34
|
-
activate: () => Promise<void>;
|
|
38
|
+
invoke: (toolCallId: string, params: unknown, signal?: AbortSignal, onUpdate?: AgentToolUpdateCallback<unknown>) => Promise<AgentToolResult<unknown>>;
|
|
35
39
|
}
|
|
36
40
|
export declare function createPlaceholderTool(info: DeferredToolInfo, direct?: PlaceholderDirectCall): AgentTool;
|
|
37
41
|
export declare function scoreToolMatch(query: string, info: DeferredToolInfo): number;
|
|
@@ -52,4 +56,5 @@ export declare function createToolSearchTool(opts: {
|
|
|
52
56
|
rematerialize: (active: ReadonlySet<string>) => Promise<void>;
|
|
53
57
|
listingRide?: (newlyActivated: readonly string[]) => string | undefined;
|
|
54
58
|
mountedNames?: () => ReadonlySet<string>;
|
|
59
|
+
directCallEnabled?: boolean;
|
|
55
60
|
}): AgentTool;
|
|
@@ -43,7 +43,8 @@ export function classifyDeferred(opts) {
|
|
|
43
43
|
deferred.add(name);
|
|
44
44
|
if (opts.deferMode === "auto") {
|
|
45
45
|
const candidates = opts.fullTools.filter((t) => !deferred.has(t.name) && !pinned.has(t.name));
|
|
46
|
-
const
|
|
46
|
+
const inlineFace = opts.fullTools.filter((t) => !deferred.has(t.name));
|
|
47
|
+
const total = inlineFace.reduce((n, t) => n + inlinedChars(t), 0);
|
|
47
48
|
const window = (opts.model.contextTokens ?? opts.model.contextWindow ?? 0) * CHARS_PER_TOKEN;
|
|
48
49
|
if (window > 0 && total > DEFER_AUTO_FRACTION * window) {
|
|
49
50
|
for (const t of candidates)
|
|
@@ -70,12 +71,19 @@ export function createPlaceholderTool(info, direct) {
|
|
|
70
71
|
return {
|
|
71
72
|
name: info.name,
|
|
72
73
|
label: info.name,
|
|
73
|
-
description: `${info.hint} — deferred:
|
|
74
|
+
description: `${info.hint} — deferred: its parameters are not listed here. Call ` +
|
|
75
|
+
`${TOOL_SEARCH_NAME}({"query":"select:${sn}"}) to load them; a call that already matches this ` +
|
|
76
|
+
`tool's real parameters runs directly and activates it.`,
|
|
74
77
|
parameters: EMPTY_PARAMS,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
...(direct.executionMode !== undefined ? { executionMode: direct.executionMode } : {}),
|
|
79
|
+
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
80
|
+
const real = direct.resolveReal();
|
|
81
|
+
if (real !== undefined && Value.Check(real.parameters, params)) {
|
|
82
|
+
const ride = await direct.activate();
|
|
83
|
+
const result = await real.invoke(toolCallId, params, signal, onUpdate);
|
|
84
|
+
if (ride === undefined || ride === "")
|
|
85
|
+
return result;
|
|
86
|
+
return { ...result, content: [...result.content, { type: "text", text: ride }] };
|
|
79
87
|
}
|
|
80
88
|
return teachingRejection();
|
|
81
89
|
},
|
|
@@ -163,28 +171,49 @@ export function resolveToolSearch(args, registry) {
|
|
|
163
171
|
}
|
|
164
172
|
export function extractDiscoveredToolNames(messages, registry) {
|
|
165
173
|
const names = new Set();
|
|
174
|
+
const pendingDirect = new Map();
|
|
166
175
|
for (const m of messages) {
|
|
176
|
+
if (m.role === "toolResult") {
|
|
177
|
+
const name = pendingDirect.get(m.toolCallId);
|
|
178
|
+
if (name !== undefined && m.isError !== true)
|
|
179
|
+
names.add(name);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
167
182
|
if (m.role !== "assistant")
|
|
168
183
|
continue;
|
|
169
184
|
for (const part of m.content) {
|
|
170
|
-
if (part.type
|
|
185
|
+
if (part.type !== "toolCall")
|
|
186
|
+
continue;
|
|
187
|
+
if (part.name === TOOL_SEARCH_NAME) {
|
|
171
188
|
for (const n of resolveToolSearch(part.arguments, registry)) {
|
|
172
189
|
names.add(n);
|
|
173
190
|
}
|
|
174
191
|
}
|
|
192
|
+
else if (registry.has(part.name)) {
|
|
193
|
+
pendingDirect.set(part.id, part.name);
|
|
194
|
+
}
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
197
|
return [...names];
|
|
178
198
|
}
|
|
179
199
|
export function createToolSearchTool(opts) {
|
|
180
200
|
const { registry, active, rematerialize, listingRide, mountedNames } = opts;
|
|
201
|
+
const directCallEnabled = opts.directCallEnabled !== false;
|
|
202
|
+
const activationPosture = directCallEnabled
|
|
203
|
+
? "Most tools start as name-only placeholders to keep requests small; activating one here loads its full " +
|
|
204
|
+
"parameter schema. Until you have that schema you cannot reliably form a call, so activate a tool rather " +
|
|
205
|
+
"than guessing its arguments — a call that does match the real schema executes and activates the tool. " +
|
|
206
|
+
"When any instruction, reminder, or another tool's description names a deferred tool, activate it here " +
|
|
207
|
+
'with query "select:<name>". '
|
|
208
|
+
: "Most tools start as name-only placeholders to keep requests small; to USE one you must activate it here " +
|
|
209
|
+
"first. When any instruction, reminder, or another tool's description names a deferred tool, activate it " +
|
|
210
|
+
'with query "select:<name>" before calling it. ';
|
|
181
211
|
let activationChain = Promise.resolve();
|
|
182
212
|
return defineTool({
|
|
183
213
|
name: TOOL_SEARCH_NAME,
|
|
184
214
|
contract: { contractId: "core.tool_search@1", implementationRevision: "1" },
|
|
185
|
-
description: "Discover and activate deferred tools.
|
|
186
|
-
|
|
187
|
-
'tool\'s description names a deferred tool, activate it with query "select:<name>" before calling it. ' +
|
|
215
|
+
description: "Discover and activate deferred tools. " +
|
|
216
|
+
activationPosture +
|
|
188
217
|
"Query forms: " +
|
|
189
218
|
'"select:ToolA,ToolB" — activate these exact tools by name; ' +
|
|
190
219
|
'"notebook jupyter" — keyword search, up to max_results best matches; ' +
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SkillSpec } from "./types.js";
|
|
2
|
-
export type SkillsDirectoryWarningCode = "no_skill_file" | "no_frontmatter" | "missing_name" | "invalid_name" | "name_mismatch" | "missing_description" | "description_too_long" | "allowed_tool_not_mounted" | "attachment_skipped" | "read_failed";
|
|
2
|
+
export type SkillsDirectoryWarningCode = "no_skill_file" | "no_frontmatter" | "missing_name" | "invalid_name" | "name_mismatch" | "missing_description" | "description_too_long" | "allowed_tool_not_mounted" | "disallowed_tools_unenforced" | "attachment_skipped" | "read_failed";
|
|
3
3
|
export interface SkillsDirectoryWarning {
|
|
4
4
|
code: SkillsDirectoryWarningCode;
|
|
5
5
|
skill: string;
|