@sema-agent/core 7.19.0 → 7.20.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 +183 -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 +67 -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 +12 -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/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 +8 -0
- package/dist/core/runner/prepare-gate-stations.js +6 -2
- 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 +7 -5
- 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/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/write-protect.d.ts +98 -18
- package/dist/core/write-protect.js +88 -34
- package/dist/index.d.ts +6 -5
- package/dist/index.js +5 -4
- 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 +317 -35
- 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 +19 -3
- package/wire-changes/core@7.20.0.json +568 -0
|
@@ -105,9 +105,12 @@ export declare function inheritedAskCarry(judged: Pick<Extract<InheritedClassifi
|
|
|
105
105
|
}>, "origin" | "fallback" | "ask">, liveApprover: boolean, tracker: AutoModeDenialTracker | undefined): AskRequestCarry;
|
|
106
106
|
/** What {@link judgeInheritedClassifier} hands back to the arm that called it. */
|
|
107
107
|
export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
108
|
-
/** The frozen classifier allowed
|
|
108
|
+
/** The frozen classifier allowed. `result` is the allow the arm RETURNS — minted at the judging station so
|
|
109
|
+
* the model attribution rides the same one object the deny arm's does (an arm re-minting its own
|
|
110
|
+
* `{action:"allow"}` drops it); it already carries the ask's rewrite when the arm declared one. */
|
|
109
111
|
{
|
|
110
112
|
kind: "allow";
|
|
113
|
+
result: PermissionResult;
|
|
111
114
|
}
|
|
112
115
|
/** A block below the bound — the exact deny the ancestor's own gate would have produced. */
|
|
113
116
|
| {
|
|
@@ -145,6 +148,13 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
145
148
|
* `req` is the request as the classifier must see it (the arm's presented / edited args);
|
|
146
149
|
* `subject` is the noun the deny names ("this call" on the wrapper arms, "the approved edit" on the
|
|
147
150
|
* re-check).
|
|
151
|
+
*
|
|
152
|
+
* ONE MINT for the classification's model attribution: every decision this station hands back — the allow it
|
|
153
|
+
* mints from `allowRewrite` as much as either deny — carries it on the identity-keyed seat beside the one
|
|
154
|
+
* carrying the FORM, and the arms return the station's object instead of re-minting their own. The
|
|
155
|
+
* descendant's fold reads both seats at its single attestation read. A decider with no candidate ladder
|
|
156
|
+
* vouches for no name and the seat stays empty (absence = unknown); a block AT the bound reaches no seat at
|
|
157
|
+
* all, since it becomes an ask and the disposition then names that resolution.
|
|
148
158
|
*/
|
|
149
159
|
export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
150
160
|
autoMode: InheritedAutoMode | undefined;
|
|
@@ -152,6 +162,9 @@ export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
|
152
162
|
req: ToolCallRequest;
|
|
153
163
|
signal: AbortSignal;
|
|
154
164
|
subject: "this call" | "the approved edit";
|
|
165
|
+
/** The rewrite the arm's allow would have carried (the ask's own `updatedInput`, the re-check's edited
|
|
166
|
+
* args) — handed IN so the allow is minted here, attested, once; absent ⇒ a bare allow. */
|
|
167
|
+
allowRewrite?: unknown;
|
|
155
168
|
}): Promise<InheritedClassifierJudgment<A>>;
|
|
156
169
|
/** The resolver's answer as the settlement reads it. */
|
|
157
170
|
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { classifierParseFailureDenyMessage, classifierUnavailableDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
|
|
2
2
|
import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
|
|
3
|
-
import { attestClassifierDenyCause } from "../tool-policy.js";
|
|
3
|
+
import { attestClassifierDenyCause, attestClassifierRound } from "../tool-policy.js";
|
|
4
4
|
import { deliverEngineNotice } from "../types.js";
|
|
5
5
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
6
6
|
import { askOriginOf, classifierMayAnswer, probeMandatedAsk } from "../ask-origin.js";
|
|
@@ -82,14 +82,15 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
82
82
|
const verdict = await autoMode.decider
|
|
83
83
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
84
84
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
85
|
+
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
|
|
85
86
|
if (verdict.kind === "allow") {
|
|
86
87
|
autoMode.denialTracking?.recordAllow();
|
|
87
|
-
return { kind: "allow" };
|
|
88
|
+
return { kind: "allow", result: attributed({ action: "allow", ...(opts.allowRewrite !== undefined ? { updatedInput: opts.allowRewrite } : {}) }) };
|
|
88
89
|
}
|
|
89
90
|
if (verdict.kind === "unavailable") {
|
|
90
91
|
return {
|
|
91
92
|
kind: "deny",
|
|
92
|
-
result: attestClassifierDenyCause({ action: "deny", message: classifierUnavailableDenyMessage(req.toolName, verdict.cause), decisionReason: "classifier" }, "unavailable", req),
|
|
93
|
+
result: attributed(attestClassifierDenyCause({ action: "deny", message: classifierUnavailableDenyMessage(req.toolName, verdict.cause), decisionReason: "classifier" }, "unavailable", req)),
|
|
93
94
|
};
|
|
94
95
|
}
|
|
95
96
|
const reason = verdict.kind === "block" ? (verdict.reason ? inlineUntrusted(verdict.reason) : "") : classifierParseFailureDenyMessage(verdict.classifier);
|
|
@@ -101,7 +102,7 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
101
102
|
message: verdict.kind === "block" ? `auto-mode classifier blocked ${opts.subject} at an inherited ancestor layer${reason ? `: ${reason}` : category ? `: [${category}]` : ""}` : `${reason} (${opts.subject}, at an inherited ancestor layer)`,
|
|
102
103
|
decisionReason: "classifier",
|
|
103
104
|
};
|
|
104
|
-
return { kind: "deny", result: verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied };
|
|
105
|
+
return { kind: "deny", result: attributed(verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied) };
|
|
105
106
|
}
|
|
106
107
|
const fallback = tracked.fallback;
|
|
107
108
|
const minted = { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExecutionEnv } from "../../internal/harness.js";
|
|
2
|
+
import type { EngineNotice } from "../engine-notice.js";
|
|
3
|
+
/** One capability key whose declared value is not a boolean, with the type it actually carries. */
|
|
4
|
+
export interface MalformedCapabilityDeclaration {
|
|
5
|
+
readonly key: string;
|
|
6
|
+
readonly valueType: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Every boolean capability this env DECLARES with a value that is not a boolean.
|
|
10
|
+
*
|
|
11
|
+
* The probe is the VALUE, not membership, because the value is what the readers get: a `capabilities`
|
|
12
|
+
* that answers `has` and `get` differently (a Proxy) would otherwise hide from the census exactly the
|
|
13
|
+
* value a reader goes on to act on. `undefined` is where absence lands — an absent key and an explicit
|
|
14
|
+
* `undefined` are the same word to every reader, and both are the documented silent reading — so one
|
|
15
|
+
* test covers both and the census needs no membership rule at all.
|
|
16
|
+
*
|
|
17
|
+
* TOTAL by construction: a diagnostic may not be the thing that fails a run. Every read of the adapter
|
|
18
|
+
* can throw on a getter or a Proxy trap — the structural probe, the block acquisition and each key —
|
|
19
|
+
* and a read that throws is itself a declaration nobody can use, so all three are reported rather than
|
|
20
|
+
* raised (`unreadable`, under the key when there is one and under `capabilities` when the block itself
|
|
21
|
+
* could not be had).
|
|
22
|
+
*
|
|
23
|
+
* What a snapshot cannot promise: this is taken once, at admission. An adapter whose capability values
|
|
24
|
+
* CHANGE afterwards can hand a later reader something this census never saw; the declaration is
|
|
25
|
+
* documented as a constant property of the adapter, and a mutable one is outside what any one-time
|
|
26
|
+
* reading covers.
|
|
27
|
+
*/
|
|
28
|
+
export declare function malformedCapabilityDeclarations(env: ExecutionEnv): MalformedCapabilityDeclaration[];
|
|
29
|
+
/** Announce each malformed declaration once, to the operator who wired the adapter. */
|
|
30
|
+
export declare function announceMalformedCapabilities(env: ExecutionEnv, onNotice: ((notice: EngineNotice) => void) | undefined): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
2
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
3
|
+
const BOOLEAN_CAPABILITY_KEYS = ["isolation", "suspendable", "canonicalPathAuthoritative"];
|
|
4
|
+
export function malformedCapabilityDeclarations(env) {
|
|
5
|
+
let caps;
|
|
6
|
+
try {
|
|
7
|
+
if (!isRemoteExecutionEnv(env))
|
|
8
|
+
return [];
|
|
9
|
+
caps = env.capabilities;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return [{ key: "capabilities", valueType: "unreadable" }];
|
|
13
|
+
}
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const key of BOOLEAN_CAPABILITY_KEYS) {
|
|
16
|
+
let value;
|
|
17
|
+
try {
|
|
18
|
+
value = caps[key];
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
out.push({ key, valueType: "unreadable" });
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (value === undefined || typeof value === "boolean")
|
|
25
|
+
continue;
|
|
26
|
+
out.push({ key, valueType: value === null ? "null" : typeof value });
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
export function announceMalformedCapabilities(env, onNotice) {
|
|
31
|
+
for (const { key, valueType } of malformedCapabilityDeclarations(env)) {
|
|
32
|
+
deliverEngineNotice(onNotice, {
|
|
33
|
+
code: "config.execution_env_capability_invalid",
|
|
34
|
+
message: `The execution environment declares capabilities.${key} as a ${valueType}, not a boolean. What a reader makes of that is the reader's own business and differs between them — the containment readers take only the boolean, so this reads as undeclared, while the suspend reader takes any truthy value, so a non-empty string reads as TRUE. That is why a declaration of the wrong shape cannot pass quietly: the engine may act on something other than what the adapter meant. Fix the adapter — this is a wiring fault, not a supported way to leave the capability unset.`,
|
|
35
|
+
detail: { key, valueType },
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TaskEvent } from "../task-event.js";
|
|
2
|
+
import type { RunnerDeps } from "../runner-deps.js";
|
|
3
|
+
import { type SafeNotifier } from "../safe-notify.js";
|
|
4
|
+
/** One run's forwarding-failure bookkeeping: opaque to everything but this module. */
|
|
5
|
+
export interface ForwardSinkScope {
|
|
6
|
+
readonly notifier: SafeNotifier;
|
|
7
|
+
}
|
|
8
|
+
/** The two seats this module reads off a run's trusted internals — spelled structurally so a SNAPSHOT
|
|
9
|
+
* of them (the delegation revive lane holds one) is as valid a holder as the live object. */
|
|
10
|
+
export interface ForwardSinkHolder {
|
|
11
|
+
onForwardEvent?: (event: TaskEvent) => void;
|
|
12
|
+
forwardScope?: ForwardSinkScope;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Mint the forwarding scope for ONE run, bound to the deployment's notice seat. Called where the run's
|
|
16
|
+
* internals object is composed (`Runner.runTaskStream`), so every copy of that object made later — the
|
|
17
|
+
* prepare re-spread, a placement-root rebind, a child's snapshot — carries the same counters.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createForwardSinkScope(onNotice: RunnerDeps["onNotice"]): ForwardSinkScope;
|
|
20
|
+
/**
|
|
21
|
+
* Forward ONE event to the deployment's display sink, containing both failure shapes.
|
|
22
|
+
*
|
|
23
|
+
* @contract runtask.forward_sink_never_faults_the_run — a `RunInternals.onForwardEvent` that throws,
|
|
24
|
+
* or that returns a promise which rejects, never reaches the run's control flow: the frame is dropped,
|
|
25
|
+
* the failure is counted on the run's own `forwardScope`, and the FIRST one is announced once per run
|
|
26
|
+
* as `config.forward_sink_failed` (later ones are counted only). Delivery is never awaited — describing
|
|
27
|
+
* a run is not part of it, so a sink that never settles cannot hold a turn open. An absent sink is a
|
|
28
|
+
* no-op, and a healthy sink sees the same call it always did, with the same receiver.
|
|
29
|
+
*/
|
|
30
|
+
export declare function forwardSafely(holder: ForwardSinkHolder | undefined, event: TaskEvent): void;
|
|
31
|
+
/** Contained forwarding failures on this scope (diagnostics; 0 on the healthy path). */
|
|
32
|
+
export declare function forwardSinkFailures(holder: ForwardSinkHolder | undefined): number;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { deliverEngineNotice } from "../engine-notice.js";
|
|
2
|
+
import { createSafeNotifier, observeThenableRejection, safeErrorMessage } from "../safe-notify.js";
|
|
3
|
+
const FORWARD_SINK_SITE = "runtask.onForwardEvent";
|
|
4
|
+
const ORPHAN_SCOPES = new WeakMap();
|
|
5
|
+
function forwardSinkFailedNotice(message) {
|
|
6
|
+
return {
|
|
7
|
+
code: "config.forward_sink_failed",
|
|
8
|
+
message: `The subagent display sink (RunInternals.onForwardEvent) failed and the frame was dropped: ${message}. ` +
|
|
9
|
+
`The run is unaffected — forwarding is a render channel — and further failures of this run are counted, not repeated.`,
|
|
10
|
+
detail: { site: FORWARD_SINK_SITE, cause: message },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createForwardSinkScope(onNotice) {
|
|
14
|
+
return {
|
|
15
|
+
notifier: createSafeNotifier({
|
|
16
|
+
onError: (f) => deliverEngineNotice(onNotice, forwardSinkFailedNotice(safeErrorMessage(f.error))),
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function scopeOf(holder) {
|
|
21
|
+
if (holder.forwardScope !== undefined)
|
|
22
|
+
return holder.forwardScope;
|
|
23
|
+
const existing = ORPHAN_SCOPES.get(holder);
|
|
24
|
+
if (existing !== undefined)
|
|
25
|
+
return existing;
|
|
26
|
+
const scope = createForwardSinkScope(undefined);
|
|
27
|
+
ORPHAN_SCOPES.set(holder, scope);
|
|
28
|
+
return scope;
|
|
29
|
+
}
|
|
30
|
+
export function forwardSafely(holder, event) {
|
|
31
|
+
if (holder?.onForwardEvent === undefined)
|
|
32
|
+
return;
|
|
33
|
+
const { notifier } = scopeOf(holder);
|
|
34
|
+
notifier.notify(() => observeThenableRejection(holder.onForwardEvent?.(event), notifier, FORWARD_SINK_SITE), FORWARD_SINK_SITE);
|
|
35
|
+
}
|
|
36
|
+
export function forwardSinkFailures(holder) {
|
|
37
|
+
if (holder === undefined)
|
|
38
|
+
return 0;
|
|
39
|
+
const scope = holder.forwardScope ?? ORPHAN_SCOPES.get(holder);
|
|
40
|
+
return scope?.notifier.failuresAt(FORWARD_SINK_SITE) ?? 0;
|
|
41
|
+
}
|
|
@@ -92,6 +92,10 @@ export interface SettlementLedger {
|
|
|
92
92
|
* `parse_error` round travelling through an ancestor wrapper into this fold); undefined for a ruling
|
|
93
93
|
* block, a non-classifier deny, an allow or an ask. */
|
|
94
94
|
causeOf(decision: PermissionResult): ClassifierDenyCause | undefined;
|
|
95
|
+
/** Read a decision's attested CLASSIFICATION for this call — which model an ancestor's frozen classifier
|
|
96
|
+
* asked and which answered, on the allow arm as much as the deny arm. Undefined when no inherited
|
|
97
|
+
* classifier judged the call, or when the ancestor's decider could vouch for no name. */
|
|
98
|
+
classifierRoundOf(decision: PermissionResult): AutoModeClassifierRound | undefined;
|
|
95
99
|
/** A surviving ask supersedes whatever an earlier decision settled. */
|
|
96
100
|
supersede(): void;
|
|
97
101
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
-
import { askUnresolvableCauseOf, classifierDenyCauseOf, decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
2
|
+
import { askUnresolvableCauseOf, classifierDenyCauseOf, classifierRoundOf, decisionText, engineSettlementOf } from "../tool-policy.js";
|
|
3
3
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
4
4
|
import { formatHookFeedback } from "../reminder-mint.js";
|
|
5
5
|
export function mintGateOutcome(facts) {
|
|
@@ -56,6 +56,9 @@ export function createSettlementLedger(call) {
|
|
|
56
56
|
causeOf(decision) {
|
|
57
57
|
return decision.action === "deny" ? classifierDenyCauseOf(decision, call) : undefined;
|
|
58
58
|
},
|
|
59
|
+
classifierRoundOf(decision) {
|
|
60
|
+
return classifierRoundOf(decision, call);
|
|
61
|
+
},
|
|
59
62
|
supersede() {
|
|
60
63
|
settled = undefined;
|
|
61
64
|
},
|
|
@@ -126,15 +126,6 @@ export interface GitStatusLaneRef {
|
|
|
126
126
|
hash: string;
|
|
127
127
|
entryId: string;
|
|
128
128
|
};
|
|
129
|
-
/** The duplicate-tolerant receipt slot: set when a frame delivery is in flight, settled by the
|
|
130
|
-
* run loop's `message_end` walk when the CARRYING message commits (matched on the exact
|
|
131
|
-
* engine-held message text). Commit-on-receipt, never commit-before-append — the git lane's
|
|
132
|
-
* error asymmetry is the REVERSE of the listing lane's (a false "announced" = stale git facts
|
|
133
|
-
* forever; a duplicate frame = a few idempotent kilobytes). */
|
|
134
|
-
pendingReceipt?: {
|
|
135
|
-
text: string;
|
|
136
|
-
commit: (entryId: string) => void;
|
|
137
|
-
};
|
|
138
129
|
}
|
|
139
130
|
/**
|
|
140
131
|
* prepare-side probe half of the lane (H4, extracted from prepareTask): run the CC-shape snapshot
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE folding engine every listing frame renders through — the `<skills>` block, the skills drift
|
|
3
|
+
* frame's add arm, the agent-type roster and its drift frame's add arm. A listing is an intact-class
|
|
4
|
+
* turn attachment (`turn-attachments.ts` `ATTACHMENT_DELIVERY_CLASS`): the delivery lane never clips
|
|
5
|
+
* it, so this engine is the frame's whole bound, byte-exact over the caller's `frame` (headers, fences,
|
|
6
|
+
* markers, joiners included).
|
|
7
|
+
*
|
|
8
|
+
* Three honesty tiers over ONE set of items, each chosen with the LATER tiers' fixed overhead already
|
|
9
|
+
* reserved so the final render never blows the budget it was sized against:
|
|
10
|
+
* A. everything fits → every item gets its full line;
|
|
11
|
+
* B. reserve the cap marker + EVERY item's name-only line first, then spend the remaining slack on
|
|
12
|
+
* full lines in PRIORITY order (`rank` ascending; absent ⇒ index), admitting each full line whose
|
|
13
|
+
* incremental cost still fits and skipping (not stopping at) the ones that do not — every item stays
|
|
14
|
+
* listed, the low-priority ones lose their detail;
|
|
15
|
+
* C. even all-name-only overflows → the longest name-only prefix that fits, closed by the caller's
|
|
16
|
+
* overflow count line.
|
|
17
|
+
* Pure: the same items, spec and frame always render the same bytes.
|
|
18
|
+
*/
|
|
19
|
+
export interface ListingFoldSpec<T> {
|
|
20
|
+
/** The whole frame's byte budget, honoured as given. */
|
|
21
|
+
blockMaxBytes: number;
|
|
22
|
+
/** One item's full line (tier A, and tier B's admitted lines). */
|
|
23
|
+
full: (item: T) => string;
|
|
24
|
+
/** One item's name-only line (tier B's degraded form, tier C's listed prefix). */
|
|
25
|
+
nameOnly: (item: T) => string;
|
|
26
|
+
/** The raw name reported for an item the budget degraded (host-facing data, not prompt text). */
|
|
27
|
+
nameOf: (item: T) => string;
|
|
28
|
+
/** Priority for keeping the full line: ascending, 0 = first. Absent ⇒ the item's index. */
|
|
29
|
+
rank?: (item: T, index: number) => number;
|
|
30
|
+
/** The marker line that opens a degraded (tier B/C) body. */
|
|
31
|
+
capMarker: string;
|
|
32
|
+
/** Tier C's closing line for `n` items that could not be listed at all. */
|
|
33
|
+
overflowLine: (n: number) => string;
|
|
34
|
+
/** Renders body lines into the lane's frame; its fixed cost is priced once by rendering it empty. */
|
|
35
|
+
frame: (lines: readonly string[]) => string;
|
|
36
|
+
}
|
|
37
|
+
/** What one fold produced: the frame, and which items the budget degraded (display order). */
|
|
38
|
+
export interface ListingFoldRender {
|
|
39
|
+
text: string;
|
|
40
|
+
/** Items listed WITHOUT their full line. */
|
|
41
|
+
nameOnly: readonly string[];
|
|
42
|
+
/** Items the frame could not list at all (tier C's overflow tail). */
|
|
43
|
+
dropped: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
export declare function foldListingWithin<T>(items: ReadonlyArray<T>, spec: ListingFoldSpec<T>): ListingFoldRender | undefined;
|
|
46
|
+
/** The removal arm's FLOOR — the departure fact with none of the names; constant-size, which is what
|
|
47
|
+
* keeps the fact from being starved by a budget the names cannot fit. */
|
|
48
|
+
export declare function removalArmFloor(header: string, count: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* A listing frame's REMOVAL arm rendered INSIDE a byte budget: every departed name when they fit,
|
|
51
|
+
* otherwise the longest prefix that does, closed by an honest count line — tier C over a list whose
|
|
52
|
+
* lines have one form, so there is no name-only tier between them. Names are neutralized on the way
|
|
53
|
+
* out: the announced map holds raw names from the commit half. Cost of the bound, stated: a name that
|
|
54
|
+
* does not fit is never announced (the commit half advances past it), so the model can still hold an
|
|
55
|
+
* entry that is gone — one failed call, against a frame that would otherwise never fit its budget.
|
|
56
|
+
*/
|
|
57
|
+
export declare function foldRemovalArmWithin(removed: readonly string[], header: string, maxBytes: number): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
|
|
2
|
+
export function foldListingWithin(items, spec) {
|
|
3
|
+
if (items.length === 0)
|
|
4
|
+
return undefined;
|
|
5
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
6
|
+
const render = spec.frame;
|
|
7
|
+
const lineCost = (l) => bytes(l) + 1;
|
|
8
|
+
const bodyBudget = spec.blockMaxBytes - bytes(render([]));
|
|
9
|
+
const fullLines = items.map((it) => spec.full(it));
|
|
10
|
+
const fullTotal = fullLines.reduce((acc, l) => acc + lineCost(l), 0);
|
|
11
|
+
if (fullTotal <= bodyBudget)
|
|
12
|
+
return { text: render(fullLines), nameOnly: [], dropped: [] };
|
|
13
|
+
const nameLines = items.map((it) => spec.nameOnly(it));
|
|
14
|
+
const namePrefix = new Array(nameLines.length + 1);
|
|
15
|
+
namePrefix[0] = 0;
|
|
16
|
+
for (let i = 0; i < nameLines.length; i++)
|
|
17
|
+
namePrefix[i + 1] = namePrefix[i] + lineCost(nameLines[i]);
|
|
18
|
+
const markerCost = lineCost(spec.capMarker);
|
|
19
|
+
const floorCost = markerCost + namePrefix[nameLines.length];
|
|
20
|
+
if (floorCost <= bodyBudget) {
|
|
21
|
+
let slack = bodyBudget - floorCost;
|
|
22
|
+
const byPriority = items
|
|
23
|
+
.map((it, i) => ({ i, rank: spec.rank?.(it, i) ?? i }))
|
|
24
|
+
.toSorted((a, b) => a.rank - b.rank || a.i - b.i);
|
|
25
|
+
const withFull = new Set();
|
|
26
|
+
for (const { i } of byPriority) {
|
|
27
|
+
const delta = lineCost(fullLines[i]) - lineCost(nameLines[i]);
|
|
28
|
+
if (delta <= slack) {
|
|
29
|
+
withFull.add(i);
|
|
30
|
+
slack -= delta;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
text: render([spec.capMarker, ...items.map((_it, i) => (withFull.has(i) ? fullLines[i] : nameLines[i]))]),
|
|
35
|
+
nameOnly: items.filter((_it, i) => !withFull.has(i)).map((it) => spec.nameOf(it)),
|
|
36
|
+
dropped: [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
for (let m = items.length - 1; m >= 0; m--) {
|
|
40
|
+
const overflow = spec.overflowLine(items.length - m);
|
|
41
|
+
if (markerCost + namePrefix[m] + lineCost(overflow) <= bodyBudget) {
|
|
42
|
+
return {
|
|
43
|
+
text: render([spec.capMarker, ...nameLines.slice(0, m), overflow]),
|
|
44
|
+
nameOnly: items.slice(0, m).map((it) => spec.nameOf(it)),
|
|
45
|
+
dropped: items.slice(m).map((it) => spec.nameOf(it)),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
text: render([spec.capMarker, spec.overflowLine(items.length)]),
|
|
51
|
+
nameOnly: [],
|
|
52
|
+
dropped: items.map((it) => spec.nameOf(it)),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function removalArmFloor(header, count) {
|
|
56
|
+
return [header, `(… +${count} more are no longer available)`].join("\n");
|
|
57
|
+
}
|
|
58
|
+
export function foldRemovalArmWithin(removed, header, maxBytes) {
|
|
59
|
+
const bytes = (s) => Buffer.byteLength(s, "utf8");
|
|
60
|
+
const lines = removed.map((n) => `- ${sanitizeUntrustedText(n, SHELLED_BODY_ENVELOPE_TAGS)}`);
|
|
61
|
+
const head = bytes(header);
|
|
62
|
+
const lineCost = (l) => bytes(l) + 1;
|
|
63
|
+
const prefix = new Array(lines.length + 1);
|
|
64
|
+
prefix[0] = 0;
|
|
65
|
+
for (let i = 0; i < lines.length; i++)
|
|
66
|
+
prefix[i + 1] = prefix[i] + lineCost(lines[i]);
|
|
67
|
+
if (head + prefix[lines.length] <= maxBytes)
|
|
68
|
+
return [header, ...lines].join("\n");
|
|
69
|
+
for (let m = lines.length - 1; m >= 0; m--) {
|
|
70
|
+
const tail = `(… +${lines.length - m} more are no longer available)`;
|
|
71
|
+
if (head + prefix[m] + lineCost(tail) <= maxBytes)
|
|
72
|
+
return [header, ...lines.slice(0, m), tail].join("\n");
|
|
73
|
+
}
|
|
74
|
+
return removalArmFloor(header, lines.length);
|
|
75
|
+
}
|
|
@@ -14,7 +14,7 @@ export interface OwnedEnvSeat {
|
|
|
14
14
|
export interface PrepareExecutionEnvInput {
|
|
15
15
|
/** borrowed-readonly — the deployment's env seats: the per-task factory (wins), the static env, the
|
|
16
16
|
* declared root fence, and the incident sink for the bounded teardown legs. */
|
|
17
|
-
deps: Pick<RunnerDeps, "executionEnvFactory" | "executionEnv" | "rootPath" | "onError">;
|
|
17
|
+
deps: Pick<RunnerDeps, "executionEnvFactory" | "executionEnv" | "rootPath" | "onError" | "onNotice">;
|
|
18
18
|
/** borrowed-readonly — the REBOUND spec; only the caller's task id rides into the factory ctx. */
|
|
19
19
|
spec: Pick<TaskSpec, "taskId">;
|
|
20
20
|
/** borrowed-readonly — the trusted spawn-side requests: the explicit cwd (a contract), the isolation
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { StubExecutionEnv } from "../stub-env.js";
|
|
2
2
|
import { hasDestroy } from "../remote-env.js";
|
|
3
|
+
import { announceMalformedCapabilities } from "./env-capability-census.js";
|
|
3
4
|
import { settleTeardownLeg } from "./teardown-bounded.js";
|
|
4
5
|
export async function prepareExecutionEnv(input) {
|
|
5
6
|
const { deps, spec, internals, resume, placementRootResolved, sessionId, forgetOnThrow, rollback } = input;
|
|
@@ -39,6 +40,7 @@ export async function prepareExecutionEnv(input) {
|
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
const executionEnv = ownedEnv ?? deps.executionEnv ?? new StubExecutionEnv();
|
|
43
|
+
announceMalformedCapabilities(executionEnv, deps.onNotice);
|
|
42
44
|
if (internals?.requestedCwd !== undefined) {
|
|
43
45
|
const canon = async (p) => {
|
|
44
46
|
const r = await executionEnv.canonicalPath(p);
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* gate outcome; nothing here changes a word, a source or an order — the stations are moved, not re-adjudicated.
|
|
24
24
|
*/
|
|
25
25
|
import type { AgentHarness, AgentTool } from "../../internal/harness.js";
|
|
26
|
+
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
26
27
|
import type { AskClass } from "../ask-class.js";
|
|
27
28
|
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
29
|
import { type ComplianceCapability } from "../compliance.js";
|
|
@@ -147,6 +148,13 @@ export interface PrepareGateStationsInput {
|
|
|
147
148
|
handsCwdRef: CwdRef | undefined;
|
|
148
149
|
/** borrowed-readonly — the read-only env capability face every hook context carries, or undefined. */
|
|
149
150
|
hookEnvFace: HookEnvCapabilities | undefined;
|
|
151
|
+
/** borrowed-readonly — the HANDS' execution environment (the one the write tools resolve against), or
|
|
152
|
+
* undefined when the run has no hands: the write-protection judge's target view resolves in it, and with
|
|
153
|
+
* none the judge answers by the spelling alone. */
|
|
154
|
+
handsEnv: ExecutionEnv | undefined;
|
|
155
|
+
/** borrowed-readonly — the resolved task root, the base a `base: "root"` write tool's relative spelling
|
|
156
|
+
* resolves against (the same seat the path policies read as `rootPath`). */
|
|
157
|
+
taskRootFinal: string;
|
|
150
158
|
/** borrowed-readonly — the per-tool irreversibility tier handed to the gate per call. */
|
|
151
159
|
irreversibilityTier: IrreversibilityTier;
|
|
152
160
|
/** borrowed-readonly — the per-tool reversibility probes handed to the gate per call. */
|
|
@@ -42,7 +42,7 @@ export function maybeHumanRejectionHalt(input) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
export function prepareGateStations(input) {
|
|
45
|
-
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash } = input;
|
|
45
|
+
const { askLane, parkAsk, tools, toolRosterDeltas, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, planModeRef, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, hookEnvFace, handsEnv, taskRootFinal, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider, autoModeDenialTracking, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash } = input;
|
|
46
46
|
if (askLane !== undefined && parkAsk !== undefined) {
|
|
47
47
|
const { adjudicate, resolveAskBound, notifyPermissionDenied, notifyHookError } = askLane;
|
|
48
48
|
const { resolveContentAsk, suspendAsk } = parkAsk;
|
|
@@ -51,7 +51,11 @@ export function prepareGateStations(input) {
|
|
|
51
51
|
if (eff !== undefined && !toolEffects.has(t.name))
|
|
52
52
|
toolEffects.set(t.name, eff);
|
|
53
53
|
}
|
|
54
|
-
const writeProtectionCheck = createWriteProtectionCheck(deps.writeProtectedPaths,
|
|
54
|
+
const writeProtectionCheck = createWriteProtectionCheck(deps.writeProtectedPaths, {
|
|
55
|
+
...(deps.memoryEngineDir !== undefined ? { dataRoot: deps.memoryEngineDir } : {}),
|
|
56
|
+
...(handsEnv !== undefined ? { env: handsEnv } : {}),
|
|
57
|
+
rootPath: taskRootFinal,
|
|
58
|
+
});
|
|
55
59
|
const writeProtectionArmed = writeProtectionCheck !== undefined && tools.some((t) => PATH_CONFINABLE_WRITE_TOOLS.has(t.name));
|
|
56
60
|
toolCallGateArmedRef.armed =
|
|
57
61
|
effectivePolicy !== undefined ||
|
|
@@ -110,6 +110,10 @@ export interface PrepareHandsReadFaceInput {
|
|
|
110
110
|
/** borrowed-readonly — B-4's settled root (T6 FINAL semantics); the containment root every face
|
|
111
111
|
* in this phase canonicalizes from. */
|
|
112
112
|
taskRootFinal: string;
|
|
113
|
+
/** borrowed-readonly — the same root, resolved to the spelling the fence compares with, by the ONE
|
|
114
|
+
* resolver at the driver's root receiving point ({@link import("./task-root-canonical.js").resolveTaskRootCanonical}):
|
|
115
|
+
* taken as an input, never re-canonicalized here, so the fence and the root notice cannot disagree. */
|
|
116
|
+
taskRootCanonical: string;
|
|
113
117
|
/** borrowed-readonly — B-4's checkpoint-path rebaser (T7): identity unless a divergent restore
|
|
114
118
|
* armed it; applied to the readFileState seed keys and the handsCwd seed. */
|
|
115
119
|
rebaseRestoredPath: (p: string) => string;
|
|
@@ -152,7 +152,7 @@ export function resolveHandsLessReadFace(input) {
|
|
|
152
152
|
}
|
|
153
153
|
export async function prepareHandsMount(input) {
|
|
154
154
|
const envHandToolNames = new Set();
|
|
155
|
-
const { handsEnabled, executionEnv,
|
|
155
|
+
const { handsEnabled, executionEnv, taskRootCanonical, rebaseRestoredPath, resume, session, sessionId, hostTaskId, taskScope, fullShellReachable, effectiveShellGate, toolFaceSnapshot, model, spec, deps, internals, memoryWriteGate, advertisedWritableDirs, firstPartyOffload, roster, egressTools, irreversibilityTier, irreversibleTools, reversibilityProbes } = input;
|
|
156
156
|
assertStringArraySeat(spec.additionalDirectories, "TaskSpec.additionalDirectories", "strings");
|
|
157
157
|
assertStringArraySeat(spec.additionalReadDirectories, "TaskSpec.additionalReadDirectories", "strings");
|
|
158
158
|
assertStringArraySeat(deps.readDenyPatterns, "RunnerDeps.readDenyPatterns", "compiler-owned");
|
|
@@ -176,15 +176,13 @@ export async function prepareHandsMount(input) {
|
|
|
176
176
|
let attachmentRootCanonical;
|
|
177
177
|
let readDenyMatcher;
|
|
178
178
|
if (handsEnabled) {
|
|
179
|
-
const
|
|
180
|
-
const canon = await executionEnv.canonicalPath(rootRaw);
|
|
181
|
-
const rootCanonical = canon.ok ? canon.value : rootRaw;
|
|
179
|
+
const rootCanonical = taskRootCanonical;
|
|
182
180
|
attachmentRootCanonical = rootCanonical;
|
|
183
181
|
const canonicalizeExtraDirs = async (dirs, field, sink) => {
|
|
184
182
|
for (const dir of dirs ?? []) {
|
|
185
183
|
if (!dir || !dir.trim())
|
|
186
184
|
continue;
|
|
187
|
-
const c = await executionEnv.canonicalPath(dir);
|
|
185
|
+
const c = await (advertisedWritableDirs?.canonicalizeDeclaredRoot(dir) ?? executionEnv.canonicalPath(dir));
|
|
188
186
|
if (c.ok) {
|
|
189
187
|
sink.push(c.value);
|
|
190
188
|
}
|
|
@@ -17,6 +17,7 @@ import type { AgentTool } from "../../internal/harness.js";
|
|
|
17
17
|
import type { TaskSpec } from "../types.js";
|
|
18
18
|
import type { AnnounceOnceSink, Prepared, PrepareResume, ToolFaceSnapshot } from "./contracts.js";
|
|
19
19
|
import type { AnnouncedListingSnapshot } from "../../internal/harness.js";
|
|
20
|
+
import type { ResolvedSkillListingBudget } from "../skill-listing-budget.js";
|
|
20
21
|
export interface PrepareListingsInput {
|
|
21
22
|
/** borrowed-readonly — the REBOUND spec (`spec′`). Read: `agents` (the audit's subject), `tools`
|
|
22
23
|
* (the roster-bearing delegation tool, by its `agentListing` marker), `sessionId` (continuation
|
|
@@ -44,6 +45,8 @@ export interface PrepareListingsInput {
|
|
|
44
45
|
/** borrowed-readonly — the project-context phase's skills-listing projection; read only for the
|
|
45
46
|
* skills opt-out advisory (entries.length). */
|
|
46
47
|
skillsListing: Prepared["skillsListing"];
|
|
48
|
+
/** borrowed-readonly — the listing family's resolved budget; the agent roster frames fold into it. */
|
|
49
|
+
listingBudget: ResolvedSkillListingBudget;
|
|
47
50
|
/** borrowed-mutable — the deferred roster's ToolSearch RIDE seat (declared by the disclosure
|
|
48
51
|
* mount, read at activation time through `listingRideRef.current?.(newly)`). Written here, once,
|
|
49
52
|
* on a deferred roster face; no later writer. Identity is the contract (the mount's closures read
|
|
@@ -2,7 +2,7 @@ import { formatLocalDate } from "../../prompts/default.js";
|
|
|
2
2
|
import { SKILL_TOOL_NAME } from "./synthetic-tools.js";
|
|
3
3
|
import { renderAgentListingDelta } from "./turn-attachments.js";
|
|
4
4
|
export function prepareListings(input) {
|
|
5
|
-
const { spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingRideRef, renderWithDate, envFacts, tzValid, userTz } = input;
|
|
5
|
+
const { spec, tools, onceLedger, sessionId, toolFaceSnapshot, activeTools, resume, skillsListing, listingBudget, listingRideRef, renderWithDate, envFacts, tzValid, userTz } = input;
|
|
6
6
|
if (spec.agents !== undefined && spec.agents.length > 0) {
|
|
7
7
|
const known = new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
|
|
8
8
|
for (const def of spec.agents) {
|
|
@@ -39,6 +39,7 @@ export function prepareListings(input) {
|
|
|
39
39
|
? {
|
|
40
40
|
entries: agentListingSpec.agentListing,
|
|
41
41
|
toolName: agentListingSpec.name,
|
|
42
|
+
budget: listingBudget,
|
|
42
43
|
seedAnnounced: resume !== undefined || spec.sessionId !== undefined,
|
|
43
44
|
...(agentListingSpec.agentModels !== undefined ? { models: agentListingSpec.agentModels } : {}),
|
|
44
45
|
}
|
|
@@ -48,6 +49,7 @@ export function prepareListings(input) {
|
|
|
48
49
|
{
|
|
49
50
|
entries: [],
|
|
50
51
|
toolName: (spec.tools ?? []).find((t) => t.agentListing !== undefined)?.name ?? "Agent",
|
|
52
|
+
budget: listingBudget,
|
|
51
53
|
seedAnnounced: true,
|
|
52
54
|
}
|
|
53
55
|
: undefined;
|
|
@@ -66,7 +68,7 @@ export function prepareListings(input) {
|
|
|
66
68
|
return undefined;
|
|
67
69
|
if ((announcedListingsRef.agents?.length ?? 0) > 0)
|
|
68
70
|
return undefined;
|
|
69
|
-
return renderAgentListingDelta({ announcedAgentTypes: undefined }, rideEntries, rosterBearingSpec.name, rideModels);
|
|
71
|
+
return renderAgentListingDelta({ announcedAgentTypes: undefined }, rideEntries, rosterBearingSpec.name, rideModels, listingBudget);
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
const dateChange = renderWithDate
|
|
@@ -4,7 +4,7 @@ import { admitMemoryScopes } from "../memory-admission.js";
|
|
|
4
4
|
import { adoptLegacyRepoDirs, canonicalize, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, isContainedIn, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
|
|
5
5
|
import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
|
|
6
6
|
import { normalizeMemorySpec } from "../memory.js";
|
|
7
|
-
import { MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA, MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_CAPTURE_INDETERMINATE_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
|
|
7
|
+
import { MEMORY_ANNOUNCEMENT_CAPTURE_OPTOUT_CODA, MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_CAPTURE_INDETERMINATE_NOTICE, MEMORY_CAPTURE_OPTOUT_NOTICE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MemoryEngine, entryFileHeadCarriesOrigin, memoryCaptureOptedOutNotice, memoryCaptureOptOutUnpersistedNotice, memoryConsolidationRecommendedNotice, memoryHarvestQuarantinedNotice, memoryHoldNotices, memoryRecallDisciplineSegment, memoryRestoreWithheldNotice, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
|
|
8
8
|
import { adjudicateCaptureOptOut, captureOptOutDeniedError, captureOptOutUnpersistedError } from "./memory-capture-optout.js";
|
|
9
9
|
import { readSessionCaptureOptOut } from "../memory-engine/layout.js";
|
|
10
10
|
import { createMemoryEngineTools } from "../memory-engine/tools.js";
|
|
@@ -522,6 +522,9 @@ export async function prepareMemory(input) {
|
|
|
522
522
|
}
|
|
523
523
|
deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(report.reportId !== undefined ? { reportId: report.reportId } : {}), ...(reason !== undefined ? { reason } : {}), sessionId, provenance: input.memoryProvenance, ...(indexOnly ? { indexRolledBack: true } : {}) }));
|
|
524
524
|
}
|
|
525
|
+
if (report.restoreWithheld !== undefined && report.restoreWithheld.length > 0) {
|
|
526
|
+
deliverEngineNotice(deps.onNotice, memoryRestoreWithheldNotice({ paths: report.restoreWithheld, sessionId }));
|
|
527
|
+
}
|
|
525
528
|
for (const notice of memoryHoldNotices(report, sessionId))
|
|
526
529
|
deliverEngineNotice(deps.onNotice, notice);
|
|
527
530
|
const consolidationNotice = memoryConsolidationRecommendedNotice(report, sessionId);
|
|
@@ -615,6 +618,9 @@ export async function prepareMemory(input) {
|
|
|
615
618
|
}
|
|
616
619
|
}
|
|
617
620
|
const sweep = await writeEngine.sweepSessionCaptureResidue(writeHandle);
|
|
621
|
+
if (sweep.withheld.length > 0) {
|
|
622
|
+
deliverEngineNotice(deps.onNotice, memoryRestoreWithheldNotice({ paths: sweep.withheld, sessionId }));
|
|
623
|
+
}
|
|
618
624
|
if (sweep.failures.length > 0) {
|
|
619
625
|
const named = sweep.failures
|
|
620
626
|
.slice(0, 5)
|