@sema-agent/server 7.140.0 → 7.141.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 +290 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +75 -13
- package/dist/approval-batch-census.d.ts +27 -0
- package/dist/approval-batch-census.js +27 -0
- package/dist/approval-card.d.ts +31 -16
- package/dist/approval-card.js +6 -3
- package/dist/approval.d.ts +15 -0
- package/dist/approval.js +10 -0
- package/dist/audit.d.ts +4 -2
- package/dist/audit.js +2 -2
- package/dist/boot/device-lane.d.ts +11 -0
- package/dist/boot/device-lane.js +16 -0
- package/dist/boot/engine-lease.d.ts +24 -3
- package/dist/boot/engine-lease.js +9 -2
- package/dist/boot/execution-env.js +8 -4
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/memory-consolidation.d.ts +1 -1
- package/dist/boot/memory-consolidation.js +1 -1
- package/dist/boot/reapers.d.ts +2 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +4 -3
- package/dist/boot/shutdown.js +5 -0
- package/dist/boot/stage-05-execution-env.js +3 -1
- package/dist/boot/stage-06-runners.d.ts +0 -2
- package/dist/boot/stage-06-runners.js +1 -1
- package/dist/boot/stage-07-capability-layer.d.ts +0 -1
- package/dist/boot/stage-07-capability-layer.js +8 -10
- package/dist/boot/stage-08-reapers.d.ts +0 -1
- package/dist/boot/stage-09-leader.d.ts +0 -1
- package/dist/boot/stage-10-http-server.d.ts +0 -1
- package/dist/boot/stage-10-http-server.js +1 -0
- package/dist/boot/stores.js +10 -4
- package/dist/boot/web-admin.js +1 -0
- package/dist/capabilities/scenarios.d.ts +1 -1
- package/dist/capability-facts.d.ts +9 -0
- package/dist/capability-facts.js +9 -0
- package/dist/card-void.d.ts +38 -0
- package/dist/card-void.js +23 -0
- package/dist/config-catalog.js +11 -8
- package/dist/config-provider.d.ts +13 -13
- package/dist/config-provider.js +19 -33
- package/dist/config-types.d.ts +26 -8
- package/dist/config.js +55 -46
- package/dist/delegated-session-owner.d.ts +21 -19
- package/dist/delegated-session-owner.js +1 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +3 -0
- package/dist/execution-config-root.d.ts +22 -0
- package/dist/execution-config-root.js +35 -0
- package/dist/execution-lane-caps.d.ts +49 -2
- package/dist/execution-lane-caps.js +90 -13
- package/dist/execution-placement.d.ts +3 -0
- package/dist/execution-placement.js +7 -0
- package/dist/fleet/drain-entry.d.ts +3 -0
- package/dist/hosted-posture.d.ts +8 -0
- package/dist/hosted-posture.js +4 -0
- package/dist/http/admission.js +3 -3
- package/dist/http/caller-trust.d.ts +3 -2
- package/dist/http/caller-trust.js +2 -1
- package/dist/http/dispatch.js +4 -0
- package/dist/http/live-frame.d.ts +24 -0
- package/dist/http/live-frame.js +16 -0
- package/dist/http/resume-legs.js +37 -20
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/approvals-assistant.js +1 -0
- package/dist/http/routes/capabilities.js +5 -4
- package/dist/http/routes/diagnostics.d.ts +0 -19
- package/dist/http/routes/diagnostics.js +16 -2
- package/dist/http/routes/memory-bundle.d.ts +2 -2
- package/dist/http/routes/memory-bundle.js +1 -1
- package/dist/http/routes/memory-compliance.js +1 -1
- package/dist/http/routes/memory-policy.js +2 -2
- package/dist/http/routes/run-permission-mode.d.ts +75 -0
- package/dist/http/routes/run-permission-mode.js +170 -0
- package/dist/http/routes/session-sync.d.ts +3 -6
- package/dist/http/routes/session-sync.js +7 -2
- package/dist/http/routes/sessions.js +5 -4
- package/dist/http/routes/tasks.js +63 -66
- package/dist/http/run-read-face.js +3 -3
- package/dist/http/send.d.ts +1 -1
- package/dist/http/send.js +2 -2
- package/dist/http/server.d.ts +9 -2
- package/dist/http/sse-log.d.ts +3 -8
- package/dist/http/stored-body-replay.d.ts +3 -3
- package/dist/http/stored-body-replay.js +3 -1
- package/dist/instruction-source-notice.d.ts +7 -0
- package/dist/instruction-source-notice.js +6 -6
- package/dist/mcp-assertion-header.d.ts +4 -0
- package/dist/mcp-assertion-header.js +5 -0
- package/dist/memory-posture.d.ts +1 -1
- package/dist/memory-scope.d.ts +4 -4
- package/dist/memory-scope.js +1 -1
- package/dist/memory-sync-client.d.ts +14 -2
- package/dist/memory-sync-client.js +80 -18
- package/dist/observability/fail-open.d.ts +30 -2
- package/dist/observability/fail-open.js +30 -2
- package/dist/observability/metrics.js +0 -1
- package/dist/observability/run-terminal.d.ts +6 -2
- package/dist/observability/run-terminal.js +25 -26
- package/dist/observability/secret-env-scrub.d.ts +20 -1
- package/dist/observability/secret-env-scrub.js +14 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
- package/dist/orchestration/workflow-completion-inbox.js +13 -5
- package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
- package/dist/orchestration/workflow-notify-journal.js +2 -1
- package/dist/permission-mode-admission.d.ts +68 -0
- package/dist/permission-mode-admission.js +23 -0
- package/dist/permission-rule-list.d.ts +55 -6
- package/dist/permission-rule-list.js +52 -5
- package/dist/plugins/caching-session-store.d.ts +6 -0
- package/dist/plugins/caching-session-store.js +3 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
- package/dist/plugins/checkpoint-store-sql.js +74 -3
- package/dist/plugins/device-store-sql.d.ts +5 -2
- package/dist/plugins/device-store-sql.js +4 -1
- package/dist/plugins/execution-placement-sql.d.ts +11 -1
- package/dist/plugins/execution-placement-sql.js +12 -1
- package/dist/plugins/file-run-store.d.ts +2 -2
- package/dist/plugins/file-run-store.js +1 -1
- package/dist/plugins/fork-routing-session-store.d.ts +10 -32
- package/dist/plugins/fork-routing-session-store.js +2 -61
- package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
- package/dist/plugins/k8s-exec-protocol.js +6 -1
- package/dist/plugins/local-checkpoint-store.d.ts +10 -1
- package/dist/plugins/local-checkpoint-store.js +22 -0
- package/dist/plugins/local-session-store.d.ts +227 -35
- package/dist/plugins/local-session-store.js +518 -37
- package/dist/plugins/local-session-window.d.ts +17 -3
- package/dist/plugins/local-session-window.js +7 -13
- package/dist/plugins/memory-run-store.d.ts +2 -2
- package/dist/plugins/memory-run-store.js +1 -1
- package/dist/plugins/one-time-migrations.d.ts +42 -2
- package/dist/plugins/one-time-migrations.js +65 -3
- package/dist/plugins/pg-pool.js +3 -2
- package/dist/plugins/pg-session-storage.d.ts +2 -0
- package/dist/plugins/pg-session-storage.js +3 -2
- package/dist/plugins/remote-env-e2b.d.ts +24 -3
- package/dist/plugins/remote-env-e2b.js +131 -18
- package/dist/plugins/remote-env-k8s.d.ts +20 -1
- package/dist/plugins/remote-env-k8s.js +148 -25
- package/dist/plugins/remote-env-local-docker.d.ts +25 -11
- package/dist/plugins/remote-env-local-docker.js +87 -21
- package/dist/plugins/run-store-sql.d.ts +3 -3
- package/dist/plugins/run-store-sql.js +4 -3
- package/dist/plugins/sandbox-read-floor.d.ts +273 -0
- package/dist/plugins/sandbox-read-floor.js +608 -0
- package/dist/plugins/session-ownership.d.ts +2 -1
- package/dist/plugins/session-placement.d.ts +17 -4
- package/dist/plugins/session-placement.js +2 -1
- package/dist/plugins/session-store.d.ts +18 -20
- package/dist/plugins/session-store.js +4 -12
- package/dist/plugins/store-backend.d.ts +31 -0
- package/dist/plugins/store-backend.js +21 -4
- package/dist/plugins/store-contracts.d.ts +9 -2
- package/dist/plugins/tidb-pool.js +2 -1
- package/dist/plugins/tidb-session-store.d.ts +4 -1
- package/dist/plugins/tidb-session-store.js +3 -2
- package/dist/run-cancel-context.d.ts +34 -0
- package/dist/run-cancel-context.js +7 -0
- package/dist/runs.d.ts +39 -12
- package/dist/runs.js +68 -53
- package/dist/security.d.ts +2 -2
- package/dist/server-secret-env.d.ts +2 -1
- package/dist/server-secret-env.js +2 -4
- package/dist/session-error-wire.d.ts +22 -4
- package/dist/session-error-wire.js +16 -0
- package/dist/session-incarnation.d.ts +9 -3
- package/dist/session-sync-kernel.d.ts +42 -1
- package/dist/session-sync-kernel.js +17 -0
- package/dist/session-sync.js +16 -4
- package/dist/sql-engine-coordinates.d.ts +49 -0
- package/dist/sql-engine-coordinates.js +27 -0
- package/dist/standing-answer-grant.d.ts +31 -2
- package/dist/standing-answer-grant.js +14 -2
- package/dist/store-posture.d.ts +1 -1
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-mcp.d.ts +1 -1
- package/dist/task-settings.d.ts +52 -25
- package/dist/task-settings.js +91 -28
- package/dist/terminal.d.ts +77 -20
- package/dist/terminal.js +66 -20
- package/dist/trace/core-keyset-guard.d.ts +79 -2
- package/dist/trace/exit-seat.d.ts +7 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +2 -2
- package/dist/trace/redact.js +323 -90
- package/dist/trace/self-minted-wire.d.ts +168 -2
- package/dist/trace/self-minted-wire.js +63 -0
- package/dist/verifier-pause-card.d.ts +8 -0
- package/dist/verifier-pause-card.js +19 -0
- package/dist/web-admin-client/apply-effective.d.ts +5 -7
- package/dist/web-admin-client/apply-effective.js +2 -2
- package/dist/web-admin-client/candidate-gate.d.ts +1 -1
- package/dist/web-admin-client/read-warnings.d.ts +2 -2
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
- package/package.json +2 -2
- package/wire-changes/server@7.141.0.json +367 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { StubExecutionEnv } from "@sema-agent/core";
|
|
3
|
-
import { EXECUTION_LANE_WORDS as DECLARABLE_LANE_WORDS, EXECUTION_LANES, isBlankCoordinate } from "@sema-agent/settings-schema/execution-lanes";
|
|
3
|
+
import { EXECUTION_LANE_WORDS as DECLARABLE_LANE_WORDS, EXECUTION_LANES, isBlankCoordinate, retiredLaneWordRefusal } from "@sema-agent/settings-schema/execution-lanes";
|
|
4
4
|
import { DECLARE_HOME_REMEDY, LANE_HOME_UNDECLARED_EVENT, LANE_HOME_UNMEASURED_EVENT, buildHomeAbsentNote, buildHomeUnmeasuredNote } from "./lane-home-probe.js";
|
|
5
5
|
const NO = "unsupported";
|
|
6
6
|
const YES = "supported";
|
|
@@ -97,27 +97,61 @@ export function executionLaneCaps(lane) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
|
+
export const SANDBOX_READ_FLOOR_ENV = "SANDBOX_READ_FLOOR";
|
|
101
|
+
export const SANDBOX_READ_FLOOR_WORDS = ["keep", "off"];
|
|
102
|
+
export const SANDBOX_READ_FLOOR_DEFAULT = "keep";
|
|
103
|
+
export function readFloorStateOf(knob, served) {
|
|
104
|
+
switch (knob) {
|
|
105
|
+
case "off":
|
|
106
|
+
return "off";
|
|
107
|
+
case "keep":
|
|
108
|
+
return served ? "kept" : "unserved";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export function sandboxReadFloorState(lane, knob) {
|
|
112
|
+
const k = knob ?? SANDBOX_READ_FLOOR_DEFAULT;
|
|
113
|
+
switch (lane) {
|
|
114
|
+
case "e2b":
|
|
115
|
+
return readFloorStateOf(k, false);
|
|
116
|
+
case "k8s":
|
|
117
|
+
return readFloorStateOf(k, false);
|
|
118
|
+
case "local-docker":
|
|
119
|
+
return readFloorStateOf(k, false);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
100
122
|
const IN_PROCESS_BLOCK = new StubExecutionEnv().capabilities;
|
|
101
123
|
const HOST_BLOCK = Object.freeze({ isolation: false, suspendable: false, sharesProcessTable: true, canonicalPathAuthoritative: true, hostLocalPaths: true, externalContentTarget: false, io: true, keepsReadFloor: false });
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
124
|
+
const E2B_SHAPE = Object.freeze({ isolation: true, suspendable: true, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: false, io: true });
|
|
125
|
+
const K8S_SHAPE = Object.freeze({ isolation: true, suspendable: false, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: false, io: true });
|
|
126
|
+
const K8S_SNAPSHOT_SHAPE = Object.freeze({ ...K8S_SHAPE, suspendable: true });
|
|
127
|
+
const LOCAL_DOCKER_SHAPE = Object.freeze({ isolation: true, suspendable: false, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: false, io: true });
|
|
128
|
+
function sandboxBlock(shape, state) {
|
|
129
|
+
return Object.freeze({ ...shape, keepsReadFloor: state === "kept" });
|
|
130
|
+
}
|
|
131
|
+
function sandboxShape(lane, snapshotStore) {
|
|
132
|
+
switch (lane) {
|
|
133
|
+
case "e2b":
|
|
134
|
+
return E2B_SHAPE;
|
|
135
|
+
case "k8s":
|
|
136
|
+
return snapshotStore ? K8S_SNAPSHOT_SHAPE : K8S_SHAPE;
|
|
137
|
+
case "local-docker":
|
|
138
|
+
return LOCAL_DOCKER_SHAPE;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
106
141
|
const SSH_BLOCK = Object.freeze({ isolation: false, suspendable: false, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false });
|
|
107
142
|
const ADB_BLOCK = Object.freeze({ isolation: false, suspendable: false, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: false, io: true, keepsReadFloor: false });
|
|
108
143
|
const DEVICE_BLOCK = Object.freeze({ isolation: false, suspendable: false, sharesProcessTable: false, canonicalPathAuthoritative: true, hostLocalPaths: false, externalContentTarget: true, io: true, keepsReadFloor: false });
|
|
109
|
-
|
|
144
|
+
const NO_SUB_KNOBS = Object.freeze({ snapshotStore: false, sandboxReadFloor: undefined });
|
|
145
|
+
function laneBlock(lane, sub) {
|
|
110
146
|
switch (lane) {
|
|
111
147
|
case "in-process":
|
|
112
148
|
return IN_PROCESS_BLOCK;
|
|
113
149
|
case "host":
|
|
114
150
|
return HOST_BLOCK;
|
|
115
151
|
case "e2b":
|
|
116
|
-
return E2B_BLOCK;
|
|
117
152
|
case "k8s":
|
|
118
|
-
return snapshotStore ? K8S_SNAPSHOT_BLOCK : K8S_BLOCK;
|
|
119
153
|
case "local-docker":
|
|
120
|
-
return
|
|
154
|
+
return sandboxBlock(sandboxShape(lane, sub.snapshotStore), sandboxReadFloorState(lane, sub.sandboxReadFloor));
|
|
121
155
|
case "ssh":
|
|
122
156
|
return SSH_BLOCK;
|
|
123
157
|
case "adb":
|
|
@@ -130,7 +164,24 @@ export function capabilitiesFor(deployment) {
|
|
|
130
164
|
const lane = executionLaneOf(deployment.remoteExec?.provider);
|
|
131
165
|
if (lane === undefined)
|
|
132
166
|
return undefined;
|
|
133
|
-
return laneBlock(lane, lane === "k8s" && Boolean(deployment.remoteExec?.s3Snapshot));
|
|
167
|
+
return laneBlock(lane, { snapshotStore: lane === "k8s" && Boolean(deployment.remoteExec?.s3Snapshot), sandboxReadFloor: deployment.sandboxReadFloor });
|
|
168
|
+
}
|
|
169
|
+
export function laneSandboxReadFloorState(deployment) {
|
|
170
|
+
const lane = executionLaneOf(deployment.remoteExec?.provider);
|
|
171
|
+
if (lane === undefined)
|
|
172
|
+
return undefined;
|
|
173
|
+
switch (lane) {
|
|
174
|
+
case "e2b":
|
|
175
|
+
case "k8s":
|
|
176
|
+
case "local-docker":
|
|
177
|
+
return sandboxReadFloorState(lane, deployment.sandboxReadFloor);
|
|
178
|
+
case "in-process":
|
|
179
|
+
case "host":
|
|
180
|
+
case "ssh":
|
|
181
|
+
case "adb":
|
|
182
|
+
case "device":
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
134
185
|
}
|
|
135
186
|
export const ALL_EXECUTION_LANES = Object.freeze(["in-process", ...DECLARABLE_LANE_WORDS]);
|
|
136
187
|
const EXECUTION_LANE_SET = new Set(ALL_EXECUTION_LANES);
|
|
@@ -166,6 +217,32 @@ export function isBlankRequiredEnvValue(value) {
|
|
|
166
217
|
export function laneMissingEnv(lane, env) {
|
|
167
218
|
return laneRequiredEnv(lane).filter((name) => isBlankRequiredEnvValue(env[name]));
|
|
168
219
|
}
|
|
220
|
+
export function readDeclaredLaneTable(raw) {
|
|
221
|
+
const segments = raw.split(",");
|
|
222
|
+
const lanes = [];
|
|
223
|
+
for (const [i, segment] of segments.entries()) {
|
|
224
|
+
if (segment === "") {
|
|
225
|
+
return { refusal: `REMOTE_EXEC="${raw}" has an empty entry at position ${i + 1} — it is an ordered, comma-separated list of execution lanes (the first is the default) with no blank entries; valid lanes: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}.` };
|
|
226
|
+
}
|
|
227
|
+
const lane = REMOTE_EXEC_PROVIDER_WORDS.find((w) => w === segment);
|
|
228
|
+
const retired = lane === undefined ? retiredLaneWordRefusal(segment) : undefined;
|
|
229
|
+
if (retired !== undefined)
|
|
230
|
+
return { refusal: `REMOTE_EXEC="${raw}": ${retired}.` };
|
|
231
|
+
if (lane === undefined) {
|
|
232
|
+
return {
|
|
233
|
+
refusal: `REMOTE_EXEC="${raw}"${segments.length > 1 ? ` lists "${segment}", which` : ""} is not an execution lane — valid: ${REMOTE_EXEC_PROVIDER_WORDS.join(" | ")}. ` +
|
|
234
|
+
`An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
if (lanes.includes(lane))
|
|
238
|
+
return { refusal: `REMOTE_EXEC="${raw}" lists "${lane}" twice — each execution lane appears once (the first one is the default).` };
|
|
239
|
+
lanes.push(lane);
|
|
240
|
+
}
|
|
241
|
+
const [first, ...rest] = lanes;
|
|
242
|
+
if (first === undefined)
|
|
243
|
+
throw new Error(`readDeclaredLaneTable: "${raw}" split into no segments`);
|
|
244
|
+
return { lanes: [first, ...rest] };
|
|
245
|
+
}
|
|
169
246
|
export function executionLaneRequiredRefusal(deployment) {
|
|
170
247
|
if (!deployment.requireExecutionLane || executionLaneOf(deployment.remoteExec?.provider) !== "in-process")
|
|
171
248
|
return undefined;
|
|
@@ -189,7 +266,7 @@ export function deployedExecutionLanes(config) {
|
|
|
189
266
|
return [executionLaneOf(config.remoteExec?.provider)];
|
|
190
267
|
}
|
|
191
268
|
export function handsOnWorkerFilePlane(lane) {
|
|
192
|
-
const block = laneBlock(lane,
|
|
269
|
+
const block = laneBlock(lane, NO_SUB_KNOBS);
|
|
193
270
|
return block.io && block.hostLocalPaths;
|
|
194
271
|
}
|
|
195
272
|
export function laneBindsReads(lane) {
|
|
@@ -236,10 +313,10 @@ export function laneReadFaceDefault(lane) {
|
|
|
236
313
|
}
|
|
237
314
|
export const LANE_OPEN_READ_FACE_CAUTION = "a deployment that mounts host paths or shared volumes into the sandbox (hostPath, bind mount) exposes those host files to reads on an open face — pin READ_FACE=roots or add read-deny rows (READ_DENY_PATTERNS) for them";
|
|
238
315
|
export function laneIsolated(lane) {
|
|
239
|
-
return laneBlock(lane,
|
|
316
|
+
return laneBlock(lane, NO_SUB_KNOBS).isolation;
|
|
240
317
|
}
|
|
241
318
|
export function laneWiresExecutionEnv(lane) {
|
|
242
|
-
return laneBlock(lane,
|
|
319
|
+
return laneBlock(lane, NO_SUB_KNOBS).io;
|
|
243
320
|
}
|
|
244
321
|
export const PLANNED_LANE_DIVERGENCES = Object.freeze([]);
|
|
245
322
|
export const E2B_SANDBOX_HOME = "/home/user";
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* `execution` 位(`plugins/local-session-store.ts`)。处处 `execution` 限定,与同行 `placement_*`(委派分区)不同义;执行格只在 `placement_kind IS NULL` 的行上 ——
|
|
9
9
|
* 委派分区行读作「不在」。
|
|
10
10
|
* · **读只有一只**:会话店的执行席 {@link ExecutionPlacementSeat.read}(一次行读:住客 + 格;行不在 / 委派分区行 ⇒ `undefined`;读故障抛,不吞成 `absent`)。
|
|
11
|
+
* 跨行的聚合也只有一只:设备店的绑定数 `countDeviceBindings`(writer(device, D) 的格数 = {@link boundDeviceOf} 的聚合;只交数,不交根 id)。
|
|
11
12
|
* · **写只按住客谓词** `(root, incarnation, owner) ∧ placement_kind IS NULL`:住客换了(删后重建,哪怕同一属主;local M10 窗内的认领)= 这一格不在 ⇒ 落空;
|
|
12
13
|
* 0 行一律按住客复读分类({@link settledPlaceOutcome} / {@link settledMirrorOutcome} / {@link settledDeviceBindOutcome}),**永不对现住客重试**。
|
|
13
14
|
* · **没有的转换**:writer ↔ mirror、writer(L) → writer(L')(device 车道的换绑 writer(device, D) → writer(device, D') 除外,它有自己的语句与门)。
|
|
@@ -248,6 +249,8 @@ export declare class InMemoryExecutionPlacementSeat implements ExecutionPlacemen
|
|
|
248
249
|
read(root: string): Promise<ExecutionPlacementReading | undefined>;
|
|
249
250
|
place(root: string, occupant: SessionFace, cell: ExecutionWriterCell): Promise<PlaceOutcome>;
|
|
250
251
|
markMirror(root: string, occupant: SessionFace): Promise<MarkMirrorOutcome>;
|
|
252
|
+
/** 绑定数(SQL 孪生 `countDeviceBindingsOn` 的对位,同一只判官):{@link boundDeviceOf} 读得出设备的格数。 */
|
|
253
|
+
countDeviceBindings(): number;
|
|
251
254
|
/** device 首绑(SQL `placeDeviceStatement` / `bindDeviceStatement` 的对位):absent 或 writer(device, ∅) ⇒ writer(device, D)、rev 0。 */
|
|
252
255
|
bindDevice(root: string, occupant: SessionFace, device: {
|
|
253
256
|
readonly id: string;
|
|
@@ -175,6 +175,13 @@ export class InMemoryExecutionPlacementSeat {
|
|
|
175
175
|
this.rows.set(root, { occupant, cell: { role: "mirror" } });
|
|
176
176
|
return { outcome: "mirrored" };
|
|
177
177
|
}
|
|
178
|
+
countDeviceBindings() {
|
|
179
|
+
let bindings = 0;
|
|
180
|
+
for (const row of this.rows.values())
|
|
181
|
+
if (boundDeviceOf(row.cell) !== undefined)
|
|
182
|
+
bindings += 1;
|
|
183
|
+
return bindings;
|
|
184
|
+
}
|
|
178
185
|
bindDevice(root, occupant, device, placedAt) {
|
|
179
186
|
const settled = settledDeviceBindOutcome(root, this.rows.get(root), occupant, device.id);
|
|
180
187
|
if (settled !== WRITABLE)
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* 纯类型 + 纯函数叶(值 import 只有 scope 读法叶与 run 行判词叶)。
|
|
31
31
|
*/
|
|
32
32
|
import type { DrainReceipt, DrainRequest, EngineDraining } from "@sema-agent/settings-schema/fleet";
|
|
33
|
+
import type { EngineLeaseFacts } from "../boot/engine-lease.js";
|
|
33
34
|
/**
|
|
34
35
|
* 引擎自铸的动作号 —— **一张表**(排空事实与回执里不来自请求体的全部拼法;请求体的 `actionId` 由调用方自取):
|
|
35
36
|
* · {@link ENGINE_DRAIN_CAUSES}:没有请求承载的排空成因(停止信号 / SIGHUP 升级 / 附着租约自退);
|
|
@@ -89,6 +90,8 @@ export interface DrainState {
|
|
|
89
90
|
lastActivityAt?: () => number;
|
|
90
91
|
attachedStreams?: () => number;
|
|
91
92
|
lastAttachEndedAt?: () => number;
|
|
93
|
+
/** [ref]:附着租约窗的读口(`/health.engineLease`)。收尾链在**装了租约腿**时才装它,读口现读租约腿的把手 —— 腿还在观察才有读数,未装配的盒子里根本没有这只口。 */
|
|
94
|
+
engineLease?: () => EngineLeaseFacts | undefined;
|
|
92
95
|
}
|
|
93
96
|
/** 排空盒子里的排空事实(三键齐才是;入口整组写,所以「缺一」只出现在手搭的夹具上)。 */
|
|
94
97
|
export declare function drainingFactOf(s: Pick<DrainState, "draining" | "since" | "actionId" | "deadlineAt">): EngineDraining | undefined;
|
package/dist/hosted-posture.d.ts
CHANGED
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
* `docs/DEPLOY-PREREQS.md` 的「托管 / 多租部署」段把它写成部署前置,并列出忘设它的后果。
|
|
66
66
|
*/
|
|
67
67
|
import { type ServiceConfigFlat } from "./config-types.js";
|
|
68
|
+
import { type PermissionMode } from "@sema-agent/core";
|
|
68
69
|
/** 托管/多租的**证据词表**(闭集;加词 ⇒ {@link hostedEvidenceNote} 的穷举 switch 编译红)。
|
|
69
70
|
* 🔴 恰一词是裁定([ref] ①),不是暂时的:加回一条推断臂 = 翻案,先要新裁定。 */
|
|
70
71
|
export declare const HOSTED_EVIDENCE_WORDS: readonly ["require-principal"];
|
|
@@ -123,6 +124,13 @@ export type TaskHostPostureScope = HostedPostureInput & Readonly<Pick<ServiceCon
|
|
|
123
124
|
* 两条腿同射程、不同处置,射程只有一处答案。
|
|
124
125
|
*/
|
|
125
126
|
export declare function taskWriteFaceRefusal(c: TaskWriteFaceInput): string | undefined;
|
|
127
|
+
/**
|
|
128
|
+
* **腿② 的词形入口** —— 一个模式词在这台部署上会不会开出它掀不开的写围栏:词的模式列开写面(引擎模式表本尊,
|
|
129
|
+
* `modePolicyOf(word).writeFace === "open"`,今天只有 `bypassPermissions`)∧ {@link taskWriteFaceRefusal} 答拒 ⇒ 拒因;否则 `undefined`。
|
|
130
|
+
* 读者两个,同是受理判官的托管一步(`permission-mode-admission.ts` 顶注「两口的调用形」):在飞 run 的切档口经判官本体;提交口与续跑重放存储体经装配期那一判
|
|
131
|
+
* (`boot/resolve-spec.ts`,问盖过章的词)。哪天 core 让第二个模式也开写面,两处当场跟上,不是再手抄一个模式词。
|
|
132
|
+
*/
|
|
133
|
+
export declare function permissionModeWriteFaceRefusal(word: PermissionMode | undefined, c: TaskWriteFaceInput): string | undefined;
|
|
126
134
|
/**
|
|
127
135
|
* **腿④** —— 这条请求要让 host shell 继承整份用户环境(`inheritEnv:"all"`),这台部署给不给?返回拒因;`undefined` = 放行。
|
|
128
136
|
* 调用方只在值为 `"all"` 时问(`"scrub"` 就是缺省,任何部署都受理)。
|
package/dist/hosted-posture.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
2
2
|
import { DEPLOYMENT_SHAPE_PREDICATES } from "./deployment-shape.js";
|
|
3
|
+
import { modePolicyOf } from "@sema-agent/core";
|
|
3
4
|
export const HOSTED_EVIDENCE_WORDS = ["require-principal"];
|
|
4
5
|
export function buildHostedPosture(c) {
|
|
5
6
|
const evidence = [];
|
|
@@ -41,6 +42,9 @@ export function taskWriteFaceRefusal(c) {
|
|
|
41
42
|
`Refused because this deployment is HOSTED/multi-tenant (${hostedBecause(facts)}), so the task layer may only TIGHTEN the write face, never open it. ` +
|
|
42
43
|
`Set TASK_WRITE_FACE_ESCALATION=allow if the DEPLOYMENT permits the task layer to open it, or omit permissionMode:"bypassPermissions".`);
|
|
43
44
|
}
|
|
45
|
+
export function permissionModeWriteFaceRefusal(word, c) {
|
|
46
|
+
return word !== undefined && modePolicyOf(word).writeFace === "open" ? taskWriteFaceRefusal(c) : undefined;
|
|
47
|
+
}
|
|
44
48
|
export function taskInheritEnvRefusal(c) {
|
|
45
49
|
if (DEPLOYMENT_SHAPE_PREDICATES.taskHostPostureDeclarations.holds(c))
|
|
46
50
|
return undefined;
|
package/dist/http/admission.js
CHANGED
|
@@ -2,11 +2,11 @@ import { APPROVAL_REACH, IMAGE_ADMISSION, isApprovalReach, isPermissionMode, isT
|
|
|
2
2
|
import { isAtMentionable, matchCatalogModel, refusedCatalogEntry, selectableCatalog } from "../model-select.js";
|
|
3
3
|
import { echoModelRef, modelUnavailableMessage } from "../model-entry-refusal.js";
|
|
4
4
|
import { HttpError, sessionIdShapeIssue } from "../security.js";
|
|
5
|
-
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, hooksKeyIssue,
|
|
5
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, hooksKeyIssue, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
6
|
import { tightenOnlySeatIssue } from "../tighten-only-seats.js";
|
|
7
7
|
import { approvalFrameFamiliesIssue } from "../tool-approval.js";
|
|
8
8
|
import { buildEntryModeClampNotice } from "../entry-mode-clamp-notice.js";
|
|
9
|
-
import {
|
|
9
|
+
import { stampSubmissionEntryMode } from "../permission-mode-admission.js";
|
|
10
10
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
11
11
|
import { FILE_HISTORY_DISABLED_NO_RESTORE, fileHistoryRestoreConflict, fileHistoryRestoreKeysInBody, inheritEnvFieldIssue, singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
12
12
|
import * as facts from "../capability-facts.js";
|
|
@@ -738,7 +738,7 @@ export function createAdmission(ctx) {
|
|
|
738
738
|
return null;
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
|
-
const { body: admitted, entry: entryMode } =
|
|
741
|
+
const { body: admitted, entry: entryMode } = stampSubmissionEntryMode(body, deps.config);
|
|
742
742
|
const lease = deps.peerDesk.lease("fresh");
|
|
743
743
|
try {
|
|
744
744
|
const auth = await deps.authorize({ req, body: admitted });
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* ## 律
|
|
5
5
|
* 契约(settings-schema `SAFETY_MERGE_SPEC.permissions` 的成员阶梯)按**层的信任档**准入每个成员:`allow` 放宽免问面(`loosen`),要 `global`;`deny` / `ask` 只收紧,
|
|
6
|
-
* 文件层最低档 `local`
|
|
7
|
-
*
|
|
6
|
+
* 文件层最低档 `local` 就收。准入不读层:提交体是合并后的结果,层只在合并端(cli 本地)存在;deny / ask 条目可以带出处层(`{ rule, layer }`),但那个层只选这条规则的
|
|
7
|
+
* `/` 读在哪个根(`permission-rule-list.ts` 的 `layerRuleRoot`),自报的层不可验、**不进本判**。所以本服务准入的「层」= **这次请求验过的凭据**:档只由部署形 + 身份来源
|
|
8
|
+
* + 登录令牌的 scope 角色推,调用方说什么(含条目上的层)都不改档。
|
|
8
9
|
*
|
|
9
10
|
* · 单用户 turnkey(`requirePrincipal !== true`,本仓 posture 判别式同一只)⇒ `global`:一台机一位超管;
|
|
10
11
|
* · 多租 · 部署的 service 凭据 ⇒ `global`:部署自己的凭据是运维档(契约给「启动方自己的行为」—— `SETTINGS_SOURCE_CONTRACT.flagSettings` —— 的档),持它的前端为自己转发的内容负责;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { domainWriteRole, isAtLeast, isRole, memberAdmission, membersOf, roleAtLeast, USER_ROLES, } from "@sema-agent/settings-schema";
|
|
2
2
|
import { entryProblem, entryProblemsFacts } from "../entry-problems.js";
|
|
3
3
|
import { ssoVerifiedRole } from "../security.js";
|
|
4
|
+
import { ruleTextOf } from "../permission-rule-list.js";
|
|
4
5
|
import { verifiedIdentitySource } from "./principal-gate.js";
|
|
5
6
|
export function callerTrust(req, config) {
|
|
6
7
|
if (config.requirePrincipal !== true)
|
|
@@ -34,7 +35,7 @@ function effectiveRuleTables(t) {
|
|
|
34
35
|
for (const member of membersOf("permissions")) {
|
|
35
36
|
const entries = t.permissions[member];
|
|
36
37
|
if (entries !== undefined)
|
|
37
|
-
out.push({ member, field: `settings.permissions.${member}`, entries, source: "settings" });
|
|
38
|
+
out.push({ member, field: `settings.permissions.${member}`, entries: entries.map(ruleTextOf), source: "settings" });
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
if (t.skills !== undefined && t.userInvokedSkills !== undefined) {
|
package/dist/http/dispatch.js
CHANGED
|
@@ -138,6 +138,10 @@ export function createDispatch(ctx) {
|
|
|
138
138
|
...(typeof deps.capabilities?.version === "string" ? { version: deps.capabilities.version } : {}),
|
|
139
139
|
...healthDrainingKeys(signal),
|
|
140
140
|
...(deps.drainState?.inflight ? { inflight: deps.drainState.inflight() } : {}),
|
|
141
|
+
...(() => {
|
|
142
|
+
const lease = deps.drainState?.engineLease?.();
|
|
143
|
+
return lease ? { engineLease: { lingerMs: lease.lingerMs, tickMs: lease.tickMs } } : {};
|
|
144
|
+
})(),
|
|
141
145
|
...(deps.storePosture?.degradedReason !== undefined ? { durable: false, storeDegraded: true } : {}),
|
|
142
146
|
...healthReadinessKeys(signal.verdict),
|
|
143
147
|
model: deps.config.model.id,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /v1/tasks/stream`(活流腿:新提交、同键重放、在飞合流的跟随方)的**唯一**帧写口。
|
|
3
|
+
*
|
|
4
|
+
* 律(一条):这条流上的每一帧都是 `[id: <eventId>\n]event: <type>\ndata: <json>\n\n`,事件名 = 载荷的 `type`。
|
|
5
|
+
* 心跳是同一只写口写的 `event: heartbeat` + `data: {}`。与 durable 尾(`http/sse-log.ts`)、sdk 的 `toSseResponse` 中继同形。
|
|
6
|
+
*
|
|
7
|
+
* `id:` 行(契约 §0.6):载荷带非空 string `eventId` 才发,值逐字 = `eventId`;没有就不发,EventSource 沿用上一游标 ——
|
|
8
|
+
* 绝不合成占位 id(那会把不可锚的位置伪装成可锚)。`id:` 行是**每帧唯一**的续流游标,所以载荷的 `eventId` 必须是**本帧
|
|
9
|
+
* 自己的**身份(server 自铸帧的唯一铸点:`trace/model-segment.ts` 的 `reasoningSegmentEventId`)。三道门各管一段:
|
|
10
|
+
* {@link LiveFrame} 管「铸点说得出这两个键的型」;本函数的运行期判据管「真的是」(通体 `Record<string, unknown>` 的
|
|
11
|
+
* 构造器返回值进得来 —— `null` 读 `.length` 会把整条流摔掉、数组会被强转成一枚编出来的游标);`test/live-sse-cursor.test.ts`
|
|
12
|
+
* 的规则式全流判据管「同一条流内不重复」。
|
|
13
|
+
*/
|
|
14
|
+
import type { ServerResponse } from "node:http";
|
|
15
|
+
/** 活流帧的载荷型:`type` 是事件名(必填,型说得出口);`eventId` 在场即**本帧自己的**续流游标。其余键由各自铸点白名单。 */
|
|
16
|
+
export type LiveFrame = {
|
|
17
|
+
readonly type: string;
|
|
18
|
+
readonly eventId?: string;
|
|
19
|
+
} & Record<string, unknown>;
|
|
20
|
+
/** 写一帧。`type` 不是非空串 = 铸点违约(型门之外漏进来的通体记录),响亮抛 —— 写一行编出来的事件名是撒谎,不写事件名就回到两只形。 */
|
|
21
|
+
export declare function writeLiveFrame(res: ServerResponse, payload: LiveFrame): void;
|
|
22
|
+
/** 心跳:真帧,不是 SSE 注释(逐帧解析的 BFF 会丢注释行,下游看到的是一段零帧的窗)。没有监听心跳的 EventSource 客户端忽略它。 */
|
|
23
|
+
export declare function writeLiveHeartbeat(res: ServerResponse): void;
|
|
24
|
+
//# sourceMappingURL=live-frame.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { liveToolEndForBacklog } from "../trace/tool-output-media.js";
|
|
2
|
+
function frameBytes(event, data, anchor) {
|
|
3
|
+
const idLine = typeof anchor === "string" && anchor.length > 0 ? `id: ${anchor}\n` : "";
|
|
4
|
+
return `${idLine}event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
5
|
+
}
|
|
6
|
+
export function writeLiveFrame(res, payload) {
|
|
7
|
+
const name = payload.type;
|
|
8
|
+
if (typeof name !== "string" || name.length === 0)
|
|
9
|
+
throw new TypeError(`live SSE frame without a string type: ${JSON.stringify(name)}`);
|
|
10
|
+
const frame = name === "tool_end" ? liveToolEndForBacklog(payload, res.writableLength) : payload;
|
|
11
|
+
res.write(frameBytes(name, frame, payload.eventId));
|
|
12
|
+
}
|
|
13
|
+
export function writeLiveHeartbeat(res) {
|
|
14
|
+
res.write(frameBytes("heartbeat", {}, undefined));
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=live-frame.js.map
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -7,9 +7,10 @@ import { headReadOf } from "../session-incarnation.js";
|
|
|
7
7
|
import { createSessionWorkspaceRecorder, legSessionWitnessOf, sessionWorkspaceStoreOf } from "../session-workspace.js";
|
|
8
8
|
const DELIVERY_NOT_RECORDED = "workflow park decide: this delivery no longer holds the host session (its claim was released) — the decision was not recorded and this delivery was not started";
|
|
9
9
|
import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
10
|
-
import { graftParkToolCallId, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
10
|
+
import { graftParkToolCallId, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort, voidHeldParkCard } from "../runs.js";
|
|
11
11
|
import { atHostBoundary, failureCodeOf, nestedPauseOf, sealResultForExit, outwardPlane } from "../terminal.js";
|
|
12
|
-
import {
|
|
12
|
+
import { voidVerifierPauseCard } from "../verifier-pause-card.js";
|
|
13
|
+
import { buildCancelledLedgerRow, CARD_SETTLED_BY_CANCEL_MESSAGE, createRunAbort, mintCancelledResult, withCardSettledByCancelMessage } from "../run-cancel-context.js";
|
|
13
14
|
import { isTerminalRunStatus } from "../plugins/store-contracts.js";
|
|
14
15
|
import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
|
|
15
16
|
import { recordResumeResourceWindow } from "../resource-window.js";
|
|
@@ -679,6 +680,7 @@ export function createResumeLegs(ctx) {
|
|
|
679
680
|
fleetPub?.onTerminal(status, residuals);
|
|
680
681
|
};
|
|
681
682
|
let result;
|
|
683
|
+
let heldPark;
|
|
682
684
|
let reopenConfirmedPark = false;
|
|
683
685
|
const confirmCheckpointReopened = async () => {
|
|
684
686
|
const store = deps.checkpointStore;
|
|
@@ -695,28 +697,32 @@ export function createResumeLegs(ctx) {
|
|
|
695
697
|
const settleCardForCancel = async () => {
|
|
696
698
|
const cs2 = deps.checkpointStore;
|
|
697
699
|
if (!cs2)
|
|
698
|
-
return;
|
|
700
|
+
return false;
|
|
699
701
|
try {
|
|
700
702
|
const live = await cs2.get(token);
|
|
701
|
-
if (live?.status
|
|
702
|
-
|
|
703
|
-
|
|
703
|
+
if (live?.status !== "pending")
|
|
704
|
+
return false;
|
|
705
|
+
if (await cs2.expire(token, live.scope))
|
|
706
|
+
return true;
|
|
707
|
+
deps.logger?.warn?.("resume_cancel_checkpoint_settle_lost", { taskId, sessionId });
|
|
708
|
+
return false;
|
|
704
709
|
}
|
|
705
710
|
catch (cancelSettleErr) {
|
|
706
711
|
deps.logger?.warn?.("resume_cancel_checkpoint_settle_failed", {
|
|
707
712
|
taskId, sessionId, err: cancelSettleErr instanceof Error ? cancelSettleErr.message : String(cancelSettleErr),
|
|
708
713
|
});
|
|
714
|
+
return false;
|
|
709
715
|
}
|
|
710
716
|
};
|
|
711
717
|
const reopenedAfterLeg = async (leg, cancelledAtDone) => {
|
|
712
718
|
if (freshRunClaimed || !legMayHaveReopenedCheckpoint(leg))
|
|
713
|
-
return false;
|
|
719
|
+
return { reopened: false, leg };
|
|
714
720
|
const pending = cancelledAtDone ? false : await confirmCheckpointReopened();
|
|
715
721
|
if (cancelledAtDone || cancelCtrl.cause() !== undefined) {
|
|
716
|
-
await settleCardForCancel();
|
|
717
|
-
return false;
|
|
722
|
+
const expired = await settleCardForCancel();
|
|
723
|
+
return { reopened: false, leg: expired ? withCardSettledByCancelMessage(leg) : leg };
|
|
718
724
|
}
|
|
719
|
-
return pending;
|
|
725
|
+
return { reopened: pending, leg };
|
|
720
726
|
};
|
|
721
727
|
let resumeHeartbeat;
|
|
722
728
|
let resumeManifestLease = 0;
|
|
@@ -850,15 +856,18 @@ export function createResumeLegs(ctx) {
|
|
|
850
856
|
resumeLegLive = true;
|
|
851
857
|
const vrEngine = await resumeWithVerification(legRunner, token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, resumeObjective, verifyRounds, verifyInternals);
|
|
852
858
|
const vrCause = cancelCtrl.cause();
|
|
859
|
+
await voidVerifierPauseCard(vrEngine, deps.checkpointStore);
|
|
853
860
|
const vrJudged = atHostBoundary(vrEngine, sessionId);
|
|
854
|
-
|
|
861
|
+
if (vrJudged.terminal.kind === "paused")
|
|
862
|
+
heldPark = vrJudged;
|
|
863
|
+
const vrMinted = vrCause !== undefined && taskId !== undefined ? mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user", engineResult: vrJudged, cause: vrCause }) : vrJudged;
|
|
855
864
|
await commitResumeCtx();
|
|
865
|
+
const { reopened: vrReopened, leg: vr } = await reopenedAfterLeg(vrMinted, vrCause !== undefined);
|
|
856
866
|
let safe = sealResultForExit(vr);
|
|
857
867
|
const vrPaused = vr.terminal.kind === "paused" ? vr.terminal : undefined;
|
|
858
868
|
const vrParkWord = vrPaused !== undefined ? outwardPlane(vrPaused).status : undefined;
|
|
859
869
|
const reParkEvent = vrParkWord !== undefined && isParkLedgerEventType(vrParkWord) ? vrParkWord : undefined;
|
|
860
870
|
const reSuspended = reParkEvent !== undefined;
|
|
861
|
-
const vrReopened = await reopenedAfterLeg(vr, vrCause !== undefined);
|
|
862
871
|
if (vrReopened)
|
|
863
872
|
reopenConfirmedPark = true;
|
|
864
873
|
if (taskId && deps.runStore) {
|
|
@@ -916,7 +925,7 @@ export function createResumeLegs(ctx) {
|
|
|
916
925
|
const captureUserMessageAnchor = deps.resumeAnchorStore && taskId
|
|
917
926
|
? async (entryId) => { await deps.resumeAnchorStore.put(sessionId, taskId, entryId, anchorOwner); }
|
|
918
927
|
: undefined;
|
|
919
|
-
const anchor = new TurnAnchorCapture(captureTurnAnchor,
|
|
928
|
+
const anchor = new TurnAnchorCapture(captureTurnAnchor, "resume", captureUserMessageAnchor);
|
|
920
929
|
const flush = async () => {
|
|
921
930
|
if (reasoning) {
|
|
922
931
|
await append("reasoning", modelTextEventData({ text: reasoning, eventId: reasoningEventId }));
|
|
@@ -997,15 +1006,18 @@ export function createResumeLegs(ctx) {
|
|
|
997
1006
|
for await (const ev of stream)
|
|
998
1007
|
await resumeWorkspace.observe(ev);
|
|
999
1008
|
const rNoRow = await stream.result();
|
|
1000
|
-
|
|
1009
|
+
const noRowLeg = await reopenedAfterLeg(rNoRow, cancelCtrl.cause() !== undefined);
|
|
1010
|
+
if (noRowLeg.reopened)
|
|
1001
1011
|
reopenConfirmedPark = true;
|
|
1002
|
-
return sealResultForExit(
|
|
1012
|
+
return sealResultForExit(noRowLeg.leg);
|
|
1003
1013
|
}
|
|
1004
1014
|
onAccepted?.(acceptedResumingReceipt(taskId, sessionId, buildSettlementEvidence({ at: "engine_accepted", commitment: args.commitment })));
|
|
1005
1015
|
let causeAtDone;
|
|
1006
1016
|
for await (const ev of stream) {
|
|
1007
1017
|
if (ev.type === "done")
|
|
1008
1018
|
causeAtDone = cancelCtrl.cause();
|
|
1019
|
+
if (ev.type === "done" && ev.result.terminal.kind === "paused")
|
|
1020
|
+
heldPark = ev.result;
|
|
1009
1021
|
recordTurnActivity(taskId);
|
|
1010
1022
|
fleetPub?.onEvent(ev);
|
|
1011
1023
|
await deps.userInputLedger?.observeLegEvent(stream, ev);
|
|
@@ -1125,6 +1137,8 @@ export function createResumeLegs(ctx) {
|
|
|
1125
1137
|
await flush();
|
|
1126
1138
|
const rEngine = await stream.result();
|
|
1127
1139
|
const r = causeAtDone !== undefined ? mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user", engineResult: rEngine, cause: causeAtDone }) : rEngine;
|
|
1140
|
+
if (r.terminal.kind === "paused")
|
|
1141
|
+
heldPark = r;
|
|
1128
1142
|
const rPark = r.terminal.kind === "paused" ? outwardPlane(r.terminal).status : undefined;
|
|
1129
1143
|
if (rPark === "suspended") {
|
|
1130
1144
|
await append("suspended", parkEventData(await graftParkToolCallId(r, deps.checkpointStore)));
|
|
@@ -1136,7 +1150,8 @@ export function createResumeLegs(ctx) {
|
|
|
1136
1150
|
await appendModelUsageDelta(append, deps.modelUsage, taskId);
|
|
1137
1151
|
return r;
|
|
1138
1152
|
}
|
|
1139
|
-
|
|
1153
|
+
const settledLeg = await reopenedAfterLeg(r, causeAtDone !== undefined);
|
|
1154
|
+
if (settledLeg.reopened) {
|
|
1140
1155
|
reopenConfirmedPark = true;
|
|
1141
1156
|
try {
|
|
1142
1157
|
await append(reparkStatus, parkEventData(r, failureCodeOf(r.terminal) ?? null));
|
|
@@ -1146,7 +1161,7 @@ export function createResumeLegs(ctx) {
|
|
|
1146
1161
|
}
|
|
1147
1162
|
return r;
|
|
1148
1163
|
}
|
|
1149
|
-
const safe = await attachModelUsage(sealResultForExit(
|
|
1164
|
+
const safe = await attachModelUsage(sealResultForExit(settledLeg.leg), { append, getEvents: (id, a) => rs.getEvents(id, a), modelUsage: deps.modelUsage, taskId });
|
|
1150
1165
|
await append("done", { result: safe });
|
|
1151
1166
|
if (taskConfig.suggestNextPrompts && safe.terminal.kind === "completed") {
|
|
1152
1167
|
const suggestions = await stream.suggestions().catch(() => []);
|
|
@@ -1184,14 +1199,14 @@ export function createResumeLegs(ctx) {
|
|
|
1184
1199
|
if (e instanceof CheckpointError) {
|
|
1185
1200
|
const disposition = resumeRefusalDisposition(e);
|
|
1186
1201
|
if (disposition === "cancelled") {
|
|
1187
|
-
await settleCardForCancel();
|
|
1202
|
+
const abortedText = (await settleCardForCancel()) ? CARD_SETTLED_BY_CANCEL_MESSAGE : e.message;
|
|
1188
1203
|
if (claimedRow && taskId && deps.runStore) {
|
|
1189
1204
|
const handleCause = cancelCtrl.cause();
|
|
1190
1205
|
if (handleCause === undefined)
|
|
1191
1206
|
recordFailOpen("server.runs.abort-cause-missing", `task=${taskId} arm=resume-aborted`);
|
|
1192
|
-
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message:
|
|
1207
|
+
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: abortedText, cause: handleCause ?? "cancel_verb" });
|
|
1193
1208
|
await cancelLedgerAppend?.("failed", buildCancelledLedgerRow(c)).catch((err) => recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-append-resume-aborted ${errorText(err)}`));
|
|
1194
|
-
await deps.runStore.setTerminal(taskId, "failed", c,
|
|
1209
|
+
await deps.runStore.setTerminal(taskId, "failed", c, abortedText).catch(() => undefined);
|
|
1195
1210
|
}
|
|
1196
1211
|
settleFleet("failed");
|
|
1197
1212
|
return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
|
|
@@ -1219,6 +1234,7 @@ export function createResumeLegs(ctx) {
|
|
|
1219
1234
|
const thrownCause = cancelCtrl.cause();
|
|
1220
1235
|
if (thrownCause !== undefined) {
|
|
1221
1236
|
if (taskId && deps.runStore) {
|
|
1237
|
+
await voidHeldParkCard(heldPark, sessionId, deps.checkpointStore, "resume");
|
|
1222
1238
|
const c = mintCancelledResult({ taskId, sessionId, startedAtMs: resumeLegStartedAt, message: "cancelled by user", cause: thrownCause });
|
|
1223
1239
|
await cancelLedgerAppend?.("failed", buildCancelledLedgerRow(c)).catch((err) => recordFailOpen("server.runs.terminal-ledger-write-failed", `arm=resume-append-thrown-cancel ${errorText(err)}`));
|
|
1224
1240
|
await deps.runStore.setTerminal(taskId, "failed", c, "cancelled by user").catch(() => undefined);
|
|
@@ -1227,6 +1243,7 @@ export function createResumeLegs(ctx) {
|
|
|
1227
1243
|
return { status: 200, body: { taskId, sessionId, status: "failed", errorCode: "cancelled" } };
|
|
1228
1244
|
}
|
|
1229
1245
|
if (claimedRow && taskId && deps.runStore) {
|
|
1246
|
+
await voidHeldParkCard(heldPark, sessionId, deps.checkpointStore, "resume");
|
|
1230
1247
|
await deps.runStore.setTerminal(taskId, "failed", null, errorText(e)).catch(() => undefined);
|
|
1231
1248
|
}
|
|
1232
1249
|
settleFleet("failed");
|
package/dist/http/route-table.js
CHANGED
|
@@ -29,6 +29,7 @@ import { handleNotifyWake, NOTIFY_WAKE_ROUTES } from "./routes/notify-wake.js";
|
|
|
29
29
|
import { handleApprovalsAssistant, APPROVALS_ASSISTANT_ROUTES } from "./routes/approvals-assistant.js";
|
|
30
30
|
import { handleRuns, handleRunVerbs, RUNS_ROUTES, RUN_VERBS_ROUTES } from "./routes/runs.js";
|
|
31
31
|
import { handleRunMemoryCapture, RUN_MEMORY_CAPTURE_ROUTES } from "./routes/run-memory-capture.js";
|
|
32
|
+
import { handlePermissionMode, RUN_PERMISSION_MODE_ROUTES } from "./routes/run-permission-mode.js";
|
|
32
33
|
import { handleMcpReconnect, MCP_RECONNECT_ROUTES } from "./routes/mcp-reconnect.js";
|
|
33
34
|
import { handleBackground, BACKGROUND_ROUTES } from "./routes/background.js";
|
|
34
35
|
import { handleSideQuery, SIDE_QUERY_ROUTES } from "./routes/side-query.js";
|
|
@@ -51,6 +52,7 @@ export const ROUTES = [
|
|
|
51
52
|
row({ handler: handleWorkflowAgentSteer, decls: WORKFLOW_AGENT_STEER_ROUTES, position: "post-credential", label: "workflow-agent-steer" }),
|
|
52
53
|
row({ handler: handleRunVerbs, decls: RUN_VERBS_ROUTES, position: "post-credential", label: "run-verbs" }),
|
|
53
54
|
row({ handler: handleRunMemoryCapture, decls: RUN_MEMORY_CAPTURE_ROUTES, position: "post-credential", label: "run-memory-capture" }),
|
|
55
|
+
row({ handler: handlePermissionMode, decls: RUN_PERMISSION_MODE_ROUTES, position: "post-credential", label: "permission-mode" }),
|
|
54
56
|
row({ handler: handleMcpReconnect, decls: MCP_RECONNECT_ROUTES, position: "post-credential", label: "mcp-reconnect" }),
|
|
55
57
|
row({ handler: handleBackground, decls: BACKGROUND_ROUTES, position: "post-credential", label: "background" }),
|
|
56
58
|
row({ handler: handleLeader, decls: LEADER_ROUTES, position: "post-credential", label: "leader" }),
|
|
@@ -408,6 +408,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
408
408
|
...(s.mandate !== undefined ? { mandate: s.mandate } : {}),
|
|
409
409
|
...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
|
|
410
410
|
...(s.origin !== undefined ? { origin: s.origin } : {}),
|
|
411
|
+
...(s.boundGrantDigest !== undefined ? { boundGrantDigest: s.boundGrantDigest } : {}),
|
|
411
412
|
...skillCardFacts(s),
|
|
412
413
|
...(s.boundGrantAbsence !== undefined ? { boundGrantAbsence: s.boundGrantAbsence } : {}),
|
|
413
414
|
objective,
|
|
@@ -24,7 +24,7 @@ import { projectStoreCapability } from "../../store-posture.js";
|
|
|
24
24
|
import { projectWebSearchCapability } from "../../plugins/web-search.js";
|
|
25
25
|
import { projectExecutionLaneCapability } from "../../capabilities/execution-lane.js";
|
|
26
26
|
import { judgeSandboxGitPlane, sandboxGitPlanePremisesOf } from "../../sandbox-git-plane.js";
|
|
27
|
-
import { MCP_ASSERTION_HEADER_SENT } from "../../mcp-assertion-header.js";
|
|
27
|
+
import { MCP_ASSERTION_HEADER_SENT, MCP_REQUEST_TRANSPORT_KINDS } from "../../mcp-assertion-header.js";
|
|
28
28
|
import { gateCompositeMembers } from "../../capabilities/composite-members.js";
|
|
29
29
|
import { projectApprovalsStreamLiveCapability } from "../../capabilities/approvals-push.js";
|
|
30
30
|
import { workflowModelAllowlistFor } from "../../task-workflow.js";
|
|
@@ -96,7 +96,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
96
96
|
maxInflightPerDevice: deps.config.remoteExec?.provider === "device" ? (deps.config.remoteExec.maxInflightPerDevice ?? 4) : 4,
|
|
97
97
|
wsPath: DEVICE_WS_PATH,
|
|
98
98
|
management: facts.deviceManagement(deps).available,
|
|
99
|
-
...(deps.config.remoteExec?.provider === "device" && deps.config.remoteExec.
|
|
99
|
+
...(deps.config.remoteExec?.provider === "device" && deps.config.remoteExec.relay.kind === "url" ? { relayUrl: deps.config.remoteExec.relay.url } : {}),
|
|
100
100
|
}
|
|
101
101
|
: false,
|
|
102
102
|
sessionEvents: facts.sessionEventStream(deps).available,
|
|
@@ -133,7 +133,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
133
133
|
oneShot: true,
|
|
134
134
|
policy: true,
|
|
135
135
|
usage: Boolean(deps.costQuota),
|
|
136
|
-
permissionModeWrite:
|
|
136
|
+
permissionModeWrite: facts.permissionModeWrite(deps).available,
|
|
137
137
|
modelSelection: true,
|
|
138
138
|
effortSelection: true,
|
|
139
139
|
toolOutput: true,
|
|
@@ -143,7 +143,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
143
143
|
subagentSteer: facts.subagentSteer(deps).available,
|
|
144
144
|
subagentResume: facts.subagentSteer(deps).available,
|
|
145
145
|
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: DEPLOYMENT_SHAPE_PREDICATES.callerCwd.holds(deps.config), hooks: taskHooksHonored(deps.config), pluginHooks: taskHooksHonored(deps.config), managedHookGroups: taskHooksHonored(deps.config), tightenOnlySeats: true, approvalFrameFamilies: true },
|
|
146
|
-
settings: { permissions: { allowGrant: true, allowLayer: true, callerTrust: callerTrust(req, deps.config).trust } },
|
|
146
|
+
settings: { permissions: { allowGrant: true, allowLayer: true, layers: true, callerTrust: callerTrust(req, deps.config).trust } },
|
|
147
147
|
permissionModeAuto: {
|
|
148
148
|
accepted: true,
|
|
149
149
|
classifierSeat: deps.autoModeFace?.seatMounted === true,
|
|
@@ -181,6 +181,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
181
181
|
mcp: true,
|
|
182
182
|
mcpProbe: true,
|
|
183
183
|
mcpAssertionHeaderSent: MCP_ASSERTION_HEADER_SENT,
|
|
184
|
+
mcpTransportKinds: [...MCP_REQUEST_TRANSPORT_KINDS],
|
|
184
185
|
mcpElicitation: facts.elicitation(deps).available,
|
|
185
186
|
askUserQuestion: facts.askUserQuestion(deps).available,
|
|
186
187
|
toolApproval: facts.toolApproval(deps).available,
|