@sema-agent/core 5.54.0 → 5.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +160 -0
- package/dist/agents/cumulative-stats.d.ts +26 -0
- package/dist/agents/cumulative-stats.js +56 -0
- package/dist/agents/observer.d.ts +11 -7
- package/dist/agents/observer.js +2 -4
- package/dist/agents/send-message-tool.js +48 -2
- package/dist/agents/subagent.js +250 -89
- package/dist/agents/verify.d.ts +27 -3
- package/dist/agents/verify.js +7 -2
- package/dist/core/auto-compaction.d.ts +17 -4
- package/dist/core/auto-compaction.js +3 -0
- package/dist/core/context-edit.d.ts +55 -6
- package/dist/core/context-edit.js +12 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +293 -11
- package/dist/core/hooks.js +159 -12
- package/dist/core/human-input-projection.d.ts +20 -2
- package/dist/core/human-input-projection.js +9 -0
- package/dist/core/lsp-diagnostics.d.ts +19 -17
- package/dist/core/lsp-diagnostics.js +11 -5
- package/dist/core/mcp.d.ts +46 -0
- package/dist/core/mcp.js +132 -6
- package/dist/core/memory-engine/consolidation.d.ts +378 -0
- package/dist/core/memory-engine/consolidation.js +342 -0
- package/dist/core/memory-engine/dual-root.js +3 -0
- package/dist/core/memory-engine/engine.d.ts +237 -4
- package/dist/core/memory-engine/engine.js +1111 -4
- package/dist/core/memory-engine/export-bundle.js +9 -0
- package/dist/core/memory-engine/file-backend.js +27 -1
- package/dist/core/memory-engine/frontmatter.d.ts +20 -1
- package/dist/core/memory-engine/frontmatter.js +111 -0
- package/dist/core/memory-engine/index.d.ts +4 -2
- package/dist/core/memory-engine/index.js +3 -1
- package/dist/core/memory-engine/memory-backend-contract.js +131 -0
- package/dist/core/memory-engine/sync-client.js +26 -0
- package/dist/core/memory-engine/tools.d.ts +9 -0
- package/dist/core/memory-engine/tools.js +57 -13
- package/dist/core/memory-engine/types.d.ts +99 -0
- package/dist/core/memory-recall.js +4 -3
- package/dist/core/memory.d.ts +33 -3
- package/dist/core/memory.js +6 -4
- package/dist/core/permission-rules.d.ts +30 -0
- package/dist/core/permission-rules.js +71 -8
- package/dist/core/reminder-disclosure.d.ts +29 -4
- package/dist/core/reminder-disclosure.js +60 -12
- package/dist/core/runner/prepare-memory.js +7 -2
- package/dist/core/runner/prepare-task.d.ts +39 -1
- package/dist/core/runner/prepare-task.js +63 -35
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +170 -31
- package/dist/core/runner/session-rule-policy.js +5 -3
- package/dist/core/runner/synthetic-tools.js +4 -2
- package/dist/core/runner/turn-attachments.d.ts +16 -6
- package/dist/core/runner/turn-attachments.js +34 -20
- package/dist/core/session-reconcile.d.ts +32 -0
- package/dist/core/session-reconcile.js +15 -0
- package/dist/core/task-notification.d.ts +34 -7
- package/dist/core/task-notification.js +11 -1
- package/dist/core/task-registry-agent.d.ts +20 -3
- package/dist/core/task-registry-agent.js +31 -2
- package/dist/core/tool-policy.d.ts +23 -0
- package/dist/core/tool-policy.js +29 -13
- package/dist/core/types.d.ts +126 -17
- package/dist/core/untrusted-egress.js +12 -2
- package/dist/core/untrusted-text.d.ts +189 -3
- package/dist/core/untrusted-text.js +424 -6
- package/dist/engine/compaction/compaction.d.ts +77 -7
- package/dist/engine/compaction/compaction.js +98 -9
- package/dist/engine/compaction/utils.d.ts +4 -0
- package/dist/engine/compaction/utils.js +6 -0
- package/dist/engine/harness/agent-harness.d.ts +84 -0
- package/dist/engine/harness/agent-harness.js +88 -12
- package/dist/engine/harness/messages.d.ts +4 -2
- package/dist/engine/harness/messages.js +7 -2
- package/dist/engine/harness/types.d.ts +11 -5
- package/dist/engine/loop/types.d.ts +14 -0
- package/dist/engine/session/import-validate.js +10 -0
- package/dist/engine/session/session.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/orchestration/run-spec.js +8 -1
- package/dist/prompts/default.d.ts +22 -6
- package/dist/tools/fs/index.d.ts +3 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +28 -1
|
@@ -31,23 +31,23 @@ import { toImageContent } from "./image.js";
|
|
|
31
31
|
import { OUTPUT_TOOL_NAME, SKILLS_LISTING_PROBE_HEADER, resolveOutputRetries } from "./synthetic-tools.js";
|
|
32
32
|
import { cacheFamilyOf, usageCostMicroUsd } from "./usage-accounting.js";
|
|
33
33
|
import { assembleResult, errorCodeOf } from "./assemble-result.js";
|
|
34
|
-
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
34
|
+
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, AGENT_LISTING_REMOVED_HEADER, SKILLS_LISTING_DELTA_HEADER, SKILLS_LISTING_REMOVED_HEADER, advanceCadenceClock, agentListingDeltaHeader, attachmentEnvelopeTags, agentListingInitialHeader, replayAnnouncedListing, replayAnnouncedModels, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, createAttachmentState, rebaseCadenceWindows, reduceToolEnd, renderAgentListingDelta, renderMcpDroppedTools, renderMcpInstructionsDelta, renderOrphanedBackgroundTasks, selectMcpDroppedBatch, renderSkillsListingDelta, renderToolsDelta, stampWriteAnchor } from "./turn-attachments.js";
|
|
35
35
|
import { buildWorkingFileAttachments, centerAdoptionOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
36
|
-
import { prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
36
|
+
import { gatedCallIdOf, prepareTask, resolveCheckpointStore } from "./prepare-task.js";
|
|
37
37
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
38
38
|
import { TOOL_SEARCH_NAME } from "./tool-disclosure.js";
|
|
39
39
|
import { hasVerifiableStructureSignal } from "./grounding-signal.js";
|
|
40
40
|
import { hasDestroy, isIsolated } from "../remote-env.js";
|
|
41
41
|
import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.js";
|
|
42
|
-
import { cloneObserverInput, formatHookFeedback } from "../hooks.js";
|
|
43
|
-
import { buildHumanInputEvent, projectHumanInput } from "../human-input-projection.js";
|
|
44
|
-
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText } from "../untrusted-text.js";
|
|
45
|
-
import { reconcileInterruptedSession } from "../session-reconcile.js";
|
|
42
|
+
import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
|
|
43
|
+
import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
|
|
44
|
+
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
45
|
+
import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
|
|
46
46
|
import { RunnerSharedToolResultStore } from "../tool-result-store.js";
|
|
47
47
|
import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
|
|
48
48
|
import { checkToolPolicyProjection, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, refuseOutOfContractDecision, screenApproverAttribution, toolPolicyNameSets } from "../tool-policy.js";
|
|
49
49
|
import { defaultTaskRegistry } from "../task-registry.js";
|
|
50
|
-
import { discloseDroppedPending, isDelegatedAgentTerminal, PendingSessionNotifications, renderTaskNotificationXml, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
50
|
+
import { discloseDroppedPending, isDelegatedAgentTerminal, isSystemInjectionPriority, PendingSessionNotifications, renderTaskNotificationXml, SYSTEM_INJECTION_PRIORITIES, SystemInjectionQueue, taskNotificationDedupKey } from "../task-notification.js";
|
|
51
51
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
52
52
|
import { createPeerInboundChainRef, createPeerSelfRef } from "../../agents/peer-admission.js";
|
|
53
53
|
import { workflowSizeGuidelineChangeNotice } from "../../orchestration/workflow-size-guideline.js";
|
|
@@ -141,17 +141,19 @@ function resumeDecisionWasNegative(resume) {
|
|
|
141
141
|
}
|
|
142
142
|
const DEFERRED_REISSUE = "[DEFERRED] This tool call shared a batch with a call that suspended for durable approval, so it was " +
|
|
143
143
|
"NOT executed on resume. If you still need it, issue it again now.";
|
|
144
|
-
function toolEndBodyFrom(result, isError, settledBy, approver, resolution) {
|
|
144
|
+
function toolEndBodyFrom(result, isError, settledBy, approver, resolution, gatedCallIdOfRun) {
|
|
145
145
|
const o = toolOutputFrom(result);
|
|
146
146
|
const st = structuredFrom(result);
|
|
147
147
|
const det = isError ? result?.details : undefined;
|
|
148
148
|
const codeRaw = det?.code;
|
|
149
149
|
const kindRaw = det?.errorKind;
|
|
150
150
|
const code = typeof codeRaw === "string" ? codeRaw : typeof kindRaw === "string" ? kindRaw : undefined;
|
|
151
|
+
const gatedCallId = code === "gate.parked" ? gatedCallIdOfRun : undefined;
|
|
151
152
|
return {
|
|
152
153
|
...(o !== undefined ? { output: o.output, ...(o.truncated ? { truncated: true } : {}), ...(o.totalChars !== undefined ? { totalChars: o.totalChars } : {}) } : {}),
|
|
153
154
|
...(st !== undefined ? { structured: st } : {}),
|
|
154
155
|
...(typeof code === "string" ? { errorCode: code } : {}),
|
|
156
|
+
...(gatedCallId !== undefined ? { gatedCallId } : {}),
|
|
155
157
|
...(settledBy !== undefined ? { settledBy } : {}),
|
|
156
158
|
...(approver !== undefined ? { approver } : {}),
|
|
157
159
|
...(resolution !== undefined ? { resolution } : {}),
|
|
@@ -543,7 +545,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
543
545
|
const files = prepared.lspDiagnostics.registry.drain(prepared.lspDiagnostics.runIdent);
|
|
544
546
|
if (files.length > 0) {
|
|
545
547
|
queue.push({ type: "diagnostics", files, isNew: true, ...ident() });
|
|
546
|
-
const block = formatDiagnosticsBlock(files);
|
|
548
|
+
const block = mintSystemReminder(formatDiagnosticsBlock(files), prepared.reminderMark);
|
|
547
549
|
void prepared.harness.steer(block, { provenance: "engine-note" }).catch(() => undefined);
|
|
548
550
|
boundarySteered = true;
|
|
549
551
|
}
|
|
@@ -746,7 +748,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
746
748
|
}
|
|
747
749
|
}
|
|
748
750
|
if (due.length > 0) {
|
|
749
|
-
attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body), prepared.reminderMark)).join("\n");
|
|
751
|
+
attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
|
|
750
752
|
for (const a of due)
|
|
751
753
|
boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
|
|
752
754
|
rs.attach.attachmentsInjected += due.length;
|
|
@@ -765,9 +767,13 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
765
767
|
const injectedThisTurn = finalVerifyInjectedThisTurn ? "final_verification" : undefined;
|
|
766
768
|
if (!boundarySteered && !prepared.abortController.signal.aborted) {
|
|
767
769
|
try {
|
|
768
|
-
const
|
|
770
|
+
const batchSeat = await runHookSeat("postToolBatch", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }, (sig) => postToolBatchHook(batch, injectedThisTurn !== undefined ? { injectedThisTurn } : undefined, { identity: prepared.hookIdentity, signal: sig }));
|
|
771
|
+
if (batchSeat.expired) {
|
|
772
|
+
runnerHooks.onError?.(hookSeatExpiredError("postToolBatch", prepared.hookTimeoutMs, batchSeat.cause, "the boundary observation was abandoned and its additionalContext dropped; the turn boundary itself is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
|
|
773
|
+
}
|
|
774
|
+
const r = batchSeat.expired ? undefined : batchSeat.value;
|
|
769
775
|
if (r?.additionalContext && injectedThisTurn === undefined) {
|
|
770
|
-
const body = sanitizeUntrustedText(r.additionalContext);
|
|
776
|
+
const body = sanitizeUntrustedText(r.additionalContext, SHELLED_BODY_ENVELOPE_TAGS);
|
|
771
777
|
const budget = ATTACHMENT_BYTE_CAP - boundaryAttachmentBytes;
|
|
772
778
|
if (budget >= BATCH_CONTEXT_MIN_KEEP_BYTES) {
|
|
773
779
|
const clipped = clipToBytes(body, budget) === body
|
|
@@ -1008,7 +1014,7 @@ function makeTurnBoundary(prepared, stats, rs, deps) {
|
|
|
1008
1014
|
return onTurnBoundary;
|
|
1009
1015
|
}
|
|
1010
1016
|
function wrapGitFrame(body, mark) {
|
|
1011
|
-
return mintSystemReminder(sanitizeUntrustedText(body), mark);
|
|
1017
|
+
return mintSystemReminder(sanitizeUntrustedText(body, SHELLED_BODY_ENVELOPE_TAGS), mark);
|
|
1012
1018
|
}
|
|
1013
1019
|
async function resolveGitLegDelivery(prepared, cpMirror, report) {
|
|
1014
1020
|
const ref = prepared.gitStatusRef;
|
|
@@ -1344,7 +1350,7 @@ function makeHarnessHandlers(prepared, stats, rs, deps) {
|
|
|
1344
1350
|
toolName: event.toolName,
|
|
1345
1351
|
...(toolLabels.get(event.toolName) !== undefined ? { label: toolLabels.get(event.toolName) } : {}),
|
|
1346
1352
|
isError: event.isError,
|
|
1347
|
-
...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver, settlement?.resolution),
|
|
1353
|
+
...toolEndBodyFrom(event.result, event.isError, settlement?.settledBy, settlement?.approver, settlement?.resolution, prepared.abortController.signal.aborted ? gatedCallIdOf(prepared) : undefined),
|
|
1348
1354
|
...ident(),
|
|
1349
1355
|
});
|
|
1350
1356
|
announceWorkspaceMove();
|
|
@@ -1502,6 +1508,7 @@ export class Runner {
|
|
|
1502
1508
|
...(typeof h.postCompact === "function" ? { postCompact: (c) => h.postCompact(c) } : {}),
|
|
1503
1509
|
...(typeof h.stopFailure === "function" ? { stopFailure: (c) => h.stopFailure(c) } : {}),
|
|
1504
1510
|
...(typeof h.permissionDenied === "function" ? { permissionDenied: (p) => h.permissionDenied(p) } : {}),
|
|
1511
|
+
...(h.timeoutMs !== undefined ? { timeoutMs: h.timeoutMs } : {}),
|
|
1505
1512
|
}
|
|
1506
1513
|
: undefined;
|
|
1507
1514
|
return {
|
|
@@ -1781,6 +1788,13 @@ export class Runner {
|
|
|
1781
1788
|
const actorIn = options?.actor;
|
|
1782
1789
|
const actor = actorIn === undefined ? undefined : snapshotActorAssertion(actorIn);
|
|
1783
1790
|
const projected = projectHumanInput({ text, actor, source: "steer" });
|
|
1791
|
+
const effectiveInputId = typeof inputId === "string" ? inputId : uuidv7();
|
|
1792
|
+
const parkRecord = {
|
|
1793
|
+
text,
|
|
1794
|
+
trusted,
|
|
1795
|
+
inputId: effectiveInputId,
|
|
1796
|
+
...(actor !== undefined ? { actor } : {}),
|
|
1797
|
+
};
|
|
1784
1798
|
let payload;
|
|
1785
1799
|
let mintsAFrame;
|
|
1786
1800
|
let replay;
|
|
@@ -1795,7 +1809,7 @@ export class Runner {
|
|
|
1795
1809
|
source: "steer",
|
|
1796
1810
|
delivery: "queued",
|
|
1797
1811
|
sessionSeq: nextHumanInputSeq(h.harness),
|
|
1798
|
-
|
|
1812
|
+
inputId: effectiveInputId,
|
|
1799
1813
|
...(actor !== undefined ? { actor } : {}),
|
|
1800
1814
|
...(actor?.issuer !== undefined ? { issuer: actor.issuer } : {}),
|
|
1801
1815
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
@@ -1812,7 +1826,7 @@ export class Runner {
|
|
|
1812
1826
|
const h = handle ?? (await orTimeout(ready));
|
|
1813
1827
|
if (!h)
|
|
1814
1828
|
throw steeringError("the task is not running");
|
|
1815
|
-
payload = trusted ? formatHookFeedback(projected, h.reminderMark) : projected;
|
|
1829
|
+
payload = trusted ? formatHookFeedback(projected, h.reminderMark) : frameMidTurnUserInput(projected);
|
|
1816
1830
|
mintsAFrame = payload.trim().length !== 0;
|
|
1817
1831
|
replay = { payload, trusted, ...(actor !== undefined ? { actor } : {}) };
|
|
1818
1832
|
if (typeof inputId === "string") {
|
|
@@ -1828,7 +1842,7 @@ export class Runner {
|
|
|
1828
1842
|
}
|
|
1829
1843
|
}
|
|
1830
1844
|
try {
|
|
1831
|
-
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, ...(actor !== undefined ? { actor } : {}) });
|
|
1845
|
+
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) });
|
|
1832
1846
|
noteAccepted(h);
|
|
1833
1847
|
return;
|
|
1834
1848
|
}
|
|
@@ -1839,7 +1853,7 @@ export class Runner {
|
|
|
1839
1853
|
const birthDeadline = Date.now() + READY_TIMEOUT_MS;
|
|
1840
1854
|
while (resultValue === undefined && !h.loop.ended && Date.now() < birthDeadline) {
|
|
1841
1855
|
try {
|
|
1842
|
-
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, ...(actor !== undefined ? { actor } : {}) });
|
|
1856
|
+
await h.harness.steer(payload, { provenance: "engine-note", callerAuthored: true, parkRecord, ...(actor !== undefined ? { actor } : {}) });
|
|
1843
1857
|
noteAccepted(h);
|
|
1844
1858
|
return;
|
|
1845
1859
|
}
|
|
@@ -1880,6 +1894,9 @@ export class Runner {
|
|
|
1880
1894
|
if (input.source !== undefined && typeof input.source !== "string") {
|
|
1881
1895
|
throw notifyError("input.source must be a string when present", "notify.invalid_payload");
|
|
1882
1896
|
}
|
|
1897
|
+
if (opts?.priority !== undefined && !isSystemInjectionPriority(opts.priority)) {
|
|
1898
|
+
throw notifyError(`opts.priority must be one of ${SYSTEM_INJECTION_PRIORITIES.join("/")} when present`, "notify.invalid_payload");
|
|
1899
|
+
}
|
|
1883
1900
|
const payload = {
|
|
1884
1901
|
task_id: input.task_id,
|
|
1885
1902
|
task_type: "external",
|
|
@@ -1943,6 +1960,8 @@ export class Runner {
|
|
|
1943
1960
|
const h = handle ?? (await orTimeout(ready));
|
|
1944
1961
|
if (!h)
|
|
1945
1962
|
return;
|
|
1963
|
+
if (!h.abortController.signal.aborted)
|
|
1964
|
+
h.loop.userInterrupted = true;
|
|
1946
1965
|
h.abortController.abort();
|
|
1947
1966
|
void h.harness.abort();
|
|
1948
1967
|
},
|
|
@@ -2027,7 +2046,18 @@ export class Runner {
|
|
|
2027
2046
|
});
|
|
2028
2047
|
const upstreamTaskNotification = internals?.onTaskNotification;
|
|
2029
2048
|
const deliveredAtTurnOpen = new Set();
|
|
2049
|
+
let priorityNowDisclosed = false;
|
|
2030
2050
|
const injectTaskNotification = (notification, opts) => {
|
|
2051
|
+
if (opts?.priority === "now" && !priorityNowDisclosed) {
|
|
2052
|
+
priorityNowDisclosed = true;
|
|
2053
|
+
deliverEngineNotice(this.deps.onNotice, {
|
|
2054
|
+
code: "task.injection_priority_unimplemented",
|
|
2055
|
+
message: `a notification was injected with priority "now", which this engine does not implement: all injection ` +
|
|
2056
|
+
`priorities deliver at the NEXT turn boundary and none aborts the running turn. The notification is ` +
|
|
2057
|
+
`delivered — only the interrupting semantics are absent.`,
|
|
2058
|
+
detail: { priority: "now", ...(spec.taskId !== undefined ? { taskId: spec.taskId } : {}) },
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2031
2061
|
if (deliveredAtTurnOpen.has(taskNotificationDedupKey(notification)))
|
|
2032
2062
|
return Promise.resolve("dropped_duplicate");
|
|
2033
2063
|
if (!notificationLaneLive) {
|
|
@@ -2096,7 +2126,12 @@ export class Runner {
|
|
|
2096
2126
|
this.pendingSessionNotifications.pend(notificationSessionId, p);
|
|
2097
2127
|
};
|
|
2098
2128
|
prepared.harness.engineInjectionsHeld = () => prepared.batchHaltRef.current !== undefined;
|
|
2129
|
+
let undrainedUserAtEnd;
|
|
2099
2130
|
prepared.harness.onUndrainedUserInputs = (counts) => {
|
|
2131
|
+
if (prepared.suspendRef.token !== undefined || prepared.reviewRef.token !== undefined) {
|
|
2132
|
+
undrainedUserAtEnd = counts;
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2100
2135
|
for (const notice of undrainedUserInputNotices(counts, spec.taskId)) {
|
|
2101
2136
|
deliverEngineNotice(this.deps.onNotice, notice);
|
|
2102
2137
|
}
|
|
@@ -2125,7 +2160,7 @@ export class Runner {
|
|
|
2125
2160
|
}
|
|
2126
2161
|
}
|
|
2127
2162
|
}
|
|
2128
|
-
const loopLatch = { ended: false };
|
|
2163
|
+
const loopLatch = { ended: false, userInterrupted: false };
|
|
2129
2164
|
onReady({ harness: prepared.harness, abortController: prepared.abortController, loop: loopLatch, reminderMark: prepared.reminderMark });
|
|
2130
2165
|
const stats = { turns: 0, tokens: 0, toolCalls: 0, promptTokens: 0, totalInputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, cacheWriteTokensLong: 0, outputTokens: 0, costMicroUsd: 0 };
|
|
2131
2166
|
prepared.liveSpendRef.get = () => ({ costMicroUsd: stats.costMicroUsd, tokens: stats.tokens, turns: stats.turns, walltimeMs: Math.round(performance.now() - rs.telemetry.taskStartMonotonic) });
|
|
@@ -2807,12 +2842,21 @@ export class Runner {
|
|
|
2807
2842
|
return [];
|
|
2808
2843
|
let result;
|
|
2809
2844
|
try {
|
|
2810
|
-
|
|
2845
|
+
const stopSeat = await runHookSeat("stop", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => stopHook({
|
|
2811
2846
|
stopHookActive: consecutiveBlocks > 0,
|
|
2812
2847
|
consecutiveBlocks,
|
|
2813
2848
|
getBranch: () => prepared.session.getBranch(),
|
|
2814
2849
|
identity: prepared.hookIdentity,
|
|
2815
|
-
|
|
2850
|
+
signal: sig,
|
|
2851
|
+
}));
|
|
2852
|
+
if (stopSeat.expired) {
|
|
2853
|
+
if (stopSeat.cause === "timeout") {
|
|
2854
|
+
this.deps.onError?.(hookSeatExpiredError("stop", prepared.hookTimeoutMs, stopSeat.cause, "the run was allowed to END (the seat's own no-opinion answer); no pushback and no additional context were injected"), { phase: "hook", sessionId: prepared.sessionId });
|
|
2855
|
+
}
|
|
2856
|
+
consecutiveBlocks = 0;
|
|
2857
|
+
return [];
|
|
2858
|
+
}
|
|
2859
|
+
result = stopSeat.value;
|
|
2816
2860
|
}
|
|
2817
2861
|
catch (err) {
|
|
2818
2862
|
this.deps.onError?.(err instanceof Error ? err : new Error(String(err)), { phase: "hook", sessionId: prepared.sessionId });
|
|
@@ -2887,7 +2931,7 @@ export class Runner {
|
|
|
2887
2931
|
...this.seamCCompactionOptions(prepared),
|
|
2888
2932
|
...gitRestateOption(prepared),
|
|
2889
2933
|
...windowSafetyOptions(prepared.harness.getModel()),
|
|
2890
|
-
...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity),
|
|
2934
|
+
...this.compactionHookOptions(spec, prepared.sessionId, "forced", prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
|
|
2891
2935
|
});
|
|
2892
2936
|
if (comp.compacted) {
|
|
2893
2937
|
compactionBreaker.failures = 0;
|
|
@@ -2952,7 +2996,7 @@ export class Runner {
|
|
|
2952
2996
|
runnerHooks: {
|
|
2953
2997
|
onError: this.deps.onError,
|
|
2954
2998
|
seamCCompactionOptions: (p) => this.seamCCompactionOptions(p),
|
|
2955
|
-
compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity),
|
|
2999
|
+
compactionHookOptions: (s, sid, trig) => this.compactionHookOptions(s, sid, trig, prepared.hookIdentity, { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal }),
|
|
2956
3000
|
recordCompactionReuse: (p, c) => this.recordCompactionReuse(p, c),
|
|
2957
3001
|
},
|
|
2958
3002
|
});
|
|
@@ -3055,6 +3099,7 @@ export class Runner {
|
|
|
3055
3099
|
let final;
|
|
3056
3100
|
let threw;
|
|
3057
3101
|
let abortedLive = false;
|
|
3102
|
+
let userInterruptedLive = false;
|
|
3058
3103
|
let strandedHumanAnswers = [];
|
|
3059
3104
|
try {
|
|
3060
3105
|
if (prepared.abortController.signal.aborted) {
|
|
@@ -3182,7 +3227,23 @@ export class Runner {
|
|
|
3182
3227
|
const userPromptSubmit = (spec.hooks ?? this.deps.hooks)?.userPromptSubmit;
|
|
3183
3228
|
if (userPromptSubmit) {
|
|
3184
3229
|
try {
|
|
3185
|
-
const
|
|
3230
|
+
const promptSeat = await runHookSeat("userPromptSubmit", { timeoutMs: prepared.hookTimeoutMs, signal: prepared.abortController.signal, abortEnds: true }, (sig) => userPromptSubmit(spec.objective, { identity: prepared.hookIdentity, signal: sig }));
|
|
3231
|
+
if (promptSeat.expired) {
|
|
3232
|
+
if (promptSeat.cause === "timeout") {
|
|
3233
|
+
try {
|
|
3234
|
+
this.deps.onError?.(hookSeatExpiredError("userPromptSubmit", prepared.hookTimeoutMs, promptSeat.cause, "the prompt was NOT submitted (fail-closed) and the task ends blocked"), { phase: "hook", sessionId: prepared.sessionId });
|
|
3235
|
+
}
|
|
3236
|
+
catch {
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
prepared.blockedRef.reason = formatHookFeedback(promptSeat.cause === "timeout"
|
|
3240
|
+
? `the deployment's userPromptSubmit hook did not answer within its ${prepared.hookTimeoutMs}ms bound while screening this prompt; ` +
|
|
3241
|
+
`the prompt was NOT submitted (fail-closed)`
|
|
3242
|
+
: `the task was cancelled while the deployment's userPromptSubmit hook was still screening this prompt; ` +
|
|
3243
|
+
`the prompt was NOT submitted (fail-closed)`, prepared.reminderMark);
|
|
3244
|
+
promptBlocked = true;
|
|
3245
|
+
}
|
|
3246
|
+
const decision = promptSeat.expired ? undefined : promptSeat.value;
|
|
3186
3247
|
if (decision?.block) {
|
|
3187
3248
|
prepared.blockedRef.reason = formatHookFeedback(decision.block, prepared.reminderMark);
|
|
3188
3249
|
promptBlocked = true;
|
|
@@ -3258,7 +3319,7 @@ export class Runner {
|
|
|
3258
3319
|
}
|
|
3259
3320
|
}
|
|
3260
3321
|
if (firstFrames.length > 0) {
|
|
3261
|
-
effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
|
|
3322
|
+
effectiveObjective = `${firstFrames.map((f) => mintSystemReminder(sanitizeUntrustedText(f.body, attachmentEnvelopeTags(f.source)), prepared.reminderMark)).join("\n")}\n${effectiveObjective}`;
|
|
3262
3323
|
}
|
|
3263
3324
|
}
|
|
3264
3325
|
const gitQueuedChars = gitLegDelivered !== undefined && gitLegDelivered.standalone ? gitLegDelivered.wrapped.length : 0;
|
|
@@ -3351,6 +3412,7 @@ export class Runner {
|
|
|
3351
3412
|
}
|
|
3352
3413
|
loopLatch.ended = true;
|
|
3353
3414
|
abortedLive = prepared.abortController.signal.aborted;
|
|
3415
|
+
userInterruptedLive = loopLatch.userInterrupted;
|
|
3354
3416
|
}
|
|
3355
3417
|
catch (err) {
|
|
3356
3418
|
loopLatch.ended = true;
|
|
@@ -3362,6 +3424,7 @@ export class Runner {
|
|
|
3362
3424
|
}
|
|
3363
3425
|
threw = err;
|
|
3364
3426
|
abortedLive = prepared.abortController.signal.aborted;
|
|
3427
|
+
userInterruptedLive = loopLatch.userInterrupted;
|
|
3365
3428
|
}
|
|
3366
3429
|
finally {
|
|
3367
3430
|
timeout.clear();
|
|
@@ -3399,13 +3462,17 @@ export class Runner {
|
|
|
3399
3462
|
const durablyPaused = suspended || reviewPaused;
|
|
3400
3463
|
const interrupted = !durablyPaused &&
|
|
3401
3464
|
(threw !== undefined || timeout.fired || rs.limits.turnsExceeded || rs.limits.budgetHit !== undefined || abortedLive || final?.stopReason === "aborted");
|
|
3465
|
+
let orphansClosed = 0;
|
|
3466
|
+
let reconcileComplete = false;
|
|
3402
3467
|
if (interrupted) {
|
|
3403
3468
|
try {
|
|
3404
3469
|
const report = await reconcileInterruptedSession(prepared.session, prepared.toolEffects, undefined, startedToolCallIds);
|
|
3470
|
+
orphansClosed = report.recovered.length;
|
|
3405
3471
|
for (const orphan of report.recovered) {
|
|
3406
3472
|
queue.push({ type: "tool_end", toolCallId: orphan.toolCallId, toolName: orphan.toolName, ...(toolLabels.has(orphan.toolName) ? { label: toolLabels.get(orphan.toolName) } : {}), isError: true, ...reconciledToolEndBody(orphan), ...ident() });
|
|
3407
3473
|
emitCommitted(orphan.entryId, "toolResult", orphan.toolCallId);
|
|
3408
3474
|
}
|
|
3475
|
+
reconcileComplete = true;
|
|
3409
3476
|
}
|
|
3410
3477
|
catch (reconcileErr) {
|
|
3411
3478
|
this.deps.onError?.(reconcileErr instanceof Error ? reconcileErr : new Error(String(reconcileErr)), {
|
|
@@ -3423,6 +3490,52 @@ export class Runner {
|
|
|
3423
3490
|
});
|
|
3424
3491
|
}
|
|
3425
3492
|
}
|
|
3493
|
+
if (interrupted && userInterruptedLive && reconcileComplete) {
|
|
3494
|
+
try {
|
|
3495
|
+
const entryId = await appendInterruptionMarker(prepared.session, { toolUseInFlight: orphansClosed > 0 });
|
|
3496
|
+
emitCommitted(entryId, "user");
|
|
3497
|
+
}
|
|
3498
|
+
catch (markerErr) {
|
|
3499
|
+
this.deps.onError?.(markerErr instanceof Error ? markerErr : new Error(String(markerErr)), {
|
|
3500
|
+
phase: "interrupt-reconcile",
|
|
3501
|
+
sessionId: prepared.sessionId,
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
let migratedParked = { steer: 0, followUp: 0 };
|
|
3506
|
+
const parkToken = prepared.suspendRef.token ?? prepared.reviewRef.token;
|
|
3507
|
+
const parkScope = prepared.suspendRef.token !== undefined ? prepared.suspendRef.scope : prepared.reviewRef.scope;
|
|
3508
|
+
if (durablyPaused && parkToken !== undefined && parkScope !== undefined && this.deps.checkpointStore !== undefined) {
|
|
3509
|
+
const store = this.deps.checkpointStore;
|
|
3510
|
+
const carried = [];
|
|
3511
|
+
for (const record of prepared.harness.readParkableUserInputs()) {
|
|
3512
|
+
try {
|
|
3513
|
+
if (!(await store.setPendingSteer(parkToken, parkScope, record)))
|
|
3514
|
+
break;
|
|
3515
|
+
carried.push(record);
|
|
3516
|
+
}
|
|
3517
|
+
catch (parkErr) {
|
|
3518
|
+
this.deps.onError?.(parkErr instanceof Error ? parkErr : new Error(String(parkErr)), {
|
|
3519
|
+
phase: "config",
|
|
3520
|
+
sessionId: prepared.sessionId,
|
|
3521
|
+
});
|
|
3522
|
+
const parkCode = errorCodeOf(parkErr);
|
|
3523
|
+
if (parkCode === "steering.invalid_content" || parkCode === "steering.queue_full")
|
|
3524
|
+
continue;
|
|
3525
|
+
break;
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
migratedParked = prepared.harness.dropParkedUserInputs(carried);
|
|
3529
|
+
}
|
|
3530
|
+
if (undrainedUserAtEnd !== undefined) {
|
|
3531
|
+
const remaining = {
|
|
3532
|
+
steer: Math.max(0, undrainedUserAtEnd.steer - migratedParked.steer),
|
|
3533
|
+
followUp: Math.max(0, undrainedUserAtEnd.followUp - migratedParked.followUp),
|
|
3534
|
+
};
|
|
3535
|
+
for (const notice of undrainedUserInputNotices(remaining, spec.taskId)) {
|
|
3536
|
+
deliverEngineNotice(this.deps.onNotice, notice);
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3426
3539
|
if (rs.attach.attachState?.postCompactPending === true && (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true) {
|
|
3427
3540
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "compaction.announce_dropped", version: 1, taskId: rs.telemetry.taskId, ts: Date.now() }));
|
|
3428
3541
|
}
|
|
@@ -3432,6 +3545,7 @@ export class Runner {
|
|
|
3432
3545
|
brain: compactionBrain,
|
|
3433
3546
|
windowSafety: windowSafetyOptions(prepared.model),
|
|
3434
3547
|
onCompactionFailed: (reason) => queue.push({ type: "compaction_outcome", outcome: "failed", trigger: "auto", reason, ...ident() }),
|
|
3548
|
+
cancelled: abortedLive,
|
|
3435
3549
|
});
|
|
3436
3550
|
try {
|
|
3437
3551
|
if (comp?.compacted) {
|
|
@@ -3594,12 +3708,20 @@ export class Runner {
|
|
|
3594
3708
|
!abortedLive &&
|
|
3595
3709
|
result.errorCode !== "conflict") {
|
|
3596
3710
|
try {
|
|
3597
|
-
await stopFailureHook({
|
|
3711
|
+
const failureSeat = await runHookSeat("stopFailure", { timeoutMs: prepared.hookTimeoutMs }, (sig) => stopFailureHook({
|
|
3598
3712
|
identity: prepared.hookIdentity,
|
|
3599
3713
|
error: result.errorMessage ?? "model error",
|
|
3600
3714
|
...(result.errorCode !== undefined ? { errorKind: result.errorCode } : {}),
|
|
3601
3715
|
turns: stats.turns,
|
|
3602
|
-
|
|
3716
|
+
signal: sig,
|
|
3717
|
+
}));
|
|
3718
|
+
if (failureSeat.expired) {
|
|
3719
|
+
try {
|
|
3720
|
+
this.deps.onError?.(hookSeatExpiredError("stopFailure", prepared.hookTimeoutMs, failureSeat.cause, "the terminal observation was abandoned; the assembled TaskResult is unchanged"), { phase: "hook", sessionId: prepared.sessionId });
|
|
3721
|
+
}
|
|
3722
|
+
catch {
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3603
3725
|
}
|
|
3604
3726
|
catch (err) {
|
|
3605
3727
|
try {
|
|
@@ -4610,10 +4732,12 @@ export class Runner {
|
|
|
4610
4732
|
consecutiveProviderReuse: prepared.compactionReuseRef.consecutive,
|
|
4611
4733
|
};
|
|
4612
4734
|
}
|
|
4613
|
-
compactionHookOptions(spec, sessionId, trigger, identity) {
|
|
4735
|
+
compactionHookOptions(spec, sessionId, trigger, identity, seatBound) {
|
|
4614
4736
|
const hooks = spec.hooks ?? this.deps.hooks;
|
|
4615
4737
|
const pre = hooks?.preCompact;
|
|
4616
4738
|
const post = hooks?.postCompact;
|
|
4739
|
+
const seatMs = seatBound?.timeoutMs ?? resolveHookTimeoutMs(hooks?.timeoutMs);
|
|
4740
|
+
const seatSignal = seatBound?.signal;
|
|
4617
4741
|
const withIdentity = (ctx) => identity !== undefined ? { ...ctx, identity } : ctx;
|
|
4618
4742
|
return {
|
|
4619
4743
|
trigger,
|
|
@@ -4629,7 +4753,12 @@ export class Runner {
|
|
|
4629
4753
|
}
|
|
4630
4754
|
};
|
|
4631
4755
|
try {
|
|
4632
|
-
const
|
|
4756
|
+
const seat = await runHookSeat("preCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => pre.call(hooks, { ...ctx, signal: sig }));
|
|
4757
|
+
if (seat.expired) {
|
|
4758
|
+
report(hookSeatExpiredError("preCompact", seatMs, seat.cause, `the "${ctx.trigger}" compaction PROCEEDED unblocked and with no additional instructions`));
|
|
4759
|
+
return undefined;
|
|
4760
|
+
}
|
|
4761
|
+
const r = seat.value;
|
|
4633
4762
|
if (r?.block && ctx.trigger === "forced") {
|
|
4634
4763
|
report(new Error(`a preCompact callback blocked a "forced" compaction — ignored (PTL/trim-pressure compaction is not optional): ${r.block}`));
|
|
4635
4764
|
}
|
|
@@ -4646,7 +4775,14 @@ export class Runner {
|
|
|
4646
4775
|
? {
|
|
4647
4776
|
postCompact: async (rawCtx) => {
|
|
4648
4777
|
try {
|
|
4649
|
-
await post.call(hooks, withIdentity(rawCtx));
|
|
4778
|
+
const seat = await runHookSeat("postCompact", { timeoutMs: seatMs, ...(seatSignal !== undefined ? { signal: seatSignal } : {}) }, (sig) => post.call(hooks, { ...withIdentity(rawCtx), signal: sig }));
|
|
4779
|
+
if (seat.expired) {
|
|
4780
|
+
try {
|
|
4781
|
+
this.deps.onError?.(hookSeatExpiredError("postCompact", seatMs, seat.cause, "the observation was abandoned; the compaction that already landed is unchanged"), { phase: "hook", sessionId });
|
|
4782
|
+
}
|
|
4783
|
+
catch {
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4650
4786
|
}
|
|
4651
4787
|
catch (err) {
|
|
4652
4788
|
try {
|
|
@@ -4690,7 +4826,10 @@ export class Runner {
|
|
|
4690
4826
|
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
4691
4827
|
...this.seamCCompactionOptions(prepared),
|
|
4692
4828
|
...gitRestateOption(prepared),
|
|
4693
|
-
...this.compactionHookOptions(spec, prepared.sessionId, "auto", prepared.hookIdentity
|
|
4829
|
+
...this.compactionHookOptions(spec, prepared.sessionId, "auto", prepared.hookIdentity, {
|
|
4830
|
+
timeoutMs: prepared.hookTimeoutMs,
|
|
4831
|
+
...(opts?.cancelled === true ? { signal: prepared.abortController.signal } : {}),
|
|
4832
|
+
}),
|
|
4694
4833
|
});
|
|
4695
4834
|
this.recordCompactionReuse(prepared, finishComp);
|
|
4696
4835
|
if (finishComp.unevaluableWindow) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalizeTarget, writeTargetPath } from "../../tools/fs/safety.js";
|
|
2
2
|
import { isWinFormPath } from "../../tools/fs/safety.js";
|
|
3
|
-
import { createCoarseCommandNamePolicy } from "../tool-policy.js";
|
|
3
|
+
import { createCoarseCommandNamePolicy, namespacedCoveringEntries, namespacedCoveringHit } from "../tool-policy.js";
|
|
4
4
|
export const PATH_WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit"]);
|
|
5
5
|
export const PATH_CONFINABLE_WRITE_TOOLS = new Set([...PATH_WRITE_TOOLS, "NotebookEdit"]);
|
|
6
6
|
export function isWithin(root, p) {
|
|
@@ -24,6 +24,8 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
24
24
|
const { env, rootPath, toolEffects } = opts;
|
|
25
25
|
const toolDeny = new Set(rules.toolDeny ?? []);
|
|
26
26
|
const toolAllow = rules.toolAllow ? new Set(rules.toolAllow) : undefined;
|
|
27
|
+
const toolDenyCovering = namespacedCoveringEntries(rules.toolDeny);
|
|
28
|
+
const toolAllowCovering = namespacedCoveringEntries(rules.toolAllow);
|
|
27
29
|
const cmdPolicy = rules.commandAllow || rules.commandDeny
|
|
28
30
|
? createCoarseCommandNamePolicy({
|
|
29
31
|
...(rules.commandAllow ? { allow: rules.commandAllow } : {}),
|
|
@@ -36,9 +38,9 @@ export function createSessionRulePolicy(rules, opts) {
|
|
|
36
38
|
nameSets: [{ ...(rules.toolDeny?.length ? { deny: [...rules.toolDeny] } : {}), ...(rules.toolAllow?.length ? { allow: [...rules.toolAllow] } : {}) }],
|
|
37
39
|
async check(req, signal) {
|
|
38
40
|
const toolName = req.toolName;
|
|
39
|
-
if (toolDeny.has(toolName))
|
|
41
|
+
if (toolDeny.has(toolName) || namespacedCoveringHit(toolDenyCovering, toolName))
|
|
40
42
|
return deny(`tool "${req.toolName}" is denied by a session rule`);
|
|
41
|
-
if (toolAllow && !toolAllow.has(toolName)) {
|
|
43
|
+
if (toolAllow && !toolAllow.has(toolName) && !namespacedCoveringHit(toolAllowCovering, toolName)) {
|
|
42
44
|
return deny(`tool "${req.toolName}" is not in the session-rule allowlist`);
|
|
43
45
|
}
|
|
44
46
|
if (cmdPolicy) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
|
+
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
3
4
|
export const OUTPUT_TOOL_NAME = "StructuredOutput";
|
|
4
5
|
export const REPORT_BLOCKED_TOOL_NAME = "ReportBlocked";
|
|
5
6
|
export const REPORT_FINDINGS_TOOL_NAME = "ReportFindings";
|
|
@@ -123,7 +124,8 @@ export function skillListingLine(s) {
|
|
|
123
124
|
const desc = s.description.length > SKILL_DESC_MAX_CHARS
|
|
124
125
|
? `${s.description.slice(0, SKILL_DESC_MAX_CHARS - 1)}…`
|
|
125
126
|
: s.description;
|
|
126
|
-
|
|
127
|
+
const safe = (t) => sanitizeUntrustedText(t, SHELLED_BODY_ENVELOPE_TAGS);
|
|
128
|
+
return `- ${safe(s.name)}: ${safe(desc)}${safe(skillFilesSuffix(s.files))}`;
|
|
127
129
|
}
|
|
128
130
|
const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached — lower-priority skills are listed name-only; descriptions via the tool)";
|
|
129
131
|
export function buildSkillsBlock(skills) {
|
|
@@ -141,7 +143,7 @@ export function buildSkillsBlock(skills) {
|
|
|
141
143
|
const lineCost = (l) => bytes(l) + 1;
|
|
142
144
|
const budget = SKILLS_BLOCK_MAX_BYTES - bytes(render([]));
|
|
143
145
|
const fullLines = skills.map((s) => skillListingLine(s));
|
|
144
|
-
const nameLines = skills.map((s) => `- ${s.name}`);
|
|
146
|
+
const nameLines = skills.map((s) => `- ${sanitizeUntrustedText(s.name, SHELLED_BODY_ENVELOPE_TAGS)}`);
|
|
145
147
|
const fullTotal = fullLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
146
148
|
if (fullTotal <= budget)
|
|
147
149
|
return render(fullLines);
|
|
@@ -65,6 +65,7 @@ export interface BackgroundTaskSnapshot {
|
|
|
65
65
|
description?: string;
|
|
66
66
|
status: string;
|
|
67
67
|
}
|
|
68
|
+
export declare function attachmentEnvelopeTags(source: AttachmentSource): string[];
|
|
68
69
|
/** One due reminder. `body` is the inner text — the steerer adds the `<system-reminder>` wrapper (F3). */
|
|
69
70
|
export interface TurnAttachment {
|
|
70
71
|
source: AttachmentSource;
|
|
@@ -638,7 +639,9 @@ export declare function replayAnnouncedListing(texts: readonly string[], headers
|
|
|
638
639
|
}): Set<string> | undefined;
|
|
639
640
|
/** Removal-frame header for the agent-type roster (single-sourced for renderer + replay parser). */
|
|
640
641
|
export declare const AGENT_LISTING_REMOVED_HEADER = "The following agent types are no longer available:";
|
|
641
|
-
/** CC-verbatim roster-drift header — same single-source rationale as {@link agentListingInitialHeader}.
|
|
642
|
+
/** CC-verbatim roster-drift header — same single-source rationale as {@link agentListingInitialHeader}.
|
|
643
|
+
* The interpolated tool name is neutralized on both headers: a caller renames the delegation tool, so
|
|
644
|
+
* the name is deployment text arriving on the marked-reminder lane like every other field here. */
|
|
642
645
|
export declare function agentListingDeltaHeader(toolName: string): string;
|
|
643
646
|
/**
|
|
644
647
|
* G1 续批 — agent-type roster announce (CC 2.1.198 `agent_listing_delta`, bundle pretty.js:599199-599223,
|
|
@@ -745,11 +748,18 @@ export declare const MCP_INSTRUCTIONS_MAX_CHARS: number;
|
|
|
745
748
|
* — fenced on the delta lane, RAW-INTERPOLATED (no fence, no bound) into the stable prompt. A single
|
|
746
749
|
* source makes the divergence unrepresentable.
|
|
747
750
|
*
|
|
748
|
-
* Truncation is DISCLOSED (§25 honesty):
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
751
|
+
* Truncation is DISCLOSED (§25 honesty): the fence clips silently with an ellipsis, which reads to the
|
|
752
|
+
* model as the server's own text. The trailing note lives OUTSIDE the fence, on a trusted line, so the
|
|
753
|
+
* model can tell "the server said less" from "we cut it".
|
|
754
|
+
*
|
|
755
|
+
* The clip verdict comes FROM the fence (`delimitUntrustedWithClip`), never from a length test
|
|
756
|
+
* re-derived here. The earlier shape did re-derive it — a local `sanitizeUntrustedText` pre-pass with
|
|
757
|
+
* the DEFAULT (reminder-only) tag set, measured against the cap — and that mirror silently stopped
|
|
758
|
+
* matching once the fence widened its neutralization to the whole authority-envelope family: each
|
|
759
|
+
* widened-family tag occurrence adds a ZWSP the pre-count never saw, so a server could land its text
|
|
760
|
+
* in the `(cap − N, cap]` window and get the body clipped with the honesty note withheld, N being its
|
|
761
|
+
* own choice of tag count. One measurement, taken where the cut happens, is the only shape that cannot
|
|
762
|
+
* drift.
|
|
753
763
|
*/
|
|
754
764
|
export declare function fenceMcpServerInstructions(server: string, text: string): string;
|
|
755
765
|
/**
|