@sema-agent/server 7.109.0 → 7.111.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.
Files changed (107) hide show
  1. package/MIGRATION.md +25 -0
  2. package/README.md +8 -0
  3. package/README.zh-CN.md +5 -0
  4. package/USAGE.md +2 -2
  5. package/dist/approval-ask-audit-store.d.ts +38 -25
  6. package/dist/approval-ask-audit-store.js +26 -59
  7. package/dist/approval-card.d.ts +6 -5
  8. package/dist/approval-card.js +8 -11
  9. package/dist/bench/s1/run-firm.d.ts +11 -0
  10. package/dist/bench/s1/run-firm.js +47 -32
  11. package/dist/boot/coordinators.js +1 -1
  12. package/dist/boot/reapers.d.ts +3 -0
  13. package/dist/boot/reapers.js +3 -2
  14. package/dist/boot/resolve-spec.js +3 -4
  15. package/dist/boot/session-faces.d.ts +4 -0
  16. package/dist/boot/session-faces.js +3 -1
  17. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  18. package/dist/boot/stage-07-capability-layer.js +18 -3
  19. package/dist/boot/stage-08-reapers.d.ts +2 -0
  20. package/dist/boot/stage-08-reapers.js +2 -2
  21. package/dist/boot/stage-09-leader.d.ts +2 -0
  22. package/dist/boot/stage-09-leader.js +2 -1
  23. package/dist/boot/stage-10-http-server.d.ts +2 -0
  24. package/dist/boot/stage-10-http-server.js +2 -1
  25. package/dist/boot/workflow-orchestration.js +1 -1
  26. package/dist/config-catalog.js +1 -1
  27. package/dist/config-types.d.ts +3 -2
  28. package/dist/config.js +19 -7
  29. package/dist/deployment-governance.d.ts +8 -2
  30. package/dist/deployment-governance.js +9 -8
  31. package/dist/hooks/hook-runner.d.ts +2 -2
  32. package/dist/hooks/hook-runner.js +40 -26
  33. package/dist/http/durable-run-leg.js +1 -1
  34. package/dist/http/resume-legs.js +12 -4
  35. package/dist/http/routes/runs.d.ts +0 -1
  36. package/dist/http/routes/runs.js +36 -1
  37. package/dist/http/routes/sessions.js +12 -2
  38. package/dist/http/routes/tasks.js +13 -7
  39. package/dist/http/server.d.ts +5 -0
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.js +1 -0
  42. package/dist/instruction-selection.d.ts +34 -0
  43. package/dist/instruction-selection.js +23 -0
  44. package/dist/instruction-source-notice.d.ts +3 -3
  45. package/dist/memory-layer-legacy-lock.d.ts +3 -3
  46. package/dist/model-entry-headers.d.ts +5 -2
  47. package/dist/model-entry-headers.js +4 -4
  48. package/dist/model-unreachable-notice.d.ts +2 -2
  49. package/dist/observability/corrupt-read-seat.d.ts +24 -0
  50. package/dist/observability/corrupt-read-seat.js +24 -0
  51. package/dist/observability/fail-open.d.ts +25 -1
  52. package/dist/observability/fail-open.js +25 -1
  53. package/dist/observability/metrics.js +2 -1
  54. package/dist/observability/run-terminal.js +10 -0
  55. package/dist/operator-ask.d.ts +9 -8
  56. package/dist/operator-ask.js +9 -1
  57. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -23
  58. package/dist/orchestration/workflow-agent-session-index.js +11 -50
  59. package/dist/orchestration/workflow-notify-journal.d.ts +11 -8
  60. package/dist/orchestration/workflow-notify-journal.js +20 -36
  61. package/dist/plugins/file-outcome-sink.d.ts +4 -1
  62. package/dist/plugins/file-outcome-sink.js +9 -3
  63. package/dist/plugins/file-run-store.js +4 -4
  64. package/dist/plugins/memory-run-store.d.ts +1 -1
  65. package/dist/plugins/memory-run-store.js +2 -1
  66. package/dist/plugins/pg-pool.js +15 -0
  67. package/dist/plugins/retention-store-sql.d.ts +1 -1
  68. package/dist/plugins/retention-store-sql.js +2 -0
  69. package/dist/plugins/run-store-sql.d.ts +1 -1
  70. package/dist/plugins/run-store-sql.js +3 -2
  71. package/dist/plugins/schema-indexes.js +2 -0
  72. package/dist/plugins/store-backend.d.ts +4 -0
  73. package/dist/plugins/store-backend.js +9 -0
  74. package/dist/plugins/store-contracts.d.ts +24 -0
  75. package/dist/plugins/store-contracts.js +10 -0
  76. package/dist/plugins/tidb-pool.js +17 -0
  77. package/dist/plugins/user-input-ledger-file.d.ts +35 -0
  78. package/dist/plugins/user-input-ledger-file.js +108 -0
  79. package/dist/plugins/user-input-ledger-sql.d.ts +54 -0
  80. package/dist/plugins/user-input-ledger-sql.js +129 -0
  81. package/dist/project-memory.d.ts +10 -2
  82. package/dist/project-memory.js +5 -2
  83. package/dist/run-local.d.ts +18 -5
  84. package/dist/run-local.js +24 -9
  85. package/dist/runs.d.ts +8 -1
  86. package/dist/runs.js +11 -5
  87. package/dist/runtime-governance.d.ts +20 -8
  88. package/dist/runtime-governance.js +7 -12
  89. package/dist/session-records-skipped-notice.d.ts +4 -3
  90. package/dist/task-settings.d.ts +0 -6
  91. package/dist/task-settings.js +0 -1
  92. package/dist/tool-approval.d.ts +8 -1
  93. package/dist/tool-approval.js +4 -1
  94. package/dist/trace/core-keyset-guard.d.ts +16 -1
  95. package/dist/trace/engine-notice-wire.d.ts +30 -35
  96. package/dist/trace/engine-notice-wire.js +23 -23
  97. package/dist/trace/ledger-sink.d.ts +4 -0
  98. package/dist/trace/ledger-sink.js +1 -1
  99. package/dist/trace/project.d.ts +11 -3
  100. package/dist/trace/project.js +6 -5
  101. package/dist/trace/self-minted-wire.d.ts +40 -0
  102. package/dist/trace/self-minted-wire.js +15 -0
  103. package/dist/trace/wire-projection-faces.d.ts +3 -3
  104. package/dist/trace/wire-projection-faces.js +4 -2
  105. package/dist/user-input-ledger.d.ts +349 -0
  106. package/dist/user-input-ledger.js +354 -0
  107. package/package.json +3 -3
@@ -0,0 +1,354 @@
1
+ import { USER_STEER_UNDRAINED_CAUSES } from "@sema-agent/core";
2
+ import { recordFailOpen } from "./observability/fail-open.js";
3
+ import { isHolderDeathVerdict } from "./plugins/store-contracts.js";
4
+ import { defaultEngineNoticeRouter } from "./trace/engine-notice-wire.js";
5
+ export const USER_INPUT_LEDGER_DIR = "user-input-ledger";
6
+ export const USER_INPUT_KIND_WORDS = { steer: true };
7
+ export const SETTLE_HOW_WORDS = { leg_end: true, not_applied: true };
8
+ const UNDRAINED_CAUSE_MINTER = { leg_end: "engine", crashed: "server" };
9
+ export const CRASHED_CAUSE = "crashed";
10
+ export function isServingLayerUndrainedCause(v) {
11
+ if (typeof v !== "string" || !USER_STEER_UNDRAINED_CAUSES.includes(v))
12
+ return false;
13
+ return UNDRAINED_CAUSE_MINTER[v] === "server";
14
+ }
15
+ export class UserInputLedgerFold {
16
+ byId = new Map();
17
+ bySession = new Map();
18
+ has(acceptId) {
19
+ return this.byId.has(acceptId);
20
+ }
21
+ get(acceptId) {
22
+ return this.byId.get(acceptId);
23
+ }
24
+ sessionSize(sessionId) {
25
+ return this.bySession.get(sessionId)?.length ?? 0;
26
+ }
27
+ admits(rec) {
28
+ switch (rec.kind) {
29
+ case "accepted":
30
+ return !this.byId.has(rec.acceptId);
31
+ case "settled":
32
+ case "converged": {
33
+ const cur = this.byId.get(rec.acceptId);
34
+ return cur !== undefined && cur.state.kind === "open";
35
+ }
36
+ case "announced": {
37
+ const cur = this.byId.get(rec.acceptId);
38
+ return cur !== undefined && cur.state.kind === "converged" && cur.announced === undefined;
39
+ }
40
+ case "erased":
41
+ return this.sessionSize(rec.sessionId) > 0;
42
+ default: {
43
+ const unreachable = rec;
44
+ return unreachable;
45
+ }
46
+ }
47
+ }
48
+ apply(rec) {
49
+ if (!this.admits(rec))
50
+ return false;
51
+ switch (rec.kind) {
52
+ case "accepted": {
53
+ this.byId.set(rec.acceptId, {
54
+ acceptId: rec.acceptId,
55
+ input: rec.input,
56
+ messageId: rec.messageId,
57
+ sessionId: rec.sessionId,
58
+ taskId: rec.taskId,
59
+ writer: rec.writer,
60
+ acceptedAtMs: rec.at,
61
+ state: { kind: "open" },
62
+ });
63
+ const list = this.bySession.get(rec.sessionId);
64
+ if (list)
65
+ list.push(rec.acceptId);
66
+ else
67
+ this.bySession.set(rec.sessionId, [rec.acceptId]);
68
+ return true;
69
+ }
70
+ case "settled": {
71
+ const cur = this.byId.get(rec.acceptId);
72
+ this.byId.set(rec.acceptId, { ...cur, state: { kind: "settled", how: rec.how, atMs: rec.at } });
73
+ return true;
74
+ }
75
+ case "converged": {
76
+ const cur = this.byId.get(rec.acceptId);
77
+ this.byId.set(rec.acceptId, { ...cur, state: { kind: "converged", cause: rec.cause, atMs: rec.at } });
78
+ return true;
79
+ }
80
+ case "announced": {
81
+ const cur = this.byId.get(rec.acceptId);
82
+ this.byId.set(rec.acceptId, { ...cur, announced: { atMs: rec.at, taskId: rec.taskId } });
83
+ return true;
84
+ }
85
+ case "erased": {
86
+ for (const acceptId of this.bySession.get(rec.sessionId))
87
+ this.byId.delete(acceptId);
88
+ this.bySession.delete(rec.sessionId);
89
+ return true;
90
+ }
91
+ default: {
92
+ const unreachable = rec;
93
+ return unreachable;
94
+ }
95
+ }
96
+ }
97
+ listOpen(after, limit) {
98
+ assertPageLimit("listOpen", limit);
99
+ const open = sortAcceptances([...this.byId.values()].filter((a) => a.state.kind === "open"));
100
+ const rest = after === undefined ? open : open.filter((a) => a.acceptedAtMs > after.acceptedAtMs || (a.acceptedAtMs === after.acceptedAtMs && a.acceptId > after.acceptId));
101
+ const page = rest.slice(0, limit);
102
+ const last = page.length === limit ? page[page.length - 1] : undefined;
103
+ return last === undefined ? { open: page } : { open: page, next: { acceptedAtMs: last.acceptedAtMs, acceptId: last.acceptId } };
104
+ }
105
+ listCrashed(sessionId, limit) {
106
+ const ids = this.bySession.get(sessionId) ?? [];
107
+ const crashed = sortAcceptances(ids.map((id) => this.byId.get(id)).filter((a) => a.state.kind === "converged"));
108
+ return crashed.slice(Math.max(0, crashed.length - limit));
109
+ }
110
+ listUnannounced(sessionId, limit) {
111
+ assertPageLimit("listUnannounced", limit);
112
+ const ids = this.bySession.get(sessionId) ?? [];
113
+ return sortAcceptances(ids.map((id) => this.byId.get(id)).filter((a) => a.state.kind === "converged" && a.announced === undefined)).slice(0, limit);
114
+ }
115
+ }
116
+ export function assertPageLimit(op, limit) {
117
+ if (!Number.isInteger(limit) || limit < 1)
118
+ throw new Error(`user-input ledger ${op}: limit must be a positive integer (got ${String(limit)})`);
119
+ return limit;
120
+ }
121
+ export function sortAcceptances(list) {
122
+ return list.sort((a, b) => a.acceptedAtMs - b.acceptedAtMs || (a.acceptId < b.acceptId ? -1 : a.acceptId > b.acceptId ? 1 : 0));
123
+ }
124
+ export const USER_INPUT_OPEN_SCAN_MAX = 1000;
125
+ export const USER_INPUT_OPEN_SCAN_PAGES = 100;
126
+ export const USER_INPUT_SESSION_READ_MAX = 500;
127
+ const USER_STEER_UNDRAINED = "task.user_steer_undrained";
128
+ export function buildCrashedSteerNotice(input) {
129
+ return {
130
+ code: USER_STEER_UNDRAINED,
131
+ message: `Check the earlier run's transcript and results before re-sending any of these steers to a live run. The server accepted ${input.steer} user steer(s) for run ${input.taskId} (each answered 200 applied or 202 queued-immediate), but the process running that run ended before the run settled; whether the run consumed them is not known. They are NOT redelivered.`,
132
+ detail: { steer: input.steer, cause: CRASHED_CAUSE, taskId: input.taskId, sessionId: input.sessionId },
133
+ sessionId: input.sessionId,
134
+ };
135
+ }
136
+ const errText = (e) => (e instanceof Error ? e.message : String(e));
137
+ const LANE_FACTS = {
138
+ exclusive_data_root: { foreignHolderMayBeAlive: false, absentRunRow: "crashed" },
139
+ run_store_verdict: { foreignHolderMayBeAlive: true, absentRunRow: "leg_over" },
140
+ };
141
+ async function judgeOpen(a, me, liveHere, kind, getRun) {
142
+ if (a.writer === me)
143
+ return liveHere.has(a.acceptId) ? "live" : "leg_over";
144
+ const lane = LANE_FACTS[kind];
145
+ const run = await getRun(a.taskId);
146
+ if (!run)
147
+ return lane.absentRunRow;
148
+ if (run.instanceId === a.writer) {
149
+ if (run.status === "running")
150
+ return lane.foreignHolderMayBeAlive ? "live" : "crashed";
151
+ if (isHolderDeathVerdict(run))
152
+ return "crashed";
153
+ }
154
+ return "leg_over";
155
+ }
156
+ export function createUserInputLedger(opts) {
157
+ const { store, writer, evidence } = opts;
158
+ const now = opts.now ?? Date.now;
159
+ const legs = new WeakMap();
160
+ const liveHere = new Set();
161
+ const track = (leg, acceptId) => {
162
+ const set = legs.get(leg);
163
+ if (set)
164
+ set.add(acceptId);
165
+ else
166
+ legs.set(leg, new Set([acceptId]));
167
+ };
168
+ const pageSize = opts.scanBudget?.pageSize ?? USER_INPUT_OPEN_SCAN_MAX;
169
+ const maxPages = opts.scanBudget?.maxPages ?? USER_INPUT_OPEN_SCAN_PAGES;
170
+ let scanCursor;
171
+ let inFlight;
172
+ const convergePass = async () => {
173
+ const reads = new Map();
174
+ const getRun = (taskId) => {
175
+ let read = reads.get(taskId);
176
+ if (read === undefined)
177
+ reads.set(taskId, (read = evidence.runs.getRun(taskId)));
178
+ return read;
179
+ };
180
+ let n = 0;
181
+ for (let page = 0; page < maxPages; page++) {
182
+ let scanned;
183
+ try {
184
+ scanned = await store.listOpen(scanCursor, pageSize);
185
+ }
186
+ catch (e) {
187
+ recordFailOpen("server.user-input-ledger.read-failed", `op=converge ${errText(e)}`);
188
+ return n;
189
+ }
190
+ n += await convergePage(scanned.open, getRun);
191
+ scanCursor = scanned.next;
192
+ if (scanCursor === undefined)
193
+ return n;
194
+ }
195
+ return n;
196
+ };
197
+ const convergePage = async (open, getRun) => {
198
+ let n = 0;
199
+ for (const a of open) {
200
+ let verdict;
201
+ try {
202
+ verdict = await judgeOpen(a, writer, liveHere, evidence.kind, getRun);
203
+ }
204
+ catch (e) {
205
+ recordFailOpen("server.user-input-ledger.read-failed", `op=judge ${errText(e)}`);
206
+ continue;
207
+ }
208
+ try {
209
+ switch (verdict) {
210
+ case "crashed":
211
+ if (await store.converge(a.acceptId, now()))
212
+ n += 1;
213
+ break;
214
+ case "leg_over":
215
+ await store.settle(a.acceptId, "leg_end", now());
216
+ break;
217
+ case "live":
218
+ break;
219
+ default: {
220
+ const unreachable = verdict;
221
+ return unreachable;
222
+ }
223
+ }
224
+ }
225
+ catch (e) {
226
+ recordFailOpen("server.user-input-ledger.append-failed", `op=converge ${errText(e)}`);
227
+ }
228
+ }
229
+ return n;
230
+ };
231
+ return {
232
+ async accept(input) {
233
+ const acceptId = opts.newId();
234
+ liveHere.add(acceptId);
235
+ try {
236
+ await store.accept({ acceptId, input: input.input, messageId: input.messageId, sessionId: input.sessionId, taskId: input.taskId, writer, acceptedAtMs: now() });
237
+ }
238
+ catch (e) {
239
+ liveHere.delete(acceptId);
240
+ throw e;
241
+ }
242
+ track(input.leg, acceptId);
243
+ return { acceptId, leg: input.leg };
244
+ },
245
+ async withdraw(receipt) {
246
+ legs.get(receipt.leg)?.delete(receipt.acceptId);
247
+ try {
248
+ await store.settle(receipt.acceptId, "not_applied", now());
249
+ }
250
+ catch (e) {
251
+ recordFailOpen("server.user-input-ledger.append-failed", `op=withdraw ${errText(e)}`);
252
+ }
253
+ finally {
254
+ liveHere.delete(receipt.acceptId);
255
+ }
256
+ },
257
+ async settleLeg(leg) {
258
+ const ids = legs.get(leg);
259
+ if (!ids)
260
+ return;
261
+ legs.delete(leg);
262
+ for (const acceptId of ids) {
263
+ try {
264
+ await store.settle(acceptId, "leg_end", now());
265
+ }
266
+ catch (e) {
267
+ recordFailOpen("server.user-input-ledger.append-failed", `op=settle ${errText(e)}`);
268
+ }
269
+ finally {
270
+ liveHere.delete(acceptId);
271
+ }
272
+ }
273
+ },
274
+ converge() {
275
+ inFlight ??= convergePass().finally(() => {
276
+ inFlight = undefined;
277
+ });
278
+ return inFlight;
279
+ },
280
+ async announceUndrained(at, router = defaultEngineNoticeRouter) {
281
+ let rows;
282
+ try {
283
+ rows = await store.listUnannounced(at.sessionId, USER_INPUT_SESSION_READ_MAX);
284
+ }
285
+ catch (e) {
286
+ recordFailOpen("server.user-input-ledger.read-failed", `op=announce ${errText(e)}`);
287
+ return;
288
+ }
289
+ const groups = new Map();
290
+ for (const a of rows) {
291
+ const g = groups.get(a.taskId);
292
+ if (g)
293
+ g.push(a);
294
+ else
295
+ groups.set(a.taskId, [a]);
296
+ }
297
+ for (const [oldTaskId, group] of groups) {
298
+ const steer = new Set(group.map((a) => a.messageId)).size;
299
+ const delivered = await router.routeConfirmed(buildCrashedSteerNotice({ steer, taskId: oldTaskId, sessionId: at.sessionId }));
300
+ if (delivered === 0)
301
+ continue;
302
+ for (const a of group) {
303
+ try {
304
+ await store.announce(a.acceptId, at.taskId, now());
305
+ }
306
+ catch (e) {
307
+ recordFailOpen("server.user-input-ledger.append-failed", `op=announce ${errText(e)}`);
308
+ }
309
+ }
310
+ }
311
+ },
312
+ async readSession(sessionId) {
313
+ const rows = await store.listCrashed(sessionId);
314
+ const seen = new Set();
315
+ const out = [];
316
+ for (const a of rows) {
317
+ if (a.state.kind !== "converged")
318
+ continue;
319
+ const key = `${a.taskId}\u0000${a.messageId}`;
320
+ if (seen.has(key))
321
+ continue;
322
+ seen.add(key);
323
+ out.push({
324
+ messageId: a.messageId,
325
+ kind: a.input,
326
+ taskId: a.taskId,
327
+ acceptedAt: new Date(a.acceptedAtMs).toISOString(),
328
+ cause: a.state.cause,
329
+ ...(a.announced !== undefined ? { announced: { at: new Date(a.announced.atMs).toISOString(), taskId: a.announced.taskId } } : {}),
330
+ });
331
+ }
332
+ return out.length > 0 ? out : undefined;
333
+ },
334
+ };
335
+ }
336
+ export async function createUserInputLedgerAtBoot(opts) {
337
+ const ledger = createUserInputLedger(opts);
338
+ const converged = await ledger.converge();
339
+ return { ledger, converged };
340
+ }
341
+ export function writerGoneEvidenceFor(backendKind, runs) {
342
+ switch (backendKind) {
343
+ case "local":
344
+ return { kind: "exclusive_data_root", runs };
345
+ case "mysql":
346
+ case "pg":
347
+ return { kind: "run_store_verdict", runs };
348
+ default: {
349
+ const unreachable = backendKind;
350
+ return unreachable;
351
+ }
352
+ }
353
+ }
354
+ //# sourceMappingURL=user-input-ledger.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "7.109.0",
3
+ "version": "7.111.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",
@@ -61,7 +61,7 @@
61
61
  "build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
62
62
  },
63
63
  "dependencies": {
64
- "@sema-agent/core": "7.37.1",
64
+ "@sema-agent/core": "7.38.2",
65
65
  "@sema-agent/settings-schema": "6.4.0",
66
66
  "e2b": "^2.28.0",
67
67
  "libsodium-wrappers": "^0.8.4",
@@ -77,7 +77,7 @@
77
77
  "sharp": "^0.35.3"
78
78
  },
79
79
  "devDependencies": {
80
- "@sema-agent/sdk": "13.1.0",
80
+ "@sema-agent/sdk": "15.1.1",
81
81
  "@types/libsodium-wrappers": "^0.7.14",
82
82
  "@types/node": "22.10.2",
83
83
  "@types/pg": "^8.20.0",