@sema-agent/server 7.133.0 → 7.134.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/MIGRATION.md +87 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +129 -11
- package/deploy/sema-up/README.md +16 -0
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +14 -0
- package/deploy/sema-up/chart/templates/server.yaml +14 -0
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval-card.js +1 -2
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +6 -0
- package/dist/approval.js +31 -14
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -2
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +9 -0
- package/dist/boot/stage-06-runners.d.ts +4 -3
- package/dist/boot/stage-06-runners.js +15 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-08-reapers.d.ts +2 -1
- package/dist/boot/stage-09-leader.d.ts +2 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -1
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +8 -12
- package/dist/boot/web-admin.d.ts +6 -0
- package/dist/boot/web-admin.js +11 -2
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +2 -0
- package/dist/caller-text-echo.js +2 -1
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/mcp-probe.js +2 -2
- package/dist/capability-facts.d.ts +2 -0
- package/dist/capability-facts.js +4 -4
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -0
- package/dist/field-conflict.d.ts +2 -1
- package/dist/field-conflict.js +1 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +4 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/admission.js +8 -0
- package/dist/http/approval-sweeps.js +3 -1
- package/dist/http/checkpoint-ctx.d.ts +157 -2
- package/dist/http/checkpoint-ctx.js +158 -2
- package/dist/http/dispatch.js +3 -3
- package/dist/http/durable-run-leg.js +3 -2
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/principal-gate.d.ts +32 -6
- package/dist/http/principal-gate.js +27 -4
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +0 -9
- package/dist/http/request-guards.js +2 -16
- package/dist/http/resume-ctx-read.d.ts +48 -1
- package/dist/http/resume-ctx-read.js +26 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +37 -22
- package/dist/http/route-ctx.d.ts +0 -9
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +43 -9
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/fleet.js +4 -4
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/routes/runs.js +30 -9
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +38 -20
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +4 -4
- package/dist/http/routes/tasks.js +21 -16
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/server.d.ts +6 -0
- package/dist/http/server.js +5 -2
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +19 -3
- package/dist/http/wire-types.d.ts +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/main.js +1 -1
- package/dist/memory-origin-fence.js +2 -2
- package/dist/model-compat.d.ts +1 -1
- package/dist/observability/prompt-manifest.d.ts +19 -8
- package/dist/observability/prompt-manifest.js +9 -8
- package/dist/observability/tool-trace.js +8 -9
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/orchestration/workflow-park-decision.js +1 -0
- package/dist/parked-decide.d.ts +4 -5
- package/dist/parked-decide.js +3 -1
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +1 -1
- package/dist/plugins/caching-session-store.js +4 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +22 -1
- package/dist/plugins/checkpoint-store-sql.js +45 -1
- package/dist/plugins/local-checkpoint-store.d.ts +13 -2
- package/dist/plugins/local-checkpoint-store.js +26 -0
- package/dist/plugins/local-session-store.d.ts +12 -9
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +12 -6
- package/dist/plugins/local-session-window.js +1 -17
- package/dist/plugins/one-time-migrations.d.ts +11 -0
- package/dist/plugins/one-time-migrations.js +12 -0
- package/dist/plugins/permission-rule-store-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +39 -17
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/tidb-session-store.d.ts +1 -1
- package/dist/plugins/tidb-session-store.js +39 -17
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -3
- package/dist/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +9 -7
- package/dist/session-error-wire.d.ts +12 -5
- package/dist/session-error-wire.js +4 -1
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- package/dist/task-a2a.js +4 -3
- package/dist/task-mcp.d.ts +2 -2
- package/dist/task-mcp.js +20 -8
- package/dist/task-settings.d.ts +5 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +151 -52
- package/dist/tool-approval.js +113 -20
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -3
- package/dist/trace/exit-seat.d.ts +13 -1
- package/dist/trace/exit-seat.js +55 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -4
- package/dist/trace/redact.d.ts +6 -3
- package/dist/trace/redact.js +191 -23
- package/dist/trace/self-minted-wire.d.ts +4 -0
- package/dist/trace/self-minted-wire.js +1 -0
- package/dist/trace/wire-projection-faces.d.ts +6 -0
- package/dist/trace/wire-projection-faces.js +6 -0
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +2 -3
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -104
package/dist/http/resume-legs.js
CHANGED
|
@@ -17,7 +17,8 @@ import { recordResumeResourceWindow } from "../resource-window.js";
|
|
|
17
17
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
18
18
|
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
|
|
19
19
|
import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
|
|
20
|
-
import {
|
|
20
|
+
import { currentApprovalGateToken } from "../approval-gate-currency.js";
|
|
21
|
+
import { createApprovalCardEmitter, declaredApprovalFrameFamilies, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerDurablyRecordable } from "../tool-approval.js";
|
|
21
22
|
import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
|
|
22
23
|
import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
|
|
23
24
|
import { serviceVersion } from "../service-version.js";
|
|
@@ -47,9 +48,9 @@ import { commitPlacement, devicePlacementErrorBody } from "./device-placement.js
|
|
|
47
48
|
import { commitSessionInheritEnv } from "./session-inherit-env.js";
|
|
48
49
|
import { ASK_USER_QUESTION_TOOL_NAME } from "../approval-content-kind.js";
|
|
49
50
|
import { deploymentListAsks } from "../approval.js";
|
|
50
|
-
import { readResumeCtx, resumeCtxCorruptAnswer } from "./resume-ctx-read.js";
|
|
51
|
-
import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
|
|
52
|
-
import { refusalBody } from "./refusal-body.js";
|
|
51
|
+
import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from "./resume-ctx-read.js";
|
|
52
|
+
import { buildCheckpointVersionKeys, resumeBlockedByPolicyAnswer, resumeRefusalWithCard, sealCheckpointCtx, storedBodyReplayAnswer, storedBodyReplayLogFields, withPermissionModeRewritten } from "./checkpoint-ctx.js";
|
|
53
|
+
import { refusalBody, resumeRefusalFacts } from "./refusal-body.js";
|
|
53
54
|
import { storedBodyReplayRefusal } from "./stored-body-replay.js";
|
|
54
55
|
import { httpErrorRefusal } from "./send.js";
|
|
55
56
|
import { createRunAcceptance } from "../run-acceptance.js";
|
|
@@ -125,6 +126,9 @@ const CHECKPOINT_ERROR_DISPOSITION = {
|
|
|
125
126
|
"steering.invalid_content": "terminal",
|
|
126
127
|
"steering.queue_full": "terminal",
|
|
127
128
|
};
|
|
129
|
+
function windowZeroSeatOf(toolApproval) {
|
|
130
|
+
return (identity) => toolApproval.boundUnattendedAsk(identity, { arm: "window_zero" });
|
|
131
|
+
}
|
|
128
132
|
export function resumeRefusalDisposition(e) {
|
|
129
133
|
const word = Object.hasOwn(CHECKPOINT_ERROR_DISPOSITION, e.code) ? CHECKPOINT_ERROR_DISPOSITION[e.code] : undefined;
|
|
130
134
|
switch (word) {
|
|
@@ -186,9 +190,7 @@ export function checkpointRefusalAnswer(e, at) {
|
|
|
186
190
|
errorCode: e.code === "checkpoint.invalid_outcome" ? (refusalCause !== undefined ? WIRE_CODE_OF_DECISION_REFUSAL[refusalCause] : UNCLASSIFIED_INVALID_OUTCOME_CODE) : e.code,
|
|
187
191
|
facts: {
|
|
188
192
|
...(at.taskId !== undefined ? { taskId: at.taskId } : {}),
|
|
189
|
-
...(detail
|
|
190
|
-
...(detail?.field ? { field: detail.field } : {}),
|
|
191
|
-
...(detail?.reason !== undefined ? { reason: detail.reason } : {}),
|
|
193
|
+
...resumeRefusalFacts(detail),
|
|
192
194
|
...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof detail?.retryAfterMs === "number" && Number.isFinite(detail.retryAfterMs) && detail.retryAfterMs > 0
|
|
193
195
|
? { retryAfterSec: Math.max(1, Math.ceil(detail.retryAfterMs / 1000)) }
|
|
194
196
|
: {}),
|
|
@@ -198,11 +200,12 @@ export function checkpointRefusalAnswer(e, at) {
|
|
|
198
200
|
}, at.retriable);
|
|
199
201
|
}
|
|
200
202
|
export function createResumeLegs(ctx) {
|
|
201
|
-
const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode,
|
|
203
|
+
const { deps, runnerFor, idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters, readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk, prepareSpec, finalizeTaskResult, ownerCostQuotaDenied, ownerFleetLeaseDenied, usageWindowDenialFor } = ctx;
|
|
204
|
+
const ctxOpening = (sessionId) => ({ sessionId, opener: deps.checkpointCtxSealing });
|
|
202
205
|
const resumeFleetScope = (req, auth) => req ? (gatedPrincipal(req, deps.config) ?? "default") : (auth.principal ?? "default");
|
|
203
206
|
async function resumeCheckpoint(sessionId, decision, reason, hostDecision, req, answer, binding, onResumeCommitted, acceptEarly, decider) {
|
|
204
207
|
const cs = deps.checkpointStore;
|
|
205
|
-
const token =
|
|
208
|
+
const token = await currentApprovalGateToken(cs, sessionId);
|
|
206
209
|
if (binding?.checkpointToken && binding.checkpointToken !== token) {
|
|
207
210
|
let seenUnreadable = false;
|
|
208
211
|
const seen = await cs.get(binding.checkpointToken).catch((e) => {
|
|
@@ -386,7 +389,7 @@ export function createResumeLegs(ctx) {
|
|
|
386
389
|
const parkedReq = {
|
|
387
390
|
token, scope: cp.scope, sessionId: cp.sessionId, pendingAction: cp.pendingAction,
|
|
388
391
|
decision, hostDecision, ...(reason !== undefined ? { reason } : {}),
|
|
389
|
-
...(binding !== undefined ? { binding:
|
|
392
|
+
...(binding !== undefined ? { binding: (({ checkpointToken: _staleGuardOnly, ...rest }) => rest)(binding) } : {}),
|
|
390
393
|
...(answer !== undefined ? { answer } : {}),
|
|
391
394
|
...(boundGrant ? { grantRemember: (rootSessionId) => boundGrant(rootSessionId) } : {}),
|
|
392
395
|
};
|
|
@@ -399,7 +402,7 @@ export function createResumeLegs(ctx) {
|
|
|
399
402
|
sessionFaceOf: deps.sessionStorage?.sessionFaceOf ? deps.sessionStorage.sessionFaceOf.bind(deps.sessionStorage) : sessionFaceUnavailable,
|
|
400
403
|
...(deps.parkedKnownAgentTypes ? { knownAgentTypes: deps.parkedKnownAgentTypes } : {}),
|
|
401
404
|
...(deps.parkedReviveInheritedGate ? { rebuildInheritedGate: deps.parkedReviveInheritedGate } : {}),
|
|
402
|
-
...(deps.toolApproval ? { approverSeatFor: deps.toolApproval
|
|
405
|
+
...(deps.toolApproval ? { approverSeatFor: windowZeroSeatOf(deps.toolApproval) } : {}),
|
|
403
406
|
...(deps.logger ? { warn: parkWarn } : {}),
|
|
404
407
|
}, parkedReq, { handle: origin.handle, row: origin.row }));
|
|
405
408
|
void ctxPromise.catch(() => { });
|
|
@@ -463,9 +466,11 @@ export function createResumeLegs(ctx) {
|
|
|
463
466
|
return out;
|
|
464
467
|
}
|
|
465
468
|
}
|
|
466
|
-
const ctxRead0 = await readResumeCtx(ctxPromise);
|
|
469
|
+
const ctxRead0 = await readResumeCtx(ctxPromise, ctxOpening(sessionId));
|
|
467
470
|
if (ctxRead0.kind === "corrupt")
|
|
468
471
|
return resumeCtxCorruptAnswer(ctxRead0.where);
|
|
472
|
+
if (ctxRead0.kind === "unsealable")
|
|
473
|
+
return resumeCtxUnsealableAnswer(ctxRead0);
|
|
469
474
|
const ctx = ctxRead0.kind === "ok" ? ctxRead0.ctx : null;
|
|
470
475
|
if (!ctx)
|
|
471
476
|
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
@@ -492,6 +497,7 @@ export function createResumeLegs(ctx) {
|
|
|
492
497
|
decision: decision === "approve" ? "allow" : "deny",
|
|
493
498
|
...(decision === "approve" && binding?.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
|
|
494
499
|
...(reason ? { reason } : {}),
|
|
500
|
+
...(binding?.feedback !== undefined ? { feedback: binding.feedback } : {}),
|
|
495
501
|
...(answer !== undefined ? { answer } : {}),
|
|
496
502
|
hostDecision,
|
|
497
503
|
};
|
|
@@ -554,7 +560,7 @@ export function createResumeLegs(ctx) {
|
|
|
554
560
|
const resumeLegStartedAt = Date.now();
|
|
555
561
|
const { runner: legRunner, token, sessionId, principal, fleetScope, taskConfig, resumeObjective, outcome, verifyRounds, resumeCtx } = args;
|
|
556
562
|
const reparkStatus = outcome.gate === "plan_review" ? "needs_review" : "suspended";
|
|
557
|
-
const commitResumeCtx = () => deps.checkpointStore.putCtx(resumeCtx.sessionId, { ...resumeCtx.ctx, writerVersion: serviceVersion() }).catch((e) => {
|
|
563
|
+
const commitResumeCtx = () => Promise.resolve().then(() => deps.checkpointStore.putCtx(resumeCtx.sessionId, sealCheckpointCtx({ ...resumeCtx.ctx, writerVersion: serviceVersion() }, deps.checkpointCtxSealing, resumeCtx.sessionId))).catch((e) => {
|
|
558
564
|
deps.metrics?.inc("resume_ctx_not_persisted_total");
|
|
559
565
|
deps.logger?.warn?.("resume_ctx_not_persisted", { sessionId: resumeCtx.sessionId, gate: outcome.gate, error: errorText(e), note: "this continuation runs with the body it was given; the durable rebuild input still holds the PREVIOUS body, so the NEXT continuation of this session rebuilds from that one (never wider than this leg's)" });
|
|
560
566
|
});
|
|
@@ -563,7 +569,7 @@ export function createResumeLegs(ctx) {
|
|
|
563
569
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
564
570
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
565
571
|
backend: deps.backend,
|
|
566
|
-
|
|
572
|
+
checkpointStore: deps.checkpointStore,
|
|
567
573
|
}).active;
|
|
568
574
|
const approvalLeg = resolveApprovalLeg({
|
|
569
575
|
streamApprovalOn,
|
|
@@ -571,6 +577,7 @@ export function createResumeLegs(ctx) {
|
|
|
571
577
|
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
572
578
|
...(typeof taskConfig.limits?.maxWalltimeMs === "number" ? { legWalltimeMs: taskConfig.limits.maxWalltimeMs } : {}),
|
|
573
579
|
nowMonotonicMs: performance.now(),
|
|
580
|
+
declaredFamilies: declaredApprovalFrameFamilies(resumeCtx.ctx.body.settings),
|
|
574
581
|
});
|
|
575
582
|
const legDeadlineMonotonic = approvalLeg.legDeadlineMonotonic;
|
|
576
583
|
const ownerQuotaDenial = args.callerInitiated === true ? ownerCostQuotaDenied(principal) : undefined;
|
|
@@ -609,8 +616,8 @@ export function createResumeLegs(ctx) {
|
|
|
609
616
|
const resumeTaskConfig = taskId ? { ...taskConfig, taskId } : taskConfig;
|
|
610
617
|
if (deps.toolApproval && taskId) {
|
|
611
618
|
const toolApproval = deps.toolApproval;
|
|
612
|
-
resumeTaskConfig.onAsk = approvalLeg.
|
|
613
|
-
? toolApproval.boundUnattendedAsk({ owner: principal ?? null, taskId, sessionId })
|
|
619
|
+
resumeTaskConfig.onAsk = approvalLeg.unattended
|
|
620
|
+
? toolApproval.boundUnattendedAsk({ owner: principal ?? null, taskId, sessionId }, approvalLeg.unattended)
|
|
614
621
|
: toolApproval.boundAsk({
|
|
615
622
|
owner: principal ?? null,
|
|
616
623
|
taskId,
|
|
@@ -1097,7 +1104,7 @@ export function createResumeLegs(ctx) {
|
|
|
1097
1104
|
const { type, ...rest } = frame;
|
|
1098
1105
|
return append(type, rest);
|
|
1099
1106
|
};
|
|
1100
|
-
const withApproval = () => deps.toolApproval && taskId && approvalLeg.active &&
|
|
1107
|
+
const withApproval = () => deps.toolApproval && taskId && approvalLeg.active && approvalLeg.unattended === undefined
|
|
1101
1108
|
? deps.toolApproval.runWithContext({
|
|
1102
1109
|
taskId,
|
|
1103
1110
|
owner: principal ?? null,
|
|
@@ -1274,9 +1281,11 @@ export function createResumeLegs(ctx) {
|
|
|
1274
1281
|
if (gateKind !== RESUMABLE_GATE_KIND) {
|
|
1275
1282
|
return { status: 409, body: refusalBody({ errorCode: "gate_not_resumable", message: `task is suspended on a '${gateKind ?? "unknown"}' gate, not a resumable resource/preempt suspension — an approval gate must be decided via POST /v1/approvals/:id/decide` }) };
|
|
1276
1283
|
}
|
|
1277
|
-
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1284
|
+
const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
|
|
1278
1285
|
if (ctxRead.kind === "corrupt")
|
|
1279
1286
|
return resumeCtxCorruptAnswer(ctxRead.where);
|
|
1287
|
+
if (ctxRead.kind === "unsealable")
|
|
1288
|
+
return resumeCtxUnsealableAnswer(ctxRead);
|
|
1280
1289
|
if (ctxRead.kind === "absent")
|
|
1281
1290
|
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
1282
1291
|
const ctx = ctxRead.ctx;
|
|
@@ -1294,9 +1303,11 @@ export function createResumeLegs(ctx) {
|
|
|
1294
1303
|
if (!deps.runStore) {
|
|
1295
1304
|
return { status: 501, body: refusalBody({ errorCode: "capability.run_store_required", message: "delivering this decision requires a durable run store (DB_BACKEND=mysql|pg|local)" }) };
|
|
1296
1305
|
}
|
|
1297
|
-
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1306
|
+
const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
|
|
1298
1307
|
if (ctxRead.kind === "corrupt")
|
|
1299
1308
|
return resumeCtxCorruptAnswer(ctxRead.where);
|
|
1309
|
+
if (ctxRead.kind === "unsealable")
|
|
1310
|
+
return resumeCtxUnsealableAnswer(ctxRead);
|
|
1300
1311
|
const ctx = ctxRead.kind === "ok" ? ctxRead.ctx : null;
|
|
1301
1312
|
if (!ctx) {
|
|
1302
1313
|
return {
|
|
@@ -1385,9 +1396,11 @@ export function createResumeLegs(ctx) {
|
|
|
1385
1396
|
throw e;
|
|
1386
1397
|
}
|
|
1387
1398
|
}
|
|
1388
|
-
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1399
|
+
const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
|
|
1389
1400
|
if (ctxRead.kind === "corrupt")
|
|
1390
1401
|
return resumeCtxCorruptAnswer(ctxRead.where);
|
|
1402
|
+
if (ctxRead.kind === "unsealable")
|
|
1403
|
+
return resumeCtxUnsealableAnswer(ctxRead);
|
|
1391
1404
|
if (ctxRead.kind === "absent")
|
|
1392
1405
|
return { status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) };
|
|
1393
1406
|
const ctx = ctxRead.ctx;
|
|
@@ -1474,9 +1487,11 @@ export function createResumeLegs(ctx) {
|
|
|
1474
1487
|
}),
|
|
1475
1488
|
});
|
|
1476
1489
|
}
|
|
1477
|
-
const ctxRead = await readResumeCtx(cs.getCtx(sessionId));
|
|
1490
|
+
const ctxRead = await readResumeCtx(cs.getCtx(sessionId), ctxOpening(sessionId));
|
|
1478
1491
|
if (ctxRead.kind === "corrupt")
|
|
1479
1492
|
return refuse(resumeCtxCorruptAnswer(ctxRead.where));
|
|
1493
|
+
if (ctxRead.kind === "unsealable")
|
|
1494
|
+
return refuse(resumeCtxUnsealableAnswer(ctxRead));
|
|
1480
1495
|
if (ctxRead.kind === "absent")
|
|
1481
1496
|
return refuse({ status: 409, body: refusalBody({ errorCode: "conflict.resume_context_unavailable", message: "resume context missing — cannot rebuild task config" }) });
|
|
1482
1497
|
const replay = storedBodyReplayRefusal(ctxRead.ctx, deps.config);
|
|
@@ -1514,7 +1529,7 @@ export function createResumeLegs(ctx) {
|
|
|
1514
1529
|
const routeCtxBase = {
|
|
1515
1530
|
deps,
|
|
1516
1531
|
registry: { idemCache, inflightRuns, preemptableRuns, steerableRuns, wakeParkMints, counters },
|
|
1517
|
-
helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode,
|
|
1532
|
+
helpers: { readJson, readRawBody, rateLimited, quotaExceeded, ownerQuotaDenied, leaseDenied, ownerLeaseDenied, usageWindowDenied, safeDecode, runOwnerOk, sessionOwnerOk, runSessionAcceptOk },
|
|
1518
1533
|
local: { images: createImagesLocal(deps), sessions: createSessionsLocal(), sessionSync: createSessionSyncLocal(), capabilities: createCapabilitiesLocal() },
|
|
1519
1534
|
legs: { prepareSpec, finalizeTaskResult, resumeCheckpoint, driveResumeIntoRunLog, resumePreempted, resumeWake, preparePlanReview, drivePlanReview },
|
|
1520
1535
|
};
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -73,21 +73,12 @@ export interface RouteHelpers {
|
|
|
73
73
|
/** [ref]-T1:治理窗 pre-admission(异步 202 车道的唯一 429 出路;key 公式与 core 单源)。 */
|
|
74
74
|
usageWindowDenied(req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
75
75
|
safeDecode(seg: string): string | null;
|
|
76
|
-
isFleetWide(req: IncomingMessage): boolean;
|
|
77
76
|
/** run 面的属主门(谓词 `runOwnerReadable`;非属主 ⇒ 404 `not_found.run` + 长文,与 run 读口的 unknown 臂同码同串,[ref])。 */
|
|
78
77
|
runOwnerOk(req: IncomingMessage, res: ServerResponse, owner: string | null): boolean;
|
|
79
78
|
/** 会话面的属主门(谓词 `sessionOwnerReadable`,S-1452:没有身份边界的部署不钉人,与会话列表同读;非属主 ⇒ 404 `not_found.session` +
|
|
80
79
|
* {@link SESSION_NOT_FOUND_MESSAGE},与会话读口的 unknown 臂同码同串 —— G52 [ref] / S-989)。每一个按会话寻址的属主判都走它。
|
|
81
80
|
* `cell` = 调用点经 `security.ts` 的 `sessionOwnerCellOf` 读出的那一格(S-1770:无主的委派子代 placed 行只对 fleet 调用方放行,判据 `ownerlessRowOpen`)。 */
|
|
82
81
|
sessionOwnerOk(req: IncomingMessage, res: ServerResponse, cell: SessionOwnerCell): boolean;
|
|
83
|
-
sessionOwnerScope(req: IncomingMessage): {
|
|
84
|
-
fleetWide: boolean;
|
|
85
|
-
gateOwner: string | null;
|
|
86
|
-
};
|
|
87
|
-
sessionOwnerScopeForWrite(req: IncomingMessage): {
|
|
88
|
-
fleetWide: boolean;
|
|
89
|
-
gateOwner: string | null;
|
|
90
|
-
};
|
|
91
82
|
runSessionAcceptOk(req: IncomingMessage, res: ServerResponse, run: {
|
|
92
83
|
sessionId: string | null;
|
|
93
84
|
}, face: string, notFoundError?: string): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { sendJson, sendError } from "../send.js";
|
|
2
|
-
import { sendUnauthorized } from "../principal-gate.js";
|
|
2
|
+
import { fleetViewOf, sendUnauthorized } from "../principal-gate.js";
|
|
3
3
|
import { assertNever } from "../route-ctx.js";
|
|
4
4
|
import * as facts from "../../capability-facts.js";
|
|
5
5
|
export const ROSTER_SCAN_LIMIT = 500;
|
|
@@ -56,10 +56,9 @@ function decodeCursor(raw) {
|
|
|
56
56
|
}
|
|
57
57
|
export async function handleAgentRoster(req, res, match, ctx) {
|
|
58
58
|
const { deps } = ctx;
|
|
59
|
-
const { sessionOwnerScope } = ctx.helpers;
|
|
60
59
|
switch (match.decl.id) {
|
|
61
60
|
case "agents-roster": {
|
|
62
|
-
const { fleetWide, gateOwner } =
|
|
61
|
+
const { fleetWide, gateOwner } = fleetViewOf(req, deps, "read");
|
|
63
62
|
if (!fleetWide && gateOwner === null) {
|
|
64
63
|
sendUnauthorized(res, "identity");
|
|
65
64
|
return;
|
|
@@ -14,7 +14,7 @@ import { redactRiskDescriptor } from "../../observability/run-terminal.js";
|
|
|
14
14
|
import { buildRunName } from "../../fleet/fleet-bus.js";
|
|
15
15
|
import { sleep } from "../sse-log.js";
|
|
16
16
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
17
|
-
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
17
|
+
import { sendJson, sendError, sseHeaders, logRequestError, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
18
18
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal, sessionCallerOf } from "../principal-gate.js";
|
|
19
19
|
import { readOwnedRun, readOwnedSession } from "../owner-access.js";
|
|
20
20
|
import { governanceOriginOf } from "../../runtime-governance.js";
|
|
@@ -23,17 +23,17 @@ import { callGrantBindingOf, questionGrantBindingOf } from "../../standing-answe
|
|
|
23
23
|
import { sendResumeOutcome } from "../route-ctx.js";
|
|
24
24
|
import { assertNever } from "../route-ctx.js";
|
|
25
25
|
import * as facts from "../../capability-facts.js";
|
|
26
|
-
import { readResumeCtx } from "../resume-ctx-read.js";
|
|
26
|
+
import { KEEP_SEALED, readResumeCtx } from "../resume-ctx-read.js";
|
|
27
27
|
import { StoreCorruptError } from "../../plugins/sql-json-column.js";
|
|
28
28
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
29
29
|
import { durableRowIdentity } from "../../checkpoint-identity.js";
|
|
30
30
|
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "../../request-key-closure.js";
|
|
31
31
|
import { fieldConflictFacts } from "../../field-conflict.js";
|
|
32
32
|
const DECIDE_BODY_DECLARED_KEYS = [
|
|
33
|
-
"answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decision", "reason", "remember", "updatedInput",
|
|
33
|
+
"answer", "boundCallId", "boundInputHash", "checkpointToken", "decidedBy", "decision", "feedback", "reason", "remember", "updatedInput",
|
|
34
34
|
];
|
|
35
35
|
const DECIDE_PROOF_COVERED_KEYS = ["answer", "boundCallId", "boundInputHash", "decision", "reason"];
|
|
36
|
-
const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "remember", "updatedInput"];
|
|
36
|
+
const DECIDE_NOT_IN_PROOF_KEYS = ["checkpointToken", "decidedBy", "feedback", "remember", "updatedInput"];
|
|
37
37
|
const PLAN_REVIEW_BODY_DECLARED_KEYS = [
|
|
38
38
|
"boundCallId", "decidedBy", "decision", "editedPlan", "permissionModeAfter", "reason",
|
|
39
39
|
];
|
|
@@ -285,8 +285,19 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
285
285
|
const scope = operator
|
|
286
286
|
? (new URLSearchParams(match.search).get("owner") ?? undefined)
|
|
287
287
|
: ownScope;
|
|
288
|
+
let durableRows;
|
|
289
|
+
try {
|
|
290
|
+
durableRows = await cs.listPending(scope);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
if (!isApprovalsReadCorrupt(err))
|
|
294
|
+
throw err;
|
|
295
|
+
logRequestError(deps.logger, req, err);
|
|
296
|
+
sendError(res, 500, "approvals.read_corrupt", "a stored approval row cannot be read - retrying this request will fail the same way until an operator repairs or removes the row");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
288
299
|
sendJson(res, 200, {
|
|
289
|
-
pending:
|
|
300
|
+
pending: durableRows.map((r) => projectPendingForWire(r, deps.config)),
|
|
290
301
|
serverNowMs: Date.now(),
|
|
291
302
|
...(deps.toolApproval ? { livePending: deps.toolApproval.listLivePending(scope) } : {}),
|
|
292
303
|
...(deps.approvalAskAudit ? { crashConverged: deps.approvalAskAudit.listCrashConverged(scope) } : {}),
|
|
@@ -342,10 +353,12 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
342
353
|
const scope = operator && ownerParam ? ownerParam : ownScope;
|
|
343
354
|
const summaries = await cs.listByScope(scope);
|
|
344
355
|
const inbox = (await Promise.all(summaries.map(async (s) => {
|
|
345
|
-
const ctxRead = await readResumeCtx(cs.getCtx(s.sessionId));
|
|
356
|
+
const ctxRead = await readResumeCtx(cs.getCtx(s.sessionId), KEEP_SEALED);
|
|
346
357
|
if (ctxRead.kind === "corrupt")
|
|
347
358
|
recordFailOpen("server.assistant-inbox.objective-ctx-corrupt", ctxRead.where);
|
|
348
|
-
const
|
|
359
|
+
const storedBody = ctxRead.kind === "ok" ? ctxRead.ctx.body : undefined;
|
|
360
|
+
const storedObjective = typeof storedBody === "object" && storedBody !== null ? storedBody.objective : undefined;
|
|
361
|
+
const objective = typeof storedObjective === "string" ? storedObjective : null;
|
|
349
362
|
const input = s.toolInput != null ? redactedPreview(s.toolInput) : null;
|
|
350
363
|
return {
|
|
351
364
|
sessionId: s.sessionId,
|
|
@@ -655,7 +668,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
655
668
|
const body = (await readJson(req));
|
|
656
669
|
const decision = body?.decision === "approve" ? "approve" : body?.decision === "deny" ? "deny" : undefined;
|
|
657
670
|
if (!decision) {
|
|
658
|
-
sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember?, decidedBy? }", retiredDecisionKeyFacts(body)?.extra);
|
|
671
|
+
sendError(res, 400, "request.body_shape", "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember?, decidedBy?, feedback? }", retiredDecisionKeyFacts(body)?.extra);
|
|
659
672
|
return;
|
|
660
673
|
}
|
|
661
674
|
{
|
|
@@ -719,6 +732,10 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
719
732
|
return;
|
|
720
733
|
}
|
|
721
734
|
}
|
|
735
|
+
if (body.feedback !== undefined && typeof body.feedback !== "string") {
|
|
736
|
+
sendError(res, 400, "request.field_invalid", "feedback must be a string (the words the approving person wrote on the card; omit the key when there are none)");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
722
739
|
if (deps.config.directDoorActive && body.updatedInput !== undefined) {
|
|
723
740
|
sendError(res, 400, "updated_input_not_in_proof", "updatedInput is not supported on a direct-door worker (not covered by the decision proof)");
|
|
724
741
|
return;
|
|
@@ -727,6 +744,10 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
727
744
|
sendError(res, 400, "checkpoint_token_not_in_proof", "checkpointToken is not supported on a direct-door worker (not covered by the decision proof); bind the decision with boundCallId and boundInputHash instead");
|
|
728
745
|
return;
|
|
729
746
|
}
|
|
747
|
+
if (deps.config.directDoorActive && body.feedback !== undefined) {
|
|
748
|
+
sendError(res, 400, "feedback_not_in_proof", "feedback is not supported on a direct-door worker (not covered by the decision proof)");
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
730
751
|
const decidedByReading = readDecidedBy(body.decidedBy, deps.config.directDoorActive);
|
|
731
752
|
switch (decidedByReading.kind) {
|
|
732
753
|
case "not_in_proof":
|
|
@@ -743,11 +764,20 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
743
764
|
}
|
|
744
765
|
}
|
|
745
766
|
const decidedBy = decidedByReading.decidedBy;
|
|
767
|
+
if (body.feedback !== undefined && decision !== "approve") {
|
|
768
|
+
sendError(res, 400, "request.field_conflict", "feedback is only valid with decision 'approve' — the words of a denial are its reason", fieldConflictFacts("approval_feedback", ["feedback", "decision"]));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (body.feedback !== undefined && decidedBy !== "person") {
|
|
772
|
+
sendError(res, 400, "request.field_conflict", "feedback is only valid on a person's decision — the agent reads it as the user's own words, which a policy decision cannot carry", fieldConflictFacts("approval_feedback", ["feedback", "decidedBy"]));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
746
775
|
const binding = {
|
|
747
776
|
checkpointToken: body.checkpointToken,
|
|
748
777
|
boundCallId: body.boundCallId,
|
|
749
778
|
boundInputHash: body.boundInputHash,
|
|
750
779
|
updatedInput: body.updatedInput,
|
|
780
|
+
...(typeof body.feedback === "string" ? { feedback: body.feedback } : {}),
|
|
751
781
|
};
|
|
752
782
|
let deciderPrincipal = principal;
|
|
753
783
|
if (deps.config.directDoorActive) {
|
|
@@ -811,6 +841,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
811
841
|
...(decision === "approve" && binding.updatedInput !== undefined ? { updatedInput: binding.updatedInput } : {}),
|
|
812
842
|
...(answer !== undefined ? { answer } : {}),
|
|
813
843
|
...((r) => (r !== undefined ? { reason: r } : {}))(decisionReasonOf(body)),
|
|
844
|
+
...(binding.feedback !== undefined ? { feedback: binding.feedback } : {}),
|
|
814
845
|
},
|
|
815
846
|
deciderPrincipal,
|
|
816
847
|
explicitOperator,
|
|
@@ -827,6 +858,9 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
827
858
|
return assertNever(match.decl.id);
|
|
828
859
|
}
|
|
829
860
|
const APPROVALS_STREAM_POLL_MS = 3000;
|
|
861
|
+
function isApprovalsReadCorrupt(err) {
|
|
862
|
+
return err instanceof StoreCorruptError;
|
|
863
|
+
}
|
|
830
864
|
function redactPendingDisclosures(row) {
|
|
831
865
|
const rd = row.riskDescriptor;
|
|
832
866
|
if (rd === null || rd === undefined)
|
|
@@ -898,7 +932,7 @@ export async function streamApprovals(req, res, cs, live, scope, governance, pol
|
|
|
898
932
|
snapshot = { pending, livePending: live ? live.listLivePending(scope) : [] };
|
|
899
933
|
}
|
|
900
934
|
catch (err) {
|
|
901
|
-
if (err
|
|
935
|
+
if (isApprovalsReadCorrupt(err)) {
|
|
902
936
|
if (!res.writableEnded)
|
|
903
937
|
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", errorCode: "approvals.read_corrupt", message: "a stored approval row cannot be read - this stream is closing, and reconnecting will fail the same way until an operator repairs or removes the row" })}\n\n`);
|
|
904
938
|
break;
|
|
@@ -131,7 +131,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
131
131
|
scratchpadDir: true,
|
|
132
132
|
subagentSteer: facts.subagentSteer(deps).available,
|
|
133
133
|
subagentResume: facts.subagentSteer(deps).available,
|
|
134
|
-
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: taskHooksHonored(deps.config), pluginHooks: taskHooksHonored(deps.config), managedHookGroups: taskHooksHonored(deps.config), tightenOnlySeats: true },
|
|
134
|
+
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: taskHooksHonored(deps.config), pluginHooks: taskHooksHonored(deps.config), managedHookGroups: taskHooksHonored(deps.config), tightenOnlySeats: true, approvalFrameFamilies: true },
|
|
135
135
|
settings: { permissions: { allowGrant: true, allowLayer: true, callerTrust: callerTrust(req, deps.config).trust } },
|
|
136
136
|
permissionModeAuto: {
|
|
137
137
|
accepted: true,
|
|
@@ -43,8 +43,8 @@ export interface ServerWiringGateInput {
|
|
|
43
43
|
streamApprovalEnabled: boolean;
|
|
44
44
|
/** 谓词自己的入参类型(结构形,不 import store 依赖树)—— 取型而非重述,两处不可能漂。 */
|
|
45
45
|
backend: StreamApprovalGateInput["backend"];
|
|
46
|
-
/**
|
|
47
|
-
checkpointStore:
|
|
46
|
+
/** 谓词自己的入参类型(只收店的形,工厂方法过不了 tsc)—— 取型而非重述,与 `backend` 同理;在场性即事实,本面不读实例的内容。 */
|
|
47
|
+
checkpointStore: StreamApprovalGateInput["checkpointStore"];
|
|
48
48
|
durableApproval: boolean;
|
|
49
49
|
/** S-178:三个 posture 旋钮的**来源**,取自 config 上与值同处写的那一格(不在这里重判)。 */
|
|
50
50
|
durableApprovalSource: PostureSource;
|
|
@@ -68,7 +68,7 @@ export interface StaticWiringAudit {
|
|
|
68
68
|
export interface StaticWiringFacts {
|
|
69
69
|
/** core `describeStaticWiring(deps, specTemplate)` 的产物。 */
|
|
70
70
|
manifest: WiringManifest;
|
|
71
|
-
/** 我方喂给 `resolveStreamApprovalGate` 的 `
|
|
71
|
+
/** 我方喂给 `resolveStreamApprovalGate` 的 `checkpointStore` 实参的在场性(= checkpoint 店在场 ∧ DURABLE_APPROVAL)。 */
|
|
72
72
|
parkFacility: boolean;
|
|
73
73
|
/** store backend 的种类;`undefined` = env-only worker(无 backend)。 */
|
|
74
74
|
backendKind: "mysql" | "pg" | "local" | undefined;
|
|
@@ -12,7 +12,7 @@ export function buildServerWiringGates(input) {
|
|
|
12
12
|
toolApprovalEnabled: input.toolApprovalEnabled,
|
|
13
13
|
streamApprovalEnabled: input.streamApprovalEnabled,
|
|
14
14
|
backend: input.backend,
|
|
15
|
-
|
|
15
|
+
checkpointStore: input.checkpointStore,
|
|
16
16
|
});
|
|
17
17
|
const noCenterArm = (knob) => `this knob has no web-admin arm on this deployment — it is set by ${knob} on this machine, or derived from the deployment posture`;
|
|
18
18
|
const noLaneArm = (knob) => `this knob has no execution-lane default — ${knob} does not depend on which execution lane this deployment runs`;
|
|
@@ -2,12 +2,11 @@ import { hookNoticeWire } from "../../fleet/fleet-bus.js";
|
|
|
2
2
|
import { FLEET_SNAPSHOT_TERMINAL_MAX_ROWS, isTerminalWorkflowRowStatus, recentTerminalWorkflowRows } from "../../fleet/fleet-terminal-window.js";
|
|
3
3
|
import { taskNotificationFoldKey } from "../../orchestration/workflow-completion-inbox.js";
|
|
4
4
|
import { sendJson, sendError, sseHeaders } from "../send.js";
|
|
5
|
-
import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
5
|
+
import { fleetViewOf, gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
6
|
import { assertNever } from "../route-ctx.js";
|
|
7
7
|
import * as facts from "../../capability-facts.js";
|
|
8
8
|
export async function handleFleet(req, res, match, ctx) {
|
|
9
9
|
const { deps } = ctx;
|
|
10
|
-
const { sessionOwnerScope } = ctx.helpers;
|
|
11
10
|
switch (match.decl.id) {
|
|
12
11
|
case "fleet-stream": {
|
|
13
12
|
const fleet = facts.fleetStream(deps);
|
|
@@ -15,7 +14,7 @@ export async function handleFleet(req, res, match, ctx) {
|
|
|
15
14
|
sendError(res, 501, "capability.self_orchestration_required", "fleet stream requires self-orchestration / fleet wiring");
|
|
16
15
|
return;
|
|
17
16
|
}
|
|
18
|
-
const { fleetWide } =
|
|
17
|
+
const { fleetWide } = fleetViewOf(req, deps, "read");
|
|
19
18
|
const principal = gatedPrincipal(req, deps.config);
|
|
20
19
|
if (deps.config.requirePrincipal && !principal && !fleetWide) {
|
|
21
20
|
sendPrincipalRefusal(res, req, deps.config);
|
|
@@ -77,7 +76,7 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
77
76
|
return rest;
|
|
78
77
|
};
|
|
79
78
|
const SCOPED_WORKFLOW_KEY_DISPOSITION = {
|
|
80
|
-
id: "keep", status: "keep", doneCount: "keep", totalCount: "keep", failedCount: "keep",
|
|
79
|
+
id: "keep", status: "keep", errorCode: "keep", doneCount: "keep", totalCount: "keep", failedCount: "keep",
|
|
81
80
|
startedCount: "keep", elapsedMs: "keep", tokens: "keep",
|
|
82
81
|
name: "rewrite", scope: "strip", sessionId: "strip",
|
|
83
82
|
description: "scrub",
|
|
@@ -90,6 +89,7 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
90
89
|
id: rest.id,
|
|
91
90
|
status: rest.status,
|
|
92
91
|
name: "(unattributed)",
|
|
92
|
+
...(rest.errorCode !== undefined ? { errorCode: rest.errorCode } : {}),
|
|
93
93
|
...(rest.doneCount !== undefined ? { doneCount: rest.doneCount } : {}),
|
|
94
94
|
...(rest.totalCount !== undefined ? { totalCount: rest.totalCount } : {}),
|
|
95
95
|
...(rest.failedCount !== undefined ? { failedCount: rest.failedCount } : {}),
|
|
@@ -114,7 +114,7 @@ export declare function buildClearanceRowWire(r: OriginClearanceRow): {
|
|
|
114
114
|
baseRev: string;
|
|
115
115
|
origin: {
|
|
116
116
|
at: number;
|
|
117
|
-
cause?: "static" | "
|
|
117
|
+
cause?: "static" | "observed" | "unattributed" | "derived" | undefined;
|
|
118
118
|
taint: "external";
|
|
119
119
|
};
|
|
120
120
|
};
|
|
@@ -6,8 +6,8 @@ import * as facts from "../../capability-facts.js";
|
|
|
6
6
|
import { createLogger } from "../../observability/logger.js";
|
|
7
7
|
import { RULE_IMPORT_RETRY_AFTER_SEC, RULE_SCOPE_REFUSAL_SENTENCE, MAX_RULE_SCOPE_CHARS, buildCcImportLayerRootRefusal, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
8
8
|
import { hasControlOrBidi } from "@sema-agent/settings-schema";
|
|
9
|
-
import { MAX_RULE_TEXT_CHARS, RULE_BEHAVIORS, isValidDurableScope } from "@sema-agent/core";
|
|
10
|
-
import {
|
|
9
|
+
import { IMPORTED_SETTINGS_LAYERS, MAX_RULE_TEXT_CHARS, PERSISTED_RULE_MATCHES, RULE_ADD_ORIGINS, RULE_BEHAVIORS, isValidDurableScope } from "@sema-agent/core";
|
|
10
|
+
import { isPersistedRuleTool, isDirectWritableRuleBehavior } from "../../permission-rule-vocab.js";
|
|
11
11
|
const logger = createLogger();
|
|
12
12
|
export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
|
|
13
13
|
export const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
|
|
@@ -23,7 +23,7 @@ const CcImportPrepareBodySchema = z
|
|
|
23
23
|
layers: z
|
|
24
24
|
.array(z
|
|
25
25
|
.object({
|
|
26
|
-
layer: z.enum(
|
|
26
|
+
layer: z.enum(IMPORTED_SETTINGS_LAYERS),
|
|
27
27
|
path: z.string().min(1).max(1024),
|
|
28
28
|
root: z.string().max(1024),
|
|
29
29
|
content: z.string().max(MAX_LAYER_CONTENT_CHARS),
|
|
@@ -41,7 +41,7 @@ const CcImportRedeemBodySchema = z.object({ ticket: TicketStringSchema }).strict
|
|
|
41
41
|
const LocalImportAddSchema = z
|
|
42
42
|
.object({
|
|
43
43
|
dot: z.object({ actor: z.string().min(1).max(190), counter: z.number().int().nonnegative().safe() }).strict(),
|
|
44
|
-
origin: z.enum(
|
|
44
|
+
origin: z.enum(RULE_ADD_ORIGINS),
|
|
45
45
|
createdAt: z.string().min(1).max(64),
|
|
46
46
|
})
|
|
47
47
|
.strict();
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -13,7 +13,8 @@ import { streamSseLog } from "../sse-log.js";
|
|
|
13
13
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
14
14
|
import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat, storedCardDemandsAPerson } from "../../approval-card.js";
|
|
15
15
|
import { heldAskOf, liveSettledBy, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
|
|
16
|
-
import { ASK_DECISION_CONSUMER_ABSENT, outcomeOfDecidedRow, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
16
|
+
import { ASK_DECISION_CONSUMER_ABSENT, approvalPreambleReplays, outcomeOfDecidedRow, resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
17
|
+
import { readRunDeclaredFamilies } from "../run-declared-families.js";
|
|
17
18
|
import { MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "../../approval-ask-machine.js";
|
|
18
19
|
import { causeOfAskRow } from "../../approval-deny-reasons.js";
|
|
19
20
|
import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
|
|
@@ -109,17 +110,26 @@ function sendHandleMiss(res, run, thisInstanceId) {
|
|
|
109
110
|
}
|
|
110
111
|
return false;
|
|
111
112
|
}
|
|
112
|
-
async function streamRunEvents(req, res, deps, runStore, taskId, staleMs) {
|
|
113
|
+
async function streamRunEvents(req, res, deps, runStore, taskId, sessionId, staleMs) {
|
|
113
114
|
await streamSseLog(req, res, {
|
|
114
115
|
preamble: async (signal) => {
|
|
115
116
|
const gate = resolveStreamApprovalGate({
|
|
116
117
|
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
117
118
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
118
119
|
backend: deps.backend,
|
|
119
|
-
|
|
120
|
+
checkpointStore: deps.checkpointStore,
|
|
120
121
|
});
|
|
121
122
|
if (!gate.active)
|
|
122
123
|
return [];
|
|
124
|
+
const declaredFamilies = await readRunDeclaredFamilies(gate.checkpointStore, sessionId, (where) => deps.logger?.warn?.("approval_preamble_declaration_unreadable", { taskId, where, face: "run.events" }));
|
|
125
|
+
if (!approvalPreambleReplays({
|
|
126
|
+
streamApprovalOn: gate.active,
|
|
127
|
+
windowMs: deps.config.streamApproval?.windowMs,
|
|
128
|
+
windowMarginMs: deps.config.streamAskWindowMarginMs,
|
|
129
|
+
declaredFamilies,
|
|
130
|
+
})) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
123
133
|
const store = gate.askStore;
|
|
124
134
|
const rows = await store.listPendingByTask(taskId, signal);
|
|
125
135
|
const { frames, skipped, dropped } = buildApprovalPreamble(rows, Date.now(), deps.config.streamApproval.replayMax);
|
|
@@ -255,7 +265,7 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
255
265
|
if (!runSessionAcceptOk(req, res, run, match.decl.id === "run-events" ? "run.events" : "run.poll"))
|
|
256
266
|
return;
|
|
257
267
|
if (match.decl.id === "run-events") {
|
|
258
|
-
await streamRunEvents(req, res, deps, runStore, taskId, deps.config.runStaleSec * 1000);
|
|
268
|
+
await streamRunEvents(req, res, deps, runStore, taskId, run.sessionId, deps.config.runStaleSec * 1000);
|
|
259
269
|
}
|
|
260
270
|
else {
|
|
261
271
|
sendJson(res, 200, await readRunFace(deps, runStore, run));
|
|
@@ -613,11 +623,15 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
613
623
|
const leafId = await Promise.resolve(deps.sessionStorage.getLeafId(run.sessionId)).catch(() => undefined);
|
|
614
624
|
if (leafId === undefined || leafId === null)
|
|
615
625
|
return;
|
|
616
|
-
const resumeRead = await readResumeCtx(cs.getCtx(run.sessionId));
|
|
626
|
+
const resumeRead = await readResumeCtx(cs.getCtx(run.sessionId), { sessionId: run.sessionId, opener: deps.checkpointCtxSealing });
|
|
617
627
|
if (resumeRead.kind === "corrupt") {
|
|
618
628
|
mintResult.corruptCtxWhere = resumeRead.where;
|
|
619
629
|
return;
|
|
620
630
|
}
|
|
631
|
+
if (resumeRead.kind === "unsealable") {
|
|
632
|
+
mintResult.unsealableCtx = resumeRead;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
621
635
|
if (resumeRead.kind === "absent") {
|
|
622
636
|
mintResult.noResumeCtx = true;
|
|
623
637
|
return;
|
|
@@ -641,10 +655,13 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
641
655
|
createdAt: Date.now(),
|
|
642
656
|
sourceTaskId: taskId,
|
|
643
657
|
});
|
|
644
|
-
const stillRead = await readResumeCtx(cs.getCtx(run.sessionId));
|
|
645
|
-
if (stillRead.kind === "corrupt") {
|
|
646
|
-
await withdrawWakeCard(wakeToken, cpScope, "resume_context_corrupt");
|
|
647
|
-
|
|
658
|
+
const stillRead = await readResumeCtx(cs.getCtx(run.sessionId), { sessionId: run.sessionId, opener: deps.checkpointCtxSealing });
|
|
659
|
+
if (stillRead.kind === "corrupt" || stillRead.kind === "unsealable") {
|
|
660
|
+
await withdrawWakeCard(wakeToken, cpScope, stillRead.kind === "corrupt" ? "resume_context_corrupt" : "resume_context_unsealable");
|
|
661
|
+
if (stillRead.kind === "corrupt")
|
|
662
|
+
mintResult.corruptCtxWhere = stillRead.where;
|
|
663
|
+
else
|
|
664
|
+
mintResult.unsealableCtx = stillRead;
|
|
648
665
|
return;
|
|
649
666
|
}
|
|
650
667
|
if (stillRead.kind !== "ok") {
|
|
@@ -681,6 +698,10 @@ export async function handleRuns(req, res, match, ctx) {
|
|
|
681
698
|
sendError(res, 500, "corrupt", "run already ended and this session's stored resume context cannot be read — nothing was queued. Ask whoever operates this deployment to repair or remove the row named in `where` before steering this session again.", { where: mintResult.corruptCtxWhere });
|
|
682
699
|
return;
|
|
683
700
|
}
|
|
701
|
+
if (mintResult.unsealableCtx !== undefined) {
|
|
702
|
+
sendError(res, 409, "conflict.checkpoint_ctx_unsealable", "run already ended and this session's stored resume context holds credentials this server cannot unseal (reason says why) — nothing was queued, because POST /v1/sessions/:id/wake could not resume it; restore the sealed-key directory that sealed them, restart this server (it reads its keys only at startup) and steer again, or submit a new task instead", { field: mintResult.unsealableCtx.field, reason: mintResult.unsealableCtx.reason });
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
684
705
|
if (mintResult.unservable !== undefined) {
|
|
685
706
|
deps.logger?.warn?.("steer_wake_park_refused_stored_body", { taskId, sessionId: run.sessionId, ...storedBodyReplayLogFields(mintResult.unservable) });
|
|
686
707
|
const { status, body: { error, errorCode, ...rest } } = storedBodyReplayAnswer(mintResult.unservable, serviceVersion(), false);
|
|
@@ -34,8 +34,9 @@ export type PendingStaging = {
|
|
|
34
34
|
declaredEntryIds: string[];
|
|
35
35
|
/** The principal that opened this staging — a different caller may not drive it (404, no oracle). */
|
|
36
36
|
openedBy: string | null;
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/** The owner cell the gate judged when it opened this staging (`ownerOf` reading: `undefined` = no such session) — the commit lands only on
|
|
38
|
+
* this same cell (`StagedCommitOptions.expectedOwner`, `session-sync-kernel.ts`); `owner` above (the re-stamped importing principal) is derived from it for a fleet caller. */
|
|
39
|
+
expectedOwner: string | null | undefined;
|
|
39
40
|
};
|
|
40
41
|
/** 单域独占状态 + 它的两个操作。每个 server 实例一份。 */
|
|
41
42
|
export interface SessionSyncLocal {
|