@sema-agent/server 1.314.0 → 1.316.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 (146) hide show
  1. package/dist/approval-hmac.d.ts +17 -0
  2. package/dist/approval-hmac.js +27 -0
  3. package/dist/config-center/apply-effective.d.ts +22 -0
  4. package/dist/config-center/apply-effective.js +283 -0
  5. package/dist/config-center/http-client.d.ts +23 -0
  6. package/dist/config-center/http-client.js +109 -0
  7. package/dist/config-center/restart-signal.d.ts +12 -0
  8. package/dist/config-center/restart-signal.js +70 -0
  9. package/dist/config-center/skills-mcp.d.ts +13 -0
  10. package/dist/config-center/skills-mcp.js +113 -0
  11. package/dist/config-center/types.d.ts +143 -0
  12. package/dist/config-center/types.js +2 -0
  13. package/dist/fleet/fleet-bus.js +92 -83
  14. package/dist/hooks/hook-runner.js +18 -9
  15. package/dist/http/idempotency.d.ts +1 -0
  16. package/dist/http/idempotency.js +3 -0
  17. package/dist/http/principal-gate.d.ts +12 -0
  18. package/dist/http/principal-gate.js +52 -0
  19. package/dist/http/route-ctx.d.ts +67 -0
  20. package/dist/http/route-ctx.js +2 -0
  21. package/dist/http/routes/attachments.d.ts +4 -0
  22. package/dist/http/routes/attachments.js +106 -0
  23. package/dist/http/routes/capabilities.d.ts +4 -0
  24. package/dist/http/routes/capabilities.js +111 -0
  25. package/dist/http/routes/images.d.ts +20 -0
  26. package/dist/http/routes/images.js +572 -0
  27. package/dist/http/routes/memory-policy.d.ts +4 -0
  28. package/dist/http/routes/memory-policy.js +105 -0
  29. package/dist/http/routes/observability.d.ts +4 -0
  30. package/dist/http/routes/observability.js +63 -0
  31. package/dist/http/routes/session-sync.d.ts +36 -0
  32. package/dist/http/routes/session-sync.js +513 -0
  33. package/dist/http/routes/sessions-list.d.ts +4 -0
  34. package/dist/http/routes/sessions-list.js +117 -0
  35. package/dist/http/routes/sessions.d.ts +17 -0
  36. package/dist/http/routes/sessions.js +746 -0
  37. package/dist/http/send.d.ts +6 -0
  38. package/dist/http/send.js +19 -0
  39. package/dist/http/server.d.ts +101 -76
  40. package/dist/http/server.js +129 -2410
  41. package/dist/http/sse-log.d.ts +25 -0
  42. package/dist/http/sse-log.js +59 -0
  43. package/dist/http/workspace-content.d.ts +3 -0
  44. package/dist/http/workspace-content.js +20 -0
  45. package/dist/index.d.ts +1 -1
  46. package/dist/leader/leader.js +5 -2
  47. package/dist/leader/wire.js +169 -165
  48. package/dist/main.js +462 -451
  49. package/dist/plugins/approval-store-sql.d.ts +52 -0
  50. package/dist/plugins/approval-store-sql.js +119 -0
  51. package/dist/plugins/breaker-state-sql.d.ts +29 -0
  52. package/dist/plugins/breaker-state-sql.js +104 -0
  53. package/dist/plugins/checkpoint-store-sql.d.ts +67 -0
  54. package/dist/plugins/checkpoint-store-sql.js +224 -0
  55. package/dist/plugins/file-snapshot-store-sql.d.ts +38 -0
  56. package/dist/plugins/file-snapshot-store-sql.js +210 -0
  57. package/dist/plugins/image-bake-store-sql.d.ts +53 -0
  58. package/dist/plugins/image-bake-store-sql.js +463 -0
  59. package/dist/plugins/image-index-sql.d.ts +33 -0
  60. package/dist/plugins/image-index-sql.js +328 -0
  61. package/dist/plugins/k8s-bg-scripts.d.ts +19 -0
  62. package/dist/plugins/k8s-bg-scripts.js +129 -0
  63. package/dist/plugins/k8s-exec-protocol.d.ts +20 -0
  64. package/dist/plugins/k8s-exec-protocol.js +87 -0
  65. package/dist/plugins/outcome-ledger-sql.d.ts +70 -0
  66. package/dist/plugins/outcome-ledger-sql.js +158 -0
  67. package/dist/plugins/pg-approval-store.d.ts +1 -27
  68. package/dist/plugins/pg-approval-store.js +1 -83
  69. package/dist/plugins/pg-breaker-state.d.ts +1 -19
  70. package/dist/plugins/pg-breaker-state.js +1 -88
  71. package/dist/plugins/pg-checkpoint-store.d.ts +1 -33
  72. package/dist/plugins/pg-checkpoint-store.js +1 -189
  73. package/dist/plugins/pg-cost-quota.js +3 -143
  74. package/dist/plugins/pg-file-snapshot-store.d.ts +1 -27
  75. package/dist/plugins/pg-file-snapshot-store.js +1 -190
  76. package/dist/plugins/pg-image-bake.d.ts +1 -40
  77. package/dist/plugins/pg-image-bake.js +1 -420
  78. package/dist/plugins/pg-image-index.d.ts +1 -21
  79. package/dist/plugins/pg-image-index.js +1 -217
  80. package/dist/plugins/pg-outcome-ledger.d.ts +2 -22
  81. package/dist/plugins/pg-outcome-ledger.js +1 -104
  82. package/dist/plugins/pg-rate-limiter.d.ts +2 -32
  83. package/dist/plugins/pg-rate-limiter.js +4 -147
  84. package/dist/plugins/pg-resume-anchor-store.d.ts +1 -13
  85. package/dist/plugins/pg-resume-anchor-store.js +1 -22
  86. package/dist/plugins/pg-run-store.d.ts +1 -75
  87. package/dist/plugins/pg-run-store.js +1 -287
  88. package/dist/plugins/pg-session-policy-store.d.ts +1 -11
  89. package/dist/plugins/pg-session-policy-store.js +1 -90
  90. package/dist/plugins/pg-tool-result-store.d.ts +1 -15
  91. package/dist/plugins/pg-tool-result-store.js +1 -52
  92. package/dist/plugins/pg-workflow-journal-store.d.ts +1 -20
  93. package/dist/plugins/pg-workflow-journal-store.js +1 -34
  94. package/dist/plugins/pg-workflow-run-store.d.ts +1 -55
  95. package/dist/plugins/pg-workflow-run-store.js +1 -220
  96. package/dist/plugins/remote-env-k8s.d.ts +5 -38
  97. package/dist/plugins/remote-env-k8s.js +7 -213
  98. package/dist/plugins/resume-anchor-store-sql.d.ts +23 -0
  99. package/dist/plugins/resume-anchor-store-sql.js +38 -0
  100. package/dist/plugins/run-store-sql.d.ts +89 -0
  101. package/dist/plugins/run-store-sql.js +397 -0
  102. package/dist/plugins/session-policy-store-sql.d.ts +22 -0
  103. package/dist/plugins/session-policy-store-sql.js +109 -0
  104. package/dist/plugins/sql-driver.d.ts +25 -0
  105. package/dist/plugins/sql-driver.js +59 -0
  106. package/dist/plugins/tidb-approval-store.d.ts +1 -39
  107. package/dist/plugins/tidb-approval-store.js +1 -71
  108. package/dist/plugins/tidb-breaker-state.d.ts +1 -17
  109. package/dist/plugins/tidb-breaker-state.js +1 -74
  110. package/dist/plugins/tidb-checkpoint-store.d.ts +1 -49
  111. package/dist/plugins/tidb-checkpoint-store.js +1 -191
  112. package/dist/plugins/tidb-file-snapshot-store.d.ts +1 -27
  113. package/dist/plugins/tidb-file-snapshot-store.js +1 -192
  114. package/dist/plugins/tidb-image-bake.d.ts +1 -38
  115. package/dist/plugins/tidb-image-bake.js +1 -348
  116. package/dist/plugins/tidb-image-index.d.ts +1 -19
  117. package/dist/plugins/tidb-image-index.js +1 -149
  118. package/dist/plugins/tidb-outcome-ledger.d.ts +1 -57
  119. package/dist/plugins/tidb-outcome-ledger.js +1 -97
  120. package/dist/plugins/tidb-resume-anchor-store.d.ts +1 -13
  121. package/dist/plugins/tidb-resume-anchor-store.js +1 -22
  122. package/dist/plugins/tidb-run-store.d.ts +1 -75
  123. package/dist/plugins/tidb-run-store.js +1 -306
  124. package/dist/plugins/tidb-session-policy-store.d.ts +1 -11
  125. package/dist/plugins/tidb-session-policy-store.js +1 -86
  126. package/dist/plugins/tidb-tool-result-store.d.ts +1 -13
  127. package/dist/plugins/tidb-tool-result-store.js +1 -38
  128. package/dist/plugins/tidb-workflow-journal-store.d.ts +1 -20
  129. package/dist/plugins/tidb-workflow-journal-store.js +1 -34
  130. package/dist/plugins/tidb-workflow-run-store.d.ts +1 -61
  131. package/dist/plugins/tidb-workflow-run-store.js +1 -231
  132. package/dist/plugins/tool-result-store-sql.d.ts +25 -0
  133. package/dist/plugins/tool-result-store-sql.js +69 -0
  134. package/dist/plugins/workflow-journal-store-sql.d.ts +30 -0
  135. package/dist/plugins/workflow-journal-store-sql.js +53 -0
  136. package/dist/plugins/workflow-run-store-sql.d.ts +85 -0
  137. package/dist/plugins/workflow-run-store-sql.js +299 -0
  138. package/dist/plugins/write-behind-counter.d.ts +14 -3
  139. package/dist/plugins/write-behind-counter.js +39 -12
  140. package/dist/principal-jwt.d.ts +44 -0
  141. package/dist/principal-jwt.js +95 -0
  142. package/dist/security.d.ts +2 -58
  143. package/dist/security.js +3 -118
  144. package/dist/sema-registry.d.ts +5 -200
  145. package/dist/sema-registry.js +4 -567
  146. package/package.json +1 -1
@@ -1,221 +1,2 @@
1
- import { summarizeWorkflowRun, isTerminalWorkflowStatus, WorkflowRunStoreError } from "@sema-agent/core";
2
- import { foldKeyFamily, MAX_PENDING_PER_SESSION, PURGE_FENCE_MS, SERVED_FENCE_MS, } from "../orchestration/workflow-completion-inbox.js";
3
- import { MAX_RUN_BLOB_BYTES, slimOversizeRun } from "./tidb-workflow-run-store.js";
4
- export class PgWorkflowRunStore {
5
- pool;
6
- constructor(pool) {
7
- this.pool = pool;
8
- }
9
- async put(id, run) {
10
- const stored = { ...run, id, rev: run.rev ?? 0 };
11
- try {
12
- await this.pool.query("INSERT INTO workflow_run (id, scope, status, run, rev, created_at, ended_at) VALUES ($1,$2,$3,$4,$5,$6,$7)", [id, run.scope, run.status, JSON.stringify(stored), stored.rev, run.createdAt, run.endedAt ?? null]);
13
- }
14
- catch (e) {
15
- if (e.code === "23505") {
16
- throw new WorkflowRunStoreError("workflow_run.already_exists", "workflow run id already exists");
17
- }
18
- throw e;
19
- }
20
- }
21
- async get(id) {
22
- const res = await this.pool.query("SELECT run, rev FROM workflow_run WHERE id = $1", [id]);
23
- const r = res.rows[0];
24
- if (!r)
25
- return null;
26
- const run = JSON.parse(String(r.run));
27
- run.rev = Number(r.rev);
28
- return run;
29
- }
30
- async update(id, scope, run, expect) {
31
- let blob = JSON.stringify({ ...run, id, scope });
32
- if (Buffer.byteLength(blob) > MAX_RUN_BLOB_BYTES) {
33
- const slim = slimOversizeRun({ ...run, id, scope }, MAX_RUN_BLOB_BYTES);
34
- if (slim === null)
35
- return false;
36
- blob = slim;
37
- }
38
- const res = await this.pool.query(`UPDATE workflow_run SET run = $1, rev = rev + 1, status = $2, ended_at = $3 WHERE id = $4 AND scope = $5${expect !== undefined ? " AND rev = $6" : ""}`, expect !== undefined ? [blob, run.status, run.endedAt ?? null, id, scope, expect.rev] : [blob, run.status, run.endedAt ?? null, id, scope]);
39
- return res.rowCount === 1;
40
- }
41
- async listByScope(scope, opts) {
42
- const params = [scope];
43
- let where = "scope = $1";
44
- if (opts?.status !== undefined) {
45
- params.push(opts.status);
46
- where += ` AND status = $${params.length}`;
47
- }
48
- if (opts?.session !== undefined) {
49
- params.push(opts.session);
50
- where += ` AND run::json->>'originatingSessionId' = $${params.length}`;
51
- }
52
- let limit = "";
53
- if (opts?.limit !== undefined) {
54
- params.push(Math.max(0, opts.limit));
55
- limit = ` LIMIT $${params.length}`;
56
- }
57
- const res = await this.pool.query(`SELECT run, rev FROM workflow_run WHERE ${where} ORDER BY created_at DESC, id DESC${limit}`, params);
58
- return res.rows.map((r) => {
59
- const run = JSON.parse(String(r.run));
60
- run.rev = Number(r.rev);
61
- return summarizeWorkflowRun(run);
62
- });
63
- }
64
- async reapAllScopes(now, opts) {
65
- const res = await this.pool.query("SELECT DISTINCT scope FROM workflow_run");
66
- let total = 0;
67
- for (const r of res.rows)
68
- total += await this.reap(r.scope, now, opts);
69
- return total;
70
- }
71
- async reap(scope, now, opts) {
72
- if (opts?.maxAgeMs === undefined && opts?.keep === undefined)
73
- return 0;
74
- const res = await this.pool.query("SELECT id, status, created_at, ended_at FROM workflow_run WHERE scope = $1 ORDER BY created_at DESC, id DESC", [scope]);
75
- const rows = res.rows;
76
- const terminal = rows.filter((r) => isTerminalWorkflowStatus(r.status));
77
- const doomed = [];
78
- for (let i = 0; i < terminal.length; i++) {
79
- const r = terminal[i];
80
- const anchor = r.ended_at != null ? Number(r.ended_at) : Number(r.created_at);
81
- const tooOld = opts.maxAgeMs !== undefined && anchor < now - opts.maxAgeMs;
82
- const overKeep = opts.keep !== undefined && i >= opts.keep;
83
- if (tooOld || overKeep)
84
- doomed.push(r.id);
85
- }
86
- if (doomed.length === 0)
87
- return 0;
88
- const del = await this.pool.query(`DELETE FROM workflow_run WHERE scope = $1 AND id = ANY($2::text[])`, [scope, doomed]);
89
- return del.rowCount ?? 0;
90
- }
91
- }
92
- const KIND_SERVED = "served";
93
- const KIND_PURGE = "purge";
94
- export class PgWorkflowCompletionInbox {
95
- pool;
96
- onWarn;
97
- constructor(pool, onWarn) {
98
- this.pool = pool;
99
- this.onWarn = onWarn;
100
- }
101
- async sweepFences(now) {
102
- await this.pool.query("DELETE FROM workflow_inbox_fence WHERE until_ms < $1", [now]);
103
- }
104
- async enqueue(entry) {
105
- const now = Date.now();
106
- await this.sweepFences(now);
107
- const served = await this.pool.query("SELECT 1 FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id IN ($3, $4) AND until_ms >= $5", [entry.sessionId, KIND_SERVED, entry.runId, foldKeyFamily(entry.runId), now]);
108
- if ((served.rowCount ?? 0) > 0) {
109
- this.onWarn?.("workflow_completion_enqueue_after_poll_served", { sessionId: entry.sessionId, runId: entry.runId });
110
- return;
111
- }
112
- const purge = await this.pool.query("SELECT owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND kind = $2 AND run_id = '' AND until_ms >= $3", [entry.sessionId, KIND_PURGE, now]);
113
- const pf = purge.rows[0];
114
- if (pf && (Number(pf.owner_known) === 0 || (pf.owner === null ? entry.owner === null : pf.owner === entry.owner))) {
115
- this.onWarn?.("workflow_completion_enqueue_after_purge", { sessionId: entry.sessionId, runId: entry.runId });
116
- return;
117
- }
118
- await this.pool.query("INSERT INTO workflow_completion_inbox (session_id, run_id, owner, status, summary, enqueued_at, kind, payload) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) " +
119
- "ON CONFLICT (session_id, run_id) DO UPDATE SET seq = nextval(pg_get_serial_sequence('workflow_completion_inbox','seq')), " +
120
- "owner = EXCLUDED.owner, status = EXCLUDED.status, summary = EXCLUDED.summary, enqueued_at = EXCLUDED.enqueued_at, kind = EXCLUDED.kind, payload = EXCLUDED.payload", [entry.sessionId, entry.runId, entry.owner, entry.status, entry.summary, entry.enqueuedAt, entry.kind ?? null, entry.payload ?? null]);
121
- const confirm = await this.pool.query("SELECT kind, owner, owner_known FROM workflow_inbox_fence WHERE session_id = $1 AND until_ms >= $2 AND (run_id IN ($3, $4) OR (kind = $5 AND run_id = ''))", [entry.sessionId, now, entry.runId, foldKeyFamily(entry.runId), KIND_PURGE]);
122
- for (const f of confirm.rows) {
123
- const isServed = f.kind === KIND_SERVED;
124
- const purgeFencesUs = !isServed && (Number(f.owner_known) === 0 || (f.owner === null ? entry.owner === null : f.owner === entry.owner));
125
- if (isServed || purgeFencesUs) {
126
- await this.ack(entry.sessionId, entry.runId);
127
- this.onWarn?.(isServed ? "workflow_completion_enqueue_after_poll_served" : "workflow_completion_enqueue_after_purge", {
128
- sessionId: entry.sessionId,
129
- runId: entry.runId,
130
- });
131
- return;
132
- }
133
- }
134
- const over = await this.pool.query("SELECT seq, run_id FROM workflow_completion_inbox WHERE session_id = $1 ORDER BY seq DESC OFFSET $2", [entry.sessionId, MAX_PENDING_PER_SESSION]);
135
- const overRows = over.rows;
136
- if (overRows.length > 0) {
137
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND seq = ANY($2::bigint[])", [
138
- entry.sessionId,
139
- overRows.map((r) => r.seq),
140
- ]);
141
- for (const r of overRows)
142
- this.onWarn?.("workflow_completion_inbox_overflow", { sessionId: entry.sessionId, droppedRunId: r.run_id });
143
- }
144
- }
145
- async pending(sessionId) {
146
- const res = await this.pool.query("SELECT i.run_id, i.owner, i.status, i.summary, i.enqueued_at, i.kind, i.payload FROM workflow_completion_inbox i " +
147
- "WHERE i.session_id = $1 AND NOT EXISTS (SELECT 1 FROM workflow_inbox_fence f WHERE f.session_id = i.session_id AND f.kind = $2 AND f.run_id IN (i.run_id, regexp_replace(i.run_id, '#[0-9]+$', '')) AND f.until_ms >= $3) " +
148
- "ORDER BY i.seq ASC", [sessionId, KIND_SERVED, Date.now()]);
149
- return res.rows.map((r) => ({
150
- sessionId,
151
- runId: r.run_id,
152
- owner: r.owner,
153
- status: r.status,
154
- summary: r.summary,
155
- enqueuedAt: Number(r.enqueued_at),
156
- ...(r.kind != null ? { kind: r.kind } : {}),
157
- ...(r.payload != null ? { payload: r.payload } : {}),
158
- }));
159
- }
160
- async ack(sessionId, runId) {
161
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id = $2", [sessionId, runId]);
162
- }
163
- async purge(sessionId, purgedOwner) {
164
- const now = Date.now();
165
- await this.sweepFences(now);
166
- await this.pool.query("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,'',$3,$4,$5) " +
167
- "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET owner = EXCLUDED.owner, owner_known = EXCLUDED.owner_known, until_ms = EXCLUDED.until_ms", [sessionId, KIND_PURGE, purgedOwner ?? null, purgedOwner !== undefined ? 1 : 0, now + PURGE_FENCE_MS]);
168
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1", [sessionId]);
169
- }
170
- async markTerminalServed(sessionId, runId) {
171
- const now = Date.now();
172
- await this.sweepFences(now);
173
- await this.pool.query("INSERT INTO workflow_inbox_fence (session_id, kind, run_id, owner, owner_known, until_ms) VALUES ($1,$2,$3,NULL,0,$4) " +
174
- "ON CONFLICT (session_id, kind, run_id) DO UPDATE SET until_ms = EXCLUDED.until_ms", [sessionId, KIND_SERVED, runId, now + SERVED_FENCE_MS]);
175
- await this.ack(sessionId, runId);
176
- await this.pool.query("DELETE FROM workflow_completion_inbox WHERE session_id = $1 AND run_id <> $2 AND regexp_replace(run_id, '#[0-9]+$', '') = $3", [sessionId, runId, runId]);
177
- }
178
- }
179
- export class PgWorkflowNotifyJournalStore {
180
- pool;
181
- constructor(pool) {
182
- this.pool = pool;
183
- }
184
- async record(input) {
185
- await this.pool.query("INSERT INTO workflow_notify_journal (run_id, scope, acked, source_task_id, principal, created_at) VALUES ($1,$2,0,$3,$4,$5) ON CONFLICT (run_id) DO NOTHING", [input.runId, input.scope, input.sourceTaskId ?? null, input.principal ?? null, input.createdAt]);
186
- }
187
- async ack(runId, ackedAt) {
188
- await this.pool.query("UPDATE workflow_notify_journal SET acked = 1, acked_at = $1 WHERE run_id = $2 AND acked = 0", [ackedAt, runId]);
189
- }
190
- async listPending() {
191
- const res = await this.pool.query("SELECT run_id, scope, source_task_id, principal, created_at FROM workflow_notify_journal WHERE acked = 0");
192
- return res.rows.map((r) => ({
193
- runId: r.run_id,
194
- scope: r.scope,
195
- acked: false,
196
- ...(r.source_task_id != null ? { sourceTaskId: r.source_task_id } : {}),
197
- ...(r.principal != null ? { principal: r.principal } : {}),
198
- createdAt: Number(r.created_at),
199
- }));
200
- }
201
- async reapAcked(before) {
202
- const res = await this.pool.query("DELETE FROM workflow_notify_journal WHERE acked = 1 AND acked_at < $1", [before]);
203
- return res.rowCount ?? 0;
204
- }
205
- async get(runId) {
206
- const res = await this.pool.query("SELECT run_id, scope, acked, source_task_id, principal, created_at, acked_at FROM workflow_notify_journal WHERE run_id = $1", [runId]);
207
- const r = res.rows[0];
208
- if (!r)
209
- return null;
210
- return {
211
- runId: r.run_id,
212
- scope: r.scope,
213
- acked: Number(r.acked) === 1,
214
- ...(r.source_task_id != null ? { sourceTaskId: r.source_task_id } : {}),
215
- ...(r.principal != null ? { principal: r.principal } : {}),
216
- createdAt: Number(r.created_at),
217
- ...(r.acked_at != null ? { ackedAt: Number(r.acked_at) } : {}),
218
- };
219
- }
220
- }
1
+ export { PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore } from "./workflow-run-store-sql.js";
221
2
  //# sourceMappingURL=pg-workflow-run-store.js.map
@@ -1,23 +1,7 @@
1
1
  import { FileError, ExecutionError, RemoteExecutionError, type ExecutionEnv, type RemoteExecutionEnv, type WorkspaceHandle, type FileInfo, type Result, type OutputChunk, type ExecStreamOptions, type RemoteConnectConfig, type SnapshotId, type SessionToken, type VmLifecycleOptions, type ExecutionEnvFactory, type BackgroundShellCapability, type BackgroundShellId, type BackgroundShellError, type BackgroundSpawnOptions, type BackgroundPoll } from "@sema-agent/core";
2
+ import type { PodSpecPatch } from "./k8s-exec-protocol.js";
2
3
  type ExecOpts = Parameters<ExecutionEnv["exec"]>[1];
3
- export declare function buildBgRunnerScript(dir: string): string;
4
- export declare function buildBgLauncherScript(dir: string, cmdShB64: string, runnerShB64: string): string;
5
- export declare function buildBgPollScript(dir: string, stdoutCursor: number, stderrCursor: number, readCap: number): string;
6
- export declare function buildBgKillScript(dir: string): string;
7
- export declare function buildBgDisposeScript(dir: string): string;
8
- export declare function buildDetachCapableExec(dir: string, command: string): string;
9
- export interface ParsedBgPoll {
10
- gone: boolean;
11
- outTotal: number;
12
- errTotal: number;
13
- exitCode?: number;
14
- alive: boolean;
15
- stdout: string;
16
- stdoutBytes: number;
17
- stderr: string;
18
- stderrBytes: number;
19
- }
20
- export declare function parseBgPollOutput(raw: string): ParsedBgPoll | undefined;
4
+ export { buildBgRunnerScript, buildBgLauncherScript, buildBgPollScript, buildBgKillScript, buildBgDisposeScript, buildDetachCapableExec, parseBgPollOutput, type ParsedBgPoll, } from "./k8s-bg-scripts.js";
21
5
  export interface K8sEnvConfig {
22
6
  apiUrl?: string;
23
7
  token?: string;
@@ -51,19 +35,7 @@ export interface K8sEnvConfig {
51
35
  podSpecPatch?: PodSpecPatch;
52
36
  podEnv?: Record<string, string>;
53
37
  }
54
- export interface PodSpecPatch {
55
- volumes?: unknown[];
56
- volumeMounts?: unknown[];
57
- podSecurityContext?: Record<string, unknown>;
58
- containerSecurityContext?: Record<string, unknown>;
59
- labels?: Record<string, string>;
60
- }
61
- export declare function applyPodSpecPatch(pod: {
62
- metadata: {
63
- labels?: Record<string, string>;
64
- };
65
- spec: Record<string, unknown>;
66
- }, patch: PodSpecPatch): void;
38
+ export { type PodSpecPatch, applyPodSpecPatch } from "./k8s-exec-protocol.js";
67
39
  type ConnectResult = {
68
40
  ok: true;
69
41
  value: WorkspaceHandle;
@@ -87,13 +59,9 @@ export interface K8sEnvDeps {
87
59
  openExec?: (url: string, headers: Record<string, string>) => ExecSocket;
88
60
  connectOnce?: (signal?: AbortSignal) => Promise<ConnectResult>;
89
61
  }
90
- export declare function execSocketTlsOptions(ca: string | undefined, insecureTls: boolean): Record<string, unknown>;
62
+ export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
91
63
  export declare function errorMessageOf(e: unknown): string;
92
- export declare function exitCodeFromStatus(statusJson: string): {
93
- exitCode: number;
94
- } | {
95
- error: string;
96
- };
64
+ export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
97
65
  export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, BackgroundShellCapability {
98
66
  readonly capabilities: {
99
67
  isolation: boolean;
@@ -207,5 +175,4 @@ export declare class RemoteK8sExecutionEnv implements RemoteExecutionEnv, Backgr
207
175
  private tempResult;
208
176
  }
209
177
  export declare function k8sExecutionEnvFactory(config: K8sEnvConfig): ExecutionEnvFactory;
210
- export {};
211
178
  //# sourceMappingURL=remote-env-k8s.d.ts.map
@@ -18,133 +18,8 @@ const K8S_BG_MAX_TIMEOUT_SEC = Math.max(K8S_BG_DEFAULT_TIMEOUT_SEC, Math.floor(N
18
18
  const K8S_BG_READ_CAP = Math.max(64 * 1024, Math.floor(Number(process.env.K8S_BG_READ_CAP ?? "")) || 1024 * 1024);
19
19
  const K8S_BG_FILE_CAP = Math.max(K8S_BG_READ_CAP, Math.floor(Number(process.env.K8S_BG_FILE_CAP ?? "")) || 64 * 1024 * 1024);
20
20
  const K8S_BG_MEM_CAP = Math.max(64 * 1024, Math.floor(Number(process.env.K8S_BG_MEM_CAP ?? "")) || 8 * 1024 * 1024);
21
- export function buildBgRunnerScript(dir) {
22
- const q = (name) => shellQuote(`${dir}/${name}`);
23
- return [
24
- `echo $$ > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`,
25
- `sh ${q("cmd.sh")} > ${q("out")} 2> ${q("err")} < /dev/null`,
26
- `__RC=$?`,
27
- `echo $__RC > ${q("exit.tmp")} 2>/dev/null && mv ${q("exit.tmp")} ${q("exit")} 2>/dev/null`,
28
- ].join("\n");
29
- }
30
- export function buildBgLauncherScript(dir, cmdShB64, runnerShB64) {
31
- const d = shellQuote(dir);
32
- const q = (name) => shellQuote(`${dir}/${name}`);
33
- return [
34
- `mkdir -p ${d} && chmod 700 ${d}`,
35
- `printf %s '${cmdShB64}' | base64 -d > ${q("cmd.sh")}`,
36
- `printf %s '${runnerShB64}' | base64 -d > ${q("runner.sh")}`,
37
- `setsid sh ${q("runner.sh")} >/dev/null 2>&1 &`,
38
- `__i=0; while [ ! -s ${q("pgid")} ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
39
- `if [ -s ${q("pgid")} ]; then echo SEMABG_LAUNCHED; else echo SEMABG_NOPGID; fi`,
40
- ].join("\n");
41
- }
42
- export function buildBgPollScript(dir, stdoutCursor, stderrCursor, readCap) {
43
- const d = shellQuote(dir);
44
- const slice = (file, cursor) => [
45
- `__N=$((__T - ${cursor}))`,
46
- `if [ "$__N" -gt 0 ]; then`,
47
- ` if [ "$__N" -le ${readCap} ]; then tail -c +$(( ${cursor} + 1 )) ${d}/${file} 2>/dev/null | head -c "$__N" | base64;`,
48
- ` else tail -c +$(( __T - ${readCap} + 1 )) ${d}/${file} 2>/dev/null | head -c ${readCap} | base64; fi`,
49
- `fi`,
50
- ].join("\n");
51
- return [
52
- `if [ ! -d ${d} ]; then echo SEMABG_GONE; exit 0; fi`,
53
- `__OT=$(wc -c < ${d}/out 2>/dev/null); __OT=$(( \${__OT:-0} + 0 ))`,
54
- `__ET=$(wc -c < ${d}/err 2>/dev/null); __ET=$(( \${__ET:-0} + 0 ))`,
55
- `__EX=$( [ -f ${d}/exit ] && cat ${d}/exit 2>/dev/null || echo - )`,
56
- `__PG=$(cat ${d}/pgid 2>/dev/null); __PG=\${__PG:--}`,
57
- `if [ "$__PG" = "-" ]; then __AL=1; elif kill -0 -"$__PG" 2>/dev/null; then __AL=1; else __AL=0; fi`,
58
- `echo "SEMABG_META o=$__OT e=$__ET x=$__EX a=$__AL"`,
59
- `echo SEMABG_O`,
60
- `__T=$__OT`,
61
- slice("out", stdoutCursor),
62
- `echo SEMABG_E`,
63
- `__T=$__ET`,
64
- slice("err", stderrCursor),
65
- `echo SEMABG_X`,
66
- ].join("\n");
67
- }
68
- export function buildBgKillScript(dir) {
69
- const d = shellQuote(dir);
70
- return [
71
- `__i=0; while [ ! -s ${d}/pgid ] && [ $__i -lt 40 ]; do __i=$((__i+1)); sleep 0.05; done`,
72
- `__PG=$(cat ${d}/pgid 2>/dev/null); __PG=\${__PG:--}`,
73
- `if [ "$__PG" != "-" ]; then kill -TERM -"$__PG" 2>/dev/null || kill -TERM "$__PG" 2>/dev/null; sleep 1; kill -KILL -"$__PG" 2>/dev/null || kill -KILL "$__PG" 2>/dev/null; fi`,
74
- `echo SEMABG_KILLED`,
75
- ].join("\n");
76
- }
77
- export function buildBgDisposeScript(dir) {
78
- const d = shellQuote(dir);
79
- return [
80
- `__PG=$(cat ${d}/pgid 2>/dev/null); __PG=\${__PG:--}`,
81
- `if [ "$__PG" != "-" ]; then kill -KILL -"$__PG" 2>/dev/null || kill -KILL "$__PG" 2>/dev/null; fi`,
82
- `rm -rf ${d} 2>/dev/null`,
83
- `echo SEMABG_DISPOSED`,
84
- ].join("\n");
85
- }
86
- export function buildDetachCapableExec(dir, command) {
87
- const d = shellQuote(dir);
88
- const q = (name) => shellQuote(`${dir}/${name}`);
89
- const cmdB64 = Buffer.from(command, "utf8").toString("base64");
90
- const writePgid = (val) => `printf %s ${val} > ${q("pgid.tmp")} 2>/dev/null && mv ${q("pgid.tmp")} ${q("pgid")} 2>/dev/null`;
91
- return [
92
- `__cmd=$(printf %s '${cmdB64}' | base64 -d)`,
93
- `mkdir -p ${d} 2>/dev/null`,
94
- `trap 'rm -rf ${d} 2>/dev/null' EXIT`,
95
- `if command -v setsid >/dev/null 2>&1; then`,
96
- ` setsid sh -c "$__cmd" &`,
97
- ` __pg=$!`,
98
- ` ${writePgid('"$__pg"')}`,
99
- ` wait "$__pg"; __rc=$?`,
100
- `else`,
101
- ` ${writePgid('"$$"')}`,
102
- ` sh -c "$__cmd"; __rc=$?`,
103
- `fi`,
104
- `exit $__rc`,
105
- ].join("\n");
106
- }
107
- export function parseBgPollOutput(raw) {
108
- if (raw.includes("SEMABG_GONE")) {
109
- return { gone: true, outTotal: 0, errTotal: 0, alive: false, stdout: "", stdoutBytes: 0, stderr: "", stderrBytes: 0 };
110
- }
111
- const lines = raw.split("\n");
112
- const metaIdx = lines.findIndex((l) => l.startsWith("SEMABG_META "));
113
- if (metaIdx < 0)
114
- return undefined;
115
- const oIdx = lines.indexOf("SEMABG_O", metaIdx + 1);
116
- const eIdx = oIdx >= 0 ? lines.indexOf("SEMABG_E", oIdx + 1) : -1;
117
- const xIdx = eIdx >= 0 ? lines.indexOf("SEMABG_X", eIdx + 1) : -1;
118
- if (oIdx < 0 || eIdx < 0 || xIdx < 0)
119
- return undefined;
120
- const m = /o=(\d+) e=(\d+) x=(\S+) a=([01])/.exec(lines[metaIdx].slice("SEMABG_META ".length));
121
- if (!m)
122
- return undefined;
123
- const outTotal = parseInt(m[1], 10);
124
- const errTotal = parseInt(m[2], 10);
125
- const exTok = m[3];
126
- const alive = m[4] === "1";
127
- const exitParsed = exTok === "-" ? NaN : parseInt(exTok, 10);
128
- const exitCode = Number.isFinite(exitParsed) ? exitParsed : undefined;
129
- const decode = (from, to) => {
130
- const b64 = lines.slice(from, to).join("").replace(/\s+/g, "");
131
- const buf = Buffer.from(b64, "base64");
132
- return { text: buf.toString("utf8"), bytes: buf.length };
133
- };
134
- const o = decode(oIdx + 1, eIdx);
135
- const e = decode(eIdx + 1, xIdx);
136
- return {
137
- gone: false,
138
- outTotal,
139
- errTotal,
140
- ...(exitCode !== undefined ? { exitCode } : {}),
141
- alive,
142
- stdout: o.text,
143
- stdoutBytes: o.bytes,
144
- stderr: e.text,
145
- stderrBytes: e.bytes,
146
- };
147
- }
21
+ import { buildBgRunnerScript, buildBgLauncherScript, buildBgPollScript, buildBgKillScript, buildBgDisposeScript, buildDetachCapableExec, parseBgPollOutput, } from "./k8s-bg-scripts.js";
22
+ export { buildBgRunnerScript, buildBgLauncherScript, buildBgPollScript, buildBgKillScript, buildBgDisposeScript, buildDetachCapableExec, parseBgPollOutput, } from "./k8s-bg-scripts.js";
148
23
  const ok = (value) => ({ ok: true, value });
149
24
  const unsupported = (op) => ({
150
25
  ok: false,
@@ -153,98 +28,17 @@ const unsupported = (op) => ({
153
28
  const SA_DIR = "/var/run/secrets/kubernetes.io/serviceaccount";
154
29
  const EXEC_SUBPROTOCOLS = ["v5.channel.k8s.io", "v4.channel.k8s.io"];
155
30
  const WRITE_CHUNK_BYTES = 64 * 1024;
156
- export function applyPodSpecPatch(pod, patch) {
157
- assertHardeningOnly(patch.podSecurityContext, "podSecurityContext");
158
- assertHardeningOnly(patch.containerSecurityContext, "containerSecurityContext");
159
- if (patch.labels) {
160
- const identity = {};
161
- for (const k of ["app", "managed-by"]) {
162
- const v = pod.metadata.labels?.[k];
163
- if (v !== undefined)
164
- identity[k] = v;
165
- }
166
- pod.metadata.labels = { ...pod.metadata.labels, ...patch.labels, ...identity };
167
- }
168
- const spec = pod.spec;
169
- assertVolumesPodLocal(patch.volumes, "volumes");
170
- if (patch.volumes?.length)
171
- spec.volumes = [...(spec.volumes ?? []), ...patch.volumes];
172
- if (patch.podSecurityContext)
173
- spec.securityContext = { ...(spec.securityContext ?? {}), ...patch.podSecurityContext };
174
- const container = spec.containers?.[0];
175
- if (container) {
176
- if (patch.volumeMounts?.length)
177
- container.volumeMounts = [...(container.volumeMounts ?? []), ...patch.volumeMounts];
178
- if (patch.containerSecurityContext)
179
- container.securityContext = { ...(container.securityContext ?? {}), ...patch.containerSecurityContext };
180
- }
181
- }
182
- function assertHardeningOnly(sc, where) {
183
- if (!sc)
184
- return;
185
- const bad = (msg) => {
186
- throw new Error(`podSpecPatch.${where} rejected — it may only HARDEN, never weaken isolation: ${msg}`);
187
- };
188
- if (sc.privileged === true)
189
- bad("privileged:true");
190
- if (sc.allowPrivilegeEscalation === true)
191
- bad("allowPrivilegeEscalation:true");
192
- if (sc.readOnlyRootFilesystem === false)
193
- bad("readOnlyRootFilesystem:false");
194
- if (sc.runAsNonRoot === false)
195
- bad("runAsNonRoot:false");
196
- if (sc.runAsUser === 0)
197
- bad("runAsUser:0 (root)");
198
- const caps = sc.capabilities;
199
- if (caps && Array.isArray(caps.add) && caps.add.length > 0)
200
- bad(`capabilities.add ${JSON.stringify(caps.add)} (a hardening patch may only .drop)`);
201
- if (sc.procMount === "Unmasked")
202
- bad('procMount:"Unmasked" (re-exposes masked /proc)');
203
- const seccomp = sc.seccompProfile;
204
- if (seccomp && seccomp.type === "Unconfined")
205
- bad('seccompProfile.type:"Unconfined" (disables syscall filtering)');
206
- }
207
- function assertVolumesPodLocal(volumes, where) {
208
- if (!volumes?.length)
209
- return;
210
- const POD_LOCAL = new Set(["emptyDir", "configMap", "secret", "projected", "downwardAPI", "ephemeral"]);
211
- for (const v of volumes) {
212
- const vol = v;
213
- const sources = Object.keys(vol).filter((k) => k !== "name");
214
- for (const src of sources) {
215
- if (!POD_LOCAL.has(src)) {
216
- throw new Error(`podSpecPatch.${where} rejected — volume source ${JSON.stringify(src)} is not pod-local (host-backed sources like hostPath/csi/flexVolume cross the Kata VM boundary into the node FS). Allowed: ${[...POD_LOCAL].join("/")}`);
217
- }
218
- }
219
- }
220
- }
221
- export function execSocketTlsOptions(ca, insecureTls) {
222
- const caList = ca ? [ca] : undefined;
223
- return { ca: caList, rejectUnauthorized: !insecureTls, tls: { ca: caList, rejectUnauthorized: !insecureTls } };
224
- }
31
+ import { applyPodSpecPatch } from "./k8s-exec-protocol.js";
32
+ export { applyPodSpecPatch } from "./k8s-exec-protocol.js";
33
+ import { execSocketTlsOptions, exitCodeFromStatus } from "./k8s-exec-protocol.js";
34
+ export { execSocketTlsOptions } from "./k8s-exec-protocol.js";
225
35
  export function errorMessageOf(e) {
226
36
  if (e instanceof Error)
227
37
  return e.message;
228
38
  const m = e?.message;
229
39
  return typeof m === "string" && m ? m : String(e);
230
40
  }
231
- export function exitCodeFromStatus(statusJson) {
232
- try {
233
- const s = JSON.parse(statusJson);
234
- if (s.status === "Success")
235
- return { exitCode: 0 };
236
- const cause = s.details?.causes?.find((c) => c.reason === "ExitCode");
237
- if (cause?.message?.trim()) {
238
- const code = Number(cause.message);
239
- if (Number.isFinite(code))
240
- return { exitCode: code };
241
- }
242
- return { error: s.message || s.reason || "exec failed without an exit code" };
243
- }
244
- catch {
245
- return { error: `unparseable exec status: ${statusJson.slice(0, 200)}` };
246
- }
247
- }
41
+ export { exitCodeFromStatus } from "./k8s-exec-protocol.js";
248
42
  export class RemoteK8sExecutionEnv {
249
43
  capabilities;
250
44
  cwd;
@@ -0,0 +1,23 @@
1
+ import type { Pool as MySqlPool } from "mysql2/promise";
2
+ import type { Pool as PgPool } from "pg";
3
+ import { type SqlDriver } from "./sql-driver.js";
4
+ export declare class SqlResumeAnchorStore {
5
+ protected readonly db: SqlDriver;
6
+ constructor(db: SqlDriver);
7
+ private q;
8
+ put(sessionId: string, eventId: string, entryId: string, owner: string | null): Promise<void>;
9
+ resolve(sessionId: string, eventId: string, owner: string | null): Promise<string | undefined>;
10
+ listBySession(sessionId: string): Promise<Array<{
11
+ eventId: string;
12
+ entryId: string;
13
+ owner: string | null;
14
+ }>>;
15
+ deleteBySession(sessionId: string): Promise<number>;
16
+ }
17
+ export declare class TiDBResumeAnchorStore extends SqlResumeAnchorStore {
18
+ constructor(pool: MySqlPool);
19
+ }
20
+ export declare class PgResumeAnchorStore extends SqlResumeAnchorStore {
21
+ constructor(pool: PgPool);
22
+ }
23
+ //# sourceMappingURL=resume-anchor-store-sql.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { mysqlDriver, pgDriver } from "./sql-driver.js";
2
+ export class SqlResumeAnchorStore {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ q(tidb, pg) {
8
+ return this.db.dialect === "tidb" ? tidb : pg;
9
+ }
10
+ async put(sessionId, eventId, entryId, owner) {
11
+ await this.db.query(this.q("INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES (?,?,?,?,?) " +
12
+ "ON DUPLICATE KEY UPDATE entry_id = VALUES(entry_id), owner = VALUES(owner)", "INSERT INTO resume_anchor (session_id, event_id, entry_id, owner, created_at) VALUES ($1,$2,$3,$4,$5) " +
13
+ "ON CONFLICT (session_id, event_id) DO UPDATE SET entry_id = EXCLUDED.entry_id, owner = EXCLUDED.owner"), [sessionId, eventId, entryId, owner, new Date()]);
14
+ }
15
+ async resolve(sessionId, eventId, owner) {
16
+ const { rows } = await this.db.query(this.q("SELECT entry_id FROM resume_anchor WHERE session_id = ? AND event_id = ? AND owner <=> ?", "SELECT entry_id FROM resume_anchor WHERE session_id = $1 AND event_id = $2 AND owner IS NOT DISTINCT FROM $3"), [sessionId, eventId, owner]);
17
+ return rows[0] ? String(rows[0].entry_id) : undefined;
18
+ }
19
+ async listBySession(sessionId) {
20
+ const { rows } = await this.db.query(this.q("SELECT event_id, entry_id, owner FROM resume_anchor WHERE session_id = ?", "SELECT event_id, entry_id, owner FROM resume_anchor WHERE session_id = $1"), [sessionId]);
21
+ return rows.map((r) => ({ eventId: String(r.event_id), entryId: String(r.entry_id), owner: r.owner == null ? null : String(r.owner) }));
22
+ }
23
+ async deleteBySession(sessionId) {
24
+ const res = await this.db.query(this.q("DELETE FROM resume_anchor WHERE session_id = ?", "DELETE FROM resume_anchor WHERE session_id = $1"), [sessionId]);
25
+ return res.affected;
26
+ }
27
+ }
28
+ export class TiDBResumeAnchorStore extends SqlResumeAnchorStore {
29
+ constructor(pool) {
30
+ super(mysqlDriver(pool));
31
+ }
32
+ }
33
+ export class PgResumeAnchorStore extends SqlResumeAnchorStore {
34
+ constructor(pool) {
35
+ super(pgDriver(pool));
36
+ }
37
+ }
38
+ //# sourceMappingURL=resume-anchor-store-sql.js.map