@sema-agent/core 7.19.0 → 7.20.1
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 +199 -2
- package/dist/agents/subagent.js +12 -20
- package/dist/agents/teacher.js +2 -5
- package/dist/agents/verify.js +21 -10
- package/dist/brain/anthropic.js +3 -1
- package/dist/brain/open-responses.js +4 -2
- package/dist/brain/openai.js +3 -1
- package/dist/brain/stream-engine.d.ts +4 -0
- package/dist/brain/stream-engine.js +4 -0
- package/dist/core/auto-mode.d.ts +37 -18
- package/dist/core/auto-mode.js +1 -1
- package/dist/core/child-clamp-seats.d.ts +79 -0
- package/dist/core/child-clamp-seats.js +29 -0
- package/dist/core/engine-notice.d.ts +76 -49
- package/dist/core/fs-write-gate-policy.d.ts +17 -9
- package/dist/core/fs-write-gate-policy.js +51 -4
- package/dist/core/gate-bounded-await.d.ts +38 -0
- package/dist/core/gate-bounded-await.js +53 -0
- package/dist/core/gate-fold.js +16 -48
- package/dist/core/gate-lanes.js +11 -3
- package/dist/core/gate-outcome.d.ts +9 -6
- package/dist/core/governance-codes.d.ts +16 -1
- package/dist/core/governance-codes.js +14 -0
- package/dist/core/hooks.d.ts +8 -8
- package/dist/core/memory-engine/engine.d.ts +14 -9
- package/dist/core/memory-engine/engine.js +21 -8
- package/dist/core/memory-engine/layout.d.ts +31 -0
- package/dist/core/memory-engine/layout.js +68 -3
- package/dist/core/memory-engine/tools.js +6 -0
- package/dist/core/memory-engine/types.d.ts +24 -0
- package/dist/core/memory.d.ts +19 -0
- package/dist/core/memory.js +12 -0
- package/dist/core/remote-env.d.ts +32 -0
- package/dist/core/remote-env.js +4 -1
- package/dist/core/runner/advertised-writable-dirs.d.ts +59 -1
- package/dist/core/runner/advertised-writable-dirs.js +51 -3
- package/dist/core/runner/announce-once-ledger.d.ts +15 -0
- package/dist/core/runner/announce-once-ledger.js +8 -0
- package/dist/core/runner/classifier-ladder.d.ts +5 -0
- package/dist/core/runner/classifier-ladder.js +13 -2
- package/dist/core/runner/contracts.d.ts +44 -1
- package/dist/core/runner/denial-limit-arms.d.ts +14 -1
- package/dist/core/runner/denial-limit-arms.js +5 -4
- package/dist/core/runner/env-capability-census.d.ts +30 -0
- package/dist/core/runner/env-capability-census.js +38 -0
- package/dist/core/runner/forward-sink.d.ts +32 -0
- package/dist/core/runner/forward-sink.js +41 -0
- package/dist/core/runner/gate-exit.d.ts +4 -0
- package/dist/core/runner/gate-exit.js +4 -1
- package/dist/core/runner/git-status-frame.d.ts +0 -9
- package/dist/core/runner/listing-fold.d.ts +57 -0
- package/dist/core/runner/listing-fold.js +75 -0
- package/dist/core/runner/prepare-execution-env.d.ts +1 -1
- package/dist/core/runner/prepare-execution-env.js +2 -0
- package/dist/core/runner/prepare-gate-stations.d.ts +6 -0
- package/dist/core/runner/prepare-gate-stations.js +1 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +3 -5
- package/dist/core/runner/prepare-listings.d.ts +3 -0
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory.js +7 -1
- package/dist/core/runner/prepare-policy-chain.js +5 -5
- package/dist/core/runner/prepare-prompt-inputs.d.ts +2 -2
- package/dist/core/runner/prepare-run-refs.d.ts +4 -2
- package/dist/core/runner/prepare-run-refs.js +5 -4
- package/dist/core/runner/prepare-task.js +8 -6
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -1
- package/dist/core/runner/prepare-wiring-manifest.js +5 -2
- package/dist/core/runner/run-clock-and-content.js +3 -7
- package/dist/core/runner/run-harness-handlers.js +10 -10
- package/dist/core/runner/run-leg.js +28 -19
- package/dist/core/runner/run-settle-and-teardown.js +2 -5
- package/dist/core/runner/run-turn-boundary.js +44 -33
- package/dist/core/runner/runtask.js +2 -1
- package/dist/core/runner/synthetic-tools.d.ts +13 -39
- package/dist/core/runner/synthetic-tools.js +15 -59
- package/dist/core/runner/task-root-canonical.d.ts +45 -0
- package/dist/core/runner/task-root-canonical.js +18 -0
- package/dist/core/runner/turn-attachments.d.ts +125 -49
- package/dist/core/runner/turn-attachments.js +124 -66
- package/dist/core/runner/write-protection-station.d.ts +59 -0
- package/dist/core/runner/write-protection-station.js +32 -0
- package/dist/core/safe-notify.d.ts +5 -0
- package/dist/core/safe-notify.js +24 -2
- package/dist/core/sensitive-path-policy.d.ts +53 -27
- package/dist/core/sensitive-path-policy.js +16 -26
- package/dist/core/skill-listing-budget.d.ts +34 -40
- package/dist/core/skill-listing-budget.js +25 -26
- package/dist/core/skills-directory.js +17 -10
- package/dist/core/task-registry.js +2 -1
- package/dist/core/task-spec.d.ts +8 -7
- package/dist/core/tool-policy.d.ts +12 -1
- package/dist/core/tool-policy.js +39 -25
- package/dist/core/wiring-manifest.d.ts +31 -0
- package/dist/core/wiring-manifest.js +1 -0
- package/dist/core/write-protect.d.ts +124 -18
- package/dist/core/write-protect.js +91 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +6 -5
- package/dist/orchestration/workflow-primitives.js +10 -20
- package/dist/stores/file/memory-store.d.ts +5 -2
- package/dist/stores/file/memory-store.js +8 -4
- package/dist/tools/fs/bash-readonly-classifier.d.ts +13 -2
- package/dist/tools/fs/bash-readonly-classifier.js +318 -36
- package/dist/tools/fs/fs-bash.d.ts +1 -1
- package/dist/tools/fs/fs-bash.js +12 -25
- package/dist/tools/fs/fs-shared.d.ts +22 -0
- package/dist/tools/fs/fs-shared.js +30 -0
- package/dist/tools/fs/index.d.ts +1 -1
- package/dist/tools/fs/index.js +1 -1
- package/package.json +2 -1
- package/test/export-surface.snapshot.json +25 -3
- package/wire-changes/core@7.20.0.json +568 -0
- package/wire-changes/core@7.20.1.json +183 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { startTimeout } from "./clock-and-limits.js";
|
|
2
|
+
import { forwardSafely } from "./forward-sink.js";
|
|
2
3
|
export function runClockAndContent(input) {
|
|
3
4
|
const { spec, prepared, queue, internals, rs, ident, parentToolCallId, startedToolCallIds } = input;
|
|
4
5
|
const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
|
|
@@ -7,13 +8,8 @@ export function runClockAndContent(input) {
|
|
|
7
8
|
const timeout = startTimeout(prepared.harness, prepared.abortController, walltimeMonotonicDeadline !== undefined ? walltimeMonotonicDeadline - performance.now() : undefined, prepared.suspendForResource !== undefined);
|
|
8
9
|
const pushContent = (e) => {
|
|
9
10
|
queue.push(e);
|
|
10
|
-
if (parentToolCallId !== undefined
|
|
11
|
-
|
|
12
|
-
internals.onForwardEvent(e);
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
}
|
|
16
|
-
}
|
|
11
|
+
if (parentToolCallId !== undefined)
|
|
12
|
+
forwardSafely(internals, e);
|
|
17
13
|
};
|
|
18
14
|
const ownCommittedTailRef = { current: undefined };
|
|
19
15
|
const emitCommitted = (entryId, role, toolCallId) => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
|
|
2
|
+
import { forwardSafely } from "./forward-sink.js";
|
|
2
3
|
import { emitTrace } from "../trace.js";
|
|
3
4
|
import { primaryActivityArg } from "../arg-summary.js";
|
|
4
5
|
import { readDegradation } from "../../brain/degrading.js";
|
|
@@ -58,17 +59,20 @@ export function createHarnessHandlers(input) {
|
|
|
58
59
|
}
|
|
59
60
|
if (event.entryId !== undefined && m.role === "user")
|
|
60
61
|
prepared.fileHistoryBoundary?.begin(event.entryId);
|
|
61
|
-
if (event.entryId !== undefined && m.role === "user" && prepared.
|
|
62
|
-
const pr = prepared.gitStatusRef.pendingReceipt;
|
|
62
|
+
if (event.entryId !== undefined && m.role === "user" && prepared.frameReceipts.pending.length > 0) {
|
|
63
63
|
const c = m.content;
|
|
64
64
|
const text = typeof c === "string"
|
|
65
65
|
? c
|
|
66
66
|
: Array.isArray(c) && c.length >= 1 && c[0].type === "text"
|
|
67
67
|
? c[0].text
|
|
68
68
|
: undefined;
|
|
69
|
-
if (text
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if (text !== undefined) {
|
|
70
|
+
const settled = prepared.frameReceipts.pending.filter((r) => r.text === text);
|
|
71
|
+
if (settled.length > 0) {
|
|
72
|
+
prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== text);
|
|
73
|
+
for (const r of settled)
|
|
74
|
+
r.commit(event.entryId);
|
|
75
|
+
}
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
if (rs.degrade.degraded === undefined && m.role === "assistant") {
|
|
@@ -309,11 +313,7 @@ export function createHarnessHandlers(input) {
|
|
|
309
313
|
...ident(),
|
|
310
314
|
};
|
|
311
315
|
queue.push(progress);
|
|
312
|
-
|
|
313
|
-
internals?.onForwardEvent?.(progress);
|
|
314
|
-
}
|
|
315
|
-
catch {
|
|
316
|
-
}
|
|
316
|
+
forwardSafely(internals, progress);
|
|
317
317
|
}
|
|
318
318
|
emitTrace(rs.telemetry.tracer, () => ({ kind: "turn.end", version: 1, taskId: rs.telemetry.taskId, turn: stats.turns, ts: Date.now() }));
|
|
319
319
|
rs.turn.turnUsage = undefined;
|
|
@@ -26,8 +26,9 @@ import { maybeHumanRejectionHalt } from "./prepare-gate-stations.js";
|
|
|
26
26
|
import { nextHumanInputSeq } from "./steer-admission.js";
|
|
27
27
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
28
28
|
import { toolEndBodyFrom, toolResultMsg } from "./tool-end-body.js";
|
|
29
|
-
import {
|
|
30
|
-
import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd,
|
|
29
|
+
import { announceListingTruncation } from "../skill-listing-budget.js";
|
|
30
|
+
import { attachmentEnvelopeTags, commitAgentListing, commitSkillsListing, reduceToolEnd, renderAgentListingFrame, renderOrphanedBackgroundTasks, renderSkillsListingFrame, stampWriteAnchor } from "./turn-attachments.js";
|
|
31
|
+
import { flushOwedAnnouncedSnapshot } from "./announce-once-ledger.js";
|
|
31
32
|
export async function runLeg(input) {
|
|
32
33
|
const { spec, queue, prepared, resume, rs, isDelegatedChild, entryActor, effectiveTimeoutMs, timeout, pushContent, ident, emitCommitted, writeFamilyOf, startedToolCallIds, announceWorkspaceMove, withBrainSinks, flushGitMirror, loopLatch, notificationLane, unsubscribeTaskNotifications, unsubGitRetry, unsubBoundary, unsub, runner, next } = input;
|
|
33
34
|
let final;
|
|
@@ -234,7 +235,8 @@ export async function runLeg(input) {
|
|
|
234
235
|
if (gitResumeDelivered !== undefined) {
|
|
235
236
|
const delivered = gitResumeDelivered;
|
|
236
237
|
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
237
|
-
prepared.
|
|
238
|
+
prepared.frameReceipts.pending.push({
|
|
239
|
+
source: "git_status",
|
|
238
240
|
text: continuation,
|
|
239
241
|
commit: (entryId) => {
|
|
240
242
|
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
@@ -242,7 +244,7 @@ export async function runLeg(input) {
|
|
|
242
244
|
rs.attach.attachmentsInjected += 1;
|
|
243
245
|
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
244
246
|
},
|
|
245
|
-
};
|
|
247
|
+
});
|
|
246
248
|
}
|
|
247
249
|
const pending = resume.cp.pendingAction;
|
|
248
250
|
const rejectionHalt = pending.kind === "tool_approval" && resume.outcome.gate === "policy_ask"
|
|
@@ -332,22 +334,22 @@ export async function runLeg(input) {
|
|
|
332
334
|
}
|
|
333
335
|
}
|
|
334
336
|
const firstFrames = [];
|
|
335
|
-
let skillsListingCost;
|
|
336
337
|
if (rs.attach.attachState !== undefined) {
|
|
337
338
|
const attach = rs.attach.attachState;
|
|
338
339
|
if (rs.attach.agentListingOn && prepared.agentListing !== undefined) {
|
|
339
340
|
const listing = prepared.agentListing;
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
341
|
+
const frame = renderAgentListingFrame(attach, listing.entries, listing.toolName, listing.models, listing.budget);
|
|
342
|
+
if (frame !== undefined) {
|
|
342
343
|
firstFrames.push({
|
|
343
344
|
source: "agent_listing",
|
|
344
|
-
body,
|
|
345
|
+
body: frame.text,
|
|
345
346
|
commit: () => {
|
|
346
347
|
commitAgentListing(attach, listing.entries, listing.models);
|
|
347
348
|
prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
|
|
348
349
|
if (listing.models !== undefined)
|
|
349
350
|
prepared.announcedListingsRef.models = [...listing.models];
|
|
350
351
|
},
|
|
352
|
+
disclose: () => announceListingTruncation(runner.deps.onNotice, { listing: "agents", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId }),
|
|
351
353
|
});
|
|
352
354
|
}
|
|
353
355
|
}
|
|
@@ -361,8 +363,8 @@ export async function runLeg(input) {
|
|
|
361
363
|
commit: () => {
|
|
362
364
|
commitSkillsListing(attach, listing.entries);
|
|
363
365
|
prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
|
|
364
|
-
skillsListingCost = { total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget };
|
|
365
366
|
},
|
|
367
|
+
disclose: () => announceListingTruncation(runner.deps.onNotice, { listing: "skills", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId }),
|
|
366
368
|
});
|
|
367
369
|
}
|
|
368
370
|
}
|
|
@@ -418,7 +420,8 @@ export async function runLeg(input) {
|
|
|
418
420
|
if (delivered.standalone)
|
|
419
421
|
await prepared.harness.nextTurn(delivered.wrapped, { engineMinted: true });
|
|
420
422
|
prepared.gitStatusRef.protectedText = delivered.wrapped;
|
|
421
|
-
prepared.
|
|
423
|
+
prepared.frameReceipts.pending.push({
|
|
424
|
+
source: "git_status",
|
|
422
425
|
text: delivered.standalone ? delivered.wrapped : effectiveObjective,
|
|
423
426
|
commit: (entryId) => {
|
|
424
427
|
prepared.gitStatusRef.announced = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
@@ -426,17 +429,20 @@ export async function runLeg(input) {
|
|
|
426
429
|
rs.attach.attachmentsInjected += 1;
|
|
427
430
|
prepared.gitStatusRef.mirrorOwed = { kind: delivered.kind, hash: delivered.hash, entryId };
|
|
428
431
|
},
|
|
429
|
-
};
|
|
432
|
+
});
|
|
430
433
|
}
|
|
431
434
|
for (const f of firstFrames) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
435
|
+
prepared.frameReceipts.pending.push({
|
|
436
|
+
source: f.source,
|
|
437
|
+
text: effectiveObjective,
|
|
438
|
+
commit: () => {
|
|
439
|
+
f.commit();
|
|
440
|
+
queue.push({ type: "steering_injected", source: f.source, preview: redactThenCut(f.body, 220), ...ident() });
|
|
441
|
+
rs.attach.attachmentsInjected += 1;
|
|
442
|
+
prepared.frameReceipts.listingSnapshotOwed = true;
|
|
443
|
+
f.disclose();
|
|
444
|
+
},
|
|
445
|
+
});
|
|
440
446
|
}
|
|
441
447
|
const enginePrefixChars = effectiveObjective.length - projectedObjective.length;
|
|
442
448
|
queue.push({
|
|
@@ -481,6 +487,9 @@ export async function runLeg(input) {
|
|
|
481
487
|
timeout.clear();
|
|
482
488
|
prepared.lspDiagnostics?.registry.releaseRun(prepared.lspDiagnostics.runIdent);
|
|
483
489
|
prepared.toolProgressRef.current?.clear();
|
|
490
|
+
if (prepared.frameReceipts.listingSnapshotOwed) {
|
|
491
|
+
await settleTeardownLeg(() => flushOwedAnnouncedSnapshot(prepared), "announced-listing snapshot (leg exit)", (e) => runner.deps.onError?.(e, { phase: "degraded", sessionId: prepared.sessionId }));
|
|
492
|
+
}
|
|
484
493
|
prepared.abortController.abort();
|
|
485
494
|
prepared.releaseSignal();
|
|
486
495
|
strandedHumanAnswers = prepared.settleContentAskBindings();
|
|
@@ -12,6 +12,7 @@ import { emitTrace } from "../trace.js";
|
|
|
12
12
|
import { deliverEngineNotice } from "../types.js";
|
|
13
13
|
import { assembleResult } from "./assemble-result.js";
|
|
14
14
|
import { discloseUnconsumedHalt } from "./halt-attribution.js";
|
|
15
|
+
import { forwardSafely } from "./forward-sink.js";
|
|
15
16
|
import { TIMER_LATENESS_REPORT_MS } from "./clock-and-limits.js";
|
|
16
17
|
import { resumeDecisionWasNegative } from "./decide-continuation.js";
|
|
17
18
|
import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
|
|
@@ -276,11 +277,7 @@ export async function runSettleAndTeardown(input) {
|
|
|
276
277
|
...ident(),
|
|
277
278
|
};
|
|
278
279
|
queue.push(terminalTick);
|
|
279
|
-
|
|
280
|
-
internals?.onForwardEvent?.(terminalTick);
|
|
281
|
-
}
|
|
282
|
-
catch {
|
|
283
|
-
}
|
|
280
|
+
forwardSafely(internals, terminalTick);
|
|
284
281
|
}
|
|
285
282
|
if (manualCompactRef.waiters.length > 0) {
|
|
286
283
|
queue.push({ type: "compaction_outcome", outcome: "mooted", trigger: "manual", reason: "task_ending", ...ident() });
|
|
@@ -4,8 +4,8 @@ import { eventDefaultOn } from "../../prompt-assembly/event-registry.js";
|
|
|
4
4
|
import { DEFAULT_COMPACTION_INSTRUCTIONS, isCompactionManualCancel, maybeCompact, nextTrimForceBackoff, recordCompactionAndCheckRapidRefill } from "../auto-compaction.js";
|
|
5
5
|
import { emitTrace } from "../trace.js";
|
|
6
6
|
import { redactThenCut } from "../../agents/subagent-steps.js";
|
|
7
|
-
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, rebaseCadenceWindows,
|
|
8
|
-
import {
|
|
7
|
+
import { ATTACHMENT_BYTE_CAP, CHANGED_FILES_MAX, TOTAL_TOKENS_REMINDER_DEFAULT_MODE, attachmentEnvelopeTags, clipToBytes, collectDateChange, collectDueAttachments, collectInstructionsChange, commitAgentListing, commitInstructionsChange, commitSkillsListing, isIntactSource, rebaseCadenceWindows, renderAgentListingFrame, renderMcpDroppedTools, renderMcpInstructionsDelta, selectMcpDroppedBatch, renderSkillsListingFrame, renderToolsDelta } from "./turn-attachments.js";
|
|
8
|
+
import { announceListingTruncation } from "../skill-listing-budget.js";
|
|
9
9
|
import { buildWorkingFileAttachments, centerAdoptionOption, contextInstructionFilesOption, emitInputTruncated, forkContextOption } from "./compaction-call-options.js";
|
|
10
10
|
import { hookSeatExpiredError, runHookSeat } from "../hooks.js";
|
|
11
11
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
@@ -203,7 +203,7 @@ export function createTurnBoundary(input) {
|
|
|
203
203
|
}
|
|
204
204
|
let boundaryAttachmentBytes = 0;
|
|
205
205
|
let attachmentsPayload;
|
|
206
|
-
|
|
206
|
+
const listingCommits = [];
|
|
207
207
|
if ((rs.attach.dateState !== undefined ||
|
|
208
208
|
rs.attach.instrState !== undefined ||
|
|
209
209
|
rs.attach.sizeGuidelineState !== undefined ||
|
|
@@ -215,6 +215,7 @@ export function createTurnBoundary(input) {
|
|
|
215
215
|
!prepared.abortController.signal.aborted) {
|
|
216
216
|
const due = [];
|
|
217
217
|
if (rs.attach.attachState !== undefined) {
|
|
218
|
+
const attachState = rs.attach.attachState;
|
|
218
219
|
const changedFilesOn = Boolean(rs.attach.attachmentsCfg?.changedFiles);
|
|
219
220
|
let changed;
|
|
220
221
|
if (changedFilesOn && prepared.detectExternalChanges !== undefined) {
|
|
@@ -265,6 +266,10 @@ export function createTurnBoundary(input) {
|
|
|
265
266
|
const undiscoveredTools = toolSearchReminderOn
|
|
266
267
|
? [...prepared.deferredToolNames].filter((n) => !prepared.activeTools.has(n)).sort()
|
|
267
268
|
: undefined;
|
|
269
|
+
const receiptPending = (source) => prepared.frameReceipts.pending.some((r) => r.source === source);
|
|
270
|
+
const agentListingDue = rs.attach.agentListingOn && prepared.agentListing !== undefined && !receiptPending("agent_listing");
|
|
271
|
+
const skillsListingDue = rs.attach.skillsListingOn && prepared.skillsListing !== undefined && !receiptPending("skills_listing");
|
|
272
|
+
const listingBudget = prepared.skillsListing?.budget ?? prepared.agentListing?.budget;
|
|
268
273
|
due.push(...collectDueAttachments(rs.attach.attachState, {
|
|
269
274
|
turn: stats.turns,
|
|
270
275
|
cadenceTurn: rs.counters.cadenceTurns,
|
|
@@ -308,16 +313,15 @@ export function createTurnBoundary(input) {
|
|
|
308
313
|
? { readdedToolsSwappedUnderStatic: pendingReaddedTools.filter((n) => prepared.staticFaceFor?.(n) !== true) }
|
|
309
314
|
: {}),
|
|
310
315
|
...(mcpToolsDelta !== undefined ? { mcpToolsDelta } : {}),
|
|
311
|
-
...(
|
|
316
|
+
...(agentListingDue
|
|
312
317
|
? {
|
|
313
318
|
agentListing: prepared.agentListing.entries,
|
|
314
319
|
agentToolName: prepared.agentListing.toolName,
|
|
315
320
|
...(prepared.agentListing.models !== undefined ? { agentModels: prepared.agentListing.models } : {}),
|
|
316
321
|
}
|
|
317
322
|
: {}),
|
|
318
|
-
...(
|
|
319
|
-
|
|
320
|
-
: {}),
|
|
323
|
+
...(skillsListingDue ? { skillsListing: prepared.skillsListing.entries } : {}),
|
|
324
|
+
...(listingBudget !== undefined ? { listingBudget } : {}),
|
|
321
325
|
...(mcpAdds !== undefined || mcpRemovals !== undefined
|
|
322
326
|
? { mcpInstructions: { added: mcpAdds ?? [], removed: mcpRemovals ?? [] } }
|
|
323
327
|
: {}),
|
|
@@ -367,31 +371,36 @@ export function createTurnBoundary(input) {
|
|
|
367
371
|
announcedOnceDirty = true;
|
|
368
372
|
}
|
|
369
373
|
}
|
|
370
|
-
if (
|
|
374
|
+
if (agentListingDue && due.some((a) => a.source === "agent_listing")) {
|
|
371
375
|
const listing = prepared.agentListing;
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
prepared.announcedListingsRef.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
376
|
+
const frame = renderAgentListingFrame(attachState, listing.entries, listing.toolName, listing.models, listing.budget);
|
|
377
|
+
listingCommits.push({
|
|
378
|
+
source: "agent_listing",
|
|
379
|
+
commit: () => {
|
|
380
|
+
commitAgentListing(attachState, listing.entries, listing.models);
|
|
381
|
+
prepared.announcedListingsRef.agents = listing.entries.map((e) => e.name);
|
|
382
|
+
if (listing.models !== undefined)
|
|
383
|
+
prepared.announcedListingsRef.models = [...listing.models];
|
|
384
|
+
prepared.frameReceipts.listingSnapshotOwed = true;
|
|
385
|
+
announceListingTruncation(deps.runner.deps.onNotice, { listing: "agents", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId });
|
|
386
|
+
},
|
|
387
|
+
});
|
|
381
388
|
}
|
|
382
|
-
if (
|
|
389
|
+
if (skillsListingDue && due.some((a) => a.source === "skills_listing")) {
|
|
383
390
|
const listing = prepared.skillsListing;
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
391
|
+
const frame = renderSkillsListingFrame(attachState, listing.entries, listing.budget);
|
|
392
|
+
listingCommits.push({
|
|
393
|
+
source: "skills_listing",
|
|
394
|
+
commit: () => {
|
|
395
|
+
commitSkillsListing(attachState, listing.entries);
|
|
396
|
+
prepared.announcedListingsRef.skills = listing.entries.map((e) => e.name);
|
|
397
|
+
prepared.frameReceipts.listingSnapshotOwed = true;
|
|
398
|
+
announceListingTruncation(deps.runner.deps.onNotice, { listing: "skills", total: frame.listed, nameOnly: frame.nameOnly, dropped: frame.dropped, budget: listing.budget, sessionId: prepared.sessionId });
|
|
399
|
+
},
|
|
400
|
+
});
|
|
392
401
|
}
|
|
393
|
-
if (announcedOnceDirty
|
|
394
|
-
|
|
402
|
+
if (announcedOnceDirty)
|
|
403
|
+
prepared.frameReceipts.listingSnapshotOwed = true;
|
|
395
404
|
}
|
|
396
405
|
if (rs.attach.dateState !== undefined && rs.turn.lastTurnHadToolCalls) {
|
|
397
406
|
const today = prepared.dateChange.today();
|
|
@@ -434,7 +443,8 @@ export function createTurnBoundary(input) {
|
|
|
434
443
|
if (due.length > 0) {
|
|
435
444
|
attachmentsPayload = due.map((a) => mintSystemReminder(sanitizeUntrustedText(a.body, attachmentEnvelopeTags(a.source)), prepared.reminderMark)).join("\n");
|
|
436
445
|
for (const a of due)
|
|
437
|
-
|
|
446
|
+
if (!isIntactSource(a.source))
|
|
447
|
+
boundaryAttachmentBytes += Buffer.byteLength(a.body, "utf8");
|
|
438
448
|
rs.attach.attachmentsInjected += due.length;
|
|
439
449
|
for (const a of due) {
|
|
440
450
|
queue.push({ type: "steering_injected", source: a.source, preview: redactThenCut(a.body, 220), ...ident() });
|
|
@@ -478,10 +488,11 @@ export function createTurnBoundary(input) {
|
|
|
478
488
|
}
|
|
479
489
|
if ((attachmentsPayload !== undefined || batchContextBlock !== undefined) && prepared.batchHaltRef.current === undefined) {
|
|
480
490
|
const payload = attachmentsPayload !== undefined && batchContextBlock !== undefined ? `${attachmentsPayload}\n${batchContextBlock}` : (attachmentsPayload ?? batchContextBlock);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
491
|
+
for (const c of listingCommits)
|
|
492
|
+
prepared.frameReceipts.pending.push({ source: c.source, text: payload, commit: c.commit });
|
|
493
|
+
void prepared.harness.steer(payload, { engineMinted: true }).catch(() => {
|
|
494
|
+
prepared.frameReceipts.pending = prepared.frameReceipts.pending.filter((r) => r.text !== payload);
|
|
495
|
+
});
|
|
485
496
|
}
|
|
486
497
|
const forceManual = manualCompactRef.requested;
|
|
487
498
|
if ((!withinTaskCompaction && !forceManual) ||
|
|
@@ -50,6 +50,7 @@ import { runToolMountFacts } from "./run-tool-mount-facts.js";
|
|
|
50
50
|
import { runReasoningSeat } from "./run-reasoning-seat.js";
|
|
51
51
|
import { runClockAndContent } from "./run-clock-and-content.js";
|
|
52
52
|
import { createToolProgressLane } from "./tool-progress-lane.js";
|
|
53
|
+
import { createForwardSinkScope } from "./forward-sink.js";
|
|
53
54
|
import { runBrainSinks } from "./run-brain-sinks.js";
|
|
54
55
|
import { runCompactionMachinery } from "./run-compaction-machinery.js";
|
|
55
56
|
import { runStopAndFinalVerify } from "./run-stop-and-final-verify.js";
|
|
@@ -344,7 +345,7 @@ export class Runner {
|
|
|
344
345
|
manualCompactRef,
|
|
345
346
|
taskIdRef,
|
|
346
347
|
resume,
|
|
347
|
-
internals: { ...(internals ?? {}), detachHub },
|
|
348
|
+
internals: { ...(internals ?? {}), detachHub, forwardScope: createForwardSinkScope(this.deps.onNotice) },
|
|
348
349
|
notifyRef,
|
|
349
350
|
captureOptOutRef,
|
|
350
351
|
entryActor,
|
|
@@ -58,11 +58,6 @@ export declare const SKILL_TOOL_NAME = "Skill";
|
|
|
58
58
|
* an oversized skill is excluded from the disclosure block and the tool, with an operator-visible config error. The compaction-time retention caps are a
|
|
59
59
|
* different door, deliberately. */
|
|
60
60
|
export declare const SKILL_CONTENT_MAX_CHARS: number;
|
|
61
|
-
/** The listing's budget vocabulary lives in `core/skill-listing-budget.ts` (the seat reader that lowers
|
|
62
|
-
* the two `TaskSpec` knobs to these numbers); re-exported here because this module is where every
|
|
63
|
-
* existing consumer of the cap imports it from, and the ceiling's structural reason is a property of
|
|
64
|
-
* the delivery lane rather than of the renderer. */
|
|
65
|
-
export { SKILLS_BLOCK_MAX_BYTES } from "../skill-listing-budget.js";
|
|
66
61
|
/** Total per-invoke cap for a skill's inlined attachments (`SkillSpec.files`) — an ATTACHMENT-face budget, distinct from the (gone) body truncation door:
|
|
67
62
|
* the body is delivered untruncated at invoke time (CC parity), while attachments ride along bounded and truncation-disclosed. */
|
|
68
63
|
export declare const SKILL_ATTACHMENTS_MAX_CHARS = 50000;
|
|
@@ -139,7 +134,7 @@ export declare function buildSkillsBlock(skills: ReadonlyArray<SkillListingEntry
|
|
|
139
134
|
* [c209-C] REVERSAL: no longer composed into the stable system prompt — this is now the RENDERER for
|
|
140
135
|
* the `skills_listing` initial frame (first-user-turn reminder / boundary re-announce), so the
|
|
141
136
|
* cached prefix carries zero skill bytes (design/C §2.3; CC 209 classic ships the same listing as a
|
|
142
|
-
* first-turn reminder
|
|
137
|
+
* first-turn reminder). `budget` absent ⇒ the seat reader's no-knob answer ({@link SKILL_LISTING_DEFAULT_BUDGET}).
|
|
143
138
|
*
|
|
144
139
|
* The fence and its four header lines are this lane's frame; the budget arithmetic and the three
|
|
145
140
|
* degradation tiers belong to {@link renderSkillLinesWithin}, which the drift frame renders through
|
|
@@ -147,42 +142,20 @@ export declare function buildSkillsBlock(skills: ReadonlyArray<SkillListingEntry
|
|
|
147
142
|
* budget cost, for the disclosure lane. */
|
|
148
143
|
export declare function renderSkillsBlock(skills: ReadonlyArray<SkillListingEntry>, budget?: ResolvedSkillListingBudget): SkillsBlockRender | undefined;
|
|
149
144
|
/**
|
|
150
|
-
* The
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
145
|
+
* The skills VOCABULARY of the one folding engine (`listing-fold.ts` `foldListingWithin`): the full
|
|
146
|
+
* `- name: description` line, the name-only line, the block's cap marker and its overflow count line,
|
|
147
|
+
* measured byte-exact inside a caller-supplied FRAME — the engine both {@link renderSkillsBlock} and
|
|
148
|
+
* the drift frame's ADD arm render through, so the initial `<skills>` block and a later "new skills are
|
|
149
|
+
* now available" announcement degrade by the same rules against the same number — the number the seat
|
|
150
|
+
* reader resolved (`@contract skills.listing_budget_enforced_as_declared`), which is the frame's whole
|
|
151
|
+
* bound: the listing is an intact-class attachment, so no delivery lane measures it a second time.
|
|
157
152
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
* [c209-C] R2 C4① — the cap is BYTE-EXACT over the whole rendered frame (see
|
|
162
|
-
* {@link SKILLS_BLOCK_MAX_BYTES}): three honesty tiers, each chosen with the LATER tiers' fixed
|
|
163
|
-
* overhead (cap marker / name-only tail / overflow count line) already reserved so the final
|
|
164
|
-
* render can never blow the budget it was sized against —
|
|
165
|
-
* A. everything fits → every skill gets its full `- name: description` line (byte-identical to
|
|
166
|
-
* the pre-C4 render for every block that was legal under the old cap);
|
|
167
|
-
* B. (CC 220 `sbs` @317547-317586) — reserve the cap marker + EVERY skill's name-only line
|
|
168
|
-
* first, then spend the remaining slack on descriptions in PRIORITY order
|
|
169
|
-
* ({@link SkillListingEntry.declaredRank}), admitting each description whose incremental cost
|
|
170
|
-
* still fits and skipping (not stopping at) the ones that do not. Every skill stays listed;
|
|
171
|
-
* the ones that lose their description are the low-priority ones, not the alphabetically late
|
|
172
|
-
* ones. This replaces the prior "longest full-line PREFIX" cut, which had two defects the
|
|
173
|
-
* anchor does not: the surviving set was decided by name sort order (a skill named `zip-deploy`
|
|
174
|
-
* lost its description to `aardvark-lint` for no reason connected to either skill), and one
|
|
175
|
-
* oversized description truncated the block at itself instead of being skipped over;
|
|
176
|
-
* C. even all-name-only overflows (pathological name census) → list what fits and close with an
|
|
177
|
-
* honest `(… +N more skills …)` count line — the tool still serves all of them.
|
|
178
|
-
*
|
|
179
|
-
* Pure: the same entries, budget and frame always render the same bytes, which is what the
|
|
180
|
-
* prompt-prefix stability contract requires of the listing frames.
|
|
153
|
+
* Tier B follows CC 220 `sbs` @317547-317586 (reserve the marker + every name-only line, then spend
|
|
154
|
+
* the slack on descriptions in {@link SkillListingEntry.declaredRank} order); tier C closes with an
|
|
155
|
+
* honest `(… +N more skills …)` count line — the tool still serves all of them.
|
|
181
156
|
*/
|
|
182
157
|
export declare function renderSkillLinesWithin(skills: ReadonlyArray<SkillListingEntry>, opts: {
|
|
183
|
-
/** The whole frame's byte budget
|
|
184
|
-
* the seat reader: this renderer is reachable from every listing lane, and the ceiling invariant
|
|
185
|
-
* has to hold for a caller that computed its own number. */
|
|
158
|
+
/** The whole frame's byte budget — honoured as given; the seat reader is the one place that decides it. */
|
|
186
159
|
blockMaxBytes: number;
|
|
187
160
|
descMaxChars: number;
|
|
188
161
|
frame: (lines: readonly string[]) => string;
|
|
@@ -203,3 +176,4 @@ export declare function renderSkillLinesWithin(skills: ReadonlyArray<SkillListin
|
|
|
203
176
|
* widening, so it stays safe-by-construction.
|
|
204
177
|
*/
|
|
205
178
|
export declare function createSkillTool(skills: SkillLike[], scope?: ActiveSkillScope): AgentTool;
|
|
179
|
+
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { defineTool } from "../tools.js";
|
|
3
3
|
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
4
|
-
import {
|
|
4
|
+
import { SKILL_LISTING_DEFAULT_BUDGET, SKILL_LISTING_DESC_MAX_CHARS_DEFAULT } from "../skill-listing-budget.js";
|
|
5
5
|
import { toolFace } from "../tool-catalog-entries.js";
|
|
6
6
|
import { substituteSkillRoot, substituteSkillRootBounded } from "./skill-base-dir.js";
|
|
7
|
+
import { foldListingWithin } from "./listing-fold.js";
|
|
7
8
|
export const OUTPUT_TOOL_NAME = "StructuredOutput";
|
|
8
9
|
export const REPORT_BLOCKED_TOOL_NAME = "ReportBlocked";
|
|
9
10
|
export const REPORT_FINDINGS_TOOL_NAME = "ReportFindings";
|
|
@@ -86,7 +87,6 @@ export function createReportFindingsTool() {
|
|
|
86
87
|
}
|
|
87
88
|
export const SKILL_TOOL_NAME = "Skill";
|
|
88
89
|
export const SKILL_CONTENT_MAX_CHARS = 1024 * 1024;
|
|
89
|
-
export { SKILLS_BLOCK_MAX_BYTES } from "../skill-listing-budget.js";
|
|
90
90
|
export const SKILL_ATTACHMENTS_MAX_CHARS = 50_000;
|
|
91
91
|
export const SKILL_ATTACHMENT_PATH_MAX_CHARS = 512;
|
|
92
92
|
const SKILL_FILES_LIST_MAX_CHARS = 200;
|
|
@@ -130,7 +130,7 @@ const SKILLS_BLOCK_CAP_MARKER = "(block size cap reached — lower-priority skil
|
|
|
130
130
|
export function buildSkillsBlock(skills, budget) {
|
|
131
131
|
return renderSkillsBlock(skills, budget)?.text;
|
|
132
132
|
}
|
|
133
|
-
export function renderSkillsBlock(skills, budget) {
|
|
133
|
+
export function renderSkillsBlock(skills, budget = SKILL_LISTING_DEFAULT_BUDGET) {
|
|
134
134
|
if (skills.length === 0)
|
|
135
135
|
return undefined;
|
|
136
136
|
const headerLines = [
|
|
@@ -141,66 +141,22 @@ export function renderSkillsBlock(skills, budget) {
|
|
|
141
141
|
"",
|
|
142
142
|
];
|
|
143
143
|
return renderSkillLinesWithin(skills, {
|
|
144
|
-
blockMaxBytes: budget
|
|
145
|
-
descMaxChars: budget
|
|
144
|
+
blockMaxBytes: budget.blockMaxBytes,
|
|
145
|
+
descMaxChars: budget.descMaxChars,
|
|
146
146
|
frame: (lines) => [...headerLines, ...lines, "</skills>"].join("\n"),
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
export function renderSkillLinesWithin(skills, opts) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (fullTotal <= bodyBudget)
|
|
161
|
-
return { text: render(fullLines), nameOnly: [], dropped: [] };
|
|
162
|
-
const nameLines = skills.map((s) => `- ${sanitizeUntrustedText(s.name, SHELLED_BODY_ENVELOPE_TAGS)}`);
|
|
163
|
-
const namePrefix = new Array(nameLines.length + 1);
|
|
164
|
-
namePrefix[0] = 0;
|
|
165
|
-
for (let i = 0; i < nameLines.length; i++)
|
|
166
|
-
namePrefix[i + 1] = namePrefix[i] + lineCost(nameLines[i]);
|
|
167
|
-
const markerCost = lineCost(SKILLS_BLOCK_CAP_MARKER);
|
|
168
|
-
const floorCost = markerCost + namePrefix[nameLines.length];
|
|
169
|
-
if (floorCost <= bodyBudget) {
|
|
170
|
-
let slack = bodyBudget - floorCost;
|
|
171
|
-
const byPriority = skills
|
|
172
|
-
.map((s, i) => ({ i, rank: s.declaredRank ?? i }))
|
|
173
|
-
.toSorted((a, b) => a.rank - b.rank || a.i - b.i);
|
|
174
|
-
const withDescription = new Set();
|
|
175
|
-
for (const { i } of byPriority) {
|
|
176
|
-
const delta = lineCost(fullLines[i]) - lineCost(nameLines[i]);
|
|
177
|
-
if (delta <= slack) {
|
|
178
|
-
withDescription.add(i);
|
|
179
|
-
slack -= delta;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, ...skills.map((_s, i) => (withDescription.has(i) ? fullLines[i] : nameLines[i]))]),
|
|
184
|
-
nameOnly: skills.filter((_s, i) => !withDescription.has(i)).map((s) => s.name),
|
|
185
|
-
dropped: [],
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
for (let m = skills.length - 1; m >= 0; m--) {
|
|
189
|
-
const overflowLine = `(… +${skills.length - m} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`;
|
|
190
|
-
const cost = markerCost + namePrefix[m] + lineCost(overflowLine);
|
|
191
|
-
if (cost <= bodyBudget) {
|
|
192
|
-
return {
|
|
193
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, ...nameLines.slice(0, m), overflowLine]),
|
|
194
|
-
nameOnly: skills.slice(0, m).map((s) => s.name),
|
|
195
|
-
dropped: skills.slice(m).map((s) => s.name),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
text: render([SKILLS_BLOCK_CAP_MARKER, `(… +${skills.length} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`]),
|
|
201
|
-
nameOnly: [],
|
|
202
|
-
dropped: skills.map((s) => s.name),
|
|
203
|
-
};
|
|
150
|
+
return foldListingWithin(skills, {
|
|
151
|
+
blockMaxBytes: opts.blockMaxBytes,
|
|
152
|
+
full: (s) => skillListingLine(s, opts.descMaxChars),
|
|
153
|
+
nameOnly: (s) => `- ${sanitizeUntrustedText(s.name, SHELLED_BODY_ENVELOPE_TAGS)}`,
|
|
154
|
+
nameOf: (s) => s.name,
|
|
155
|
+
rank: (s, i) => s.declaredRank ?? i,
|
|
156
|
+
capMarker: SKILLS_BLOCK_CAP_MARKER,
|
|
157
|
+
overflowLine: (n) => `(… +${n} more skills not listed — the ${SKILL_TOOL_NAME} tool serves them all)`,
|
|
158
|
+
frame: opts.frame,
|
|
159
|
+
});
|
|
204
160
|
}
|
|
205
161
|
export function createSkillTool(skills, scope) {
|
|
206
162
|
const byName = new Map(skills.map((s) => [s.name, s]));
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The task root's CANONICAL spelling — and the one loud outlet for a host that hands in another one.
|
|
3
|
+
*
|
|
4
|
+
* Containment is judged on the canonical form: the roots fence resolves every target through the same
|
|
5
|
+
* `ExecutionEnv` and compares canonical keys, the shell read boundary compares a command's words
|
|
6
|
+
* against the declared roots, and every refusal message quotes them. A deployment that hands the
|
|
7
|
+
* runner a root which is NOT its own realpath (a symlinked temp directory; `/var` → `/private/var` on
|
|
8
|
+
* macOS) therefore reasons in one spelling while the engine judges in another, and a target the
|
|
9
|
+
* deployment considers inside the root can be judged outside it. Before this seat that difference was
|
|
10
|
+
* SILENT — the engine simply canonicalized and carried on.
|
|
11
|
+
*
|
|
12
|
+
* The bad-value discipline's announcement dialect applies (announce, do not refuse): an existing
|
|
13
|
+
* deployment may well be running correctly on the non-canonical spelling, so the judgment is unchanged
|
|
14
|
+
* and only the difference is disclosed. Nothing here repairs the host's spelling either — the engine
|
|
15
|
+
* cannot know which of the two the deployment's own gates key on, and rewriting one into the other
|
|
16
|
+
* would move a fence nobody asked to move.
|
|
17
|
+
*/
|
|
18
|
+
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
19
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
20
|
+
/** The canonical root plus whether the difference was announced (the pin's observable). */
|
|
21
|
+
export interface TaskRootCanonical {
|
|
22
|
+
/** The spelling every fence judges with — the env's canonical form, or the given spelling when the
|
|
23
|
+
* env cannot resolve it (an unresolvable root narrows nothing here; the fence's own skip speaks). */
|
|
24
|
+
canonical: string;
|
|
25
|
+
/** True exactly when the given spelling differed and the notice was delivered. */
|
|
26
|
+
announced: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the run's task root to the spelling its fences judge with, announcing a host spelling that is
|
|
30
|
+
* not that spelling.
|
|
31
|
+
*
|
|
32
|
+
* @contract task_root.canonical_is_the_judge — the engine judges containment on the task root's
|
|
33
|
+
* CANONICAL spelling; when the root the host handed in is not its own canonical form the run announces
|
|
34
|
+
* `config.task_root_not_canonical` exactly once, carrying both spellings, and then judges by the
|
|
35
|
+
* canonical form exactly as before. The notice discloses the difference — it never repairs the host's
|
|
36
|
+
* spelling, never narrows the fence, and never fires for a root that already is its own canonical form.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveTaskRootCanonical(input: {
|
|
39
|
+
env: Pick<ExecutionEnv, "canonicalPath">;
|
|
40
|
+
/** The root as the host spelled it (`deps.rootPath` / the restored workspace root). */
|
|
41
|
+
given: string;
|
|
42
|
+
onNotice: RunnerDeps["onNotice"];
|
|
43
|
+
sessionId: string;
|
|
44
|
+
runId: string;
|
|
45
|
+
}): Promise<TaskRootCanonical>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
+
export async function resolveTaskRootCanonical(input) {
|
|
3
|
+
const c = await input.env.canonicalPath(input.given);
|
|
4
|
+
if (!c.ok)
|
|
5
|
+
return { canonical: input.given, announced: false };
|
|
6
|
+
if (c.value === input.given)
|
|
7
|
+
return { canonical: c.value, announced: false };
|
|
8
|
+
deliverEngineNotice(input.onNotice, {
|
|
9
|
+
code: "config.task_root_not_canonical",
|
|
10
|
+
message: `The task root handed to this run is "${input.given}", which is not its own canonical path ("${c.value}"). ` +
|
|
11
|
+
`Containment is judged on the canonical spelling — the roots fence, the shell read boundary and every ` +
|
|
12
|
+
`refusal message read it — so a target this deployment considers inside the root can be judged outside ` +
|
|
13
|
+
`it whenever the two spellings differ (a symlinked directory; /var → /private/var on macOS). The run ` +
|
|
14
|
+
`proceeds on the canonical spelling exactly as before: hand the canonical path in to make the two agree.`,
|
|
15
|
+
detail: { given: input.given, canonical: c.value, sessionId: input.sessionId, runId: input.runId },
|
|
16
|
+
});
|
|
17
|
+
return { canonical: c.value, announced: true };
|
|
18
|
+
}
|