@sema-agent/core 2.1.0 → 2.3.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/observer.d.ts +14 -0
- package/dist/agents/observer.js +58 -9
- package/dist/agents/send-message-tool.js +55 -10
- package/dist/agents/subagent.d.ts +1 -0
- package/dist/agents/subagent.js +69 -15
- package/dist/agents/teacher.js +51 -23
- package/dist/core/context-edit.js +16 -3
- package/dist/core/file-snapshot-store.js +10 -1
- package/dist/core/runner/assemble-result.d.ts +1 -0
- package/dist/core/runner/assemble-result.js +12 -8
- package/dist/core/runner/prepare-task.d.ts +1 -0
- package/dist/core/runner/prepare-task.js +40 -10
- package/dist/core/runner/runtask.js +27 -5
- package/dist/core/runner/synthetic-tools.d.ts +1 -0
- package/dist/core/runner/synthetic-tools.js +18 -15
- package/dist/core/runner/turn-attachments.d.ts +12 -2
- package/dist/core/runner/turn-attachments.js +33 -3
- package/dist/core/task-registry-agent.d.ts +11 -1
- package/dist/core/task-registry-agent.js +40 -3
- package/dist/core/task-registry-monitor.js +149 -29
- package/dist/core/task-registry-shared.d.ts +25 -2
- package/dist/core/task-registry-shared.js +26 -2
- package/dist/core/task-registry.d.ts +5 -0
- package/dist/core/task-registry.js +25 -26
- package/dist/core/tools.d.ts +2 -0
- package/dist/core/tools.js +9 -0
- package/dist/core/types.d.ts +3 -1
- package/dist/core/workflow-journal-store.d.ts +16 -0
- package/dist/core/workflow-journal-store.js +28 -0
- package/dist/engine/session/memory-repo.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
- package/dist/orchestration/workflow-size-guideline.js +19 -9
- package/dist/orchestration/workflow.d.ts +16 -0
- package/dist/orchestration/workflow.js +80 -20
- package/dist/prompt-assembly/assemble.js +3 -7
- package/dist/prompt-assembly/packs/sema-default.js +8 -5
- package/dist/prompts/coordinator.d.ts +1 -1
- package/dist/prompts/coordinator.js +45 -0
- package/dist/prompts/default.d.ts +4 -5
- package/dist/prompts/default.js +16 -18
- package/dist/prompts/simple-sections.d.ts +3 -1
- package/dist/prompts/simple-sections.js +11 -1
- package/dist/stores/file/workflow-journal-store.d.ts +7 -1
- package/dist/stores/file/workflow-journal-store.js +70 -33
- package/dist/tools/fs/bash-readonly-classifier.js +20 -1
- package/dist/tools/fs/fs-bash.d.ts +1 -0
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/fs-read.js +10 -10
- package/dist/tools/fs/fs-search-tools.js +42 -7
- package/dist/tools/fs/fs-write.js +18 -6
- package/dist/tools/fs/index.d.ts +1 -0
- package/dist/tools/fs/index.js +2 -1
- package/dist/tools/fs/safety.d.ts +4 -0
- package/dist/tools/fs/safety.js +102 -6
- package/dist/tools/fs/search.d.ts +1 -0
- package/dist/tools/fs/search.js +23 -3
- package/dist/tools/monitor.d.ts +2 -0
- package/dist/tools/monitor.js +3 -1
- package/package.json +3 -2
|
@@ -28,7 +28,7 @@ import { cloneObserverInput, formatHookFeedback, runToolGate } from "../hooks.js
|
|
|
28
28
|
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
29
29
|
import { CacheBreakDetector, toolsToFingerprintInputs } from "../cache-break-detector.js";
|
|
30
30
|
import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
|
|
31
|
-
import { defineTool } from "../tools.js";
|
|
31
|
+
import { defineTool, isDefineToolProduct } from "../tools.js";
|
|
32
32
|
import { canonicalToolName } from "../tool-name-aliases.js";
|
|
33
33
|
import { pathToUri } from "../lsp-protocol.js";
|
|
34
34
|
import { DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, buildToolResultRef, firstPartyOffloadPolicy, InMemoryToolResultStore, RunnerSharedToolResultStore, ScopedToolResultStore, isVolatileOffloadStore, OFFLOAD_TOOL_NAME, createReadToolResultTool, withToolResultOffload, } from "../tool-result-store.js";
|
|
@@ -93,6 +93,9 @@ export function resolveModelPromptTraits(model, spec, internals) {
|
|
|
93
93
|
fableMitigations: isFableFamilyModelId(model.id),
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
|
+
function isDelegatedNonForkChild(internals) {
|
|
97
|
+
return internals?.isDelegatedChild === true && internals?.insideFork !== true;
|
|
98
|
+
}
|
|
96
99
|
export function batchContextAt(messages, currentId) {
|
|
97
100
|
let batch = [];
|
|
98
101
|
for (const m of messages) {
|
|
@@ -742,6 +745,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
742
745
|
model: harnessRef.current?.getModel(),
|
|
743
746
|
thinkingLevel: harnessRef.current?.getThinkingLevel(),
|
|
744
747
|
principal: spec.principal,
|
|
748
|
+
oneShot: spec.oneShot,
|
|
745
749
|
clientContext: spec.clientContext,
|
|
746
750
|
excludeTools: toolFaceSnapshot.exclude,
|
|
747
751
|
deferTools: toolFaceSnapshot.defer,
|
|
@@ -792,10 +796,15 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
792
796
|
requestStopAfterTurn,
|
|
793
797
|
...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
|
|
794
798
|
});
|
|
795
|
-
const tools = (spec.tools ?? []).map((t) =>
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
+
const tools = (spec.tools ?? []).map((t) => {
|
|
800
|
+
if (isDefineToolProduct(t)) {
|
|
801
|
+
return maybeOffload(t, t);
|
|
802
|
+
}
|
|
803
|
+
return maybeOffload(defineTool({
|
|
804
|
+
...t,
|
|
805
|
+
execute: (args, ctx) => t.execute(args, enrichSpecToolCtx(ctx)),
|
|
806
|
+
}), t);
|
|
807
|
+
});
|
|
799
808
|
const blockedRef = {};
|
|
800
809
|
if (spec.enableBlockedReport !== false) {
|
|
801
810
|
tools.push(createReportBlockedTool(blockedRef));
|
|
@@ -1164,6 +1173,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1164
1173
|
taskRegistry: defaultTaskRegistry,
|
|
1165
1174
|
taskOwner: hostTaskId,
|
|
1166
1175
|
taskScope,
|
|
1176
|
+
oneShot: spec.oneShot,
|
|
1167
1177
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1168
1178
|
...(internals?.onTaskNotification !== undefined ? { taskNotification: internals.onTaskNotification } : {}),
|
|
1169
1179
|
...(internals?.detachHub !== undefined ? { detachHub: internals.detachHub } : {}),
|
|
@@ -1212,7 +1222,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1212
1222
|
if (backgroundTaskToolsActive || workflowToolsActive) {
|
|
1213
1223
|
toolEffects.set("TaskOutput", "read");
|
|
1214
1224
|
toolEffects.set("TaskStop", "write");
|
|
1215
|
-
tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, ...(callCapRef ? { deadlineMs: () => toolCutDeadlineMs(callCapRef, Date.now()) } : {}) })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
|
|
1225
|
+
tools.push(firstPartyOffload(createTaskOutputTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore, notificationWired: internals?.onTaskNotification !== undefined, oneShot: spec.oneShot, toolResultStore: offloadStore, ...(callCapRef ? { deadlineMs: () => toolCutDeadlineMs(callCapRef, Date.now()) } : {}) })), firstPartyOffload(createTaskStopTool({ registry: defaultTaskRegistry, owner: hostTaskId, scope: taskScope, sessionId, workflowStore: deps.workflowRunStore, agentStore: deps.backgroundAgentStore })));
|
|
1216
1226
|
if (runnerSelf && !(spec.tools ?? []).some((t) => t.name === SEND_MESSAGE_TOOL_NAME)) {
|
|
1217
1227
|
const delegationForRevive = (spec.tools ?? []).find((t) => t.agentListing !== undefined);
|
|
1218
1228
|
const reviveSpawn = delegationForRevive !== undefined && deps.backgroundAgentStore !== undefined && deps.mailboxStore !== undefined
|
|
@@ -1272,6 +1282,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1272
1282
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1273
1283
|
...(internals?.onTaskNotification !== undefined ? { onTaskNotification: internals.onTaskNotification } : {}),
|
|
1274
1284
|
...(handsCwdRef !== undefined ? { cwdRef: handsCwdRef } : {}),
|
|
1285
|
+
...(offloadStore !== undefined ? { toolResultStore: offloadStore } : {}),
|
|
1275
1286
|
})));
|
|
1276
1287
|
}
|
|
1277
1288
|
if (handsCwdRef !== undefined) {
|
|
@@ -1405,7 +1416,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1405
1416
|
loaded = await Promise.resolve(deps.loadProjectMemory({
|
|
1406
1417
|
cwd: taskRootPath,
|
|
1407
1418
|
handsEnabled,
|
|
1408
|
-
isSubagent: internals
|
|
1419
|
+
isSubagent: isDelegatedNonForkChild(internals),
|
|
1409
1420
|
...(internals?.agentName ? { agentName: internals.agentName } : {}),
|
|
1410
1421
|
sessionId,
|
|
1411
1422
|
phase: projectMemoryPhase,
|
|
@@ -1432,6 +1443,10 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1432
1443
|
memoryBlock = memoryBlock ? `${memoryBlock}\n\n${projectBlock}` : projectBlock;
|
|
1433
1444
|
}
|
|
1434
1445
|
}
|
|
1446
|
+
const declaredSkillRank = new Map();
|
|
1447
|
+
for (const s of spec.skills ?? [])
|
|
1448
|
+
if (!declaredSkillRank.has(s.name))
|
|
1449
|
+
declaredSkillRank.set(s.name, declaredSkillRank.size);
|
|
1435
1450
|
const skillSpecs = normalizeSkills(spec.skills ?? []).filter((s) => {
|
|
1436
1451
|
if (s.content.length <= SKILL_CONTENT_MAX_CHARS)
|
|
1437
1452
|
return true;
|
|
@@ -1457,6 +1472,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1457
1472
|
name: s.name,
|
|
1458
1473
|
description: s.description,
|
|
1459
1474
|
...(s.files !== undefined ? { files: s.files.map((f) => ({ path: f.path })) } : {}),
|
|
1475
|
+
...(declaredSkillRank.get(s.name) !== undefined ? { declaredRank: declaredSkillRank.get(s.name) } : {}),
|
|
1460
1476
|
})),
|
|
1461
1477
|
seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
|
|
1462
1478
|
}
|
|
@@ -1485,7 +1501,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1485
1501
|
awarenessEnabled: thinking !== undefined && ULTRA_REASONING_TIERS.has(thinking),
|
|
1486
1502
|
worktreeIsolated: internals?.isolation === "worktree" && ownedEnv !== undefined,
|
|
1487
1503
|
withinTaskCompactionEnabled: (spec.compaction?.enabled ?? true) && (spec.compaction?.withinTask ?? true),
|
|
1488
|
-
isSubagent: internals
|
|
1504
|
+
isSubagent: isDelegatedNonForkChild(internals),
|
|
1489
1505
|
};
|
|
1490
1506
|
const userSystemPrompt = spec.systemPrompt ?? resolvedRole.systemPrompt ?? internals?.defaultSystemPrompt;
|
|
1491
1507
|
const userAppendSystemPrompt = spec.appendSystemPrompt;
|
|
@@ -2382,7 +2398,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
2382
2398
|
: foldedPolicy;
|
|
2383
2399
|
const hooks = spec.hooks ?? deps.hooks;
|
|
2384
2400
|
const onAsk = spec.onAsk ?? deps.onAsk;
|
|
2385
|
-
const handWriteTools = handsEnabled
|
|
2401
|
+
const handWriteTools = handsEnabled && spec.handsReadOnly !== true
|
|
2386
2402
|
? Object.keys(HAND_TOOL_EFFECTS).filter((name) => {
|
|
2387
2403
|
const eff = HAND_TOOL_EFFECTS[name];
|
|
2388
2404
|
return eff === "write" || eff === "idempotent";
|
|
@@ -3167,15 +3183,29 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3167
3183
|
const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
|
|
3168
3184
|
? (attachedComplete, preserveReadState) => applyCompactionToReadFileState(readFileStateForCheckpoint, attachedComplete, preserveReadState)
|
|
3169
3185
|
: undefined;
|
|
3186
|
+
const changedFilesReadDenied = denyNarrowingPolicy === undefined
|
|
3187
|
+
? undefined
|
|
3188
|
+
: async (path) => {
|
|
3189
|
+
try {
|
|
3190
|
+
const d = await denyNarrowingPolicy.check({ toolName: "Read", args: { file_path: path }, toolCallId: "changed-files-visibility-probe" }, abortController.signal);
|
|
3191
|
+
return d.action === "deny";
|
|
3192
|
+
}
|
|
3193
|
+
catch {
|
|
3194
|
+
return false;
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3170
3197
|
const detectExternalChanges = handsEnabled && readFileStateForCheckpoint
|
|
3171
3198
|
? async (maxFiles) => {
|
|
3172
3199
|
const candidates = [...readFileStateForCheckpoint.entries()]
|
|
3173
3200
|
.filter(([, e]) => e.lastReadAt !== undefined)
|
|
3201
|
+
.filter(([, e]) => e.truncated !== true)
|
|
3174
3202
|
.sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
|
|
3175
3203
|
.slice(0, Math.max(0, maxFiles));
|
|
3176
3204
|
const changed = [];
|
|
3177
3205
|
const evicted = [];
|
|
3178
3206
|
for (const [path, entry] of candidates) {
|
|
3207
|
+
if (changedFilesReadDenied !== undefined && (await changedFilesReadDenied(path)))
|
|
3208
|
+
continue;
|
|
3179
3209
|
try {
|
|
3180
3210
|
const info = await executionEnv.fileInfo(path, abortController.signal);
|
|
3181
3211
|
if (!info.ok) {
|
|
@@ -3249,7 +3279,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3249
3279
|
: undefined;
|
|
3250
3280
|
overheadState.promptChars = systemPrompt.length;
|
|
3251
3281
|
const preparedHolder = {};
|
|
3252
|
-
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, 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 } : {}) });
|
|
3282
|
+
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 } : {}) });
|
|
3253
3283
|
const prepared = buildPrepared();
|
|
3254
3284
|
preparedHolder.current = prepared;
|
|
3255
3285
|
return prepared;
|
|
@@ -26,6 +26,7 @@ import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
|
26
26
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
27
27
|
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
28
28
|
import { prepareTask } from "./prepare-task.js";
|
|
29
|
+
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
29
30
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
30
31
|
import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
31
32
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
@@ -44,7 +45,7 @@ import { structuredFrom, toolOutputFrom } from "./tool-output-projection.js";
|
|
|
44
45
|
export { DEFAULT_MAX_TURNS };
|
|
45
46
|
function createRunState() {
|
|
46
47
|
return {
|
|
47
|
-
telemetry: { cacheFamily: "input-excludes-cached", pricing: { inputPer1M: 0, outputPer1M: 0 }, tracer: undefined, taskId: "", taskStart: 0, taskStartMonotonic: 0, cacheBreakReported: false },
|
|
48
|
+
telemetry: { cacheFamily: "input-excludes-cached", pricing: { inputPer1M: 0, outputPer1M: 0 }, pricingConfigured: true, unpricedSpend: false, tracer: undefined, taskId: "", taskStart: 0, taskStartMonotonic: 0, cacheBreakReported: false },
|
|
48
49
|
degrade: { degradeToModel: undefined, degraded: undefined, outputErrorStreak: 0, outputInvalid: false, recordDegraded: () => { } },
|
|
49
50
|
limits: { turnsExceeded: false, budgetHit: undefined, outputRetryCap: 0, effectiveMaxTurns: undefined },
|
|
50
51
|
budget: { remainingMicroUsd: undefined, maxCostMicroUsd: undefined, overBudget: () => false, streamCancel: false, callOutputChars: 0, lastStreamBudgetCheck: 0, projectedOverBudget: () => false },
|
|
@@ -150,6 +151,8 @@ function writeFamilyOfCanonical(name) {
|
|
|
150
151
|
return "task";
|
|
151
152
|
if (name === "TodoWrite")
|
|
152
153
|
return "todo";
|
|
154
|
+
if (name === TOOL_SEARCH_NAME)
|
|
155
|
+
return "tool_search";
|
|
153
156
|
return undefined;
|
|
154
157
|
}
|
|
155
158
|
function toolResultMsg(toolCallId, toolName, text, isError) {
|
|
@@ -366,6 +369,10 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
366
369
|
const mcpRemovals = mcpInstructionsOn && mcpDelta.pendingRemovals.length > 0 ? [...mcpDelta.pendingRemovals] : undefined;
|
|
367
370
|
const mcpDropped = mcpInstructionsOn && prepared.mcp.droppedTools.length > 0 ? selectMcpDroppedBatch(prepared.mcp.droppedTools) : undefined;
|
|
368
371
|
const budgetUsdOn = rs.attach.attachmentsCfg?.budgetUsd === true && rs.budget.maxCostMicroUsd !== undefined && rs.turn.lastTurnHadToolCalls;
|
|
372
|
+
const toolSearchReminderOn = rs.attach.attachmentsCfg?.toolSearchReminder === true && prepared.deferredToolNames !== undefined;
|
|
373
|
+
const undiscoveredTools = toolSearchReminderOn
|
|
374
|
+
? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
|
|
375
|
+
: undefined;
|
|
369
376
|
due.push(...collectDueAttachments(rs.attach.attachState, {
|
|
370
377
|
turn: stats.turns,
|
|
371
378
|
cadenceTurn: rs.counters.cadenceTurns,
|
|
@@ -375,6 +382,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
375
382
|
config: {
|
|
376
383
|
todoReminder: rs.attach.attachmentsCfg?.todoReminder === true,
|
|
377
384
|
...(rs.attach.attachmentsCfg?.todoReminderMode !== undefined ? { todoReminderMode: rs.attach.attachmentsCfg.todoReminderMode } : {}),
|
|
385
|
+
toolSearchReminder: toolSearchReminderOn,
|
|
378
386
|
changedFiles: changedFilesOn,
|
|
379
387
|
planModeReminder: rs.attach.attachmentsCfg?.planModeReminder === true,
|
|
380
388
|
budgetUsd: budgetUsdOn,
|
|
@@ -384,6 +392,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
384
392
|
skillsListing: rs.attach.skillsListingOn,
|
|
385
393
|
mcpInstructions: mcpInstructionsOn,
|
|
386
394
|
},
|
|
395
|
+
...(undiscoveredTools !== undefined && undiscoveredTools.length > 0 ? { undiscoveredTools } : {}),
|
|
387
396
|
...(changed !== undefined ? { changedFiles: changed } : {}),
|
|
388
397
|
...(budgetUsdOn && rs.budget.maxCostMicroUsd !== undefined
|
|
389
398
|
? { budgetUsd: { used: stats.costMicroUsd / 1e6, total: rs.budget.maxCostMicroUsd / 1e6 } }
|
|
@@ -779,6 +788,8 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
779
788
|
const u = m.usage;
|
|
780
789
|
stats.tokens += u.totalTokens || 0;
|
|
781
790
|
const { totalInputTokens: turnInput, costMicroUsd: turnCostMicroUsd } = usageCostMicroUsd(rs.telemetry.cacheFamily, u, rs.telemetry.pricing);
|
|
791
|
+
if (!rs.telemetry.pricingConfigured)
|
|
792
|
+
rs.telemetry.unpricedSpend = true;
|
|
782
793
|
stats.promptTokens += turnInput;
|
|
783
794
|
stats.cachedTokens += u.cacheRead || 0;
|
|
784
795
|
stats.cacheWriteTokens += u.cacheWrite || 0;
|
|
@@ -817,7 +828,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
817
828
|
latencyMs: rs.turn.callStartAt !== undefined ? callEndAt - rs.turn.callStartAt : 0,
|
|
818
829
|
firstTokenMs: rs.turn.callStartAt !== undefined && rs.turn.firstTokenAt !== undefined ? rs.turn.firstTokenAt - rs.turn.callStartAt : undefined,
|
|
819
830
|
...(callIssuedAt !== undefined ? { callStartedAt: callIssuedAt } : {}),
|
|
820
|
-
costMicroUsd: turnCostMicroUsd,
|
|
831
|
+
...(rs.telemetry.pricingConfigured ? { costMicroUsd: turnCostMicroUsd } : {}),
|
|
821
832
|
...(capLast !== undefined ? { callCap: capLast.cap, capThinkingSkipped: capLast.thinkingSkipped } : {}),
|
|
822
833
|
...(m.role === "assistant" && typeof m.stopReason === "string"
|
|
823
834
|
? { stopReason: m.stopReason }
|
|
@@ -1619,6 +1630,7 @@ export class Runner {
|
|
|
1619
1630
|
const rs = createRunState();
|
|
1620
1631
|
rs.telemetry.cacheFamily = cacheFamilyOf(prepared.model);
|
|
1621
1632
|
rs.telemetry.pricing = this.deps.pricing?.[prepared.model.id] ?? modelCostToPricing(prepared.model.cost);
|
|
1633
|
+
rs.telemetry.pricingConfigured = this.deps.pricing?.[prepared.model.id] !== undefined || prepared.model.cost !== undefined;
|
|
1622
1634
|
if (spec.degrade) {
|
|
1623
1635
|
try {
|
|
1624
1636
|
rs.degrade.degradeToModel = resolveModel(spec.degrade.to, this.deps.models);
|
|
@@ -1660,11 +1672,17 @@ export class Runner {
|
|
|
1660
1672
|
}
|
|
1661
1673
|
if (m) {
|
|
1662
1674
|
rs.telemetry.pricing = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
1675
|
+
rs.telemetry.pricingConfigured = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
|
|
1663
1676
|
rs.telemetry.cacheFamily = cacheFamilyOf(m);
|
|
1664
1677
|
}
|
|
1665
1678
|
else {
|
|
1666
|
-
if (this.deps.pricing?.[info.to])
|
|
1679
|
+
if (this.deps.pricing?.[info.to]) {
|
|
1667
1680
|
rs.telemetry.pricing = this.deps.pricing[info.to];
|
|
1681
|
+
rs.telemetry.pricingConfigured = true;
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
rs.telemetry.pricingConfigured = false;
|
|
1685
|
+
}
|
|
1668
1686
|
rs.telemetry.cacheFamily = "input-excludes-cached";
|
|
1669
1687
|
}
|
|
1670
1688
|
rs.degrade.degraded = info;
|
|
@@ -2067,6 +2085,9 @@ export class Runner {
|
|
|
2067
2085
|
return;
|
|
2068
2086
|
const fam = cacheFamilyOf(m);
|
|
2069
2087
|
const price = this.deps.pricing?.[m.id] ?? modelCostToPricing(m.cost);
|
|
2088
|
+
const priced = this.deps.pricing?.[m.id] !== undefined || m.cost !== undefined;
|
|
2089
|
+
if (!priced)
|
|
2090
|
+
rs.telemetry.unpricedSpend = true;
|
|
2070
2091
|
const { totalInputTokens, costMicroUsd } = usageCostMicroUsd(fam, u, price);
|
|
2071
2092
|
stats.tokens += u.totalTokens || 0;
|
|
2072
2093
|
stats.promptTokens += totalInputTokens;
|
|
@@ -2079,7 +2100,7 @@ export class Runner {
|
|
|
2079
2100
|
kind: "brain.call", version: 1, taskId: rs.telemetry.taskId, model: m.id, provider: m.provider,
|
|
2080
2101
|
promptTokens: totalInputTokens, completionTokens: u.output || 0,
|
|
2081
2102
|
cacheRead: u.cacheRead || 0, cacheWrite: u.cacheWrite || 0,
|
|
2082
|
-
latencyMs: 0, costMicroUsd, ...(typeof msg?.stopReason === "string" ? { stopReason: msg.stopReason } : {}), ts: Date.now(),
|
|
2103
|
+
latencyMs: 0, ...(priced ? { costMicroUsd } : {}), ...(typeof msg?.stopReason === "string" ? { stopReason: msg.stopReason } : {}), ts: Date.now(),
|
|
2083
2104
|
}));
|
|
2084
2105
|
};
|
|
2085
2106
|
const compactionBrain = {
|
|
@@ -2708,6 +2729,7 @@ export class Runner {
|
|
|
2708
2729
|
const result = assembleResult(spec, prepared.sessionId, final, stats, {
|
|
2709
2730
|
threw,
|
|
2710
2731
|
model: prepared.model.id,
|
|
2732
|
+
unpricedSpend: rs.telemetry.unpricedSpend,
|
|
2711
2733
|
abortedForTimeout: timeout.fired,
|
|
2712
2734
|
abortedForTurns: rs.limits.turnsExceeded,
|
|
2713
2735
|
abortedLive,
|
|
@@ -2826,7 +2848,7 @@ export class Runner {
|
|
|
2826
2848
|
errorCode: result.errorCode,
|
|
2827
2849
|
turns: stats.turns,
|
|
2828
2850
|
tokens: stats.tokens,
|
|
2829
|
-
costMicroUsd: stats.costMicroUsd,
|
|
2851
|
+
...(rs.telemetry.unpricedSpend ? {} : { costMicroUsd: stats.costMicroUsd }),
|
|
2830
2852
|
durationMs: Date.now() - rs.telemetry.taskStart,
|
|
2831
2853
|
...(prepared.outputRef.set ? { hasStructuredOutput: true } : {}),
|
|
2832
2854
|
...(stats.mechanisms !== undefined
|
|
@@ -47,6 +47,7 @@ export interface SkillListingEntry {
|
|
|
47
47
|
files?: Array<{
|
|
48
48
|
path: string;
|
|
49
49
|
}>;
|
|
50
|
+
declaredRank?: number;
|
|
50
51
|
}
|
|
51
52
|
export declare function normalizeSkills(skills: SkillLike[] | undefined): SkillLike[];
|
|
52
53
|
export declare const SKILLS_LISTING_PROBE_HEADER = "The following skills are available for this task.";
|
|
@@ -85,7 +85,7 @@ export function createReportFindingsTool() {
|
|
|
85
85
|
}
|
|
86
86
|
export const SKILL_TOOL_NAME = "Skill";
|
|
87
87
|
export const SKILL_CONTENT_MAX_CHARS = 1024 * 1024;
|
|
88
|
-
const SKILL_DESC_MAX_CHARS =
|
|
88
|
+
const SKILL_DESC_MAX_CHARS = 1_536;
|
|
89
89
|
export const SKILLS_BLOCK_MAX_BYTES = 8_000;
|
|
90
90
|
export const SKILL_ATTACHMENTS_MAX_CHARS = 50_000;
|
|
91
91
|
export const SKILL_ATTACHMENT_PATH_MAX_CHARS = 512;
|
|
@@ -121,11 +121,11 @@ export function normalizeSkills(skills) {
|
|
|
121
121
|
export const SKILLS_LISTING_PROBE_HEADER = "The following skills are available for this task.";
|
|
122
122
|
export function skillListingLine(s) {
|
|
123
123
|
const desc = s.description.length > SKILL_DESC_MAX_CHARS
|
|
124
|
-
? `${s.description.slice(0, SKILL_DESC_MAX_CHARS)}
|
|
124
|
+
? `${s.description.slice(0, SKILL_DESC_MAX_CHARS - 1)}…`
|
|
125
125
|
: s.description;
|
|
126
126
|
return `- ${s.name}: ${desc}${skillFilesSuffix(s.files)}`;
|
|
127
127
|
}
|
|
128
|
-
const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached —
|
|
128
|
+
const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached — lower-priority skills are listed name-only; descriptions via the tool)";
|
|
129
129
|
export function buildSkillsBlock(skills) {
|
|
130
130
|
if (skills.length === 0)
|
|
131
131
|
return undefined;
|
|
@@ -145,20 +145,23 @@ export function buildSkillsBlock(skills) {
|
|
|
145
145
|
const fullTotal = fullLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
146
146
|
if (fullTotal <= budget)
|
|
147
147
|
return render(fullLines);
|
|
148
|
-
const suffixNameCost = new Array(skills.length + 1);
|
|
149
|
-
suffixNameCost[skills.length] = 0;
|
|
150
|
-
for (let i = skills.length - 1; i >= 0; i--)
|
|
151
|
-
suffixNameCost[i] = suffixNameCost[i + 1] + lineCost(nameLines[i]);
|
|
152
148
|
const markerCost = lineCost(SKILLS_BLOCK_CAP_MARKER);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
const floorCost = markerCost + nameLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
150
|
+
if (floorCost <= budget) {
|
|
151
|
+
let slack = budget - floorCost;
|
|
152
|
+
const byPriority = skills
|
|
153
|
+
.map((s, i) => ({ i, rank: s.declaredRank ?? i }))
|
|
154
|
+
.toSorted((a, b) => a.rank - b.rank || a.i - b.i);
|
|
155
|
+
const withDescription = new Set();
|
|
156
|
+
for (const { i } of byPriority) {
|
|
157
|
+
const delta = lineCost(fullLines[i]) - lineCost(nameLines[i]);
|
|
158
|
+
if (delta <= slack) {
|
|
159
|
+
withDescription.add(i);
|
|
160
|
+
slack -= delta;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return render([SKILLS_BLOCK_CAP_MARKER, ...skills.map((_s, i) => (withDescription.has(i) ? fullLines[i] : nameLines[i]))]);
|
|
159
164
|
}
|
|
160
|
-
if (bestK >= 0)
|
|
161
|
-
return render([...fullLines.slice(0, bestK), SKILLS_BLOCK_CAP_MARKER, ...nameLines.slice(bestK)]);
|
|
162
165
|
for (let m = skills.length - 1; m >= 0; m--) {
|
|
163
166
|
const overflowLine = `(… +${skills.length - m} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`;
|
|
164
167
|
const cost = markerCost + nameLines.slice(0, m).reduce((acc, l) => acc + lineCost(l), 0) + lineCost(overflowLine);
|
|
@@ -7,10 +7,14 @@ export declare const PLAN_MODE_ATTACHMENT_CONFIG: {
|
|
|
7
7
|
readonly TURNS_BETWEEN_ATTACHMENTS: 5;
|
|
8
8
|
readonly FULL_REMINDER_EVERY_N_ATTACHMENTS: 5;
|
|
9
9
|
};
|
|
10
|
+
export declare const TOOL_SEARCH_REMINDER_CONFIG: {
|
|
11
|
+
readonly EVERY_N_TURNS: 15;
|
|
12
|
+
readonly MAX_NAMES: 10;
|
|
13
|
+
};
|
|
10
14
|
export declare const CHANGED_FILES_MAX = 20;
|
|
11
15
|
export declare const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
12
16
|
export declare const ATTACHMENT_BYTE_CAP: number;
|
|
13
|
-
export type AttachmentSource = "todo_reminder" | "task_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
17
|
+
export type AttachmentSource = "todo_reminder" | "task_reminder" | "tool_search_usage_reminder" | "changed_files" | "plan_mode" | "date_change" | "instructions_change" | "budget_usd" | "background_tasks" | "tools_delta" | "agent_listing" | "skills_listing" | "mcp_instructions" | "mcp_dropped_tools";
|
|
14
18
|
export interface AgentListingEntry {
|
|
15
19
|
name: string;
|
|
16
20
|
description: string;
|
|
@@ -42,6 +46,8 @@ export interface AttachmentState {
|
|
|
42
46
|
todoLastWriteTurn?: number;
|
|
43
47
|
lastTodoReminderTurn: number;
|
|
44
48
|
lastTaskReminderTurn: number;
|
|
49
|
+
toolSearchLastUseTurn?: number;
|
|
50
|
+
lastToolSearchReminderTurn: number;
|
|
45
51
|
planAttachmentCount: number;
|
|
46
52
|
lastPlanReminderTurn: number;
|
|
47
53
|
surfacedMtime: Map<string, number>;
|
|
@@ -60,7 +66,7 @@ export interface InstructionsChangeState {
|
|
|
60
66
|
export declare const INSTRUCTIONS_CHANGE_BYTE_CAP = 512;
|
|
61
67
|
export declare function createAttachmentState(): AttachmentState;
|
|
62
68
|
export declare function reduceToolEnd(state: AttachmentState, details: unknown): void;
|
|
63
|
-
export type WriteFamily = "todo" | "task";
|
|
69
|
+
export type WriteFamily = "todo" | "task" | "tool_search";
|
|
64
70
|
export declare function stampWriteAnchor(state: AttachmentState, family: WriteFamily, clock: number): void;
|
|
65
71
|
export declare function advanceCadenceClock(state: AttachmentState, clock: number, content: unknown, writeFamilyOf: (toolName: string) => WriteFamily | undefined): number;
|
|
66
72
|
export declare function rebaseCadenceWindows(state: AttachmentState, clock: number): void;
|
|
@@ -77,6 +83,7 @@ export interface AttachmentInputs {
|
|
|
77
83
|
config: {
|
|
78
84
|
todoReminder: boolean;
|
|
79
85
|
todoReminderMode?: "baseline" | "off";
|
|
86
|
+
toolSearchReminder?: boolean;
|
|
80
87
|
changedFiles: boolean;
|
|
81
88
|
planModeReminder: boolean;
|
|
82
89
|
budgetUsd?: boolean;
|
|
@@ -86,6 +93,7 @@ export interface AttachmentInputs {
|
|
|
86
93
|
skillsListing?: boolean;
|
|
87
94
|
mcpInstructions?: boolean;
|
|
88
95
|
};
|
|
96
|
+
undiscoveredTools?: readonly string[];
|
|
89
97
|
changedFiles?: ReadonlyArray<{
|
|
90
98
|
path: string;
|
|
91
99
|
mtimeMs: number;
|
|
@@ -111,6 +119,7 @@ export interface AttachmentInputs {
|
|
|
111
119
|
}>;
|
|
112
120
|
}
|
|
113
121
|
export declare function collectDueAttachments(state: AttachmentState, inp: AttachmentInputs): readonly TurnAttachment[];
|
|
122
|
+
export declare function renderToolSearchUsageReminder(undiscovered: readonly string[]): string;
|
|
114
123
|
export declare function renderDateChange(newDate: string): string;
|
|
115
124
|
export declare function collectDateChange(state: DateChangeState, today: string): TurnAttachment | undefined;
|
|
116
125
|
export declare function collectInstructionsChange(state: InstructionsChangeState, probed: ReadonlyArray<{
|
|
@@ -144,6 +153,7 @@ export interface McpToolsDeltaFacts {
|
|
|
144
153
|
export declare function renderToolsDelta(input: {
|
|
145
154
|
added?: readonly string[];
|
|
146
155
|
} & McpToolsDeltaFacts): string | undefined;
|
|
156
|
+
export declare const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
147
157
|
export declare const AGENT_CONCURRENCY_NOTE = "When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.";
|
|
148
158
|
export declare const AMBIENT_CONTEXT_NOTE = "This is ambient context \u2014 do not narrate it to the user unless they ask or it is directly relevant to their request.";
|
|
149
159
|
export declare function agentListingInitialHeader(toolName: string): string;
|
|
@@ -10,6 +10,10 @@ export const PLAN_MODE_ATTACHMENT_CONFIG = {
|
|
|
10
10
|
TURNS_BETWEEN_ATTACHMENTS: 5,
|
|
11
11
|
FULL_REMINDER_EVERY_N_ATTACHMENTS: 5,
|
|
12
12
|
};
|
|
13
|
+
export const TOOL_SEARCH_REMINDER_CONFIG = {
|
|
14
|
+
EVERY_N_TURNS: 15,
|
|
15
|
+
MAX_NAMES: 10,
|
|
16
|
+
};
|
|
13
17
|
export const CHANGED_FILES_MAX = 20;
|
|
14
18
|
export const CHANGED_FILES_MTIME_EPS_MS = 2000;
|
|
15
19
|
export const ATTACHMENT_BYTE_CAP = 8 * 1024;
|
|
@@ -20,6 +24,7 @@ export function createAttachmentState() {
|
|
|
20
24
|
return {
|
|
21
25
|
lastTodoReminderTurn: 0,
|
|
22
26
|
lastTaskReminderTurn: 0,
|
|
27
|
+
lastToolSearchReminderTurn: 0,
|
|
23
28
|
planAttachmentCount: 0,
|
|
24
29
|
lastPlanReminderTurn: 0,
|
|
25
30
|
surfacedMtime: new Map(),
|
|
@@ -84,6 +89,8 @@ export function reduceToolEnd(state, details) {
|
|
|
84
89
|
export function stampWriteAnchor(state, family, clock) {
|
|
85
90
|
if (family === "task")
|
|
86
91
|
state.taskLastWriteTurn = clock;
|
|
92
|
+
else if (family === "tool_search")
|
|
93
|
+
state.toolSearchLastUseTurn = clock;
|
|
87
94
|
else
|
|
88
95
|
state.todoLastWriteTurn = clock;
|
|
89
96
|
}
|
|
@@ -109,6 +116,8 @@ export function rebaseCadenceWindows(state, clock) {
|
|
|
109
116
|
state.todoLastWriteTurn = clock;
|
|
110
117
|
state.lastTaskReminderTurn = clock;
|
|
111
118
|
state.lastTodoReminderTurn = clock;
|
|
119
|
+
state.toolSearchLastUseTurn = clock;
|
|
120
|
+
state.lastToolSearchReminderTurn = clock;
|
|
112
121
|
}
|
|
113
122
|
function countStatuses(items) {
|
|
114
123
|
const counts = {};
|
|
@@ -138,6 +147,17 @@ export function collectDueAttachments(state, inp) {
|
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
}
|
|
150
|
+
if (inp.config.toolSearchReminder &&
|
|
151
|
+
inp.undiscoveredTools !== undefined &&
|
|
152
|
+
inp.undiscoveredTools.length > 0 &&
|
|
153
|
+
!(out ?? []).some((a) => a.source === "todo_reminder" || a.source === "task_reminder")) {
|
|
154
|
+
const cadenceNow = inp.cadenceTurn ?? inp.turn;
|
|
155
|
+
const sinceUse = state.toolSearchLastUseTurn === undefined ? Number.POSITIVE_INFINITY : cadenceNow - state.toolSearchLastUseTurn;
|
|
156
|
+
if (sinceUse >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS && cadenceNow - state.lastToolSearchReminderTurn >= TOOL_SEARCH_REMINDER_CONFIG.EVERY_N_TURNS) {
|
|
157
|
+
state.lastToolSearchReminderTurn = cadenceNow;
|
|
158
|
+
(out ??= []).push({ source: "tool_search_usage_reminder", body: renderToolSearchUsageReminder(inp.undiscoveredTools) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
141
161
|
if (inp.config.planModeReminder && inp.planActive) {
|
|
142
162
|
const due = state.planAttachmentCount === 0 ||
|
|
143
163
|
inp.turn - state.lastPlanReminderTurn >= PLAN_MODE_ATTACHMENT_CONFIG.TURNS_BETWEEN_ATTACHMENTS;
|
|
@@ -228,6 +248,15 @@ function renderTaskReminder(state) {
|
|
|
228
248
|
}
|
|
229
249
|
return message;
|
|
230
250
|
}
|
|
251
|
+
export function renderToolSearchUsageReminder(undiscovered) {
|
|
252
|
+
const shown = undiscovered.slice(0, TOOL_SEARCH_REMINDER_CONFIG.MAX_NAMES);
|
|
253
|
+
const more = undiscovered.length - shown.length;
|
|
254
|
+
const names = `${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
255
|
+
return (`Some available tools' schemas are not loaded in this conversation yet: ${names}. Before concluding a ` +
|
|
256
|
+
`capability is missing or building a workaround, use ${TOOL_SEARCH_TOOL_NAME} to find and load relevant tools — ` +
|
|
257
|
+
`keywords to search, or query "select:<name>[,<name>...]" for specific tools. Calling a tool before its schema ` +
|
|
258
|
+
`is loaded will fail. This is just a gentle reminder - ignore if not applicable to the current work.`);
|
|
259
|
+
}
|
|
231
260
|
export function renderDateChange(newDate) {
|
|
232
261
|
return `The date has changed. Today's date is now ${newDate}. DO NOT mention this to the user explicitly because they are already aware.`;
|
|
233
262
|
}
|
|
@@ -399,6 +428,7 @@ export function renderToolsDelta(input) {
|
|
|
399
428
|
}
|
|
400
429
|
return blocks.length > 0 ? blocks.join("\n\n") : undefined;
|
|
401
430
|
}
|
|
431
|
+
export const AGENT_TOOLS_NOTE_DEFAULT = "All tools";
|
|
402
432
|
export const AGENT_CONCURRENCY_NOTE = "When you launch multiple agents for independent work, send them in a single message with multiple tool uses so they run concurrently.";
|
|
403
433
|
export const AMBIENT_CONTEXT_NOTE = "This is ambient context — do not narrate it to the user unless they ask or it is directly relevant to their request.";
|
|
404
434
|
export function agentListingInitialHeader(toolName) {
|
|
@@ -451,7 +481,7 @@ export function agentListingDeltaHeader(toolName) {
|
|
|
451
481
|
export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
452
482
|
const line = (e) => {
|
|
453
483
|
const base = e.description ? `- ${e.name}: ${e.description}` : `- ${e.name}`;
|
|
454
|
-
return
|
|
484
|
+
return `${base} (Tools: ${e.tools ?? AGENT_TOOLS_NOTE_DEFAULT})`;
|
|
455
485
|
};
|
|
456
486
|
const announced = state.announcedAgentTypes;
|
|
457
487
|
if (announced === undefined) {
|
|
@@ -464,8 +494,8 @@ export function renderAgentListingDelta(state, entries, toolName, models) {
|
|
|
464
494
|
blocks.push(modelsAvailableLine(models));
|
|
465
495
|
return blocks.join("\n\n");
|
|
466
496
|
}
|
|
467
|
-
const added = entries.filter((e) => !announced.has(e.name));
|
|
468
|
-
const removed = [...announced.keys()].filter((n) => !entries.some((e) => e.name === n));
|
|
497
|
+
const added = entries.filter((e) => !announced.has(e.name)).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
498
|
+
const removed = [...announced.keys()].filter((n) => !entries.some((e) => e.name === n)).sort();
|
|
469
499
|
const modelsDrifted = state.announcedModels !== undefined &&
|
|
470
500
|
models !== undefined &&
|
|
471
501
|
(state.announcedModels.length !== models.length || state.announcedModels.some((m, i) => m !== models[i]));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BackgroundAgentRecord, type BackgroundAgentStore } from "./background-agent-store.js";
|
|
2
2
|
import { type StopSource, type TaskAccess, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
|
|
3
|
+
import { type ToolResultStore } from "./tool-result-store.js";
|
|
3
4
|
export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void;
|
|
4
5
|
export declare function durableAgentWriteLane(handle: BackgroundAgentTaskHandle, patch: Partial<BackgroundAgentRecord>, clear?: (keyof BackgroundAgentRecord)[]): void;
|
|
5
6
|
export declare function durableAgentArmedLane(core: DurableAgentCore, id: string): boolean;
|
|
@@ -110,6 +111,15 @@ export declare function deliverToRunningAgentLane(core: DurableAgentCore, id: st
|
|
|
110
111
|
reason: "not_found" | "not_running" | "no_channel";
|
|
111
112
|
}>;
|
|
112
113
|
export declare function runningBackgroundAgentLabelsLane(core: DurableAgentCore, access: TaskAccess): string[];
|
|
114
|
+
export declare function runningAgentFooterLane(core: DurableAgentCore, access: TaskAccess): {
|
|
115
|
+
named: string[];
|
|
116
|
+
background: string[];
|
|
117
|
+
};
|
|
118
|
+
export declare function notFoundRunningAgentsTail(footer: {
|
|
119
|
+
named: string[];
|
|
120
|
+
background: string[];
|
|
121
|
+
}): string;
|
|
113
122
|
export declare function serveDurableAgentRowLane(row: BackgroundAgentRecord): UnifiedTaskResult;
|
|
114
|
-
export declare function
|
|
123
|
+
export declare function spillClippedAgentResult(handle: BackgroundAgentTaskHandle, full: string, clipped: string, store: ToolResultStore | undefined, sessionId: string | undefined): Promise<string>;
|
|
124
|
+
export declare function pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal, oneShot?: boolean, store?: ToolResultStore, sessionId?: string): Promise<UnifiedTaskResult>;
|
|
115
125
|
export declare function stopBackgroundAgentLane(core: DurableAgentCore, handle: BackgroundAgentTaskHandle): Promise<UnifiedTaskResult>;
|
|
@@ -4,6 +4,7 @@ import { canAccessAgentRecord, BackgroundAgentStoreError, } from "./background-a
|
|
|
4
4
|
import { shutdownDebug } from "./shutdown-debug.js";
|
|
5
5
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
|
|
7
|
+
import { buildToolResultRef, OFFLOAD_TOOL_NAME } from "./tool-result-store.js";
|
|
7
8
|
export function ensureDurableHeartbeatLane(core) {
|
|
8
9
|
if (core.durableHeartbeatTimer !== undefined)
|
|
9
10
|
return;
|
|
@@ -834,6 +835,7 @@ export function reviveBackgroundAgentLane(core, id, access, abort) {
|
|
|
834
835
|
handle.abort = abort;
|
|
835
836
|
handle.result = undefined;
|
|
836
837
|
handle.resultFull = undefined;
|
|
838
|
+
handle.spillRef = undefined;
|
|
837
839
|
handle.error = undefined;
|
|
838
840
|
handle.resultIsPartial = undefined;
|
|
839
841
|
handle.stopSource = undefined;
|
|
@@ -972,6 +974,24 @@ export function runningBackgroundAgentLabelsLane(core, access) {
|
|
|
972
974
|
}
|
|
973
975
|
return out;
|
|
974
976
|
}
|
|
977
|
+
export function runningAgentFooterLane(core, access) {
|
|
978
|
+
const named = [];
|
|
979
|
+
const background = [];
|
|
980
|
+
for (const h of core.handles.values()) {
|
|
981
|
+
if (h.type !== "background_agent" || h.status !== "running" || !canAccess(h, access))
|
|
982
|
+
continue;
|
|
983
|
+
if (h.name !== undefined && h.name !== "") {
|
|
984
|
+
named.push(h.name);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
background.push(h.description ? `${h.id} (${h.description})` : h.id);
|
|
988
|
+
}
|
|
989
|
+
return { named, background };
|
|
990
|
+
}
|
|
991
|
+
export function notFoundRunningAgentsTail(footer) {
|
|
992
|
+
return ((footer.named.length > 0 ? `. Running named agents: ${footer.named.join(", ")}` : "") +
|
|
993
|
+
(footer.background.length > 0 ? `. Running background agents: ${footer.background.join(", ")}` : ""));
|
|
994
|
+
}
|
|
975
995
|
export function serveDurableAgentRowLane(row) {
|
|
976
996
|
if (row.status === "parked") {
|
|
977
997
|
return {
|
|
@@ -1004,7 +1024,19 @@ ${clipTaskOutput(row.finalOutputFull ?? row.finalOutput)}` : "(no result text)"}
|
|
|
1004
1024
|
},
|
|
1005
1025
|
};
|
|
1006
1026
|
}
|
|
1007
|
-
export async function
|
|
1027
|
+
export async function spillClippedAgentResult(handle, full, clipped, store, sessionId) {
|
|
1028
|
+
if (clipped === full)
|
|
1029
|
+
return clipped;
|
|
1030
|
+
if (store === undefined)
|
|
1031
|
+
return clipped;
|
|
1032
|
+
if (handle.spillRef === undefined) {
|
|
1033
|
+
const ref = buildToolResultRef(sessionId ?? "no-session", `${handle.id}_c${handle.reviveCycle ?? 0}`);
|
|
1034
|
+
await store.put(ref, full);
|
|
1035
|
+
handle.spillRef = ref;
|
|
1036
|
+
}
|
|
1037
|
+
return `${clipped}\n\n[full output persisted — call ${OFFLOAD_TOOL_NAME} with ref "${handle.spillRef}" to read it back.]`;
|
|
1038
|
+
}
|
|
1039
|
+
export async function pollBackgroundAgentLane(handle, deadline, signal, oneShot, store, sessionId) {
|
|
1008
1040
|
while (handle.status === "running" && deadline !== undefined && Date.now() < deadline && !signal?.aborted) {
|
|
1009
1041
|
await sleepPollStep(deadline, signal);
|
|
1010
1042
|
}
|
|
@@ -1023,13 +1055,18 @@ The agent is durably suspended, waiting for an approval decision. It resumes whe
|
|
|
1023
1055
|
},
|
|
1024
1056
|
};
|
|
1025
1057
|
}
|
|
1058
|
+
const fullResult = handle.result ? (handle.resultFull ?? handle.result) : undefined;
|
|
1059
|
+
const resultText = fullResult !== undefined ? await spillClippedAgentResult(handle, fullResult, clipTaskOutput(fullResult, handle.outputFile), store, sessionId) : undefined;
|
|
1026
1060
|
const body = running
|
|
1027
|
-
?
|
|
1061
|
+
? oneShot === true
|
|
1062
|
+
? `status: running
|
|
1063
|
+
This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput({ task_id: "${handle.id}", block: true }). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.`
|
|
1064
|
+
: `status: running
|
|
1028
1065
|
The agent is still working — you will be notified when it completes.`
|
|
1029
1066
|
: `status: ${handle.status}
|
|
1030
1067
|
${handle.error ? `error: ${handle.error}
|
|
1031
1068
|
` : ""}${handle.result ? `--- result${handle.resultIsPartial ? " (partial — produced before the task was stopped)" : ""} ---
|
|
1032
|
-
${
|
|
1069
|
+
${resultText}` : "(no result text)"}`;
|
|
1033
1070
|
return {
|
|
1034
1071
|
content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
|
|
1035
1072
|
details: {
|