@sema-agent/core 7.56.0 → 7.57.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 +541 -0
- package/LICENSE +1 -1
- package/dist/agents/org-delivery-gate.js +3 -4
- package/dist/agents/peer-held-queue.d.ts +69 -49
- package/dist/agents/peer-held-queue.js +255 -106
- package/dist/agents/peer-session-drain.js +63 -43
- package/dist/agents/send-message-tool.js +3 -2
- package/dist/agents/subagent.js +8 -8
- package/dist/brain/anthropic.js +16 -8
- package/dist/brain/open-responses.js +10 -8
- package/dist/brain/openai.js +11 -9
- package/dist/brain/provider-dialect.d.ts +3 -2
- package/dist/brain/provider-dialect.js +2 -2
- package/dist/brain/request-params.d.ts +6 -0
- package/dist/brain/request-params.js +14 -1
- package/dist/brain/stream-engine.d.ts +9 -0
- package/dist/brain/stream-engine.js +24 -10
- package/dist/brain/stream-shared.d.ts +5 -4
- package/dist/brain/stream-shared.js +4 -4
- package/dist/core/allow-entry.d.ts +3 -10
- package/dist/core/allow-entry.js +17 -25
- package/dist/core/approval-object.d.ts +21 -5
- package/dist/core/approval-object.js +44 -17
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/auto-compaction.d.ts +71 -6
- package/dist/core/auto-compaction.js +42 -26
- package/dist/core/background-agent-store.d.ts +6 -2
- package/dist/core/background-agent-store.js +1 -1
- package/dist/core/checkpoint-requirements.d.ts +7 -1
- package/dist/core/checkpoint-requirements.js +2 -0
- package/dist/core/checkpoint-store.d.ts +17 -29
- package/dist/core/checkpoint-store.js +19 -11
- package/dist/core/cross-boundary-taint.js +4 -2
- package/dist/core/engine-notice.d.ts +3 -2
- package/dist/core/engine-notice.js +6 -0
- package/dist/core/file-history-store.d.ts +14 -8
- package/dist/core/file-history-store.js +14 -4
- package/dist/core/gate-lanes.js +2 -3
- package/dist/core/hooks.d.ts +2 -2
- package/dist/core/mailbox-store.d.ts +62 -1
- package/dist/core/mailbox-store.js +59 -0
- package/dist/core/mcp-name-judge.d.ts +46 -0
- package/dist/core/mcp-name-judge.js +61 -0
- package/dist/core/mcp-probe.d.ts +1 -1
- package/dist/core/mcp-probe.js +2 -5
- package/dist/core/mcp.js +10 -30
- package/dist/core/memory-engine/consolidation-driver.js +4 -3
- package/dist/core/memory-engine/distiller.js +26 -17
- package/dist/core/memory-engine/layout.js +5 -2
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-consent.d.ts +5 -3
- package/dist/core/permission-rule-consent.js +20 -15
- package/dist/core/permission-rule-model.d.ts +43 -2
- package/dist/core/permission-rule-model.js +52 -8
- package/dist/core/permission-rule-org.js +1 -1
- package/dist/core/protocol-http-auth.d.ts +16 -2
- package/dist/core/protocol-http-auth.js +42 -11
- package/dist/core/protocol-naming.d.ts +20 -0
- package/dist/core/protocol-naming.js +20 -3
- package/dist/core/read-only-shell.d.ts +20 -5
- package/dist/core/read-only-shell.js +29 -14
- package/dist/core/remote-env.d.ts +37 -1
- package/dist/core/remote-env.js +11 -0
- package/dist/core/resume-startup-cause.d.ts +24 -2
- package/dist/core/resume-startup-cause.js +20 -1
- package/dist/core/roles.js +50 -7
- package/dist/core/runner/compact-session.d.ts +10 -0
- package/dist/core/runner/compact-session.js +202 -0
- package/dist/core/runner/compaction-call-options.d.ts +2 -2
- package/dist/core/runner/compaction-seams.d.ts +21 -1
- package/dist/core/runner/compaction-seams.js +11 -8
- package/dist/core/runner/contracts.d.ts +23 -16
- package/dist/core/runner/derived-route-fallback.d.ts +13 -0
- package/dist/core/runner/derived-route-fallback.js +6 -0
- package/dist/core/runner/env-capability-census.d.ts +3 -2
- package/dist/core/runner/env-capability-census.js +9 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
- package/dist/core/runner/permission-rule-lanes.js +17 -11
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -0
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.js +2 -4
- package/dist/core/runner/prepare-task.d.ts +4 -1
- package/dist/core/runner/prepare-task.js +23 -27
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
- package/dist/core/runner/rewind-files.d.ts +2 -5
- package/dist/core/runner/rewind-files.js +9 -51
- package/dist/core/runner/run-leg.js +10 -21
- package/dist/core/runner/run-notification-lane.d.ts +2 -0
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/run-recovery-lanes.js +3 -4
- package/dist/core/runner/run-settle-and-teardown.js +5 -11
- package/dist/core/runner/run-terminal-adoption.js +3 -4
- package/dist/core/runner/run-turn-boundary.js +3 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +28 -34
- package/dist/core/runner/send-message-continuation.js +1 -1
- package/dist/core/runner/session-verb-doors.d.ts +28 -0
- package/dist/core/runner/session-verb-doors.js +48 -0
- package/dist/core/runner/session-write-ledger.d.ts +30 -0
- package/dist/core/runner/session-write-ledger.js +101 -0
- package/dist/core/runner/stream-settle-backstop.js +11 -14
- package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
- package/dist/core/runner/sub-agent-deny-lists.js +21 -10
- package/dist/core/runner/teardown-bounded.d.ts +1 -0
- package/dist/core/runner/teardown-bounded.js +1 -0
- package/dist/core/runner-deps.d.ts +2 -2
- package/dist/core/secret-env.d.ts +4 -0
- package/dist/core/secret-env.js +3 -0
- package/dist/core/session-policy-store.d.ts +7 -2
- package/dist/core/session-policy-store.js +1 -1
- package/dist/core/shell-wrapper-projection.d.ts +1 -1
- package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/store-contracts/file-history-store-contract.js +9 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
- package/dist/core/task-registry-agent.d.ts +8 -4
- package/dist/core/task-registry-agent.js +14 -8
- package/dist/core/task-registry-shared.d.ts +35 -6
- package/dist/core/task-registry-shared.js +11 -0
- package/dist/core/task-registry.d.ts +1 -1
- package/dist/core/task-registry.js +1 -1
- package/dist/core/tool-catalog-entries.d.ts +6 -0
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-name-aliases.d.ts +32 -2
- package/dist/core/tool-name-aliases.js +23 -0
- package/dist/core/tool-name-readings.d.ts +4 -0
- package/dist/core/tool-name-readings.js +1 -0
- package/dist/core/tool-policy.d.ts +10 -6
- package/dist/core/tool-policy.js +4 -7
- package/dist/core/trace.d.ts +4 -1
- package/dist/core/trace.js +2 -1
- package/dist/core/untrusted-egress.d.ts +20 -0
- package/dist/core/untrusted-egress.js +77 -3
- package/dist/engine/harness/agent-harness.d.ts +7 -5
- package/dist/engine/harness/agent-harness.js +11 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -4
- package/dist/engine/loop/agent-loop.js +26 -4
- package/dist/index.d.ts +12 -7
- package/dist/index.js +7 -4
- package/dist/internal/harness.d.ts +2 -1
- package/dist/internal/harness.js +1 -0
- package/dist/primitives/credential-ref.d.ts +89 -0
- package/dist/primitives/credential-ref.js +125 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +0 -4
- package/dist/stores/file/checkpoint-store.js +0 -1
- package/dist/stores/file/file-history-store.js +12 -4
- package/dist/stores/file/mailbox-store.d.ts +2 -1
- package/dist/stores/file/mailbox-store.js +26 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
- package/dist/tools/fs/bash-readonly-classifier.js +5 -10
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
- package/dist/tools/fs/pattern-kill-guard.js +68 -36
- package/dist/tools/fs/shell-read-check.d.ts +10 -0
- package/dist/tools/fs/shell-read-check.js +7 -1
- package/dist/tools/fs/shell-read-identity.d.ts +12 -9
- package/dist/tools/fs/shell-read-identity.js +9 -4
- package/dist/tools/web.js +9 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +79 -3
- package/wire-changes/core-stores-sql@7.57.0.json +29 -0
- package/wire-changes/core@7.57.0.json +1487 -0
- package/wire-changes/mods-kernel@7.57.0.json +29 -0
- package/wire-changes/primitives@7.57.0.json +245 -0
|
@@ -13,7 +13,7 @@ import { announceReadDenySwap, screenSwappableDeps } from "../swappable-deps.js"
|
|
|
13
13
|
import { runSideQuery } from "../side-query.js";
|
|
14
14
|
import { PushQueue } from "../push-queue.js";
|
|
15
15
|
import { TtlSessionStore } from "../session-store.js";
|
|
16
|
-
import {
|
|
16
|
+
import { compensateUnstarted, unstartedRowSentence } from "../resume-startup-cause.js";
|
|
17
17
|
import { effectiveDelegationFacts, prepareTask } from "./prepare-task.js";
|
|
18
18
|
import { SessionReadFileStates } from "./session-read-file-states.js";
|
|
19
19
|
import { SessionModeSeats } from "./session-mode-seats.js";
|
|
@@ -24,6 +24,8 @@ import { PendingSessionNotifications } from "../task-notification.js";
|
|
|
24
24
|
import { ToolDetachHub } from "../tool-detach.js";
|
|
25
25
|
import { assertContextSettingsValue, assertContextSettingsWords, contextOverlayFor } from "../context-settings.js";
|
|
26
26
|
import { createRunState } from "./initial-run-state.js";
|
|
27
|
+
import { SessionWriteLedger } from "./session-write-ledger.js";
|
|
28
|
+
import { teardownLegBudget } from "./teardown-bounded.js";
|
|
27
29
|
import { emitSettled, reconciledEnd } from "./tool-end-body.js";
|
|
28
30
|
import { answerFaceForRedeemedCall } from "./decide-continuation.js";
|
|
29
31
|
import { createTurnBoundary } from "./run-turn-boundary.js";
|
|
@@ -63,11 +65,14 @@ import { runRecoveryLanes } from "./run-recovery-lanes.js";
|
|
|
63
65
|
import { runGitLane } from "./run-git-lane.js";
|
|
64
66
|
import { deploymentCanRerootChild } from "./child-tree.js";
|
|
65
67
|
import { rewindSessionFiles } from "./rewind-files.js";
|
|
66
|
-
import {
|
|
68
|
+
import { compactIdleSession } from "./compact-session.js";
|
|
69
|
+
import { egressThrownText } from "../untrusted-egress.js";
|
|
67
70
|
export class Runner {
|
|
68
71
|
deps;
|
|
69
72
|
sessions;
|
|
70
73
|
sessionLocks = new Map();
|
|
74
|
+
sessionWrites = new SessionWriteLedger();
|
|
75
|
+
verbLockSeats = { sessionBusy: (id) => this.sessionLocks.has(id) || this.sessionWrites.busy(id), takeSessionLock: (id) => this.acquireSessionLock(id) };
|
|
71
76
|
sessionReadStates = new SessionReadFileStates();
|
|
72
77
|
sessionModeSeats = new SessionModeSeats();
|
|
73
78
|
pendingSessionNotifications = new PendingSessionNotifications();
|
|
@@ -96,7 +101,7 @@ export class Runner {
|
|
|
96
101
|
version: 1,
|
|
97
102
|
sessionId: context.sessionId,
|
|
98
103
|
phase: context.phase,
|
|
99
|
-
reason: (
|
|
104
|
+
reason: egressThrownText("same-principal", sinkErr).slice(0, 256),
|
|
100
105
|
ts: Date.now(),
|
|
101
106
|
}));
|
|
102
107
|
}
|
|
@@ -159,11 +164,14 @@ export class Runner {
|
|
|
159
164
|
const chain = prev.then(() => mine);
|
|
160
165
|
this.sessionLocks.set(sessionId, chain);
|
|
161
166
|
await prev.catch(() => { });
|
|
162
|
-
|
|
167
|
+
const releaseHold = () => {
|
|
163
168
|
release();
|
|
164
169
|
if (this.sessionLocks.get(sessionId) === chain)
|
|
165
170
|
this.sessionLocks.delete(sessionId);
|
|
166
171
|
};
|
|
172
|
+
if (this.sessionWrites.busy(sessionId))
|
|
173
|
+
await this.sessionWrites.settleBeforeHold(sessionId, teardownLegBudget()).catch((err) => { releaseHold(); throw err; });
|
|
174
|
+
return releaseHold;
|
|
167
175
|
}
|
|
168
176
|
swapDeps(next) {
|
|
169
177
|
const screened = screenSwappableDeps(next);
|
|
@@ -233,7 +241,8 @@ export class Runner {
|
|
|
233
241
|
sideQuery(spec) {
|
|
234
242
|
return runWithReasoningWireFacts(() => { }, () => runWithCapabilityWithheld(() => { }, () => runSideQuery(spec, { brain: this.deps.brain, models: this.deps.models, roles: this.deps.roles })));
|
|
235
243
|
}
|
|
236
|
-
rewindFiles(request) { return rewindSessionFiles({ deps: this.deps, sessions: this.sessions, readStates: this.sessionReadStates,
|
|
244
|
+
rewindFiles(request) { return rewindSessionFiles({ deps: this.deps, sessions: this.sessions, readStates: this.sessionReadStates, ...this.verbLockSeats }, request); }
|
|
245
|
+
compactSession(request) { return compactIdleSession({ deps: this.deps, sessions: this.sessions, ...this.verbLockSeats }, request); }
|
|
237
246
|
runTaskStream(spec, resume, internals) {
|
|
238
247
|
if (resume !== undefined && (typeof resume !== "object" || resume.outcome === undefined)) {
|
|
239
248
|
throw new TypeError("runTaskStream: `resume` must be a ResumeRun carrying `outcome` — got a value without one. " +
|
|
@@ -281,14 +290,16 @@ export class Runner {
|
|
|
281
290
|
return held.taken;
|
|
282
291
|
};
|
|
283
292
|
const namedSessionId = spec.sessionId;
|
|
293
|
+
const legWrites = this.sessionWrites.openLeg((err, sessionId) => this.deps.onError?.(err, { phase: "degraded", sessionId }));
|
|
284
294
|
const run = (async () => {
|
|
285
|
-
if (namedSessionId)
|
|
286
|
-
await holdSessionLock(namedSessionId);
|
|
287
295
|
try {
|
|
296
|
+
if (namedSessionId)
|
|
297
|
+
await holdSessionLock(namedSessionId);
|
|
288
298
|
await this.runLocked({
|
|
289
299
|
spec,
|
|
290
300
|
queue,
|
|
291
301
|
holdSessionLock: namedSessionId ? undefined : holdSessionLock,
|
|
302
|
+
legWrites,
|
|
292
303
|
setResult: (r) => {
|
|
293
304
|
resultValue = r;
|
|
294
305
|
const pcs = internals?.inheritedGate?.parentConstraints;
|
|
@@ -331,6 +342,7 @@ export class Runner {
|
|
|
331
342
|
});
|
|
332
343
|
}
|
|
333
344
|
finally {
|
|
345
|
+
legWrites.close();
|
|
334
346
|
releaseLock?.();
|
|
335
347
|
publishReady(handle);
|
|
336
348
|
manualCompactRef.closed = true;
|
|
@@ -382,9 +394,9 @@ export class Runner {
|
|
|
382
394
|
};
|
|
383
395
|
}
|
|
384
396
|
runLocked(input) {
|
|
385
|
-
const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, holdSessionLock, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
|
|
397
|
+
const { spec, queue, setResult, onSuggestions, onReady, onSuspend, manualCompactRef, taskIdRef, holdSessionLock, legWrites, resume, internals, notifyRef, captureOptOutRef, mcpRedialRef, entryActor, entryTracer } = input;
|
|
386
398
|
return runNotificationLane({
|
|
387
|
-
spec, queue, resume, internals, taskIdRef, holdSessionLock,
|
|
399
|
+
spec, queue, resume, internals, taskIdRef, holdSessionLock, legWrites,
|
|
388
400
|
runner: this.depsSeat, self: this, sessions: this.sessions, sessionReadStates: this.sessionReadStates, sessionModeSeats: this.sessionModeSeats, pendingSessionNotifications: this.pendingSessionNotifications,
|
|
389
401
|
prepareTask,
|
|
390
402
|
next: ({ prepared, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, modelCatalog, notificationLane, unsubscribeTaskNotifications }) => {
|
|
@@ -527,37 +539,19 @@ export class Runner {
|
|
|
527
539
|
preemptSignal: config.preemptSignal?.aborted ? undefined : config.preemptSignal,
|
|
528
540
|
...(answerFace !== undefined ? { onQuestion: answerFace } : {}),
|
|
529
541
|
};
|
|
530
|
-
const
|
|
542
|
+
const reopenUnstarted = async (reason) => ((await store.reopen(token, cp.scope, reason)) ? "reopened" : "declined");
|
|
531
543
|
const recordExecutionOutcome = (gate) => store.recordExecutionOutcome(token, cp.scope, gate);
|
|
532
|
-
const onEnvRestoreFailed = reopenFn
|
|
533
|
-
? async (reason) => {
|
|
534
|
-
const reopened = await reopenFn(token, cp.scope, reason);
|
|
535
|
-
if (!reopened) {
|
|
536
|
-
throw new CheckpointError("checkpoint.reopen_failed", "checkpoint could not be reopened for a retry (missing, expired, or changed concurrently) — the resume failure is terminal");
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
: undefined;
|
|
540
544
|
if (resumeSignal?.aborted === true && consumeFlipDone) {
|
|
541
545
|
throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted after its row was already claimed — the checkpoint stays consumed and the caller's rollback settles the outcome-unknown terminal (reopening here would strand a pending approval no redemption path can reach)");
|
|
542
546
|
}
|
|
543
547
|
if (resumeSignal?.aborted === true) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
catch (err) {
|
|
551
|
-
reopenOutcome = errorCodeOf(err) === "checkpoint.reopen_failed" ? "refused" : "unknown";
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
throw reopenOutcome === "reopened"
|
|
555
|
-
? new CheckpointError("checkpoint.resume_aborted", "the resume was aborted before the approved action could run — the checkpoint was reopened, so the same approval can be resumed with a live signal")
|
|
556
|
-
: reopenOutcome === "refused"
|
|
557
|
-
? new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run AND the store refused to reopen the checkpoint — the approval is terminally consumed and the suspended work was not executed; a retry needs a fresh approval")
|
|
558
|
-
: new CheckpointError("checkpoint.reopen_failed", "the resume was aborted before the approved action could run and the reopen attempt FAILED IN FLIGHT — the checkpoint's state is unprovable from here: it may already be pending again. Re-read it before deciding; do NOT issue a fresh approval on the assumption the old one is dead (the approved action did NOT run either way)");
|
|
548
|
+
const row = await compensateUnstarted(reopenUnstarted, "env_failed");
|
|
549
|
+
const said = `the resume was aborted before the approved action could run — ${unstartedRowSentence(row)}`;
|
|
550
|
+
if (row.kind === "reopened")
|
|
551
|
+
throw new CheckpointError("checkpoint.resume_aborted", said);
|
|
552
|
+
throw new CheckpointError("checkpoint.reopen_failed", said);
|
|
559
553
|
}
|
|
560
|
-
return this.runTaskStream(spec, { cp, outcome: outcomeForStore,
|
|
554
|
+
return this.runTaskStream(spec, { cp, outcome: outcomeForStore, reopenUnstarted, recordExecutionOutcome, ...(wakeMessage !== undefined ? { wakeMessage } : {}), ...(wakeHookContext !== undefined ? { wakeMessageHookContext: wakeHookContext } : {}), ...(legMods !== undefined ? { legMods } : {}) }, resumeInternals);
|
|
561
555
|
}
|
|
562
556
|
}
|
|
563
557
|
export function runTask(spec, deps) {
|
|
@@ -9,7 +9,7 @@ function buildTier3ReviveSpawn(delegationForRevive, enrichSpecToolCtx) {
|
|
|
9
9
|
prompt: req.prompt,
|
|
10
10
|
run_in_background: true,
|
|
11
11
|
...(req.row.agentType !== undefined ? { subagent_type: req.row.agentType } : {}),
|
|
12
|
-
|
|
12
|
+
name: req.row.name,
|
|
13
13
|
...(req.row.model !== undefined ? { model: req.row.model } : {}),
|
|
14
14
|
}, enrichSpecToolCtx({ toolCallId: `rv-${randomBytes(8).toString("hex")}`, reviveClaim: { row: req.row, rev: req.rev, ...(req.peerSeed !== undefined ? { peerSeed: req.peerSeed } : {}) } }));
|
|
15
15
|
const o = (typeof out === "string" ? { content: out } : out);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AcquiredSession, SessionStore } from "../session.js";
|
|
2
|
+
/** What the Runner lends an out-of-run verb for the session's lock. */
|
|
3
|
+
export interface SessionLockSeats {
|
|
4
|
+
/** Whether a leg of this Runner holds or awaits `sessionId`'s lock, or a write one issued to it has not settled. */
|
|
5
|
+
sessionBusy(sessionId: string): boolean;
|
|
6
|
+
/** Take `sessionId`'s lock; called synchronously after `sessionBusy` answered false, so nothing slips in between. */
|
|
7
|
+
takeSessionLock(sessionId: string): Promise<() => void>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The request door: the request must be a plain data object — not a Proxy (its traps are the caller's code, and could
|
|
11
|
+
* answer the caller's read of a member differently from this one), its prototype `Object.prototype` or none (so no
|
|
12
|
+
* member is inherited) — judged before any member is read; its members are then taken from ONE snapshot of its own
|
|
13
|
+
* data properties, so no code of the request itself (a trap, a getter) runs while it is read and nothing read can
|
|
14
|
+
* change what is read next (an accessor member is a bad request); own keys inside the verb's closed set. `bad`
|
|
15
|
+
* throws the verb's own refusal; the member types are the verb's to judge on the returned snapshot.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readPlainRequest(request: unknown, keys: ReadonlySet<string>, bad: (what: string) => never): Record<string, unknown>;
|
|
18
|
+
/** A value as a refusal names it: a string clipped, a primitive spelled, anything else by its type. */
|
|
19
|
+
export declare function shownValue(v: unknown): string;
|
|
20
|
+
/**
|
|
21
|
+
* The idle claim: refused with `busy()` when a leg of this Runner is running or queued on the session (its state is
|
|
22
|
+
* not moved under it); otherwise the session's lock is claimed in this same synchronous step and held by the verb
|
|
23
|
+
* until it releases it, so a leg that arrives meanwhile waits for the verb.
|
|
24
|
+
*/
|
|
25
|
+
export declare function claimIdleSession(seats: SessionLockSeats, sessionId: string, busy: () => Error): Promise<() => void>;
|
|
26
|
+
/** The session must exist — the same door and code a run with `requireExistingSession` meets. `verb` prefixes the
|
|
27
|
+
* message, `nothingDone` says what the verb did not do. */
|
|
28
|
+
export declare function acquireExistingSession(sessions: SessionStore, sessionId: string, verb: string, nothingDone: string): Promise<AcquiredSession>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { types } from "node:util";
|
|
2
|
+
import { isPlainRecord } from "../mods/kernel/receive.js";
|
|
3
|
+
import { readMember } from "../thrown-value.js";
|
|
4
|
+
export function readPlainRequest(request, keys, bad) {
|
|
5
|
+
if (typeof request !== "object" || request === null)
|
|
6
|
+
return bad(`the request must be a plain object, got ${request === null ? "null" : typeof request}`);
|
|
7
|
+
if (types.isProxy(request) || !isPlainRecord(request))
|
|
8
|
+
return bad("the request must be a plain data object (not a Proxy, and with no prototype that could supply a member)");
|
|
9
|
+
let descriptors;
|
|
10
|
+
try {
|
|
11
|
+
descriptors = Object.getOwnPropertyDescriptors(request);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return bad("the request could not be read");
|
|
15
|
+
}
|
|
16
|
+
const own = Reflect.ownKeys(descriptors);
|
|
17
|
+
const unknown = own.filter((k) => typeof k !== "string" || !keys.has(k));
|
|
18
|
+
if (unknown.length > 0)
|
|
19
|
+
return bad(`unknown key ${unknown.map((k) => JSON.stringify(String(k))).join(", ")}`);
|
|
20
|
+
const read = {};
|
|
21
|
+
for (const k of own) {
|
|
22
|
+
const d = descriptors[k];
|
|
23
|
+
if (!("value" in d))
|
|
24
|
+
return bad(`member ${JSON.stringify(k)} is an accessor, not a value`);
|
|
25
|
+
read[k] = d.value;
|
|
26
|
+
}
|
|
27
|
+
return read;
|
|
28
|
+
}
|
|
29
|
+
export function shownValue(v) {
|
|
30
|
+
return typeof v === "string" ? JSON.stringify(v.slice(0, 40)) : v === null || typeof v === "boolean" || typeof v === "number" ? String(v) : `a ${typeof v}`;
|
|
31
|
+
}
|
|
32
|
+
export function claimIdleSession(seats, sessionId, busy) {
|
|
33
|
+
if (seats.sessionBusy(sessionId))
|
|
34
|
+
throw busy();
|
|
35
|
+
return seats.takeSessionLock(sessionId);
|
|
36
|
+
}
|
|
37
|
+
export async function acquireExistingSession(sessions, sessionId, verb, nothingDone) {
|
|
38
|
+
try {
|
|
39
|
+
return await sessions.acquire(sessionId, { requireExisting: true });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (readMember(err, "code") !== "not_found")
|
|
43
|
+
throw err;
|
|
44
|
+
const e = new Error(`${verb}: session "${sessionId}" does not exist — ${nothingDone}`);
|
|
45
|
+
e.code = "resume.session_not_found";
|
|
46
|
+
throw e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SessionStorage } from "../session.js";
|
|
2
|
+
import type { LegWrites } from "./contracts.js";
|
|
3
|
+
export type { LegWrites };
|
|
4
|
+
export declare class SessionWriteLedger {
|
|
5
|
+
#private;
|
|
6
|
+
/** A leg's view. `report` receives the incidents (a refused write, a late settlement); a throwing sink is swallowed. */
|
|
7
|
+
openLeg(report: (err: Error, sessionId: string) => void): LegWrites;
|
|
8
|
+
/**
|
|
9
|
+
* The session's next hold starts here: resolves once `sessionId` has no write in flight, and throws `SessionError("conflict")`
|
|
10
|
+
* — the leg about to start has written nothing — when one is still unsettled after `ms`.
|
|
11
|
+
*/
|
|
12
|
+
settleBeforeHold(sessionId: string, ms: number): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Whether `sessionId` has a write in flight — a synchronous read. The session's next holder asks it before {@link settleBeforeHold}
|
|
15
|
+
* and awaits only on `true`: a session with nothing in flight takes no suspension point at its hold. The two reads sit on one tick,
|
|
16
|
+
* and nothing joins the set between the check and the hold's return — the previous holder's view is closed before its lock is
|
|
17
|
+
* released, the lock serialises holders, and a holder that is no leg (the rewind verb) issues no write through a view.
|
|
18
|
+
*/
|
|
19
|
+
busy(sessionId: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Resolves `true` once `sessionId` has no write in flight, `false` when `ms` elapse first. The timer is not unref'd: the
|
|
22
|
+
* caller awaits this in foreground control flow (the teardown-bounded discipline), and it is cleared on settle.
|
|
23
|
+
*/
|
|
24
|
+
drained(sessionId: string, ms: number): Promise<boolean>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The acquired storage as a leg writes it: the two write members go through `leg.issue` (named by the entry type, `leaf` for a
|
|
28
|
+
* leaf move), every other member is the storage's own. Per leg and non-mutating (a Proxy), like the conflict watch above it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writesThroughLeg(storage: SessionStorage, leg: LegWrites, sessionId: string): SessionStorage;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { SessionError } from "../session.js";
|
|
2
|
+
import { thrownText } from "../thrown-value.js";
|
|
3
|
+
export class SessionWriteLedger {
|
|
4
|
+
#inFlight = new Map();
|
|
5
|
+
#drainWaiters = new Map();
|
|
6
|
+
openLeg(report) {
|
|
7
|
+
let closedAt;
|
|
8
|
+
const tell = (sessionId, message) => {
|
|
9
|
+
try {
|
|
10
|
+
report(new Error(message), sessionId);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
issue: (sessionId, what, start) => {
|
|
17
|
+
if (closedAt !== undefined) {
|
|
18
|
+
const message = `session "${sessionId}": a ${what} write was issued after its leg ended and was not started`;
|
|
19
|
+
tell(sessionId, message);
|
|
20
|
+
return Promise.reject(new SessionError("conflict", message));
|
|
21
|
+
}
|
|
22
|
+
let write;
|
|
23
|
+
try {
|
|
24
|
+
write = Promise.resolve(start());
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return Promise.reject(err);
|
|
28
|
+
}
|
|
29
|
+
this.#add(sessionId, write);
|
|
30
|
+
const settledLate = (outcome) => {
|
|
31
|
+
this.#remove(sessionId, write);
|
|
32
|
+
if (closedAt !== undefined)
|
|
33
|
+
tell(sessionId, `session "${sessionId}": a ${what} write issued by a leg that has since ended settled ${Date.now() - closedAt}ms after the leg ended — ${outcome}`);
|
|
34
|
+
};
|
|
35
|
+
write.then(() => settledLate("it landed"), (err) => settledLate(`it failed: ${thrownText(err)}`));
|
|
36
|
+
return write;
|
|
37
|
+
},
|
|
38
|
+
close: () => {
|
|
39
|
+
closedAt ??= Date.now();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async settleBeforeHold(sessionId, ms) {
|
|
44
|
+
if (await this.drained(sessionId, ms))
|
|
45
|
+
return;
|
|
46
|
+
throw new SessionError("conflict", `session "${sessionId}": a write an earlier leg of this session issued has not settled after ${ms}ms — this leg wrote nothing; retry once it settles`);
|
|
47
|
+
}
|
|
48
|
+
busy(sessionId) {
|
|
49
|
+
return (this.#inFlight.get(sessionId)?.size ?? 0) > 0;
|
|
50
|
+
}
|
|
51
|
+
drained(sessionId, ms) {
|
|
52
|
+
if (!this.busy(sessionId))
|
|
53
|
+
return Promise.resolve(true);
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const waiters = this.#drainWaiters.get(sessionId) ?? new Set();
|
|
56
|
+
this.#drainWaiters.set(sessionId, waiters);
|
|
57
|
+
const done = () => {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
waiters.delete(done);
|
|
60
|
+
if (waiters.size === 0 && this.#drainWaiters.get(sessionId) === waiters)
|
|
61
|
+
this.#drainWaiters.delete(sessionId);
|
|
62
|
+
resolve(true);
|
|
63
|
+
};
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
waiters.delete(done);
|
|
66
|
+
if (waiters.size === 0 && this.#drainWaiters.get(sessionId) === waiters)
|
|
67
|
+
this.#drainWaiters.delete(sessionId);
|
|
68
|
+
resolve(false);
|
|
69
|
+
}, ms);
|
|
70
|
+
waiters.add(done);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
#add(sessionId, write) {
|
|
74
|
+
const set = this.#inFlight.get(sessionId) ?? new Set();
|
|
75
|
+
set.add(write);
|
|
76
|
+
this.#inFlight.set(sessionId, set);
|
|
77
|
+
}
|
|
78
|
+
#remove(sessionId, write) {
|
|
79
|
+
const set = this.#inFlight.get(sessionId);
|
|
80
|
+
if (set === undefined)
|
|
81
|
+
return;
|
|
82
|
+
set.delete(write);
|
|
83
|
+
if (set.size > 0)
|
|
84
|
+
return;
|
|
85
|
+
this.#inFlight.delete(sessionId);
|
|
86
|
+
for (const wake of [...(this.#drainWaiters.get(sessionId) ?? [])])
|
|
87
|
+
wake();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function writesThroughLeg(storage, leg, sessionId) {
|
|
91
|
+
return new Proxy(storage, {
|
|
92
|
+
get(target, prop, receiver) {
|
|
93
|
+
if (prop === "appendEntry")
|
|
94
|
+
return (...args) => leg.issue(sessionId, args[0].type, () => target.appendEntry(...args));
|
|
95
|
+
if (prop === "setLeafId")
|
|
96
|
+
return (...args) => leg.issue(sessionId, "leaf", () => target.setLeafId(...args));
|
|
97
|
+
const value = Reflect.get(target, prop, receiver);
|
|
98
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { reopenReasonOf, resumeStartupCauseOf } from "../resume-startup-cause.js";
|
|
1
|
+
import { thrownFailureOf } from "./terminal-account.js";
|
|
2
|
+
import { compensateUnstarted, reopenReasonOf, resumeStartupCauseOf, unstartedRowSentence } from "../resume-startup-cause.js";
|
|
3
3
|
import { resumeDecisionWasNegative } from "./decide-continuation.js";
|
|
4
4
|
import { drainForwardedFramesBeforeDone, forwardsSubagentEvents } from "./prepare-run-refs.js";
|
|
5
5
|
import { hasDestroy } from "../remote-env.js";
|
|
@@ -13,23 +13,20 @@ export function streamSettleBackstop(input) {
|
|
|
13
13
|
const onRunRejected = async (err) => {
|
|
14
14
|
let { code, message, detail } = thrownFailureOf(err, thrownText(err));
|
|
15
15
|
const reopenReason = reopenReasonOf(resumeStartupCauseOf(code));
|
|
16
|
-
const reopenable = resume !== undefined &&
|
|
16
|
+
const reopenable = resume !== undefined && live.resultValue === undefined && resume.pendingActionStarted !== true && !(resumeDecisionWasNegative(resume) && resume.decisionDelivered === true);
|
|
17
17
|
let reopenCommitted = false;
|
|
18
18
|
let reopenStateUnknown = false;
|
|
19
|
-
if (reopenable && resume
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
if (reopenable && resume !== undefined) {
|
|
20
|
+
const row = await compensateUnstarted(resume.reopenUnstarted, reopenReason);
|
|
21
|
+
if (row.kind === "reopened") {
|
|
22
22
|
reopenCommitted = true;
|
|
23
|
+
message = `${message} — ${unstartedRowSentence(row)}`;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
else {
|
|
25
26
|
parentConstraintRegistry.delete(resume.cp.token);
|
|
26
27
|
const original = asThrownError(err);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
reopenStateUnknown = !definitive;
|
|
30
|
-
err = Object.assign(new Error(definitive
|
|
31
|
-
? `${reopenMsg} (original resume failure: ${original.message})`
|
|
32
|
-
: `checkpoint reopen threw mid-flight — state UNKNOWN, confirm via the checkpoint store before retrying: ${reopenMsg} (original resume failure: ${original.message})`, { cause: original }), { code: "checkpoint.reopen_failed" });
|
|
28
|
+
reopenStateUnknown = row.kind === "unknown";
|
|
29
|
+
err = Object.assign(new Error(`${original.message} — ${unstartedRowSentence(row)}`, { cause: original }), { code: "checkpoint.reopen_failed" });
|
|
33
30
|
const replacement = thrownFailureOf(err, thrownText(err));
|
|
34
31
|
code = replacement.code;
|
|
35
32
|
message = replacement.message;
|
|
@@ -38,7 +35,7 @@ export function streamSettleBackstop(input) {
|
|
|
38
35
|
}
|
|
39
36
|
if (resume)
|
|
40
37
|
locallyClaimedTokens.delete(resume.cp.token);
|
|
41
|
-
if (resume && !reopenCommitted) {
|
|
38
|
+
if (resume && !reopenCommitted && live.resultValue === undefined) {
|
|
42
39
|
parentConstraintRegistry.delete(resume.cp.token);
|
|
43
40
|
await settleTeardownLeg(() => sessions.unpin?.(resume.cp.sessionId), "sessions.unpin (resume-failure leg)", (e) => runner.deps.onError?.(e, { phase: "config", sessionId: resume.cp.sessionId }));
|
|
44
41
|
if (!reopenStateUnknown) {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
import type { AgentTool } from "../../internal/harness.js";
|
|
37
37
|
import { valveLeaves } from "../../agents/tool-filter.js";
|
|
38
38
|
import { type ProtocolId } from "../protocol-table.js";
|
|
39
|
-
import type { ToolLegKind, ToolSelectorLayer } from "../tool-face.js";
|
|
39
|
+
import type { ToolLegKind, ToolSelector, ToolSelectorLayer } from "../tool-face.js";
|
|
40
40
|
import { type ConstraintChainEntry } from "../tool-policy.js";
|
|
41
41
|
import type { TaskSpec } from "../types.js";
|
|
42
42
|
import type { CheckpointErrorReason } from "../checkpoint-store.js";
|
|
@@ -87,6 +87,27 @@ export type PersistedToolFaceReason = Extract<CheckpointErrorReason, "not_a_list
|
|
|
87
87
|
* leg's roster and a sink; the leg's own policy-name audit is what reads them.
|
|
88
88
|
*/
|
|
89
89
|
export declare function deniedToolNamesOf(where: string, value: unknown): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Whose lists a run's own layer are, in the words every sentence about them uses: the delegation tool (and agent definition) that wrote
|
|
92
|
+
* them when the run records one (`RunInternals.ownToolLists`), else `undefined` — the run's own `TaskSpec` seats.
|
|
93
|
+
*/
|
|
94
|
+
export declare function ownListsWhereOf(ownBy: RunInternals["ownToolLists"]): string | undefined;
|
|
95
|
+
/**
|
|
96
|
+
* THE build of a leg's tool selector: `layers` are the ceiling it was spawned under, then its own lists (the last is its own), and
|
|
97
|
+
* `legs` the kinds of leg it is. Every layer is read by the one layer reader ({@link readSelectorLayer}), once, whoever judged it and
|
|
98
|
+
* whatever evidence the run carries — a live ceiling and the run's own lists were judged by the engine that wrote them, which may have
|
|
99
|
+
* read what this one cannot (a list that is not one, a member that is not a string, an entry no rule syntax reads), so the leg does not
|
|
100
|
+
* start without a layer that says something. The selector is compiled from what was read, never from the layers again: a getter that
|
|
101
|
+
* answers differently the second time is not asked a second time, and the selector's layers (handed to the leg's children as their
|
|
102
|
+
* ceiling) are the frozen product. Built before anything reads it, and before the session is acquired, so a refusal needs nothing given
|
|
103
|
+
* back. `where` is whose lists the own layer is ({@link ownListsWhereOf}). (A ceiling a resumed row restored was read by the same reader
|
|
104
|
+
* before the claim — `persistedToolNamesRefusal`.)
|
|
105
|
+
*/
|
|
106
|
+
export declare function readToolSelector(input: {
|
|
107
|
+
readonly layers: readonly unknown[];
|
|
108
|
+
readonly where: string | undefined;
|
|
109
|
+
readonly legs: readonly ToolLegKind[];
|
|
110
|
+
}): ToolSelector;
|
|
90
111
|
/**
|
|
91
112
|
* THE read-back of the tool names a checkpoint row carries, before the claim: the ceiling the resumed leg will run under when it is
|
|
92
113
|
* the row's (`ceiling`: absent when a live seat hands one) — each layer read by {@link readSelectorLayer} as a recorded layer — and
|
|
@@ -141,8 +162,9 @@ export interface RunToolListsInput {
|
|
|
141
162
|
readonly allow: readonly string[] | undefined;
|
|
142
163
|
readonly exclude: readonly string[] | undefined;
|
|
143
164
|
};
|
|
144
|
-
/** borrowed-readonly —
|
|
145
|
-
|
|
165
|
+
/** borrowed-readonly — the run's selector, as {@link readToolSelector} built it: every layer of it (the ceiling, then its own) already read, and judged here as read. */
|
|
166
|
+
selector: ToolSelector;
|
|
167
|
+
/** The kinds of leg the run is (the ones the selector was built for). */
|
|
146
168
|
legs: readonly ToolLegKind[];
|
|
147
169
|
/** True ⇔ the spawning run's prepare judged exactly these own lists where they were mounted, against a pool it could list (as the
|
|
148
170
|
* child-facing lists of the delegation tool or definition that wrote them) — the evidence digest it handed down (live, or recorded
|
|
@@ -150,14 +150,31 @@ function readSelectorLayer(layer, layerWhere, listWhere, recorded) {
|
|
|
150
150
|
if (!isToolSelectorLayer(layer)) {
|
|
151
151
|
throw refused(`${layerWhere} are a value that is ${valueTypeLabel(layer)}, not a pair of tool-name lists (an allowed and a denied list) — the leg does not start. Fix the layer.`, "config.tool_face_control", "not_a_list");
|
|
152
152
|
}
|
|
153
|
+
const lists = {};
|
|
153
154
|
for (const side of ["allowed", "denied"]) {
|
|
154
155
|
const where = listWhere(side);
|
|
155
|
-
const
|
|
156
|
+
const value = side === "allowed" ? layer.allow : layer.deny;
|
|
157
|
+
const entries = readableToolNameList(where, side, value, "config.tool_face_control");
|
|
156
158
|
const written = recorded ? entries.find((read) => !isCanonicalToolName(read.entry)) : undefined;
|
|
157
159
|
if (written !== undefined) {
|
|
158
160
|
throw refused(`${where} declares ${renderListEntry(written.entry)} in its ${LIST_WORD[side]}: a row records each tool name in its bare spelling (\`Read\`, never \`Read()\`, \`Read(*)\` or \`Read(…)\`), and this entry was recorded by an engine that read the list otherwise — it is read here neither as that engine read it nor as this one would`, "config.tool_face_control", "unsupported_pattern");
|
|
159
161
|
}
|
|
162
|
+
if (value !== undefined && value !== null)
|
|
163
|
+
lists[side] = Object.freeze(entries.map((read) => read.entry));
|
|
160
164
|
}
|
|
165
|
+
return Object.freeze({ ...(lists.allowed !== undefined ? { allow: lists.allowed } : {}), deny: lists.denied ?? Object.freeze([]) });
|
|
166
|
+
}
|
|
167
|
+
export function ownListsWhereOf(ownBy) {
|
|
168
|
+
return ownBy !== undefined ? delegationListWhere(ownBy.toolName, ownBy.definition) : undefined;
|
|
169
|
+
}
|
|
170
|
+
export function readToolSelector(input) {
|
|
171
|
+
const layers = [...input.layers];
|
|
172
|
+
const ceilings = layers.length - 1;
|
|
173
|
+
const read = layers.map((layer, i) => {
|
|
174
|
+
const inherited = i === ceilings ? undefined : inheritedListsWhere(ceilings, i);
|
|
175
|
+
return readSelectorLayer(layer, inherited ?? "this run's own tool lists", (side) => inherited ?? input.where ?? (side === "allowed" ? "TaskSpec.allowTools" : "TaskSpec.excludeTools"), false);
|
|
176
|
+
});
|
|
177
|
+
return compileToolSelector(read, input.legs);
|
|
161
178
|
}
|
|
162
179
|
function ownToolListsFlaw(value) {
|
|
163
180
|
try {
|
|
@@ -255,14 +272,9 @@ export function judgeRunToolLists(input) {
|
|
|
255
272
|
catch {
|
|
256
273
|
}
|
|
257
274
|
};
|
|
258
|
-
const ownWhere =
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const own = i === ceilings;
|
|
262
|
-
const inherited = own ? undefined : inheritedListsWhere(ceilings, i);
|
|
263
|
-
readSelectorLayer(layer, inherited ?? "this run's own tool lists", (side) => inherited ?? ownWhere ?? (side === "allowed" ? "TaskSpec.allowTools" : "TaskSpec.excludeTools"), false);
|
|
264
|
-
});
|
|
265
|
-
const denied = deniedProtocolTools(input.layers, input.legs);
|
|
275
|
+
const ownWhere = ownListsWhereOf(input.ownBy);
|
|
276
|
+
const selector = input.selector;
|
|
277
|
+
const denied = deniedProtocolTools(selector.layers, input.legs);
|
|
266
278
|
if (denied.length > 0) {
|
|
267
279
|
const names = [...new Set(denied.map((h) => `"${h.name}"`))];
|
|
268
280
|
const one = names.length === 1;
|
|
@@ -273,7 +285,6 @@ export function judgeRunToolLists(input) {
|
|
|
273
285
|
const by = [...deniedBy(true, ownWhere ?? "this run's own excludeTools"), ...deniedBy(false, "an inherited list (the spawning run's excludeTools, or one it inherits)")];
|
|
274
286
|
throw refused(`This kind of leg (${input.legs.join(", ")}) relies on ${names.join(", ")}, but ${by.join(" and ")} — the leg cannot work without ${one ? "it" : "them"}. Remove the entry from the list that names it.`, "config.tool_face_control", "protocol_denied");
|
|
275
287
|
}
|
|
276
|
-
const selector = compileToolSelector(input.layers, input.legs);
|
|
277
288
|
const pool = delegationPoolReading(spec.tools ?? [], valveLeaves(spec, selector));
|
|
278
289
|
const mounted = mountedToolNames([...tools, ...(spec.tools ?? [])]);
|
|
279
290
|
if (!input.judgedUpstream) {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
export declare const TEARDOWN_LEG_MAX_MS = 15000;
|
|
28
28
|
/** Test hook (fs-atomic TxnLockTimings precedent): shrink the budget so wedge pins need no real 15s. */
|
|
29
29
|
export declare function setTeardownLegBudgetForTests(ms: number | undefined): void;
|
|
30
|
+
export declare const teardownLegBudget: () => number;
|
|
30
31
|
/** The typed timeout incident — `leg` names the site so a leak is attributable from the report alone. */
|
|
31
32
|
export declare class TeardownLegTimeoutError extends Error {
|
|
32
33
|
readonly leg: string;
|
|
@@ -4,6 +4,7 @@ let teardownLegBudgetMs = TEARDOWN_LEG_MAX_MS;
|
|
|
4
4
|
export function setTeardownLegBudgetForTests(ms) {
|
|
5
5
|
teardownLegBudgetMs = ms ?? TEARDOWN_LEG_MAX_MS;
|
|
6
6
|
}
|
|
7
|
+
export const teardownLegBudget = () => teardownLegBudgetMs;
|
|
7
8
|
export class TeardownLegTimeoutError extends Error {
|
|
8
9
|
leg;
|
|
9
10
|
constructor(leg, budgetMs) {
|
|
@@ -968,8 +968,8 @@ export interface RunnerDeps {
|
|
|
968
968
|
* organization's delivery gate appends cross-principal letters; the mailbox contract's fourth chain).
|
|
969
969
|
* Absent = no consumer, no lane (byte for byte as before); a deployment that mounts a gate DECLARES
|
|
970
970
|
* `{ assistant: "<agent handle>" }` (the scope's assistant is the only consumer; no terminal leg touches the
|
|
971
|
-
* box)
|
|
972
|
-
*
|
|
971
|
+
* box) or `"terminals"` (each terminal drains the box as a second lane; a held letter's decision is the
|
|
972
|
+
* letter's own field in the box, so every terminal reads the same one). Letters are judged on the principal's
|
|
973
973
|
* `crossSessionInbound` layers (managed, user), held when unset. ANY other value fails the prepare of a leg
|
|
974
974
|
* that drains a session box with `config.principal_inbox_consumer_invalid` (`detail.reason: "malformed"`).
|
|
975
975
|
*/
|
|
@@ -61,6 +61,10 @@ export interface WithheldEnvDeclaration {
|
|
|
61
61
|
* a flag or placeholder word, one character repeated, or fewer than eight characters. */
|
|
62
62
|
readonly values: readonly string[];
|
|
63
63
|
}
|
|
64
|
+
/** {@link notACredentialReason}'s answer as a yes: the same guess for the second place that pays for withholding a string — every
|
|
65
|
+
* occurrence of a request's key taken out of a remote's words (`remoteWords`): a key that names no secret (`EMPTY`, a local gateway's
|
|
66
|
+
* one-letter key) is left in the words, as it is left to every child. */
|
|
67
|
+
export declare function namesASecret(value: string): boolean;
|
|
64
68
|
/**
|
|
65
69
|
* Declare this deployment's OWN credentials in this process's environment — the engine's or its host's: an approval
|
|
66
70
|
* bearer, a service token — by name and by value. From then on no child spawned on this host inherits one, whatever the
|
package/dist/core/secret-env.js
CHANGED
|
@@ -209,6 +209,9 @@ function notACredentialReason(value) {
|
|
|
209
209
|
return "one character repeated, which names no secret";
|
|
210
210
|
return undefined;
|
|
211
211
|
}
|
|
212
|
+
export function namesASecret(value) {
|
|
213
|
+
return notACredentialReason(value) === undefined;
|
|
214
|
+
}
|
|
212
215
|
function credentialValueProblem(v) {
|
|
213
216
|
if (typeof v !== "string")
|
|
214
217
|
return kindOf(v);
|
|
@@ -76,7 +76,12 @@ export type SessionRuleEntryField = {
|
|
|
76
76
|
* name starting with `*` as no rule; in `toolAllow` it grants nothing by itself and the entries beside it keep their reading,
|
|
77
77
|
* where grammars 1–4 voided a list holding it. An engine of grammar 4 given a row written in grammar 5 refuses it rather than
|
|
78
78
|
* read a `*` deny as nothing. In the same revision a host pattern with an empty label (`WebFetch(domain:.evil.test)`) or a `*`
|
|
79
|
-
* the URL parser folds (`*.`) is no rule, where grammar 4 read the exact host it spelled; an earlier row's such entry reads as no rule
|
|
79
|
+
* the URL parser folds (`*.`) is no rule, where grammar 4 read the exact host it spelled; an earlier row's such entry reads as no rule;
|
|
80
|
+
* - 6, a persisted rule with no content (`SendUserFile`, `Bash`, `Bash(*)`) is the WHOLE-TOOL rule (`match: "tool"`), where grammar 5's
|
|
81
|
+
* persisted grammar read every bare name as no rule. A session's entries mean what they meant (the entry reader already read a bare
|
|
82
|
+
* name as the whole tool), so no earlier entry reads differently; the stamp moves for the persisted store's buckets and the sync
|
|
83
|
+
* wire's states, so an engine of grammar 5 given a bucket written in grammar 6 refuses it whole rather than drop a whole-tool deny
|
|
84
|
+
* as damage. No persisted row of an earlier stamp can hold a bare name (every entrance refused one), so none is read anew.
|
|
80
85
|
* A row an earlier grammar wrote keeps the meaning that grammar gave its entries, and an entry that grammar could not read takes
|
|
81
86
|
* this one's ({@link earlierGrammarReads}, over the revisions above as one table); an entry this grammar reads as no rule reads as
|
|
82
87
|
* `earlier_grammar` ({@link compileSessionRuleBucket}).
|
|
@@ -89,7 +94,7 @@ export type SessionRuleEntryField = {
|
|
|
89
94
|
* what an existing persisted rule text means owes that store a reading of its earlier stamps, as {@link earlierGrammarReads}
|
|
90
95
|
* is for entries. Every stamp is judged by {@link readRuleGrammarStamp}.
|
|
91
96
|
*/
|
|
92
|
-
export declare const SESSION_RULES_GRAMMAR =
|
|
97
|
+
export declare const SESSION_RULES_GRAMMAR = 6;
|
|
93
98
|
/** {@link SessionPermissionRules} plus the monotonic `rev` the store stamps (the OCC key). */
|
|
94
99
|
export interface StoredSessionRules extends SessionPermissionRules {
|
|
95
100
|
rev: number;
|
|
@@ -12,7 +12,7 @@ const BUCKETS = Object.keys(SESSION_RULE_BUCKETS);
|
|
|
12
12
|
const ENTRY_FIELDS = BUCKETS.filter((f) => SESSION_RULE_BUCKETS[f] !== "dirs");
|
|
13
13
|
const STORE_STAMPS = new Set(["rev", "gen", "grammar"]);
|
|
14
14
|
const OPERATOR_PART = "operatorRules";
|
|
15
|
-
export const SESSION_RULES_GRAMMAR =
|
|
15
|
+
export const SESSION_RULES_GRAMMAR = 6;
|
|
16
16
|
export class SessionPolicyError extends Error {
|
|
17
17
|
code;
|
|
18
18
|
detail;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ShellWrapperRow } from "./shell-wrapper-table.js";
|
|
2
2
|
/**
|
|
3
|
-
* The PROJECTION {@link import("./shell-wrapper-table.js").SHELL_WRAPPER_TABLE} is: the
|
|
3
|
+
* The PROJECTION {@link import("./shell-wrapper-table.js").SHELL_WRAPPER_TABLE} is: the tightening reader's peel view of the program read model's rows (`program-read-model.ts`
|
|
4
4
|
* — one table of program facts, read through the binary gate). A launcher row's options by arity (a value word, a
|
|
5
5
|
* switch or an attached-only value, a command string), its required operand (a duration, or the spelling it must have),
|
|
6
6
|
* its bare-dash switch and its `NAME=value` operands; a row that runs its operands, or a code body, as shell input
|