@sema-agent/server 2.0.0 → 3.0.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 +74 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +391 -0
- package/dist/boot/budget-tracing.d.ts +48 -0
- package/dist/boot/budget-tracing.js +86 -0
- package/dist/boot/config-center.d.ts +62 -0
- package/dist/boot/config-center.js +1002 -0
- package/dist/boot/coordinators.d.ts +33 -0
- package/dist/boot/coordinators.js +97 -0
- package/dist/boot/execution-env.d.ts +26 -0
- package/dist/boot/execution-env.js +370 -0
- package/dist/boot/leader.d.ts +27 -0
- package/dist/boot/leader.js +81 -0
- package/dist/boot/reapers.d.ts +53 -0
- package/dist/boot/reapers.js +252 -0
- package/dist/boot/resolve-spec.d.ts +70 -0
- package/dist/boot/resolve-spec.js +1072 -0
- package/dist/boot/runner-deps.d.ts +101 -0
- package/dist/boot/runner-deps.js +343 -0
- package/dist/boot/runtime-caps.d.ts +21 -0
- package/dist/boot/runtime-caps.js +62 -0
- package/dist/boot/session-faces.d.ts +57 -0
- package/dist/boot/session-faces.js +157 -0
- package/dist/boot/shutdown.d.ts +50 -0
- package/dist/boot/shutdown.js +129 -0
- package/dist/boot/stores.d.ts +32 -0
- package/dist/boot/stores.js +361 -0
- package/dist/boot/workflow-orchestration.d.ts +46 -0
- package/dist/boot/workflow-orchestration.js +150 -0
- package/dist/capabilities/scenarios.d.ts +5 -3
- package/dist/capabilities/scenarios.js +5 -3
- package/dist/config-center/apply-effective.js +4 -3
- package/dist/config-lkg.d.ts +2 -1
- package/dist/config-lkg.js +2 -1
- package/dist/config-types.d.ts +47 -7
- package/dist/config.d.ts +30 -13
- package/dist/config.js +562 -387
- package/dist/hooks/hook-llm.js +9 -0
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/attachments.js +2 -2
- package/dist/http/routes/memory-policy.js +3 -3
- package/dist/http/routes/runs.js +1 -1
- package/dist/http/routes/session-sync.js +2 -2
- package/dist/http/routes/sessions.js +2 -2
- package/dist/http/routes/tasks.js +2 -2
- package/dist/http/routes/trace-usage.js +2 -2
- package/dist/http/routes/workflows.js +3 -1
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +25 -5
- package/dist/http/sse-log.js +1 -1
- package/dist/main.js +164 -3799
- package/dist/model-select.d.ts +1 -1
- package/dist/model-select.js +1 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +13 -5
- package/dist/plugins/checkpoint-store-sql.js +10 -3
- package/dist/plugins/local-checkpoint-store.js +8 -2
- package/dist/plugins/remote-env-host.d.ts +2 -1
- package/dist/run-local.js +2 -1
- package/dist/session-titler.d.ts +3 -1
- package/dist/session-titler.js +2 -2
- package/dist/trace/project.js +4 -1
- package/package.json +5 -3
package/dist/model-select.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* untrusted body/text can pick WHICH configured model but can never inject a baseUrl/apiKey (parseModelMention
|
|
11
11
|
* only ever returns an allow-listed catalog name).
|
|
12
12
|
*
|
|
13
|
-
* [865]②(
|
|
13
|
+
* [865]②(生产实锤:面板上选了 A 模型,实际跑的是目录里的另一个):
|
|
14
14
|
* - explicit ref 走 name+id 双键索引({@link matchCatalogModel})——壳 boot 回读的 mainLoopModel 是 **id 形**,
|
|
15
15
|
* 目录键是 **name 形**,单键索引把 id 形请求当「未知」静默落 default = session 中途静默换模型(最恶性的
|
|
16
16
|
* 上下文污染路径)。
|
package/dist/model-select.js
CHANGED
|
@@ -11,7 +11,7 @@ import { parseModelMention } from "@sema-agent/core";
|
|
|
11
11
|
* untrusted body/text can pick WHICH configured model but can never inject a baseUrl/apiKey (parseModelMention
|
|
12
12
|
* only ever returns an allow-listed catalog name).
|
|
13
13
|
*
|
|
14
|
-
* [865]②(
|
|
14
|
+
* [865]②(生产实锤:面板上选了 A 模型,实际跑的是目录里的另一个):
|
|
15
15
|
* - explicit ref 走 name+id 双键索引({@link matchCatalogModel})——壳 boot 回读的 mainLoopModel 是 **id 形**,
|
|
16
16
|
* 目录键是 **name 形**,单键索引把 id 形请求当「未知」静默落 default = session 中途静默换模型(最恶性的
|
|
17
17
|
* 上下文污染路径)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
2
2
|
import type { Pool as PgPool } from "pg";
|
|
3
|
-
import { type Checkpoint, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type ResumeOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor } from "@sema-agent/core";
|
|
3
|
+
import { type Checkpoint, type CheckpointGate, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type ResumeOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor } from "@sema-agent/core";
|
|
4
4
|
import { type SqlDriver } from "./sql-driver.js";
|
|
5
5
|
/**
|
|
6
6
|
* design/80 D-1 (§3 invariant #3 — crash-safe reaper backstop): an ABSOLUTE upper bound on a pending
|
|
@@ -38,10 +38,18 @@ export interface PendingCheckpoint {
|
|
|
38
38
|
* resume binds to the EXACT action they saw (a stale view is then rejected fail-closed PRE-CAS). `boundCallId`
|
|
39
39
|
* === `toolCallId` (the pending tool call); `boundInputHash` is the server-minted opaque hash of the shown
|
|
40
40
|
* input (NEVER recomputed by the portal — echoed verbatim). Surfacing them here is what makes the D-1 TOCTOU
|
|
41
|
-
* guard REACHABLE by the portal (without it the portal can only do the unbound legacy fallback).
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
* guard REACHABLE by the portal (without it the portal can only do the unbound legacy fallback).
|
|
42
|
+
* 🔴 [1995]②: ABSENT (key omitted, never `null`) on a park with no tool action (plan_review /
|
|
43
|
+
* resource_limit / task_done) or a pre-D-1 legacy row ⇒ the consumer falls back to the unbound resolve.
|
|
44
|
+
* The wire contract (§1) is "an absent nullable field is OMITTED, not null" — a `null` here invited a
|
|
45
|
+
* consumer to treat the key's PRESENCE as "there is a binding" and echo `null` back into /decide. */
|
|
46
|
+
boundCallId?: string;
|
|
47
|
+
boundInputHash?: string;
|
|
48
|
+
/** 🔴 [1995]③ (additive): the checkpoint's gate kind — the operator queue mixes tool approvals with
|
|
49
|
+
* plan_review / resource_limit / task_done parks (all of which carry `toolName: null`), so a durable-recovery
|
|
50
|
+
* consumer needs this to ROUTE a row to the right wire (`/decide` vs `/plan_review` vs `/resume`). Absent
|
|
51
|
+
* only for a legacy SQL row suspended before the `gate_kind` column existed. */
|
|
52
|
+
gateKind?: CheckpointGate["kind"];
|
|
45
53
|
/** The suspended run's taskId (task_active join) — the key to the run/trace ("view task context"). */
|
|
46
54
|
taskId: string | null;
|
|
47
55
|
/** The pending tool call's args (post-hook), redacted + bounded — for tool_approval the write payload, for a
|
|
@@ -305,13 +305,15 @@ export class SqlCheckpointStore {
|
|
|
305
305
|
* joined from task_active (the JOIN KEY to the run/trace — a suspended run KEEPS its
|
|
306
306
|
* session claim, so the join is live for every pending row; null only in pathological windows). */
|
|
307
307
|
async listPending(scope) {
|
|
308
|
-
const base = "SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, c.tool_input, c.bound_input_hash, c.risk_descriptor, c.created_at, c.deadline, ta.task_id " +
|
|
308
|
+
const base = "SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, c.tool_input, c.bound_input_hash, c.risk_descriptor, c.created_at, c.deadline, c.gate_kind, ta.task_id " +
|
|
309
309
|
"FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
|
|
310
310
|
const { rows } = scope
|
|
311
311
|
? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at ASC`, [scope])
|
|
312
312
|
: await this.db.query(`${base} ORDER BY c.created_at ASC`);
|
|
313
313
|
const out = rows.map((r) => {
|
|
314
314
|
const toolCallId = r.tool_call_id ?? null;
|
|
315
|
+
const boundInputHash = r.bound_input_hash ?? null;
|
|
316
|
+
const gateKind = r.gate_kind ?? null;
|
|
315
317
|
return {
|
|
316
318
|
sessionId: String(r.session_id),
|
|
317
319
|
scope: String(r.scope),
|
|
@@ -319,8 +321,13 @@ export class SqlCheckpointStore {
|
|
|
319
321
|
toolCallId,
|
|
320
322
|
// D-1: boundCallId === the pending tool call id; boundInputHash is the server-minted opaque hash. The
|
|
321
323
|
// portal echoes BOTH on /decide so the resume binds to the exact action shown (TOCTOU guard reachable).
|
|
322
|
-
|
|
323
|
-
|
|
324
|
+
// [1995]②: a park with no tool action (plan_review / resource_limit / task_done — both columns NULL)
|
|
325
|
+
// OMITS the keys entirely; the wire contract says absent, never `null` (LOCAL twin does the same).
|
|
326
|
+
...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
|
|
327
|
+
...(boundInputHash !== null ? { boundInputHash } : {}),
|
|
328
|
+
// [1995]③ additive: the gate kind the row is parked on (NULL only on a pre-`gate_kind` legacy row) —
|
|
329
|
+
// lets a durable-recovery consumer route plan / tool / resource off this ONE queue.
|
|
330
|
+
...(gateKind !== null ? { gateKind } : {}),
|
|
324
331
|
taskId: r.task_id ?? null,
|
|
325
332
|
// Both drivers return the JSON column already parsed; null for pre-migration rows. PG 库内恒干净
|
|
326
333
|
// (拒绝式)——直读即审阅面=执行面。
|
|
@@ -192,8 +192,14 @@ export class LocalCheckpointStore {
|
|
|
192
192
|
scope: cp.scope,
|
|
193
193
|
toolName: pa?.toolName ?? null,
|
|
194
194
|
toolCallId,
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
// [1995]②: the D-1 binding fields are OMITTED when the park has no tool action (plan_review /
|
|
196
|
+
// resource_limit / task_done) — the wire contract says an absent nullable field is ABSENT, not `null`
|
|
197
|
+
// (a consumer branching on "key present ⇒ there is a binding" would echo `null` back into /decide).
|
|
198
|
+
...(toolCallId !== null ? { boundCallId: toolCallId } : {}),
|
|
199
|
+
...(pa?.boundInputHash !== undefined ? { boundInputHash: pa.boundInputHash } : {}),
|
|
200
|
+
// [1995]③ additive: which gate this row is parked on, so a durable-recovery consumer can route
|
|
201
|
+
// plan / tool / resource off the SAME queue (toolName is null for every non-tool gate).
|
|
202
|
+
...(gate?.kind !== undefined ? { gateKind: gate.kind } : {}),
|
|
197
203
|
taskId: (await this.opts.getActiveTaskId?.(cp.sessionId)) ?? null,
|
|
198
204
|
input: boundedToolInput(pa?.args),
|
|
199
205
|
createdAt: cp.createdAt,
|
|
@@ -43,7 +43,8 @@ export interface HostEnvConfig {
|
|
|
43
43
|
* reports `supported:false` → INERT (no scheduler tools). Injected at boot only on the single-user host lane. */
|
|
44
44
|
scheduler?: SchedulerCapability;
|
|
45
45
|
/** design/103 background shell: `false` → INERT (multi-tenant host lane gate). Default (undefined) = on for the
|
|
46
|
-
* single-user TOC host lane. `HOST_BG_ENABLED=false` is an additional global kill switch(旧名
|
|
46
|
+
* single-user TOC host lane. `HOST_BG_ENABLED=false` is an additional global kill switch(🪦 旧名
|
|
47
|
+
* `HOST_BG_DISABLED` 自 server 3.0.0 起是 fail-loud 墓碑:设了即拒启并指路新名,不再被读取)。 */
|
|
47
48
|
backgroundShell?: boolean;
|
|
48
49
|
}
|
|
49
50
|
export declare class RemoteHostExecutionEnv implements RemoteExecutionEnv, SchedulerCapability, BackgroundShellCapability {
|
package/dist/run-local.js
CHANGED
|
@@ -291,7 +291,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
291
291
|
// TOC local LSP (core 1.190): run-local IS the pure-local host lane, so mount core's `NodeLspManager` (local
|
|
292
292
|
// child_process language servers over stdio, CC `services/lsp` parity) on the host lane. Default ON (degrades
|
|
293
293
|
// gracefully to grep/read if a server binary is absent); `LSP_HOST_ENABLED=false` opts out (design/158 B4 split
|
|
294
|
-
// this lane off `LSP_ENABLED
|
|
294
|
+
// this lane off `LSP_ENABLED`; 🪦 server 3.0.0 起 `LSP_ENABLED=false` 是 fail-loud 墓碑,拒启指路新名,
|
|
295
|
+
// 不再是可用的 opt-out)。`requirePrincipal !== true`
|
|
295
296
|
// keeps it uniform with main.ts (a no-op here — run-local is single-user — but never relies on that). Both runners.
|
|
296
297
|
const lspManager = hostRx && config.lspHostEnabled && config.requirePrincipal !== true ? new NodeLspManager({ log: (event, fields) => logger.info(event, fields) }) : undefined;
|
|
297
298
|
// Long-term memory (design/138 S1): run-local IS the single-user lane, so the file-based memory ENGINE is
|
package/dist/session-titler.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface SessionTitlerDeps {
|
|
|
29
29
|
llm(opts: {
|
|
30
30
|
prompt: string;
|
|
31
31
|
timeoutMs: number;
|
|
32
|
+
model?: string;
|
|
32
33
|
}): Promise<{
|
|
33
34
|
ok: true;
|
|
34
35
|
text: string;
|
|
@@ -47,7 +48,8 @@ export interface SessionTitlerDeps {
|
|
|
47
48
|
}
|
|
48
49
|
export interface SessionTitler {
|
|
49
50
|
/** Fire-and-forget: maybe generate + persist a title for this session. Never throws, never blocks the caller. */
|
|
50
|
-
|
|
51
|
+
/** `model`([1992]②)= 本 turn 真实驱动的模型——带上则标题 hook 同链路,不再裸落 env 缺省。 */
|
|
52
|
+
maybeTitle(sessionId: string, objective: string, model?: string): void;
|
|
51
53
|
}
|
|
52
54
|
export declare const TITLE_MAX_CHARS = 80;
|
|
53
55
|
/** Single line, control chars stripped, hard cap; surrounding quotes (a common LLM tic) trimmed. */
|
package/dist/session-titler.js
CHANGED
|
@@ -65,7 +65,7 @@ export function createSessionTitler(deps) {
|
|
|
65
65
|
deps.logger?.warn?.("session_title_failed", { ...meta, note: "同 cause 后续降为 debug 级" });
|
|
66
66
|
};
|
|
67
67
|
return {
|
|
68
|
-
maybeTitle(sessionId, objective) {
|
|
68
|
+
maybeTitle(sessionId, objective, model) {
|
|
69
69
|
if (!sessionId || !objective.trim())
|
|
70
70
|
return;
|
|
71
71
|
if (seen.has(sessionId))
|
|
@@ -93,7 +93,7 @@ export function createSessionTitler(deps) {
|
|
|
93
93
|
deps.metrics?.inc("session_title_total", { outcome: "already_titled" });
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
|
-
const r = await deps.llm({ prompt: buildTitlePrompt(objective), timeoutMs: LLM_TIMEOUT_MS });
|
|
96
|
+
const r = await deps.llm({ prompt: buildTitlePrompt(objective), timeoutMs: LLM_TIMEOUT_MS, model });
|
|
97
97
|
if (!r.ok) {
|
|
98
98
|
deps.metrics?.inc("session_title_total", { outcome: "llm_failed" });
|
|
99
99
|
logFailure(sessionId, r.error);
|
package/dist/trace/project.js
CHANGED
|
@@ -510,7 +510,10 @@ export function mapTraceEvent(type, seq, data) {
|
|
|
510
510
|
case "failed":
|
|
511
511
|
// BL-13: redact at the read boundary too (defense-in-depth) — covers any errorMessage that reached the
|
|
512
512
|
// log un-redacted via another write path; idempotent on the BL-12-redacted catch-path message.
|
|
513
|
-
|
|
513
|
+
// A1/A6/B(复审 2026-07-29):error 帧统一为 `{ type:"error", errorCode, message }` —— 与 sse-log /
|
|
514
|
+
// approvals 的帧同形。这一处是 3.0.0 摘 legacy `code` 键时的漏网:帧在 src/trace/ 铸,而当时的门只扫
|
|
515
|
+
// src/http/,于是"SSE error 帧机器键统一为 errorCode"的公开声明与 wire 上跑的东西不一致(门是绿的)。
|
|
516
|
+
return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactSecrets(String(data.errorMessage ?? "run failed")) } };
|
|
514
517
|
default:
|
|
515
518
|
return null; // compacted etc. — not a client trace event
|
|
516
519
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
"!dist/run-local-linux-x64",
|
|
24
24
|
"!dist/run-local-darwin-arm64",
|
|
25
25
|
"skills",
|
|
26
|
-
"deploy/sema-up"
|
|
26
|
+
"deploy/sema-up",
|
|
27
|
+
"USAGE.md",
|
|
28
|
+
"MIGRATION.md"
|
|
27
29
|
],
|
|
28
30
|
"bin": {
|
|
29
31
|
"run-local": "dist/run-local.js",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
52
54
|
},
|
|
53
55
|
"dependencies": {
|
|
54
|
-
"@sema-agent/core": "^2.
|
|
56
|
+
"@sema-agent/core": "^2.1.0",
|
|
55
57
|
"@sema-agent/registry-core": "^0.10.21",
|
|
56
58
|
"e2b": "^2.28.0",
|
|
57
59
|
"libsodium-wrappers": "^0.8.4",
|