@sema-agent/server 7.49.0 → 7.51.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/USAGE.md +39 -0
- package/dist/boot/device-lane.d.ts +79 -0
- package/dist/boot/device-lane.js +63 -0
- package/dist/boot/engine-lease.d.ts +95 -0
- package/dist/boot/engine-lease.js +56 -0
- package/dist/boot/execution-env.d.ts +48 -2
- package/dist/boot/execution-env.js +57 -5
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +12 -9
- package/dist/boot/session-faces.d.ts +5 -0
- package/dist/boot/session-faces.js +4 -1
- package/dist/boot/shutdown.d.ts +10 -0
- package/dist/boot/shutdown.js +19 -1
- package/dist/boot/stores.js +9 -0
- package/dist/config-center/types.d.ts +10 -3
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +18 -0
- package/dist/config-types.d.ts +32 -1
- package/dist/config.js +69 -10
- package/dist/device-enrollment.d.ts +125 -0
- package/dist/device-enrollment.js +156 -0
- package/dist/device-store.d.ts +385 -0
- package/dist/device-store.js +407 -0
- package/dist/device-ws-hub.d.ts +182 -0
- package/dist/device-ws-hub.js +1012 -0
- package/dist/device-ws-protocol.d.ts +429 -0
- package/dist/device-ws-protocol.js +464 -0
- package/dist/env-facts.d.ts +4 -1
- package/dist/env-facts.js +1 -0
- package/dist/execution-lane-caps.d.ts +152 -0
- package/dist/execution-lane-caps.js +166 -0
- package/dist/fleet/subagent-tail-bus.d.ts +1 -1
- package/dist/fleet/subagent-tail-bus.js +10 -0
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +7 -2
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/tasks.js +12 -6
- package/dist/http/send.d.ts +14 -0
- package/dist/http/send.js +5 -0
- package/dist/http/server.d.ts +19 -0
- package/dist/http/server.js +21 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/leader/wire.js +4 -2
- package/dist/main.js +10 -3
- package/dist/orchestration/hardened-vm-runner.d.ts +7 -0
- package/dist/orchestration/hardened-vm-runner.js +11 -1
- package/dist/orchestration/hardened-vm-worker-runner.js +2 -2
- package/dist/plugins/device-store-sql.d.ts +130 -0
- package/dist/plugins/device-store-sql.js +574 -0
- package/dist/plugins/remote-env-device.d.ts +271 -0
- package/dist/plugins/remote-env-device.js +727 -0
- package/dist/plugins/remote-scratchpad.js +1 -1
- package/dist/plugins/store-backend.d.ts +9 -0
- package/dist/plugins/store-backend.js +3 -0
- package/dist/plugins/usage-window-store-sql.d.ts +2 -2
- package/dist/plugins/usage-window-store-sql.js +17 -6
- package/dist/task-cwd.d.ts +25 -0
- package/dist/task-cwd.js +3 -0
- package/dist/task-settings.js +3 -1
- package/dist/trace/core-keyset-guard.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const NO = "unsupported";
|
|
2
|
+
const YES = "supported";
|
|
3
|
+
const SU = "single-user-only";
|
|
4
|
+
export function executionLaneCaps(lane) {
|
|
5
|
+
switch (lane) {
|
|
6
|
+
case "in-process":
|
|
7
|
+
return {
|
|
8
|
+
callerCwd: NO,
|
|
9
|
+
backgroundShell: NO,
|
|
10
|
+
scheduler: NO,
|
|
11
|
+
lsp: NO,
|
|
12
|
+
sandboxSendUserFile: NO,
|
|
13
|
+
isolation: NO,
|
|
14
|
+
suspendable: NO,
|
|
15
|
+
remoteScratchpad: NO,
|
|
16
|
+
sandboxPathAdjudication: NO,
|
|
17
|
+
worktreeIsolation: NO,
|
|
18
|
+
memoryPersistenceCapable: NO,
|
|
19
|
+
hostFilePlane: YES,
|
|
20
|
+
coreRemoteExecutionEnv: NO,
|
|
21
|
+
};
|
|
22
|
+
case "host":
|
|
23
|
+
return {
|
|
24
|
+
callerCwd: SU,
|
|
25
|
+
backgroundShell: SU,
|
|
26
|
+
scheduler: SU,
|
|
27
|
+
lsp: SU,
|
|
28
|
+
sandboxSendUserFile: NO,
|
|
29
|
+
isolation: NO,
|
|
30
|
+
suspendable: NO,
|
|
31
|
+
remoteScratchpad: NO,
|
|
32
|
+
sandboxPathAdjudication: NO,
|
|
33
|
+
worktreeIsolation: YES,
|
|
34
|
+
memoryPersistenceCapable: YES,
|
|
35
|
+
hostFilePlane: YES,
|
|
36
|
+
coreRemoteExecutionEnv: YES,
|
|
37
|
+
};
|
|
38
|
+
case "e2b":
|
|
39
|
+
return {
|
|
40
|
+
callerCwd: NO,
|
|
41
|
+
backgroundShell: YES,
|
|
42
|
+
scheduler: NO,
|
|
43
|
+
lsp: YES,
|
|
44
|
+
sandboxSendUserFile: YES,
|
|
45
|
+
isolation: YES,
|
|
46
|
+
suspendable: YES,
|
|
47
|
+
remoteScratchpad: YES,
|
|
48
|
+
sandboxPathAdjudication: YES,
|
|
49
|
+
worktreeIsolation: NO,
|
|
50
|
+
memoryPersistenceCapable: NO,
|
|
51
|
+
hostFilePlane: NO,
|
|
52
|
+
coreRemoteExecutionEnv: YES,
|
|
53
|
+
};
|
|
54
|
+
case "k8s":
|
|
55
|
+
return {
|
|
56
|
+
callerCwd: NO,
|
|
57
|
+
backgroundShell: YES,
|
|
58
|
+
scheduler: NO,
|
|
59
|
+
lsp: YES,
|
|
60
|
+
sandboxSendUserFile: YES,
|
|
61
|
+
isolation: YES,
|
|
62
|
+
suspendable: YES,
|
|
63
|
+
remoteScratchpad: YES,
|
|
64
|
+
sandboxPathAdjudication: YES,
|
|
65
|
+
worktreeIsolation: NO,
|
|
66
|
+
memoryPersistenceCapable: NO,
|
|
67
|
+
hostFilePlane: NO,
|
|
68
|
+
coreRemoteExecutionEnv: YES,
|
|
69
|
+
};
|
|
70
|
+
case "ssh":
|
|
71
|
+
return {
|
|
72
|
+
callerCwd: NO,
|
|
73
|
+
backgroundShell: NO,
|
|
74
|
+
scheduler: NO,
|
|
75
|
+
lsp: NO,
|
|
76
|
+
sandboxSendUserFile: SU,
|
|
77
|
+
isolation: NO,
|
|
78
|
+
suspendable: NO,
|
|
79
|
+
remoteScratchpad: YES,
|
|
80
|
+
sandboxPathAdjudication: YES,
|
|
81
|
+
worktreeIsolation: NO,
|
|
82
|
+
memoryPersistenceCapable: NO,
|
|
83
|
+
hostFilePlane: NO,
|
|
84
|
+
coreRemoteExecutionEnv: YES,
|
|
85
|
+
};
|
|
86
|
+
case "adb":
|
|
87
|
+
return {
|
|
88
|
+
callerCwd: NO,
|
|
89
|
+
backgroundShell: NO,
|
|
90
|
+
scheduler: NO,
|
|
91
|
+
lsp: NO,
|
|
92
|
+
sandboxSendUserFile: NO,
|
|
93
|
+
isolation: NO,
|
|
94
|
+
suspendable: NO,
|
|
95
|
+
remoteScratchpad: NO,
|
|
96
|
+
sandboxPathAdjudication: YES,
|
|
97
|
+
worktreeIsolation: NO,
|
|
98
|
+
memoryPersistenceCapable: NO,
|
|
99
|
+
hostFilePlane: NO,
|
|
100
|
+
coreRemoteExecutionEnv: YES,
|
|
101
|
+
};
|
|
102
|
+
case "local-docker":
|
|
103
|
+
return {
|
|
104
|
+
callerCwd: NO,
|
|
105
|
+
backgroundShell: NO,
|
|
106
|
+
scheduler: NO,
|
|
107
|
+
lsp: NO,
|
|
108
|
+
sandboxSendUserFile: NO,
|
|
109
|
+
isolation: YES,
|
|
110
|
+
suspendable: NO,
|
|
111
|
+
remoteScratchpad: YES,
|
|
112
|
+
sandboxPathAdjudication: YES,
|
|
113
|
+
worktreeIsolation: NO,
|
|
114
|
+
memoryPersistenceCapable: NO,
|
|
115
|
+
hostFilePlane: NO,
|
|
116
|
+
coreRemoteExecutionEnv: YES,
|
|
117
|
+
};
|
|
118
|
+
case "device":
|
|
119
|
+
return {
|
|
120
|
+
callerCwd: YES,
|
|
121
|
+
backgroundShell: NO,
|
|
122
|
+
scheduler: NO,
|
|
123
|
+
lsp: NO,
|
|
124
|
+
sandboxSendUserFile: NO,
|
|
125
|
+
isolation: NO,
|
|
126
|
+
suspendable: NO,
|
|
127
|
+
remoteScratchpad: YES,
|
|
128
|
+
sandboxPathAdjudication: YES,
|
|
129
|
+
worktreeIsolation: NO,
|
|
130
|
+
memoryPersistenceCapable: NO,
|
|
131
|
+
hostFilePlane: NO,
|
|
132
|
+
coreRemoteExecutionEnv: YES,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const EXECUTION_LANE_WORDS = {
|
|
137
|
+
"in-process": true,
|
|
138
|
+
host: true,
|
|
139
|
+
e2b: true,
|
|
140
|
+
k8s: true,
|
|
141
|
+
ssh: true,
|
|
142
|
+
adb: true,
|
|
143
|
+
"local-docker": true,
|
|
144
|
+
device: true,
|
|
145
|
+
};
|
|
146
|
+
export function isExecutionLane(word) {
|
|
147
|
+
return Object.hasOwn(EXECUTION_LANE_WORDS, word);
|
|
148
|
+
}
|
|
149
|
+
export const ALL_EXECUTION_LANES = Object.freeze(Object.keys(EXECUTION_LANE_WORDS).filter(isExecutionLane));
|
|
150
|
+
export function executionLaneOf(provider) {
|
|
151
|
+
if (provider === undefined)
|
|
152
|
+
return "in-process";
|
|
153
|
+
return isExecutionLane(provider) ? provider : undefined;
|
|
154
|
+
}
|
|
155
|
+
export function laneCapabilityHolds(cap, deployment) {
|
|
156
|
+
switch (cap) {
|
|
157
|
+
case "supported":
|
|
158
|
+
return true;
|
|
159
|
+
case "unsupported":
|
|
160
|
+
return false;
|
|
161
|
+
case "single-user-only":
|
|
162
|
+
return deployment.requirePrincipal !== true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export const PLANNED_LANE_DIVERGENCES = Object.freeze([]);
|
|
166
|
+
//# sourceMappingURL=execution-lane-caps.js.map
|
|
@@ -7,7 +7,7 @@ export type SubagentTailFrame = Record<string, unknown> & {
|
|
|
7
7
|
type: string;
|
|
8
8
|
};
|
|
9
9
|
/** core forward 事件 → wire 投影(sync 主流五分支同源;不认识的帧类型=null 不发——tail 面只承诺
|
|
10
|
-
* content
|
|
10
|
+
* content 六类,新 core 帧类型先经这里显式收编再出 wire)。 */
|
|
11
11
|
export declare function projectTailFrame(e: Record<string, unknown> & {
|
|
12
12
|
type?: string;
|
|
13
13
|
}): SubagentTailFrame | null;
|
|
@@ -18,6 +18,16 @@ export function projectTailFrame(e) {
|
|
|
18
18
|
const td = e;
|
|
19
19
|
return { type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) };
|
|
20
20
|
}
|
|
21
|
+
if (t === "text_end") {
|
|
22
|
+
if (typeof e.content !== "string" || e.content.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
type: "text_end",
|
|
26
|
+
content: e.content,
|
|
27
|
+
...(typeof e.eventId === "string" ? { eventId: e.eventId } : {}),
|
|
28
|
+
...(typeof e.parentToolCallId === "string" ? { parentToolCallId: e.parentToolCallId } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
21
31
|
if (t === "reasoning_delta") {
|
|
22
32
|
const rd = e;
|
|
23
33
|
return { type: "reasoning_delta", delta: redactSecrets(rd.delta), ...(rd.eventId ? { eventId: rd.eventId } : {}), ...(rd.parentToolCallId ? { parentToolCallId: rd.parentToolCallId } : {}) };
|
|
@@ -60,7 +60,7 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
60
60
|
return undefined;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
if (row.boundInputHash !==
|
|
63
|
+
if (binding.boundInputHash !== undefined && row.boundInputHash !== binding.boundInputHash)
|
|
64
64
|
return undefined;
|
|
65
65
|
if (row.decision !== decision)
|
|
66
66
|
return undefined;
|
|
@@ -2,7 +2,8 @@ import { DEFAULT_EFFORT_LEVELS, expandTiers, routePairingStatus } from "@sema-ag
|
|
|
2
2
|
import { isModelAllowlisted } from "../../model-select.js";
|
|
3
3
|
import { resolveModelApiKey } from "../../key-resolver.js";
|
|
4
4
|
import { isSealedKeyPoison } from "../../sealed-key.js";
|
|
5
|
-
import { cwdHonored } from "../../task-cwd.js";
|
|
5
|
+
import { cwdHonored, deviceCwdHonored } from "../../task-cwd.js";
|
|
6
|
+
import { DEVICE_PROTOCOL_VERSION, DEVICE_WS_PATH } from "../../device-ws-protocol.js";
|
|
6
7
|
import { a2aInjectionHonored } from "../../task-a2a.js";
|
|
7
8
|
import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
8
9
|
import { sendJson, sendError } from "../send.js";
|
|
@@ -47,6 +48,9 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
47
48
|
artifacts: Boolean(deps.runStore),
|
|
48
49
|
approvals: Boolean(deps.checkpointStore),
|
|
49
50
|
sessions: Boolean(deps.sessionAudit),
|
|
51
|
+
deviceExecutor: deps.deviceHub
|
|
52
|
+
? { enabled: true, protocolVersion: DEVICE_PROTOCOL_VERSION, maxInflightPerDevice: deps.config.remoteExec?.provider === "device" ? (deps.config.remoteExec.maxInflightPerDevice ?? 4) : 4, wsPath: DEVICE_WS_PATH }
|
|
53
|
+
: false,
|
|
50
54
|
sessionEvents: Boolean(deps.sessionWatch && deps.sessionStorage?.getLeafId && deps.sessionStorage?.ownerOf),
|
|
51
55
|
fleet: deps.fleetBus
|
|
52
56
|
? { stream: true, sessionScope: true, observe: true, resume: "snapshot", bgNotifyFailClosed: true, steerPriority: "advisory", hookFailureNotice: true }
|
|
@@ -86,7 +90,7 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
86
90
|
scratchpadDir: true,
|
|
87
91
|
subagentSteer: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
88
92
|
subagentResume: Boolean(deps.subagentSteerRegistry) && Boolean(deps.runStore),
|
|
89
|
-
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config), hooks: deps.config.requirePrincipal !== true },
|
|
93
|
+
taskSettings: { permissions: true, permissionMode: true, model: true, outputStyle: true, env: cwdHonored(deps.config) || deviceCwdHonored(deps.config), hooks: deps.config.requirePrincipal !== true },
|
|
90
94
|
modeShellGateTranslation: true,
|
|
91
95
|
permissionModeAuto: {
|
|
92
96
|
accepted: true,
|
|
@@ -103,6 +107,7 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
103
107
|
retainBackgroundProcesses: deps.config.requirePrincipal !== true,
|
|
104
108
|
interactiveTools: true,
|
|
105
109
|
projectContext: cwdHonored(deps.config),
|
|
110
|
+
callerCwd: cwdHonored(deps.config) || deviceCwdHonored(deps.config),
|
|
106
111
|
mcpInjection: mcpInjectionHonored(deps.config),
|
|
107
112
|
a2a: true,
|
|
108
113
|
a2aInjection: a2aInjectionHonored(deps.config),
|
|
@@ -366,7 +366,7 @@ async function handleSessionsBody(req, res, url, ctx, miss) {
|
|
|
366
366
|
sendError(res, 503, "state.sse_probe_cap", "session-events probe cap reached — fall back to /head polling");
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
|
-
res
|
|
369
|
+
sseHeaders(res, { "cache-control": "no-cache, no-transform", "x-accel-buffering": "no" });
|
|
370
370
|
writeRaw(": connected\n\n");
|
|
371
371
|
started = true;
|
|
372
372
|
if (preVal !== undefined)
|
|
@@ -331,6 +331,10 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
331
331
|
const td = e;
|
|
332
332
|
sseData(res, { type: "text_delta", delta: td.delta, ...(td.eventId ? { eventId: td.eventId } : {}), ...(td.parentToolCallId ? { parentToolCallId: td.parentToolCallId } : {}) });
|
|
333
333
|
}
|
|
334
|
+
else if (t === "text_end") {
|
|
335
|
+
const tend = e;
|
|
336
|
+
sseData(res, { type: "text_end", content: tend.content, ...(tend.eventId ? { eventId: tend.eventId } : {}), ...(tend.parentToolCallId ? { parentToolCallId: tend.parentToolCallId } : {}) });
|
|
337
|
+
}
|
|
334
338
|
else if (t === "reasoning_delta") {
|
|
335
339
|
const rd = e;
|
|
336
340
|
sseData(res, { type: "reasoning_delta", delta: redactSecrets(rd.delta), ...(rd.eventId ? { eventId: rd.eventId } : {}), ...(rd.parentToolCallId ? { parentToolCallId: rd.parentToolCallId } : {}) });
|
|
@@ -459,7 +463,7 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
459
463
|
else if (ev.type === "human_input") {
|
|
460
464
|
sseData(res, { type: "human_input", ...humanInputEventData(ev) });
|
|
461
465
|
}
|
|
462
|
-
else if (ev.type === "text_delta" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
|
|
466
|
+
else if (ev.type === "text_delta" || ev.type === "text_end" || ev.type === "turn_end" || ev.type === "message_committed" || ev.type === "context_usage") {
|
|
463
467
|
const e = ev;
|
|
464
468
|
const ident = {
|
|
465
469
|
...(e.eventId !== undefined ? { eventId: e.eventId } : {}),
|
|
@@ -469,11 +473,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
469
473
|
};
|
|
470
474
|
const arm = ev.type === "text_delta"
|
|
471
475
|
? { type: "text_delta", delta: e.delta, ...ident }
|
|
472
|
-
: ev.type === "
|
|
473
|
-
? { type: "
|
|
474
|
-
: ev.type === "
|
|
475
|
-
? { type: "
|
|
476
|
-
:
|
|
476
|
+
: ev.type === "text_end"
|
|
477
|
+
? { type: "text_end", content: e.content, ...ident }
|
|
478
|
+
: ev.type === "turn_end"
|
|
479
|
+
? { type: "turn_end", ...(e.usage !== undefined ? { usage: e.usage } : {}), ...(e.usageMissing !== undefined ? { usageMissing: e.usageMissing } : {}), ...(e.stopReason !== undefined ? { stopReason: e.stopReason } : {}), ...ident }
|
|
480
|
+
: ev.type === "message_committed"
|
|
481
|
+
? { type: "message_committed", entryId: e.entryId, role: e.role, ...(e.toolCallId !== undefined ? { toolCallId: e.toolCallId } : {}), ...ident }
|
|
482
|
+
: { type: "context_usage", ...contextUsageEventData(ev), ...ident };
|
|
477
483
|
sseData(res, arm);
|
|
478
484
|
}
|
|
479
485
|
else {
|
package/dist/http/send.d.ts
CHANGED
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
* now imports them from here, so all ~500 existing `sendJson(...)` call sites are unchanged text.
|
|
9
9
|
*/
|
|
10
10
|
import type { ServerResponse } from "node:http";
|
|
11
|
+
/**
|
|
12
|
+
* #118(黑板 [5371]②)—— 一条响应「是 SSE 流」的**在场标记**。
|
|
13
|
+
*
|
|
14
|
+
* 引擎附着租约把**打开着的 SSE 流**当作壳的附着租约来数,于是 `handle()` 需要判「这条响应是不是流」。
|
|
15
|
+
* 🔴 **不能靠读回 content-type**(本机 node v24.2.0 实测):`res.writeHead(status, headersObject)` 直接
|
|
16
|
+
* 把头写进网络缓冲,`res.getHeader("content-type")` / `res.hasHeader(...)` 在那之后**仍然**回
|
|
17
|
+
* `undefined` / `false`。照那条路写会得到一个恒为 0 的附着计数 —— 而恒 0 的方向恰好是「以为没人附着」
|
|
18
|
+
* ⇒ 在一只有人用的引擎底下触发自退。所以判据必须是我们自己在开流那一刻盖的这个章。
|
|
19
|
+
*
|
|
20
|
+
* symbol 键:不上 wire、不进 `JSON.stringify`、不与任何应用属性撞名。
|
|
21
|
+
*/
|
|
22
|
+
export declare const SSE_RESPONSE_MARK: unique symbol;
|
|
23
|
+
/** 这条响应是否已开成 SSE 流(= 走过 {@link sseHeaders})。 */
|
|
24
|
+
export declare function isSseResponse(res: ServerResponse): boolean;
|
|
11
25
|
export declare function sseHeaders(res: ServerResponse, extra?: Record<string, string>): void;
|
|
12
26
|
/**
|
|
13
27
|
* 一条 SSE 连接的墙钟上限。到点发一帧 `error`/`STREAM_MAX_DURATION`(**不是**静默关闭 —— 客户端分不出
|
package/dist/http/send.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
export const SSE_RESPONSE_MARK = Symbol("sema.http.sse-response");
|
|
2
|
+
export function isSseResponse(res) {
|
|
3
|
+
return res[SSE_RESPONSE_MARK] === true;
|
|
4
|
+
}
|
|
1
5
|
export function sseHeaders(res, extra) {
|
|
6
|
+
res[SSE_RESPONSE_MARK] = true;
|
|
2
7
|
res.writeHead(200, {
|
|
3
8
|
"content-type": "text/event-stream",
|
|
4
9
|
"cache-control": "no-cache",
|
package/dist/http/server.d.ts
CHANGED
|
@@ -242,6 +242,20 @@ export interface ServiceCoordinatorDeps {
|
|
|
242
242
|
* 重组零件——见 backend 的同款论证)。parked-revive 三件是同族的构件缝(boot 期从生产装配取的裸
|
|
243
243
|
* ToolSpec + 成员集 + 父约束重建器),与 backgroundAgentStore 齐备才构成赎回腿。 */
|
|
244
244
|
export interface ServiceSeamDeps {
|
|
245
|
+
/**
|
|
246
|
+
* device lane 的 WS 汇聚端(design/device-executor-lane-v2 §5,车A-3)—— 本仓**首个** WS 服务端。
|
|
247
|
+
* 给了就把 `GET /v1/device/ws` 的 upgrade 处理器挂到这台 `http.Server` 上;缺席 = 本部署没有 device
|
|
248
|
+
* 车道 ⇒ 该路径连 upgrade 都不认(与 `REMOTE_EXEC=device` 的拒启门同一条诚实线)。
|
|
249
|
+
*
|
|
250
|
+
* ⚠️ 排空联动由**装配层**驱动:`drainState.draining` 翻真时,main.ts 须同时调 `deviceHub.beginDrain()`
|
|
251
|
+
* ——两者刻意不在这里自动耦合,因为 §8-R7 的顺序(新提交 503 → 新 upgrade 拒 → 停发新指令 →
|
|
252
|
+
* 在途结果照收 → 才断连)是**部署编排**的语义,不是 HTTP 层能自作主张的。
|
|
253
|
+
*
|
|
254
|
+
* 🔴 **当前生产装配恒缺席,这是有意的**(`parkedKnownAgentTypes` 同族形):车A-3 只交汇聚端与这条
|
|
255
|
+
* 挂载缝;`main.ts` 侧的真装配(建店 → 建 hub → 传这个键 → 与 `drainState` 联动 → 与
|
|
256
|
+
* `REMOTE_EXEC=device` 拒启门对齐)属**车A-4/端点车**。在那之前本键只有测试口消费,不是死枝。
|
|
257
|
+
*/
|
|
258
|
+
deviceHub?: import("../device-ws-hub.js").DeviceWsHub;
|
|
245
259
|
/** Audit回溯: current context (+ summary) for a session, plus its owner. Enables GET /v1/sessions/:id. */
|
|
246
260
|
sessionAudit?: (sessionId: string) => Promise<({
|
|
247
261
|
owner: string | null;
|
|
@@ -513,6 +527,9 @@ export interface ServiceDeploymentDeps {
|
|
|
513
527
|
* • `inflight` is ASSIGNED BY createServer (a live union of this replica's in-flight legs: durable bg/resume
|
|
514
528
|
* `inflightRuns` + live sync/resume streams `steerableRuns`) — main.ts polls it to know when drain is done.
|
|
515
529
|
* • /health carries `draining:true` (k8s readiness 摘流信号) + `version` (build self-description).
|
|
530
|
+
* • #118:`attachedStreams` 同样 ASSIGNED BY createServer —— 本副本当前打开着的 SSE 流条数
|
|
531
|
+
* (= 引擎附着租约集的大小)。消费方是 `boot/engine-lease.ts` 的自退判据;与 `inflight` 同族,
|
|
532
|
+
* 同一个共享盒子,不另开第二条 main↔server 的通道。
|
|
516
533
|
*/
|
|
517
534
|
drainState?: {
|
|
518
535
|
draining: boolean;
|
|
@@ -520,6 +537,8 @@ export interface ServiceDeploymentDeps {
|
|
|
520
537
|
reason?: string;
|
|
521
538
|
inflight?: () => number;
|
|
522
539
|
lastActivityAt?: () => number;
|
|
540
|
+
attachedStreams?: () => number;
|
|
541
|
+
lastAttachEndedAt?: () => number;
|
|
523
542
|
};
|
|
524
543
|
/** boot ready 门(b):false = registry 部署无显式 env 模型且首次 effective pull 尚未落 roster
|
|
525
544
|
* (worker 只有占位模型)。计费提交 503 + /health 加性 `ready:false`。absent = 恒 ready(env 模型在/非
|
package/dist/http/server.js
CHANGED
|
@@ -70,7 +70,7 @@ import { cascadeConfig } from "./run-meta.js";
|
|
|
70
70
|
export { cascadeConfig };
|
|
71
71
|
import { handleImages, createImagesLocal, coarseStatusForState, errorCodeForExit } from "./routes/images.js";
|
|
72
72
|
export { coarseStatusForState, errorCodeForExit };
|
|
73
|
-
import { sendJson, sendError, httpErrorCode, msg } from "./send.js";
|
|
73
|
+
import { sendJson, sendError, httpErrorCode, msg, isSseResponse } from "./send.js";
|
|
74
74
|
import { authorized, systemFor, gatedPrincipal, explicitOperatorOk, isOperator } from "./principal-gate.js";
|
|
75
75
|
import { buildActiveRunConflict, resumeEntryForGate } from "./active-run-conflict.js";
|
|
76
76
|
export { explicitOperatorOk, isOperator };
|
|
@@ -179,6 +179,18 @@ export function createHttpServer(rawDeps) {
|
|
|
179
179
|
};
|
|
180
180
|
if (deps.drainState)
|
|
181
181
|
deps.drainState.lastActivityAt = () => lastBillableActivityAt;
|
|
182
|
+
const openResponses = new Set();
|
|
183
|
+
let lastAttachEndedAt = 0;
|
|
184
|
+
if (deps.drainState) {
|
|
185
|
+
deps.drainState.attachedStreams = () => {
|
|
186
|
+
let n = 0;
|
|
187
|
+
for (const r of openResponses)
|
|
188
|
+
if (isSseResponse(r))
|
|
189
|
+
n++;
|
|
190
|
+
return n;
|
|
191
|
+
};
|
|
192
|
+
deps.drainState.lastAttachEndedAt = () => lastAttachEndedAt;
|
|
193
|
+
}
|
|
182
194
|
if (deps.hookWakeBus) {
|
|
183
195
|
deps.hookWakeBus.deliver = async (sessionId, text) => {
|
|
184
196
|
try {
|
|
@@ -232,6 +244,7 @@ export function createHttpServer(rawDeps) {
|
|
|
232
244
|
res.end();
|
|
233
245
|
});
|
|
234
246
|
});
|
|
247
|
+
deps.deviceHub?.attach(server);
|
|
235
248
|
const routeCtxBase = {
|
|
236
249
|
deps,
|
|
237
250
|
registry: { idemCache, inflightRuns, preemptableRuns, cancelledViaVerb, steerableRuns, wakeParkMints, counters },
|
|
@@ -247,6 +260,8 @@ export function createHttpServer(rawDeps) {
|
|
|
247
260
|
const admitted = method !== "OPTIONS" && url !== "/health" && !url.startsWith("/metrics");
|
|
248
261
|
if (admitted)
|
|
249
262
|
counters.admittedInflight++;
|
|
263
|
+
if (admitted)
|
|
264
|
+
openResponses.add(res);
|
|
250
265
|
let logged = false;
|
|
251
266
|
const reqState = { streamTaskId: undefined, streamDetached: false, source: null };
|
|
252
267
|
const ctx = { deps: routeCtxBase.deps, registry: routeCtxBase.registry, helpers: routeCtxBase.helpers, local: routeCtxBase.local, legs: routeCtxBase.legs, req: reqState };
|
|
@@ -256,6 +271,8 @@ export function createHttpServer(rawDeps) {
|
|
|
256
271
|
logged = true;
|
|
257
272
|
if (admitted)
|
|
258
273
|
counters.admittedInflight--;
|
|
274
|
+
if (openResponses.delete(res) && isSseResponse(res))
|
|
275
|
+
lastAttachEndedAt = performance.now();
|
|
259
276
|
const route = routeLabel(method, url);
|
|
260
277
|
const seconds = (Date.now() - startedAt) / 1000;
|
|
261
278
|
const aborted = viaClose && !res.writableEnded;
|
|
@@ -1588,6 +1605,8 @@ export function createHttpServer(rawDeps) {
|
|
|
1588
1605
|
e.code === "resume.constraint_unprojectable" ||
|
|
1589
1606
|
e.code === "resume.usage_window_exhausted" ||
|
|
1590
1607
|
e.code === "resume.principal_mismatch" ||
|
|
1608
|
+
e.code === "resume.placement_mismatch" ||
|
|
1609
|
+
e.code === "resume.preflight_rejected" ||
|
|
1591
1610
|
e.code.startsWith("wake.") ||
|
|
1592
1611
|
(e.code === "checkpoint.unsupported_version" && checkpointRowRedeemableElsewhere(e.detail?.reason));
|
|
1593
1612
|
if (claimedRow && taskId && deps.runStore) {
|
|
@@ -1617,7 +1636,7 @@ export function createHttpServer(rawDeps) {
|
|
|
1617
1636
|
? (outcome.gate === "policy_ask" ? "approval_binding_mismatch" : "resume_outcome_invalid")
|
|
1618
1637
|
: e.code,
|
|
1619
1638
|
...(e.detail?.field ? { field: e.detail.field } : {}),
|
|
1620
|
-
...(e.code === "resume.usage_window_exhausted" && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
|
|
1639
|
+
...((e.code === "resume.usage_window_exhausted" || e.code === "resume.preflight_rejected") && typeof e.detail?.retryAfterMs === "number" && Number.isFinite(e.detail.retryAfterMs) && e.detail.retryAfterMs > 0
|
|
1621
1640
|
? { retryAfterSec: Math.max(1, Math.ceil(e.detail.retryAfterMs / 1000)) }
|
|
1622
1641
|
: {}),
|
|
1623
1642
|
...(retriable ? { retriable: true } : {}),
|
|
@@ -216,7 +216,7 @@ export interface TaskRequestBody {
|
|
|
216
216
|
skillsListing?: boolean;
|
|
217
217
|
};
|
|
218
218
|
/** C1 (core 1.219, subagent viewing pane): opt-in — forward a delegated child's live CONTENT events
|
|
219
|
-
* (text_delta/reasoning_delta/tool_start/tool_end, each stamped `parentToolCallId`) onto this run's stream/durable
|
|
219
|
+
* (text_delta/text_end/reasoning_delta/tool_start/tool_end, each stamped `parentToolCallId`) onto this run's stream/durable
|
|
220
220
|
* log, so a shell can render the child's transcript live ("enter 看详情"). Default OFF = progress-only (prior
|
|
221
221
|
* behavior). Purely a render channel — core never merges the child stream into the parent's model context. */
|
|
222
222
|
forwardSubagentEvents?: boolean;
|
package/dist/leader/wire.js
CHANGED
|
@@ -366,7 +366,8 @@ export function createLeaderRunner(cfg) {
|
|
|
366
366
|
};
|
|
367
367
|
const provisionIntegrationSandbox = async () => {
|
|
368
368
|
if (cfg.envFactory) {
|
|
369
|
-
const
|
|
369
|
+
const integSessionId = `leader-integ-${Date.now()}`;
|
|
370
|
+
const env = cfg.envFactory({ sessionId: integSessionId, placementRootSessionId: integSessionId });
|
|
370
371
|
try {
|
|
371
372
|
await sh(env)(body.seedCmd);
|
|
372
373
|
await injectOracles(env);
|
|
@@ -412,7 +413,8 @@ export function createLeaderRunner(cfg) {
|
|
|
412
413
|
const resolveRepair = async (solo, _sub) => {
|
|
413
414
|
let env;
|
|
414
415
|
if (cfg.envFactory) {
|
|
415
|
-
|
|
416
|
+
const graderSessionId = `leader-grader-${Date.now()}`;
|
|
417
|
+
env = cfg.envFactory({ sessionId: graderSessionId, placementRootSessionId: graderSessionId });
|
|
416
418
|
}
|
|
417
419
|
else {
|
|
418
420
|
if (!cfg.e2bApiKey)
|
package/dist/main.js
CHANGED
|
@@ -50,6 +50,7 @@ import { assertRetentionLaneWirable, startRetentionLane, RETENTION_LEASE_TTL_FAC
|
|
|
50
50
|
import { assertConsolidationDriverWirable, buildConsolidationValveAudit, createMemoryConsolidationFaces, resolveConsolidationDriverSeat } from "./boot/memory-consolidation.js";
|
|
51
51
|
import { startFleetReconciler } from "./fleet/fleet-reconciler.js";
|
|
52
52
|
import { openStores } from "./boot/stores.js";
|
|
53
|
+
import { assertDeviceLaneWired, createDeviceLane } from "./boot/device-lane.js";
|
|
53
54
|
import { runAdoptionBootScan } from "./boot/adoption.js";
|
|
54
55
|
import { auditDormantPermissionRules } from "./boot/permission-rules-audit.js";
|
|
55
56
|
import { createBudgetAndTracing } from "./boot/budget-tracing.js";
|
|
@@ -117,6 +118,7 @@ async function main() {
|
|
|
117
118
|
const configCenter = await createConfigCenterRuntime({ config, logger, metrics, localRoot });
|
|
118
119
|
await configCenter.applyLocalRemoteExec();
|
|
119
120
|
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, } = await openStores({ config, logger, metrics, localRoot });
|
|
121
|
+
const instanceId = uuidv7();
|
|
120
122
|
const memoryBundleFaces = memoryEngine ? createMemoryBundleFaces(memoryEngine) : undefined;
|
|
121
123
|
const memoryComplianceFaces = memoryEngine
|
|
122
124
|
? createMemoryComplianceFaces(memoryEngine, {
|
|
@@ -144,7 +146,9 @@ async function main() {
|
|
|
144
146
|
});
|
|
145
147
|
}
|
|
146
148
|
await auditDormantPermissionRules({ stores: permissionRuleStores, logger, explicit: config.permissionRulesEnabledExplicit });
|
|
147
|
-
const
|
|
149
|
+
const deviceLane = config.remoteExec?.provider === "device" ? createDeviceLane({ config, logger, metrics, backend, replicaId: instanceId, rateGate: () => rateLimiter }) : undefined;
|
|
150
|
+
const { perTaskImage, sessionEnvSelection, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, dropSessionScoped, executionEnvFactory, worktreeReap, sendUserFileTaskEnvs, lspManager, } = createExecutionEnv({ config, logger, metrics, taskAttachmentStore, ...(deviceLane ? { deviceLane } : {}) });
|
|
151
|
+
assertDeviceLaneWired(config.remoteExec?.provider === "device", deviceLane, executionEnvFactory !== undefined);
|
|
148
152
|
const toolTracer = config.toolTrace ? createToolTracer(logger) : undefined;
|
|
149
153
|
if (toolTracer)
|
|
150
154
|
logger.info("tool_trace_enabled", {});
|
|
@@ -328,7 +332,6 @@ async function main() {
|
|
|
328
332
|
const runStore = backend ? backend.run() : undefined;
|
|
329
333
|
const resumeAnchorStore = backend ? backend.resumeAnchor() : undefined;
|
|
330
334
|
const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
|
|
331
|
-
const instanceId = uuidv7();
|
|
332
335
|
const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
|
|
333
336
|
const capEnv = (name) => {
|
|
334
337
|
const r = parseCapEnv(name, process.env[name]);
|
|
@@ -515,6 +518,8 @@ async function main() {
|
|
|
515
518
|
configCenter.startRefreshLoop({ runnerTierFrozen, pricing, limitSync, swapRunnerModels });
|
|
516
519
|
configCenter.initKeyResolver();
|
|
517
520
|
const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
|
|
521
|
+
deviceStore: deviceLane?.store,
|
|
522
|
+
dropSessionScoped,
|
|
518
523
|
config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
|
|
519
524
|
resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileSnapshotStore,
|
|
520
525
|
workflowCompletionInbox, taskListLane,
|
|
@@ -585,6 +590,7 @@ async function main() {
|
|
|
585
590
|
sessionTitler: sessionTitler ? sessionTitler : undefined,
|
|
586
591
|
};
|
|
587
592
|
const seams = {
|
|
593
|
+
deviceHub: deviceLane?.hub,
|
|
588
594
|
sessionAudit,
|
|
589
595
|
routeJudgeBrain: brain,
|
|
590
596
|
purgeSession: purgeSession ? purgeSession : undefined,
|
|
@@ -665,7 +671,7 @@ async function main() {
|
|
|
665
671
|
config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver,
|
|
666
672
|
getCenterPrompts: () => configCenter.getCenterPrompts(),
|
|
667
673
|
getKeyResolver: () => configCenter.getKeyResolver(),
|
|
668
|
-
taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv,
|
|
674
|
+
taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner,
|
|
669
675
|
hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec,
|
|
670
676
|
selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
|
|
671
677
|
singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
|
|
@@ -785,6 +791,7 @@ async function main() {
|
|
|
785
791
|
version: serviceVersion(),
|
|
786
792
|
runner, subRunner, lspManager, workflowNotifyJournal, fleetClient, backend, drainState,
|
|
787
793
|
storeLiveProbe, configCenter,
|
|
794
|
+
deviceHub: deviceLane?.hub,
|
|
788
795
|
retentionLane: retentionLaneStarted,
|
|
789
796
|
releaseRetentionLease: retentionLaneStore !== undefined ? () => retentionLaneStore.release(instanceId) : undefined,
|
|
790
797
|
});
|
|
@@ -62,6 +62,13 @@ export interface HardenedVmLimits {
|
|
|
62
62
|
* A script exceeding it OOMs the WORKER (killed), never the shared main process. Default 128. */
|
|
63
63
|
maxHeapMb?: number;
|
|
64
64
|
}
|
|
65
|
+
/** [5434]①(clay 亲机四 run 三次恰在 600s 整点全灭):workflow 总超时默认与旋钮的**单点**。
|
|
66
|
+
* 旧默认 600_000(10 分钟)对真实 workflow(验证 agent 逐条重跑清单/任何带审批等待的车)结构性不够——
|
|
67
|
+
* 默认抬到 **1 小时**;部署经 `WORKFLOW_TOTAL_TIMEOUT_MS` 显式配,域 [60_000, 86_400_000](1 分钟..24h),
|
|
68
|
+
* 坏值**响亮拒**(#210 立律:旋钮坏值禁静默回默认)。0/负数不是「禁用」:无界 runaway 脚本会吞掉共享
|
|
69
|
+
* 副本的事件循环/堆,禁用臂刻意不提供——要更久就把值配大。模块加载时读一次(坏 env = boot fail-loud)。
|
|
70
|
+
* 超时终态分型(partial vs failed,[5434]① 后半)涉 wire status 语义,与 core workflow 终态形另批对表。 */
|
|
71
|
+
export declare function workflowTotalTimeoutMsDefault(): number;
|
|
65
72
|
/**
|
|
66
73
|
* The realm test itself, exported so there is exactly ONE ruler for "did this rejection come from an untrusted
|
|
67
74
|
* script?" in the process. A promise minted INSIDE a vm context fails `instanceof` against the HOST `Promise`
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import vm from "node:vm";
|
|
2
2
|
import { splitWorkflowMeta, WorkflowScriptError, } from "@sema-agent/core";
|
|
3
|
+
export function workflowTotalTimeoutMsDefault() {
|
|
4
|
+
const raw = process.env.WORKFLOW_TOTAL_TIMEOUT_MS;
|
|
5
|
+
if (raw === undefined || raw === "")
|
|
6
|
+
return 3_600_000;
|
|
7
|
+
const n = Number(raw);
|
|
8
|
+
if (!Number.isSafeInteger(n) || n < 60_000 || n > 86_400_000) {
|
|
9
|
+
throw new Error(`env WORKFLOW_TOTAL_TIMEOUT_MS must be an integer in [60000, 86400000] ms (got ${JSON.stringify(raw)}) — unset it for the 3600000 default`);
|
|
10
|
+
}
|
|
11
|
+
return n;
|
|
12
|
+
}
|
|
3
13
|
const DEFAULTS = {
|
|
4
|
-
totalTimeoutMs:
|
|
14
|
+
totalTimeoutMs: workflowTotalTimeoutMsDefault(),
|
|
5
15
|
syncTimeoutMs: 5_000,
|
|
6
16
|
maxAgents: 1000,
|
|
7
17
|
concurrency: 12,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Worker } from "node:worker_threads";
|
|
2
2
|
import { WorkflowScriptError } from "@sema-agent/core";
|
|
3
|
-
import { scopedPhaseGate } from "./hardened-vm-runner.js";
|
|
4
|
-
const DEFAULTS = { totalTimeoutMs:
|
|
3
|
+
import { scopedPhaseGate, workflowTotalTimeoutMsDefault } from "./hardened-vm-runner.js";
|
|
4
|
+
const DEFAULTS = { totalTimeoutMs: workflowTotalTimeoutMsDefault(), syncTimeoutMs: 5_000, maxAgents: 1000, concurrency: 12, maxHeapMb: 128 };
|
|
5
5
|
const IS_TS = import.meta.url.endsWith(".ts");
|
|
6
6
|
const WORKER_URL = new URL(IS_TS ? "./hardened-vm-worker.ts" : "./hardened-vm-worker.js", import.meta.url);
|
|
7
7
|
const WORKER_EXEC_ARGV = IS_TS ? ["--import", "tsx/esm"] : undefined;
|