@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.
Files changed (61) hide show
  1. package/dist/agents/observer.d.ts +14 -0
  2. package/dist/agents/observer.js +58 -9
  3. package/dist/agents/send-message-tool.js +55 -10
  4. package/dist/agents/subagent.d.ts +1 -0
  5. package/dist/agents/subagent.js +69 -15
  6. package/dist/agents/teacher.js +51 -23
  7. package/dist/core/context-edit.js +16 -3
  8. package/dist/core/file-snapshot-store.js +10 -1
  9. package/dist/core/runner/assemble-result.d.ts +1 -0
  10. package/dist/core/runner/assemble-result.js +12 -8
  11. package/dist/core/runner/prepare-task.d.ts +1 -0
  12. package/dist/core/runner/prepare-task.js +40 -10
  13. package/dist/core/runner/runtask.js +27 -5
  14. package/dist/core/runner/synthetic-tools.d.ts +1 -0
  15. package/dist/core/runner/synthetic-tools.js +18 -15
  16. package/dist/core/runner/turn-attachments.d.ts +12 -2
  17. package/dist/core/runner/turn-attachments.js +33 -3
  18. package/dist/core/task-registry-agent.d.ts +11 -1
  19. package/dist/core/task-registry-agent.js +40 -3
  20. package/dist/core/task-registry-monitor.js +149 -29
  21. package/dist/core/task-registry-shared.d.ts +25 -2
  22. package/dist/core/task-registry-shared.js +26 -2
  23. package/dist/core/task-registry.d.ts +5 -0
  24. package/dist/core/task-registry.js +25 -26
  25. package/dist/core/tools.d.ts +2 -0
  26. package/dist/core/tools.js +9 -0
  27. package/dist/core/types.d.ts +3 -1
  28. package/dist/core/workflow-journal-store.d.ts +16 -0
  29. package/dist/core/workflow-journal-store.js +28 -0
  30. package/dist/engine/session/memory-repo.js +5 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/orchestration/workflow-size-guideline.d.ts +6 -1
  34. package/dist/orchestration/workflow-size-guideline.js +19 -9
  35. package/dist/orchestration/workflow.d.ts +16 -0
  36. package/dist/orchestration/workflow.js +80 -20
  37. package/dist/prompt-assembly/assemble.js +3 -7
  38. package/dist/prompt-assembly/packs/sema-default.js +8 -5
  39. package/dist/prompts/coordinator.d.ts +1 -1
  40. package/dist/prompts/coordinator.js +45 -0
  41. package/dist/prompts/default.d.ts +4 -5
  42. package/dist/prompts/default.js +16 -18
  43. package/dist/prompts/simple-sections.d.ts +3 -1
  44. package/dist/prompts/simple-sections.js +11 -1
  45. package/dist/stores/file/workflow-journal-store.d.ts +7 -1
  46. package/dist/stores/file/workflow-journal-store.js +70 -33
  47. package/dist/tools/fs/bash-readonly-classifier.js +20 -1
  48. package/dist/tools/fs/fs-bash.d.ts +1 -0
  49. package/dist/tools/fs/fs-bash.js +10 -3
  50. package/dist/tools/fs/fs-read.js +10 -10
  51. package/dist/tools/fs/fs-search-tools.js +42 -7
  52. package/dist/tools/fs/fs-write.js +18 -6
  53. package/dist/tools/fs/index.d.ts +1 -0
  54. package/dist/tools/fs/index.js +2 -1
  55. package/dist/tools/fs/safety.d.ts +4 -0
  56. package/dist/tools/fs/safety.js +102 -6
  57. package/dist/tools/fs/search.d.ts +1 -0
  58. package/dist/tools/fs/search.js +23 -3
  59. package/dist/tools/monitor.d.ts +2 -0
  60. package/dist/tools/monitor.js +3 -1
  61. 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) => maybeOffload(defineTool({
796
- ...t,
797
- execute: (args, ctx) => t.execute(args, enrichSpecToolCtx(ctx)),
798
- }), t));
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?.parentTaskId !== undefined,
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?.isDelegatedChild === true && internals?.insideFork !== true,
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 = 1_000;
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)}…[description truncated]`
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 — remaining skills listed name-only; descriptions via the tool)";
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
- let bestK = -1;
154
- let prefixCost = 0;
155
- for (let k = 0; k < skills.length; k++) {
156
- if (prefixCost + markerCost + suffixNameCost[k] <= budget)
157
- bestK = k;
158
- prefixCost += lineCost(fullLines[k]);
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 e.tools !== undefined ? `${base} (Tools: ${e.tools})` : base;
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 pollBackgroundAgentLane(handle: BackgroundAgentTaskHandle, deadline?: number, signal?: AbortSignal): Promise<UnifiedTaskResult>;
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 pollBackgroundAgentLane(handle, deadline, signal) {
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
- ? `status: running
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
- ${clipTaskOutput(handle.resultFull ?? handle.result, handle.outputFile)}` : "(no result text)"}`;
1069
+ ${resultText}` : "(no result text)"}`;
1033
1070
  return {
1034
1071
  content: delimitUntrusted(`TaskOutput ${handle.id}`, body),
1035
1072
  details: {