@sema-agent/server 4.1.0 → 4.1.2

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.
Files changed (55) hide show
  1. package/dist/approval.d.ts +3 -2
  2. package/dist/approval.js +0 -6
  3. package/dist/bake-runner/main.js +1 -5
  4. package/dist/boot/config-center.js +2 -1
  5. package/dist/boot/execution-env.d.ts +2 -2
  6. package/dist/boot/execution-env.js +17 -12
  7. package/dist/boot/resolve-spec.js +3 -3
  8. package/dist/bounded-session-map.d.ts +25 -0
  9. package/dist/bounded-session-map.js +37 -0
  10. package/dist/capabilities/center-plugins.d.ts +6 -1
  11. package/dist/capabilities/center-plugins.js +32 -9
  12. package/dist/capabilities/center-prompts.js +2 -1
  13. package/dist/capabilities/sandbox-file-send.d.ts +2 -2
  14. package/dist/capabilities/sandbox-file-send.js +6 -4
  15. package/dist/capabilities/select-environment-tool.d.ts +1 -2
  16. package/dist/capabilities/select-environment-tool.js +7 -11
  17. package/dist/capabilities/skills.d.ts +4 -0
  18. package/dist/capabilities/skills.js +5 -4
  19. package/dist/config-center/skills-mcp.js +2 -1
  20. package/dist/config-provider.js +6 -0
  21. package/dist/digest-form.d.ts +15 -0
  22. package/dist/digest-form.js +15 -0
  23. package/dist/http/route-ctx.d.ts +3 -8
  24. package/dist/http/routes/approvals-assistant.js +3 -2
  25. package/dist/http/routes/runs.js +2 -2
  26. package/dist/http/routes/session-sync.js +11 -11
  27. package/dist/http/server.d.ts +2 -2
  28. package/dist/http/server.js +18 -40
  29. package/dist/http/verify-rounds.d.ts +24 -0
  30. package/dist/http/verify-rounds.js +18 -0
  31. package/dist/images/bake-validate.d.ts +5 -2
  32. package/dist/images/bake-validate.js +5 -2
  33. package/dist/index.d.ts +1 -0
  34. package/dist/leader/grader-env-factory.js +1 -2
  35. package/dist/leader/merge.js +1 -1
  36. package/dist/leader/repair-oracle.js +1 -2
  37. package/dist/per-task-image.d.ts +1 -16
  38. package/dist/per-task-image.js +21 -11
  39. package/dist/plugins/approval-store-sql.js +2 -3
  40. package/dist/plugins/checkpoint-store-sql.js +2 -3
  41. package/dist/plugins/image-bake-store-sql.js +3 -3
  42. package/dist/plugins/outcome-ledger-sql.js +2 -3
  43. package/dist/plugins/remote-env-e2b.js +1 -5
  44. package/dist/plugins/remote-shell.d.ts +2 -1
  45. package/dist/plugins/remote-shell.js +2 -1
  46. package/dist/plugins/run-store-sql.js +3 -3
  47. package/dist/plugins/sql-driver.d.ts +17 -1
  48. package/dist/plugins/sql-driver.js +12 -0
  49. package/dist/prompts-domain-validate.js +3 -3
  50. package/dist/runs.d.ts +2 -4
  51. package/dist/security.d.ts +11 -0
  52. package/dist/security.js +11 -0
  53. package/dist/session-sync.d.ts +12 -1
  54. package/dist/session-sync.js +15 -3
  55. package/package.json +1 -1
@@ -60,7 +60,12 @@
60
60
  * $n::jsonb) · null-safe compare (<=> vs IS NOT DISTINCT FROM) AND its placeholder-arity fallout ·
61
61
  * key-column byte semantics (VARBINARY vs COLLATE "C") · isolation/txn verbs · error classification
62
62
  * (ER_DUP_ENTRY/errno vs SQLSTATE, sqlMessage vs err.constraint) · PG-only protocol-byte handling
63
- * (pgSafeJsonStringify / pgProtocolJsonStringify / pgSanitizeText / pgHasUnstorable) · DDL.
63
+ * (pgSafeJsonStringify / pgProtocolJsonStringify / pgSanitizeText / pgHasUnstorable) · DDL. Each arm's
64
+ * BEHAVIOR (what pgSafeJsonStringify/pgProtocolJsonStringify actually do to the bytes) stays written out
65
+ * in `pg-safe-json.ts`, untouched — `dialectJsonEncoder`/`dialectProtocolJsonEncoder` below (design/158
66
+ * redesign①) only collect the identical `dialect === "tidb" ? JSON.stringify(v) : pgSafe…(v)` DISPATCH
67
+ * ternary that five call sites duplicated verbatim; they are not a new abstraction over the encodings
68
+ * themselves, so this does not violate the rule above.
64
69
  *
65
70
  * WHAT MAY BE PARAMETERIZED
66
71
  * result unwrapping (rows/affected) · transaction plumbing · numeric coercion of BIGINT-as-string
@@ -99,4 +104,15 @@ export interface SqlDriver extends SqlExec {
99
104
  export declare function mysqlDriver(pool: MySqlPool): SqlDriver;
100
105
  /** PostgreSQL driver. Transaction control is issued as STATEMENTS (node-pg has no native verbs). */
101
106
  export declare function pgDriver(pool: PgPool): SqlDriver;
107
+ /** design/158 redesign① — the "safe" JSON-column encoder DISPATCH (run-store/outcome-ledger's identical
108
+ * ternary, collected): TiDB stores `JSON.stringify` verbatim; PG goes through `pgSafeJsonStringify`'s
109
+ * protocol-byte defenses (a raw NUL/lone-surrogate would otherwise throw mid-write). Returns a 1-arg
110
+ * encoder; call sites invoke `dialectJsonEncoder(this.db.dialect)(value)` inline — the dispatch is the
111
+ * collected thing, not a cached field. */
112
+ export declare const dialectJsonEncoder: (dialect: SqlDialect) => ((v: unknown) => string);
113
+ /** design/158 redesign① — the "protocol" JSON-column encoder DISPATCH (approval-store/checkpoint-store's
114
+ * identical ternary, collected): TiDB stores `JSON.stringify` verbatim (the `label` arg is ignored on that
115
+ * arm — TiDB has no protocol-byte envelope to name); PG goes through `pgProtocolJsonStringify(v, label)`,
116
+ * whose `label` names the field in its error text on an unstorable-byte reject. */
117
+ export declare const dialectProtocolJsonEncoder: (dialect: SqlDialect) => ((v: unknown, label: string) => string);
102
118
  //# sourceMappingURL=sql-driver.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { pgSafeJsonStringify, pgProtocolJsonStringify } from "./pg-safe-json.js";
1
2
  /** mysql2 hands back `[rows]` for a SELECT and `[ResultSetHeader]` for a write. */
2
3
  function mysqlResult(r) {
3
4
  if (Array.isArray(r))
@@ -60,4 +61,15 @@ export function pgDriver(pool) {
60
61
  },
61
62
  };
62
63
  }
64
+ /** design/158 redesign① — the "safe" JSON-column encoder DISPATCH (run-store/outcome-ledger's identical
65
+ * ternary, collected): TiDB stores `JSON.stringify` verbatim; PG goes through `pgSafeJsonStringify`'s
66
+ * protocol-byte defenses (a raw NUL/lone-surrogate would otherwise throw mid-write). Returns a 1-arg
67
+ * encoder; call sites invoke `dialectJsonEncoder(this.db.dialect)(value)` inline — the dispatch is the
68
+ * collected thing, not a cached field. */
69
+ export const dialectJsonEncoder = (dialect) => dialect === "tidb" ? JSON.stringify : pgSafeJsonStringify;
70
+ /** design/158 redesign① — the "protocol" JSON-column encoder DISPATCH (approval-store/checkpoint-store's
71
+ * identical ternary, collected): TiDB stores `JSON.stringify` verbatim (the `label` arg is ignored on that
72
+ * arm — TiDB has no protocol-byte envelope to name); PG goes through `pgProtocolJsonStringify(v, label)`,
73
+ * whose `label` names the field in its error text on an unstorable-byte reject. */
74
+ export const dialectProtocolJsonEncoder = (dialect) => dialect === "tidb" ? (v) => JSON.stringify(v) : pgProtocolJsonStringify;
63
75
  //# sourceMappingURL=sql-driver.js.map
@@ -16,9 +16,9 @@ import { createRequire } from "node:module";
16
16
  import { readFileSync } from "node:fs";
17
17
  import { dirname, join } from "node:path";
18
18
  import { verifyPromptArtifact } from "@sema-agent/core";
19
+ import { SHA256_DIGEST_RE } from "./digest-form.js";
19
20
  const ID_RE = /^[a-z0-9][a-z0-9/._-]*$/; // core assemble 同形(≤128、非 core/ 前缀在下面单查)
20
21
  const SLOTS = new Set(["identity", "scenario", "behavior"]);
21
- const HASH_RE = /^sha256:[0-9a-f]{64}$/;
22
22
  function declarationIssue(d, where) {
23
23
  if (d === null || typeof d !== "object")
24
24
  return `${where}: declaration is not an object`;
@@ -35,7 +35,7 @@ function declarationIssue(d, where) {
35
35
  // contentHash?),hash 缺席/被 core 丢弃(错值)后**同长度文本编辑不改 artifactDigest=不重 pin**,
36
36
  // 恰是撤 restart 臂所依赖的审计不变量被击穿。center 链路恒逐 section 铸([1065]),正常路径零影响;
37
37
  // 这里拒的是坏发布/被改载荷(整拒候选,LKG/内置继续服务)。
38
- if (typeof o.contentHash !== "string" || !HASH_RE.test(o.contentHash))
38
+ if (typeof o.contentHash !== "string" || !SHA256_DIGEST_RE.test(o.contentHash))
39
39
  return `${where}: contentHash is required (sha256:<64hex> — the epoch declaration axis needs it for byte-level change detection)`;
40
40
  const expected = `sha256:${createHash("sha256").update(o.text).digest("hex")}`;
41
41
  if (o.contentHash !== expected)
@@ -53,7 +53,7 @@ export function validateCenterPrompts(raw) {
53
53
  const p = raw;
54
54
  if (typeof p.packId !== "string" || !/^center:[0-9a-f]{12}$/.test(p.packId))
55
55
  return { ok: false, error: "bad packId form (center:<digest12>)" };
56
- if (typeof p.contentDigest !== "string" || !HASH_RE.test(p.contentDigest))
56
+ if (typeof p.contentDigest !== "string" || !SHA256_DIGEST_RE.test(p.contentDigest))
57
57
  return { ok: false, error: "bad contentDigest form" };
58
58
  // 发版复审 F2/F4 两条硬化:
59
59
  // - 数组内 id 必须唯一(core composer 对 duplicate section id 是 THROW——独立判形查不出对撞,必须
package/dist/runs.d.ts CHANGED
@@ -7,6 +7,7 @@ import type { ElicitationCoordinator } from "./elicitation.js";
7
7
  import type { QuestionCoordinator } from "./question.js";
8
8
  import { type FleetRunPublisher } from "./fleet/fleet-bus.js";
9
9
  import { type WorkflowCompletionInbox } from "./orchestration/workflow-completion-inbox.js";
10
+ import type { VerifyRoundsSpec } from "./http/verify-rounds.js";
10
11
  /**
11
12
  * stoppedBy (core 1.252): a service cancel is a USER stop — mark the run's still-running background
12
13
  * children BEFORE core's teardown reaps them (bare abort = attribution falls back to "system"; core's own
@@ -201,10 +202,7 @@ export declare function evictIfConflict(runner: Runner, sessionId: string | unde
201
202
  * only — a suspended run is steered durably via the checkpoint's `setPendingSteer` instead, and only the
202
203
  * plain-stream path holds a steerable stream (the verify/cascade branches return a result, not a stream).
203
204
  */
204
- export declare function runInBackground(runner: Runner, spec: TaskSpec, runStore: RunStore, taskId: string, metrics?: Metrics, principal?: string, verify?: {
205
- maxRounds: number;
206
- costCeilingMicroUsd?: number;
207
- }, // 快审 F1:number 参升整对象——cost 顶随传
205
+ export declare function runInBackground(runner: Runner, spec: TaskSpec, runStore: RunStore, taskId: string, metrics?: Metrics, principal?: string, verify?: VerifyRoundsSpec, // 快审 F1:number 参升整对象——cost 顶随传
208
206
  cascade?: CascadeConfig, instrumentDegenerate?: (result: TaskResult) => void, planCacheProbe?: {
209
207
  record: (scope: string | undefined, objective: string, completed: boolean) => void;
210
208
  }, persistThinking?: boolean, inflight?: Map<string, AbortController>, preemptable?: Map<string, AbortController>, steerable?: Map<string, TaskStream>, modelUsage?: ModelUsageTracker, elicitation?: ElicitationCoordinator,
@@ -194,6 +194,17 @@ export declare const PRINCIPAL_MAX_LENGTH = 190;
194
194
  export declare const RESERVED_PRINCIPALS: readonly string[];
195
195
  export declare const RESERVED_PRINCIPAL_PREFIXES: readonly string[];
196
196
  export declare function assertPrincipalShape(principal: string): void;
197
+ /** The checkpoint `scope` column's single-user/anonymous sentinel — the same "_" documented above
198
+ * ({@link RESERVED_PRINCIPALS}) as the checkpoint/sendfile/resource-suspend scope family's shared value.
199
+ * Single attested constant + codec pair (naming precedent: {@link SENDFILE_SINGLE_USER_SCOPE} in
200
+ * ./plugins/send-file-ledger.js) so every checkpoint scope read/write goes through one place instead of
201
+ * each call site re-deriving the "_" ⇄ no-principal mapping inline. */
202
+ export declare const CHECKPOINT_PUBLIC_SCOPE: "_";
203
+ /** Encode a principal (or none) as the checkpoint `scope` column value. */
204
+ export declare const encodeCheckpointScope: (principal: string | null | undefined) => string;
205
+ /** Decode a checkpoint `scope` column value back to a principal — {@link CHECKPOINT_PUBLIC_SCOPE} maps to
206
+ * `undefined` (no principal), everything else passes through verbatim. */
207
+ export declare const decodeCheckpointScope: (scope: string) => string | undefined;
197
208
  /** Read the authenticated principal from the trusted header. Never from the body. */
198
209
  export declare function principalFrom(req: IncomingMessage, config: ServiceConfig): string | undefined;
199
210
  /** Header carrying the crypto-verified principal JWT on a direct-door worker (the issuer signs it; the worker only
package/dist/security.js CHANGED
@@ -256,6 +256,17 @@ export function assertPrincipalShape(principal) {
256
256
  throw new HttpError(400, `principal exceeds ${PRINCIPAL_MAX_LENGTH} characters (the scope-column width every store pins, e.g. sendfile_link.scope VARCHAR(190)); got ${principal.length}`, { code: "principal_too_long" });
257
257
  }
258
258
  }
259
+ /** The checkpoint `scope` column's single-user/anonymous sentinel — the same "_" documented above
260
+ * ({@link RESERVED_PRINCIPALS}) as the checkpoint/sendfile/resource-suspend scope family's shared value.
261
+ * Single attested constant + codec pair (naming precedent: {@link SENDFILE_SINGLE_USER_SCOPE} in
262
+ * ./plugins/send-file-ledger.js) so every checkpoint scope read/write goes through one place instead of
263
+ * each call site re-deriving the "_" ⇄ no-principal mapping inline. */
264
+ export const CHECKPOINT_PUBLIC_SCOPE = "_";
265
+ /** Encode a principal (or none) as the checkpoint `scope` column value. */
266
+ export const encodeCheckpointScope = (principal) => principal ?? CHECKPOINT_PUBLIC_SCOPE;
267
+ /** Decode a checkpoint `scope` column value back to a principal — {@link CHECKPOINT_PUBLIC_SCOPE} maps to
268
+ * `undefined` (no principal), everything else passes through verbatim. */
269
+ export const decodeCheckpointScope = (scope) => (scope === CHECKPOINT_PUBLIC_SCOPE ? undefined : scope);
259
270
  /** Read the authenticated principal from the trusted header. Never from the body. */
260
271
  export function principalFrom(req, config) {
261
272
  if (!config.principalHeader)
@@ -15,10 +15,21 @@
15
15
  */
16
16
  import type { StoreBackend } from "./plugins/store-backend.js";
17
17
  import { type SyncRelation } from "./session-sync-kernel.js";
18
- import type { SessionTreeEntry, SessionRulesRecord } from "@sema-agent/core";
18
+ import type { SessionTreeEntry, SessionRulesRecord, FileSnapshotResult } from "@sema-agent/core";
19
19
  /** A blob getter: content-addressed bytes for a sha256 hash, or `undefined` if the source can't supply them.
20
20
  * Returned by {@link exportSession} (closes over the source store), consumed by {@link importSession}. */
21
21
  export type BlobGetter = (hash: string) => Promise<Uint8Array | undefined>;
22
+ /** The snapshot-import capability face of a `FileSnapshotStore` — signature verbatim = the authoritative
23
+ * implementation, `FileSnapshotStoreSql.importManifest` (src/plugins/file-snapshot-store-sql.ts:201). */
24
+ export interface FileSnapshotImportFace {
25
+ importManifest(scope: string, key: string, manifest: Map<string, string>, srcGetBlob: BlobGetter): Promise<FileSnapshotResult>;
26
+ }
27
+ /** Single-point capability probe (design/158 R7): does `fs` carry the optional `importManifest` snapshot-import
28
+ * seam? Returns the bound method (never a naked function reference — `this` inside `importManifest` must resolve
29
+ * to `fs`, exactly like `asServiceDeps`'s other capability-probe-then-bind authorities) or `undefined` when the
30
+ * seam is absent (an older/in-memory dev store). Callers gate on the RETURN VALUE, not a separate `typeof` check,
31
+ * so there is exactly one place that knows the shape of the probe. */
32
+ export declare function fileSnapshotImportFace(fs: unknown): FileSnapshotImportFace | undefined;
22
33
  export { stagingIdFor, STAGING_ID_MARKER, realIdOfStaging, classifySyncRelationship, classifySyncRelationshipByIds, SyncConflictError } from "./session-sync-kernel.js";
23
34
  export type { StagingHandle, ImportStagingStore, SyncRelation } from "./session-sync-kernel.js";
24
35
  /**
@@ -1,6 +1,17 @@
1
1
  import { identicalIdsAlsoIdenticalContent } from "./session-sync-content.js";
2
2
  import { classifySyncRelationship, SyncConflictError } from "./session-sync-kernel.js";
3
3
  import { validateEntriesForImport, normalizeRules, stripRev } from "@sema-agent/core";
4
+ /** Single-point capability probe (design/158 R7): does `fs` carry the optional `importManifest` snapshot-import
5
+ * seam? Returns the bound method (never a naked function reference — `this` inside `importManifest` must resolve
6
+ * to `fs`, exactly like `asServiceDeps`'s other capability-probe-then-bind authorities) or `undefined` when the
7
+ * seam is absent (an older/in-memory dev store). Callers gate on the RETURN VALUE, not a separate `typeof` check,
8
+ * so there is exactly one place that knows the shape of the probe. */
9
+ export function fileSnapshotImportFace(fs) {
10
+ const candidate = fs?.importManifest;
11
+ if (typeof candidate !== "function")
12
+ return undefined;
13
+ return { importManifest: candidate.bind(fs) };
14
+ }
4
15
  // ── PURE KERNEL moved to ./session-sync-kernel.ts (design/158 A2) ─────────────────────────────────────────────
5
16
  // The §7 relation classifier (classifySyncRelationship[ByIds]), the staging-id scheme (stagingIdFor /
6
17
  // STAGING_ID_MARKER / realIdOfStaging), the StagingHandle / ImportStagingStore contracts and SyncConflictError
@@ -196,7 +207,8 @@ export async function exportSessionManifest(sessionId, srcBackend) {
196
207
  export async function importSession(bundle, getBlob, dstBackend, importingPrincipal, opts) {
197
208
  const sessionId = bundle.sessionId;
198
209
  const fs = dstBackend.fileSnapshot();
199
- const canImportSnapshots = typeof fs.importManifest === "function";
210
+ const importFace = fileSnapshotImportFace(fs);
211
+ const canImportSnapshots = importFace !== undefined;
200
212
  // ── §7 classification gate, BEFORE any write ───────────────────────────────────────────────────────────────
201
213
  // Read the destination's CURRENT entry log and classify the relationship (sound entry-id set comparison). A fork or
202
214
  // a stale source would lose dst history → refuse with a typed 409 unless the caller explicitly resolved overwrite-dst.
@@ -290,8 +302,8 @@ export async function importSession(bundle, getBlob, dstBackend, importingPrinci
290
302
  // written (the commit point is inside importEntries, step ②), so the dst stays clean (only collectable orphans).
291
303
  let snapshotsImported = 0;
292
304
  let snapshotsSkipped = 0;
293
- if (canImportSnapshots) {
294
- const importManifest = fs.importManifest.bind(fs);
305
+ if (importFace) {
306
+ const importManifest = importFace.importManifest;
295
307
  for (const snap of bundle.snapshots) {
296
308
  const r = await importManifest(sessionId, snap.key, new Map(snap.manifest), getBlob);
297
309
  if (!r.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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",