@sema-agent/server 7.108.0 → 7.110.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 +60 -1
- package/README.md +8 -0
- package/README.zh-CN.md +5 -0
- package/USAGE.md +19 -29
- package/dist/approval-ask-machine.d.ts +9 -25
- package/dist/approval-ask-machine.js +5 -2
- package/dist/approval-card.d.ts +33 -3
- package/dist/approval-card.js +26 -2
- package/dist/bench/s1/live-deps.d.ts +4 -5
- package/dist/bench/s1/live-deps.js +9 -10
- package/dist/boot/leader.js +0 -2
- package/dist/boot/reapers.d.ts +3 -0
- package/dist/boot/reapers.js +3 -2
- package/dist/boot/resolve-spec.d.ts +1 -1
- package/dist/boot/resolve-spec.js +6 -12
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-07-capability-layer.js +17 -3
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-10-http-server.d.ts +1 -0
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/capabilities/scenarios.d.ts +3 -2
- package/dist/config-catalog.js +2 -4
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +5 -11
- package/dist/config.js +21 -12
- package/dist/deployment-governance.d.ts +8 -2
- package/dist/deployment-governance.js +9 -8
- package/dist/hooks/hook-runner.d.ts +2 -2
- package/dist/hooks/hook-runner.js +40 -26
- package/dist/http/admission.js +8 -2
- package/dist/http/durable-run-leg.js +1 -1
- package/dist/http/resume-legs.js +6 -1
- package/dist/http/route-ctx.d.ts +3 -3
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/runs.d.ts +0 -1
- package/dist/http/routes/runs.js +22 -0
- package/dist/http/routes/sessions.js +12 -2
- package/dist/http/routes/tasks.js +5 -1
- package/dist/http/server.d.ts +7 -2
- package/dist/http/wire-types.d.ts +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/instruction-source-notice.d.ts +3 -3
- package/dist/leader/diffup.d.ts +53 -0
- package/dist/leader/diffup.js +95 -1
- package/dist/leader/leader.d.ts +0 -6
- package/dist/leader/leader.js +1 -1
- package/dist/leader/repair-wire.js +1 -1
- package/dist/leader/wire.d.ts +3 -10
- package/dist/leader/wire.js +39 -13
- package/dist/memory-layer-legacy-lock.d.ts +3 -3
- package/dist/model-entry-headers.d.ts +5 -2
- package/dist/model-entry-headers.js +4 -4
- package/dist/model-unreachable-notice.d.ts +2 -2
- package/dist/observability/corrupt-read-seat.d.ts +6 -0
- package/dist/observability/corrupt-read-seat.js +6 -0
- package/dist/observability/fail-open.d.ts +13 -5
- package/dist/observability/fail-open.js +13 -5
- package/dist/observability/metrics.js +2 -1
- package/dist/observability/run-terminal.js +10 -0
- package/dist/operator-ask.d.ts +9 -8
- package/dist/operator-ask.js +9 -1
- package/dist/plugins/approval-ask-store-file.d.ts +2 -1
- package/dist/plugins/approval-ask-store-file.js +3 -0
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
- package/dist/plugins/approval-ask-store-memory.js +7 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +18 -0
- package/dist/plugins/approval-ask-store-sql.js +7 -0
- package/dist/plugins/file-run-store.js +2 -2
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-run-store.js +2 -1
- package/dist/plugins/pg-pool.js +15 -0
- package/dist/plugins/run-store-sql.d.ts +1 -1
- package/dist/plugins/run-store-sql.js +3 -2
- package/dist/plugins/schema-indexes.js +2 -0
- package/dist/plugins/store-backend.d.ts +4 -0
- package/dist/plugins/store-backend.js +8 -0
- package/dist/plugins/store-contracts.d.ts +7 -0
- package/dist/plugins/store-contracts.js +1 -0
- package/dist/plugins/tidb-pool.js +17 -0
- package/dist/plugins/user-input-ledger-file.d.ts +33 -0
- package/dist/plugins/user-input-ledger-file.js +98 -0
- package/dist/plugins/user-input-ledger-sql.d.ts +44 -0
- package/dist/plugins/user-input-ledger-sql.js +101 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/run-local.d.ts +18 -5
- package/dist/run-local.js +26 -12
- package/dist/runs.d.ts +8 -1
- package/dist/runs.js +6 -2
- package/dist/runtime-governance.d.ts +23 -11
- package/dist/runtime-governance.js +7 -12
- package/dist/session-records-skipped-notice.d.ts +4 -3
- package/dist/task-settings.d.ts +0 -6
- package/dist/task-settings.js +0 -1
- package/dist/tool-approval.d.ts +44 -15
- package/dist/tool-approval.js +12 -8
- package/dist/trace/core-keyset-guard.d.ts +17 -2
- package/dist/trace/engine-notice-wire.d.ts +30 -35
- package/dist/trace/engine-notice-wire.js +23 -23
- package/dist/trace/project.js +2 -3
- package/dist/trace/self-minted-wire.d.ts +14 -0
- package/dist/trace/self-minted-wire.js +6 -0
- package/dist/unattended-permission-mode.d.ts +54 -93
- package/dist/unattended-permission-mode.js +8 -61
- package/dist/user-input-ledger.d.ts +296 -0
- package/dist/user-input-ledger.js +337 -0
- package/package.json +4 -4
|
@@ -7,7 +7,7 @@ import { randomBytes } from "node:crypto";
|
|
|
7
7
|
import { AppendLog, atomicWriteFile, ensureDir, isProcessLive, processFingerprint, sanitizePathComponent, writeThenLink } from "@sema-agent/core";
|
|
8
8
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
9
9
|
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
10
|
-
import { StoreBootRefusalError } from "./store-contracts.js";
|
|
10
|
+
import { RUN_STALLED_ERROR, StoreBootRefusalError } from "./store-contracts.js";
|
|
11
11
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
12
12
|
import { migrateRetiredAskOrigin } from "./one-time-migrations.js";
|
|
13
13
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
@@ -592,7 +592,7 @@ export class FileRunStore {
|
|
|
592
592
|
}
|
|
593
593
|
async reapStale(olderThanMs) {
|
|
594
594
|
const cutoff = Date.now() - olderThanMs;
|
|
595
|
-
return this.reapRunning((r) => r.updatedAt.getTime() < cutoff,
|
|
595
|
+
return this.reapRunning((r) => r.updatedAt.getTime() < cutoff, RUN_STALLED_ERROR);
|
|
596
596
|
}
|
|
597
597
|
async reclaimOrphanedAtBoot(isResumable) {
|
|
598
598
|
const orphans = [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type UsageRow } from "../usage-analytics.js";
|
|
2
2
|
import type { TaskStatus } from "@sema-agent/core";
|
|
3
|
-
import type
|
|
3
|
+
import { type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
4
4
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
5
5
|
/**
|
|
6
6
|
* [ref] P0 — the local lane's stand-in for the SQL twins' checkpoint-table JOIN. The file/memory run stores
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { redactErrorMessage, redactLedgerEventData, sealRunRowResult } from "../observability/run-terminal.js";
|
|
2
2
|
import { outwardPlane } from "../terminal.js";
|
|
3
3
|
import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
|
|
4
|
+
import { RUN_STALLED_ERROR } from "./store-contracts.js";
|
|
4
5
|
import { notifyRunTerminal } from "../observability/run-terminal.js";
|
|
5
6
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
6
7
|
export class MemoryRunStore {
|
|
@@ -298,7 +299,7 @@ export class MemoryRunStore {
|
|
|
298
299
|
if (r.status === "running" && r.updatedAt.getTime() < cutoff) {
|
|
299
300
|
r.status = "failed";
|
|
300
301
|
r.result = null;
|
|
301
|
-
r.error =
|
|
302
|
+
r.error = RUN_STALLED_ERROR;
|
|
302
303
|
r.updatedAt = new Date();
|
|
303
304
|
reaped += 1;
|
|
304
305
|
}
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -126,6 +126,21 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
126
126
|
consumed_at_ms BIGINT NOT NULL,
|
|
127
127
|
expires_at_ms BIGINT NOT NULL,
|
|
128
128
|
PRIMARY KEY (session_id, nonce)
|
|
129
|
+
)`,
|
|
130
|
+
`CREATE TABLE IF NOT EXISTS user_input_acceptance (
|
|
131
|
+
accept_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
132
|
+
input_kind VARCHAR(16) COLLATE "C" NOT NULL,
|
|
133
|
+
message_id VARCHAR(128) COLLATE "C" NOT NULL,
|
|
134
|
+
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
135
|
+
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
136
|
+
writer VARCHAR(64) COLLATE "C" NOT NULL,
|
|
137
|
+
accepted_at_ms BIGINT NOT NULL,
|
|
138
|
+
state VARCHAR(16) COLLATE "C" NOT NULL,
|
|
139
|
+
state_at_ms BIGINT,
|
|
140
|
+
settle_how VARCHAR(16) COLLATE "C",
|
|
141
|
+
announced_at_ms BIGINT,
|
|
142
|
+
announced_task_id VARCHAR(64) COLLATE "C",
|
|
143
|
+
PRIMARY KEY (accept_id)
|
|
129
144
|
)`,
|
|
130
145
|
`CREATE TABLE IF NOT EXISTS sendfile_link (
|
|
131
146
|
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -2,7 +2,7 @@ import type { TaskStatus } from "@sema-agent/core";
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool } from "pg";
|
|
4
4
|
import { type UsageRow } from "../usage-analytics.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
|
|
6
6
|
import { type SqlDriver } from "./sql-driver.js";
|
|
7
7
|
import type { LedgerEventType } from "../trace/ledger-events.js";
|
|
8
8
|
export type { RunRecord, SessionSummary, RunEvent, LatestRunRef } from "./store-contracts.js";
|
|
@@ -6,6 +6,7 @@ import { pgSanitizeText } from "./pg-safe-json.js";
|
|
|
6
6
|
import { toIso as iso } from "./sql-row-helpers.js";
|
|
7
7
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
8
8
|
import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonObjectColumn, readNonNullJsonValueColumn } from "./sql-json-column.js";
|
|
9
|
+
import { RUN_STALLED_ERROR } from "./store-contracts.js";
|
|
9
10
|
import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
|
|
10
11
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
11
12
|
import { isDupKeyError } from "./sql-errors.js";
|
|
@@ -423,8 +424,8 @@ export class SqlRunStore {
|
|
|
423
424
|
const cutoff = new Date(Date.now() - olderThanMs);
|
|
424
425
|
let reaped = 0;
|
|
425
426
|
await this.tx(async (conn) => {
|
|
426
|
-
const { affected } = await conn.query(this.q(
|
|
427
|
-
"WHERE status = 'running' AND updated_at < ?",
|
|
427
|
+
const { affected } = await conn.query(this.q(`UPDATE task_run SET status = 'failed', result = NULL, error = '${RUN_STALLED_ERROR}', updated_at = ? ` +
|
|
428
|
+
"WHERE status = 'running' AND updated_at < ?", `UPDATE task_run SET status = 'failed', result = NULL, error = '${RUN_STALLED_ERROR}', updated_at = $1 ` +
|
|
428
429
|
"WHERE status = 'running' AND updated_at < $2"), [new Date(), cutoff]);
|
|
429
430
|
reaped = affected;
|
|
430
431
|
await conn.query(this.q("DELETE ta FROM task_active ta JOIN task_run tr ON ta.task_id = tr.task_id WHERE tr.status NOT IN ('running','suspended','needs_review')", "DELETE FROM task_active ta USING task_run tr WHERE ta.task_id = tr.task_id AND tr.status NOT IN ('running','suspended','needs_review')"));
|
|
@@ -8,6 +8,8 @@ export const CENTRAL_SCHEMA_INDEXES = [
|
|
|
8
8
|
{ table: "checkpoint", name: "idx_checkpoint_session_status", columns: ["session_id", "status"] },
|
|
9
9
|
{ table: "resume_anchor", name: "idx_resume_anchor_session", columns: ["session_id"] },
|
|
10
10
|
{ table: "approval_nonce", name: "idx_approval_nonce_expires", columns: ["expires_at_ms"] },
|
|
11
|
+
{ table: "user_input_acceptance", name: "idx_user_input_acceptance_session", columns: ["session_id", "state", "accepted_at_ms"] },
|
|
12
|
+
{ table: "user_input_acceptance", name: "idx_user_input_acceptance_state", columns: ["state", "accepted_at_ms", "accept_id"] },
|
|
11
13
|
{ table: "sendfile_link", name: "idx_sendfile_link_scope", columns: ["scope", "id"] },
|
|
12
14
|
{ table: "session_policy", name: "idx_session_policy_session", columns: ["session_id"] },
|
|
13
15
|
{ table: "session_policy_rev_mark", name: "idx_session_policy_rev_mark_session", columns: ["session_id"] },
|
|
@@ -12,6 +12,7 @@ import { type ApprovalNonceStore } from "./approval-nonce-store.js";
|
|
|
12
12
|
import { type ApprovalAskStore as ApprovalAskStoreType } from "./approval-ask-store-sql.js";
|
|
13
13
|
import { type MemoryOptOutGrantStore } from "./memory-optout-grant-store-sql.js";
|
|
14
14
|
import { type SendFileLedger } from "./send-file-ledger.js";
|
|
15
|
+
import type { UserInputLedgerStore } from "../user-input-ledger.js";
|
|
15
16
|
import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
|
|
16
17
|
import { type SessionPolicyStoreLogger } from "./session-policy-store-sql.js";
|
|
17
18
|
import type { ServiceConfig } from "../config-types.js";
|
|
@@ -195,6 +196,9 @@ export interface StoreBackend {
|
|
|
195
196
|
/** SendUserFile scope↔object ledger (multi-tenant list/revoke handle; the hashed key segment hides the
|
|
196
197
|
* mapping from URLs). REQUIRED on all backends (works local — one JSONL, like approvalExemption). */
|
|
197
198
|
sendFileLedger(): SendFileLedger;
|
|
199
|
+
/** S-928 用户输入受理账本的存储孪生(「200 applied」受理的影子;语义属主 `src/user-input-ledger.ts`)。REQUIRED on all backends ——
|
|
200
|
+
* local = File(数据根下 `user-input-ledger/ledger.jsonl`,singleton:追加 fd 与内存折叠都在实例里),SQL = 表 `user_input_acceptance`。 */
|
|
201
|
+
userInputLedger(): UserInputLedgerStore;
|
|
198
202
|
/** E6 durable SessionPolicyStore (operator-tightened per-session tool rules). REQUIRED on all backends — works local
|
|
199
203
|
* (local = core's FileSessionPolicyStore), like resumeAnchor. Backs core's RunnerDeps.sessionPolicyStore.
|
|
200
204
|
* `logger`:SQL 孪生抹除封键那一臂(S-891)的点名口(缺席 ⇒ 店退回 `console.warn`);local(core 的 File 店)不收。 */
|
|
@@ -23,6 +23,8 @@ import { FileApprovalAskStore } from "./approval-ask-store-file.js";
|
|
|
23
23
|
import { TiDBMemoryOptOutGrantStore, PgMemoryOptOutGrantStore } from "./memory-optout-grant-store-sql.js";
|
|
24
24
|
import { TiDBSessionCaptureRecordStore, PgSessionCaptureRecordStore } from "./session-capture-record-store-sql.js";
|
|
25
25
|
import { TiDBSendFileLedger, PgSendFileLedger, FileSendFileLedger } from "./send-file-ledger.js";
|
|
26
|
+
import { FileUserInputLedgerStore } from "./user-input-ledger-file.js";
|
|
27
|
+
import { TiDBUserInputLedgerStore, PgUserInputLedgerStore } from "./user-input-ledger-sql.js";
|
|
26
28
|
import { TiDBResumeAnchorStore, PgResumeAnchorStore } from "./resume-anchor-store-sql.js";
|
|
27
29
|
import { TiDBSessionPolicyStore, PgSessionPolicyStore } from "./session-policy-store-sql.js";
|
|
28
30
|
import { TiDBWorkflowJournalStore, PgWorkflowJournalStore } from "./workflow-journal-store-sql.js";
|
|
@@ -123,6 +125,7 @@ class TiDBBackend {
|
|
|
123
125
|
approvalAsk() { return new TiDBApprovalAskStore(this.pool); }
|
|
124
126
|
permissionRule() { return createSqlPermissionRuleStores(mysqlDriver(this.pool)); }
|
|
125
127
|
sendFileLedger() { return new TiDBSendFileLedger(this.pool); }
|
|
128
|
+
userInputLedger() { return new TiDBUserInputLedgerStore(this.pool); }
|
|
126
129
|
sessionPolicy(logger) { return new TiDBSessionPolicyStore(this.pool, logger); }
|
|
127
130
|
fileHistory() { return new TiDBFileHistoryStore(this.pool, historyBlobBackend(this.config, "tidb", this.pool), this.config.fileHistory); }
|
|
128
131
|
memoryOptOutGrant() { return new TiDBMemoryOptOutGrantStore(this.pool); }
|
|
@@ -171,6 +174,7 @@ class PgBackend {
|
|
|
171
174
|
approvalAsk() { return new PgApprovalAskStore(this.pool); }
|
|
172
175
|
permissionRule() { return createSqlPermissionRuleStores(pgDriver(this.pool)); }
|
|
173
176
|
sendFileLedger() { return new PgSendFileLedger(this.pool); }
|
|
177
|
+
userInputLedger() { return new PgUserInputLedgerStore(this.pool); }
|
|
174
178
|
sessionPolicy(logger) { return new PgSessionPolicyStore(this.pool, logger); }
|
|
175
179
|
fileHistory() { return new PgFileHistoryStore(this.pool, historyBlobBackend(this.config, "pg", this.pool), this.config.fileHistory); }
|
|
176
180
|
memoryOptOutGrant() { return new PgMemoryOptOutGrantStore(this.pool); }
|
|
@@ -212,6 +216,7 @@ class LocalBackend {
|
|
|
212
216
|
approvalNonceStore;
|
|
213
217
|
approvalAskStore;
|
|
214
218
|
sendFileLedgerStore;
|
|
219
|
+
userInputLedgerStore;
|
|
215
220
|
workflowJournalStore;
|
|
216
221
|
outcomeSinkInst;
|
|
217
222
|
onWarn;
|
|
@@ -249,6 +254,7 @@ class LocalBackend {
|
|
|
249
254
|
this.approvalNonceStore = new FileApprovalNonceStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
250
255
|
this.approvalAskStore = new FileApprovalAskStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
251
256
|
this.sendFileLedgerStore = new FileSendFileLedger(this.fileBackend.root, { warn: corruptReadWarn });
|
|
257
|
+
this.userInputLedgerStore = new FileUserInputLedgerStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
252
258
|
this.workflowJournalStore = new FileWorkflowJournalStore(this.fileBackend.root, undefined, {
|
|
253
259
|
onCorruptRead: createCorruptReadSeat((msg, fields) => this.discloseWarn(msg, fields), "workflow_journal_corrupt_read"),
|
|
254
260
|
});
|
|
@@ -270,6 +276,7 @@ class LocalBackend {
|
|
|
270
276
|
this.approvalNonceStore.dispose();
|
|
271
277
|
this.approvalAskStore.dispose();
|
|
272
278
|
this.sendFileLedgerStore.dispose();
|
|
279
|
+
this.userInputLedgerStore.dispose();
|
|
273
280
|
this.workflowJournalStore.dispose();
|
|
274
281
|
this.checkpointStoreInst?.close();
|
|
275
282
|
this.permissionRuleStoresInst?.dispose();
|
|
@@ -291,6 +298,7 @@ class LocalBackend {
|
|
|
291
298
|
}
|
|
292
299
|
permissionRuleStoresInst;
|
|
293
300
|
sendFileLedger() { return this.sendFileLedgerStore; }
|
|
301
|
+
userInputLedger() { return this.userInputLedgerStore; }
|
|
294
302
|
checkpoint() {
|
|
295
303
|
if (!this.checkpointStoreInst) {
|
|
296
304
|
const cs = new LocalCheckpointStore(this.fileBackend.root, {
|
|
@@ -104,6 +104,13 @@ export interface RunCancelContext {
|
|
|
104
104
|
export type PersistedTaskResult = TaskResult & {
|
|
105
105
|
cancelContext?: RunCancelContext;
|
|
106
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* 🔴 run 店「持有它的实例丢了」那一判的**行上落款**(S-928 codex r1-F1 提为常量):三只 run 店的 `reapStale` 把心跳过期的 running 行翻成
|
|
109
|
+
* `failed` 并写下这一句。它是 run 店死亡判决留在行上的唯一证据 —— 用户输入受理账本的 SQL 收敛臂据它在 reap **之后**仍能判出「写者死了」
|
|
110
|
+
* (`user-input-ledger.ts` 的判词)。改这一句 = 改那条判据;所以三个写者与那一个读者都只引用本常量。读时折叠成同一句话的 wire 展示站点
|
|
111
|
+
* (`routes/runs.ts` 的 stale 折叠、`routes/trace-usage.ts` 的 WORKER_DOWN 帧)刻意留字面量:它们由文案冻结门逐字钉着,且不参与任何判据。
|
|
112
|
+
*/
|
|
113
|
+
export declare const RUN_STALLED_ERROR = "run stalled (instance lost?)";
|
|
107
114
|
export interface RunRecord {
|
|
108
115
|
taskId: string;
|
|
109
116
|
sessionId: string;
|
|
@@ -349,6 +349,23 @@ export const SCHEMA_STATEMENTS = [
|
|
|
349
349
|
expires_at_ms BIGINT NOT NULL,
|
|
350
350
|
PRIMARY KEY (session_id, nonce),
|
|
351
351
|
KEY idx_approval_nonce_expires (expires_at_ms)
|
|
352
|
+
) COLLATE utf8mb4_bin`,
|
|
353
|
+
`CREATE TABLE IF NOT EXISTS user_input_acceptance (
|
|
354
|
+
accept_id VARCHAR(64) NOT NULL,
|
|
355
|
+
input_kind VARCHAR(16) NOT NULL,
|
|
356
|
+
message_id VARCHAR(128) NOT NULL,
|
|
357
|
+
session_id VARCHAR(64) NOT NULL,
|
|
358
|
+
task_id VARCHAR(64) NOT NULL,
|
|
359
|
+
writer VARCHAR(64) NOT NULL,
|
|
360
|
+
accepted_at_ms BIGINT NOT NULL,
|
|
361
|
+
state VARCHAR(16) NOT NULL,
|
|
362
|
+
state_at_ms BIGINT NULL,
|
|
363
|
+
settle_how VARCHAR(16) NULL,
|
|
364
|
+
announced_at_ms BIGINT NULL,
|
|
365
|
+
announced_task_id VARCHAR(64) NULL,
|
|
366
|
+
PRIMARY KEY (accept_id),
|
|
367
|
+
KEY idx_user_input_acceptance_session (session_id, state, accepted_at_ms),
|
|
368
|
+
KEY idx_user_input_acceptance_state (state, accepted_at_ms, accept_id)
|
|
352
369
|
) COLLATE utf8mb4_bin`,
|
|
353
370
|
`CREATE TABLE IF NOT EXISTS sendfile_link (
|
|
354
371
|
id VARCHAR(64) NOT NULL,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Logger } from "../observability/logger.js";
|
|
2
|
+
import { UserInputLedgerFold, type NewUserInputAcceptance, type OpenScanCursor, type SettleHow, type UserInputAcceptance, type UserInputLedgerRecord, type UserInputLedgerStore } from "../user-input-ledger.js";
|
|
3
|
+
/** 账本文件名(取证按文件字节对账 —— 崩溃语义的真源是文件)。 */
|
|
4
|
+
export declare const USER_INPUT_LEDGER_FILE = "ledger.jsonl";
|
|
5
|
+
/** 纯内存孪生(测试 / 夹具)。 */
|
|
6
|
+
export declare class MemoryUserInputLedgerStore implements UserInputLedgerStore {
|
|
7
|
+
protected readonly fold: UserInputLedgerFold;
|
|
8
|
+
/** 落一行:守卫不过 ⇒ false、零写;过了 ⇒ 先交给 {@link persist}(File 孪生在这里 fsync),再翻折叠。 */
|
|
9
|
+
protected write(rec: UserInputLedgerRecord): Promise<boolean>;
|
|
10
|
+
/** 持久面挂点(Memory = 无)。抛 ⇒ 这一行不存在,折叠不动。 */
|
|
11
|
+
protected persist(_rec: UserInputLedgerRecord): void;
|
|
12
|
+
accept(a: NewUserInputAcceptance): Promise<void>;
|
|
13
|
+
settle(acceptId: string, how: SettleHow, atMs: number): Promise<boolean>;
|
|
14
|
+
converge(acceptId: string, atMs: number): Promise<boolean>;
|
|
15
|
+
announce(acceptId: string, taskId: string, atMs: number): Promise<boolean>;
|
|
16
|
+
listOpen(after?: OpenScanCursor, limit?: number): Promise<UserInputAcceptance[]>;
|
|
17
|
+
listCrashed(sessionId: string): Promise<UserInputAcceptance[]>;
|
|
18
|
+
listUnannounced(sessionId: string, limit: number): Promise<UserInputAcceptance[]>;
|
|
19
|
+
}
|
|
20
|
+
/** local 车道(File)孪生。 */
|
|
21
|
+
export declare class FileUserInputLedgerStore extends MemoryUserInputLedgerStore {
|
|
22
|
+
/** 账本绝对路径。 */
|
|
23
|
+
readonly path: string;
|
|
24
|
+
private log;
|
|
25
|
+
/** @param opts.warn 腐读披露座(缺席 ⇒ 重放属主退回 `console.warn`)。 */
|
|
26
|
+
constructor(root: string, opts?: {
|
|
27
|
+
warn?: Logger["warn"];
|
|
28
|
+
});
|
|
29
|
+
protected persist(rec: UserInputLedgerRecord): void;
|
|
30
|
+
/** 收追加口(LocalBackend.close → 优雅重启重开)。core `AppendLog.close()` 自己吞 `closeSync` 的错,不抛。 */
|
|
31
|
+
dispose(): void;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=user-input-ledger-file.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { AppendLog, ensureDir } from "@sema-agent/core";
|
|
3
|
+
import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
|
|
4
|
+
import { CRASHED_CAUSE, SETTLE_HOW_WORDS, USER_INPUT_KIND_WORDS, USER_INPUT_LEDGER_DIR, USER_INPUT_OPEN_SCAN_MAX, USER_INPUT_SESSION_READ_MAX, UserInputLedgerFold, isServingLayerUndrainedCause, } from "../user-input-ledger.js";
|
|
5
|
+
export const USER_INPUT_LEDGER_FILE = "ledger.jsonl";
|
|
6
|
+
function duplicateAcceptance(acceptId) {
|
|
7
|
+
return new Error(`user-input ledger: acceptId ${acceptId} is already recorded — an acceptance id is one acceptance`);
|
|
8
|
+
}
|
|
9
|
+
export class MemoryUserInputLedgerStore {
|
|
10
|
+
fold = new UserInputLedgerFold();
|
|
11
|
+
async write(rec) {
|
|
12
|
+
if (!this.fold.admits(rec))
|
|
13
|
+
return false;
|
|
14
|
+
this.persist(rec);
|
|
15
|
+
this.fold.apply(rec);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
persist(_rec) { }
|
|
19
|
+
async accept(a) {
|
|
20
|
+
const rec = { kind: "accepted", acceptId: a.acceptId, input: a.input, messageId: a.messageId, sessionId: a.sessionId, taskId: a.taskId, writer: a.writer, at: a.acceptedAtMs };
|
|
21
|
+
if (!(await this.write(rec)))
|
|
22
|
+
throw duplicateAcceptance(a.acceptId);
|
|
23
|
+
}
|
|
24
|
+
settle(acceptId, how, atMs) {
|
|
25
|
+
return this.write({ kind: "settled", acceptId, how, at: atMs });
|
|
26
|
+
}
|
|
27
|
+
converge(acceptId, atMs) {
|
|
28
|
+
return this.write({ kind: "converged", acceptId, cause: CRASHED_CAUSE, at: atMs });
|
|
29
|
+
}
|
|
30
|
+
announce(acceptId, taskId, atMs) {
|
|
31
|
+
return this.write({ kind: "announced", acceptId, taskId, at: atMs });
|
|
32
|
+
}
|
|
33
|
+
async listOpen(after, limit = USER_INPUT_OPEN_SCAN_MAX) {
|
|
34
|
+
return this.fold.listOpen(after, limit);
|
|
35
|
+
}
|
|
36
|
+
async listCrashed(sessionId) {
|
|
37
|
+
return this.fold.listCrashed(sessionId, USER_INPUT_SESSION_READ_MAX);
|
|
38
|
+
}
|
|
39
|
+
async listUnannounced(sessionId, limit) {
|
|
40
|
+
return this.fold.listUnannounced(sessionId, limit);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const RECORD_SHAPE = {
|
|
44
|
+
what: "user-input ledger record of an unknown kind or with a missing / out-of-vocabulary member",
|
|
45
|
+
read(record) {
|
|
46
|
+
const o = record;
|
|
47
|
+
const str = (k) => (typeof o[k] === "string" && o[k].length > 0 ? o[k] : undefined);
|
|
48
|
+
const at = typeof o.at === "number" && Number.isFinite(o.at) ? o.at : undefined;
|
|
49
|
+
const acceptId = str("acceptId");
|
|
50
|
+
if (acceptId === undefined || at === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
switch (o.kind) {
|
|
53
|
+
case "accepted": {
|
|
54
|
+
const input = typeof o.input === "string" && Object.hasOwn(USER_INPUT_KIND_WORDS, o.input) ? o.input : undefined;
|
|
55
|
+
const messageId = str("messageId");
|
|
56
|
+
const sessionId = str("sessionId");
|
|
57
|
+
const taskId = str("taskId");
|
|
58
|
+
const writer = str("writer");
|
|
59
|
+
if (input === undefined || messageId === undefined || sessionId === undefined || taskId === undefined || writer === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
return { kind: "accepted", acceptId, input, messageId, sessionId, taskId, writer, at };
|
|
62
|
+
}
|
|
63
|
+
case "settled": {
|
|
64
|
+
const how = typeof o.how === "string" && Object.hasOwn(SETTLE_HOW_WORDS, o.how) ? o.how : undefined;
|
|
65
|
+
return how === undefined ? undefined : { kind: "settled", acceptId, how, at };
|
|
66
|
+
}
|
|
67
|
+
case "converged":
|
|
68
|
+
return isServingLayerUndrainedCause(o.cause) ? { kind: "converged", acceptId, cause: o.cause, at } : undefined;
|
|
69
|
+
case "announced": {
|
|
70
|
+
const taskId = str("taskId");
|
|
71
|
+
return taskId === undefined ? undefined : { kind: "announced", acceptId, taskId, at };
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export class FileUserInputLedgerStore extends MemoryUserInputLedgerStore {
|
|
79
|
+
path;
|
|
80
|
+
log;
|
|
81
|
+
constructor(root, opts) {
|
|
82
|
+
super();
|
|
83
|
+
const dir = join(root, USER_INPUT_LEDGER_DIR);
|
|
84
|
+
ensureDir(dir);
|
|
85
|
+
this.path = join(dir, USER_INPUT_LEDGER_FILE);
|
|
86
|
+
for (const rec of replayJsonlSkipping(this.path, "user-input-ledger", RECORD_SHAPE, opts?.warn).records)
|
|
87
|
+
this.fold.apply(rec);
|
|
88
|
+
}
|
|
89
|
+
persist(rec) {
|
|
90
|
+
this.log ??= new AppendLog(this.path);
|
|
91
|
+
this.log.append(rec, true);
|
|
92
|
+
}
|
|
93
|
+
dispose() {
|
|
94
|
+
this.log?.close();
|
|
95
|
+
this.log = undefined;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=user-input-ledger-file.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-928 用户输入受理账本的 **SQL** 孪生(单文件双方言,[ref] A12 收拢形;两条语句并排写死,方言差异显式)。
|
|
3
|
+
* 语义属主 = `src/user-input-ledger.ts`;File / Memory 两只在 `user-input-ledger-file.ts`。四只孪生吃同一组试剂盒断言
|
|
4
|
+
* (`test/helpers/user-input-ledger-kit.ts`;本孪生的真双库腿在 `test/store-contract-kit-db-integration.test.ts`)。
|
|
5
|
+
*
|
|
6
|
+
* ## 一张表,一行一受理(四形行落在列上)
|
|
7
|
+
* File 孪生把四形行逐行追加;这里把同一组转移落在**一行**的列上:`accepted` = INSERT(`state='open'`),`settled` / `converged` =
|
|
8
|
+
* `state` 列的 CAS(`WHERE state='open'`,互斥、先到者赢),`announced` = `announced_*` 两列的 CAS(`WHERE state='converged' AND
|
|
9
|
+
* announced_at_ms IS NULL`,至多一次)。File 的「行序 = 语义序」在这里由 CAS 谓词结构性给出 —— 两者同义的证据是同一组试剂盒格。
|
|
10
|
+
* 多副本并发:两台副本同时收敛同一行 ⇒ 单赢者;settle 与 converge 互斥(同一行只会从 open 走出去一次)。
|
|
11
|
+
*
|
|
12
|
+
* ## 身份列
|
|
13
|
+
* 无 owner / principal / scope 列(行随它挂靠的会话走 —— 收编形Ⅲ);`writer` 是受理进程的 `instanceId`(随进程消亡,不是租户轴)。
|
|
14
|
+
* 行里不存输入正文(只有身份与时刻)。
|
|
15
|
+
*
|
|
16
|
+
* ## 不在本表上的东西
|
|
17
|
+
* 会话删除 / 托管留存的级联**未接**(行至多是几条身份 + 时刻;S-928 收车档登记的欠账)。
|
|
18
|
+
*/
|
|
19
|
+
import type { Pool as MysqlPool } from "mysql2/promise";
|
|
20
|
+
import type { Pool as PgPool } from "pg";
|
|
21
|
+
import { type SqlDriver } from "./sql-driver.js";
|
|
22
|
+
import { type NewUserInputAcceptance, type OpenScanCursor, type SettleHow, type UserInputAcceptance, type UserInputLedgerStore } from "../user-input-ledger.js";
|
|
23
|
+
export declare const USER_INPUT_ACCEPTANCE_TABLE = "user_input_acceptance";
|
|
24
|
+
export declare class SqlUserInputLedgerStore implements UserInputLedgerStore {
|
|
25
|
+
private readonly db;
|
|
26
|
+
constructor(db: SqlDriver);
|
|
27
|
+
private q;
|
|
28
|
+
accept(a: NewUserInputAcceptance): Promise<void>;
|
|
29
|
+
settle(acceptId: string, how: SettleHow, atMs: number): Promise<boolean>;
|
|
30
|
+
converge(acceptId: string, atMs: number): Promise<boolean>;
|
|
31
|
+
announce(acceptId: string, taskId: string, atMs: number): Promise<boolean>;
|
|
32
|
+
listOpen(after?: OpenScanCursor, limit?: number): Promise<UserInputAcceptance[]>;
|
|
33
|
+
listCrashed(sessionId: string): Promise<UserInputAcceptance[]>;
|
|
34
|
+
listUnannounced(sessionId: string, limit: number): Promise<UserInputAcceptance[]>;
|
|
35
|
+
}
|
|
36
|
+
/** MySQL 协议腿的构造绑定。 */
|
|
37
|
+
export declare class TiDBUserInputLedgerStore extends SqlUserInputLedgerStore {
|
|
38
|
+
constructor(pool: MysqlPool);
|
|
39
|
+
}
|
|
40
|
+
/** PostgreSQL 腿的构造绑定。 */
|
|
41
|
+
export declare class PgUserInputLedgerStore extends SqlUserInputLedgerStore {
|
|
42
|
+
constructor(pool: PgPool);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=user-input-ledger-sql.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isDupKeyError } from "./sql-errors.js";
|
|
2
|
+
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
3
|
+
import { assertPageLimit, CRASHED_CAUSE, SETTLE_HOW_WORDS, USER_INPUT_KIND_WORDS, USER_INPUT_OPEN_SCAN_MAX, USER_INPUT_SESSION_READ_MAX, } from "../user-input-ledger.js";
|
|
4
|
+
export const USER_INPUT_ACCEPTANCE_TABLE = "user_input_acceptance";
|
|
5
|
+
const COLUMNS = "accept_id, input_kind, message_id, session_id, task_id, writer, accepted_at_ms, state, state_at_ms, settle_how, announced_at_ms, announced_task_id";
|
|
6
|
+
function rowState(r) {
|
|
7
|
+
const state = String(r["state"]);
|
|
8
|
+
const at = r["state_at_ms"] == null ? Number.NaN : Number(r["state_at_ms"]);
|
|
9
|
+
switch (state) {
|
|
10
|
+
case "open":
|
|
11
|
+
return { kind: "open" };
|
|
12
|
+
case "settled": {
|
|
13
|
+
const how = String(r["settle_how"]);
|
|
14
|
+
if (!Object.hasOwn(SETTLE_HOW_WORDS, how) || !Number.isFinite(at))
|
|
15
|
+
break;
|
|
16
|
+
return { kind: "settled", how: how, atMs: at };
|
|
17
|
+
}
|
|
18
|
+
case "converged":
|
|
19
|
+
if (!Number.isFinite(at))
|
|
20
|
+
break;
|
|
21
|
+
return { kind: "converged", cause: CRASHED_CAUSE, atMs: at };
|
|
22
|
+
default:
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`${USER_INPUT_ACCEPTANCE_TABLE} row ${String(r["accept_id"])}: unreadable state (${String(r["state"])} / ${String(r["settle_how"])} / ${String(r["state_at_ms"])})`);
|
|
26
|
+
}
|
|
27
|
+
function rowToAcceptance(r) {
|
|
28
|
+
const input = String(r["input_kind"]);
|
|
29
|
+
if (!Object.hasOwn(USER_INPUT_KIND_WORDS, input))
|
|
30
|
+
throw new Error(`${USER_INPUT_ACCEPTANCE_TABLE} row ${String(r["accept_id"])}: input_kind ${input} is not a ledger word`);
|
|
31
|
+
const announcedAt = r["announced_at_ms"] == null ? undefined : Number(r["announced_at_ms"]);
|
|
32
|
+
return {
|
|
33
|
+
acceptId: String(r["accept_id"]),
|
|
34
|
+
input: input,
|
|
35
|
+
messageId: String(r["message_id"]),
|
|
36
|
+
sessionId: String(r["session_id"]),
|
|
37
|
+
taskId: String(r["task_id"]),
|
|
38
|
+
writer: String(r["writer"]),
|
|
39
|
+
acceptedAtMs: Number(r["accepted_at_ms"]),
|
|
40
|
+
state: rowState(r),
|
|
41
|
+
...(announcedAt !== undefined ? { announced: { atMs: announcedAt, taskId: String(r["announced_task_id"]) } } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export class SqlUserInputLedgerStore {
|
|
45
|
+
db;
|
|
46
|
+
constructor(db) {
|
|
47
|
+
this.db = db;
|
|
48
|
+
}
|
|
49
|
+
q(tidb, pg) {
|
|
50
|
+
return this.db.dialect === "tidb" ? tidb : pg;
|
|
51
|
+
}
|
|
52
|
+
async accept(a) {
|
|
53
|
+
try {
|
|
54
|
+
await this.db.query(this.q(`INSERT INTO ${USER_INPUT_ACCEPTANCE_TABLE} (accept_id, input_kind, message_id, session_id, task_id, writer, accepted_at_ms, state) VALUES (?,?,?,?,?,?,?,'open')`, `INSERT INTO ${USER_INPUT_ACCEPTANCE_TABLE} (accept_id, input_kind, message_id, session_id, task_id, writer, accepted_at_ms, state) VALUES ($1,$2,$3,$4,$5,$6,$7,'open')`), [a.acceptId, a.input, a.messageId, a.sessionId, a.taskId, a.writer, a.acceptedAtMs]);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
if (isDupKeyError(this.db.dialect, err))
|
|
58
|
+
throw new Error(`user-input ledger: acceptId ${a.acceptId} is already recorded — an acceptance id is one acceptance`);
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async settle(acceptId, how, atMs) {
|
|
63
|
+
const res = await this.db.query(this.q(`UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET state = 'settled', state_at_ms = ?, settle_how = ? WHERE accept_id = ? AND state = 'open'`, `UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET state = 'settled', state_at_ms = $1, settle_how = $2 WHERE accept_id = $3 AND state = 'open'`), [atMs, how, acceptId]);
|
|
64
|
+
return res.affected > 0;
|
|
65
|
+
}
|
|
66
|
+
async converge(acceptId, atMs) {
|
|
67
|
+
const res = await this.db.query(this.q(`UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET state = 'converged', state_at_ms = ? WHERE accept_id = ? AND state = 'open'`, `UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET state = 'converged', state_at_ms = $1 WHERE accept_id = $2 AND state = 'open'`), [atMs, acceptId]);
|
|
68
|
+
return res.affected > 0;
|
|
69
|
+
}
|
|
70
|
+
async announce(acceptId, taskId, atMs) {
|
|
71
|
+
const res = await this.db.query(this.q(`UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET announced_at_ms = ?, announced_task_id = ? WHERE accept_id = ? AND state = 'converged' AND announced_at_ms IS NULL`, `UPDATE ${USER_INPUT_ACCEPTANCE_TABLE} SET announced_at_ms = $1, announced_task_id = $2 WHERE accept_id = $3 AND state = 'converged' AND announced_at_ms IS NULL`), [atMs, taskId, acceptId]);
|
|
72
|
+
return res.affected > 0;
|
|
73
|
+
}
|
|
74
|
+
async listOpen(after, limit = USER_INPUT_OPEN_SCAN_MAX) {
|
|
75
|
+
assertPageLimit("listOpen", limit);
|
|
76
|
+
const { rows } = after === undefined
|
|
77
|
+
? await this.db.query(this.q(`SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE state = 'open' ORDER BY accepted_at_ms, accept_id LIMIT ${limit}`, `SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE state = 'open' ORDER BY accepted_at_ms, accept_id LIMIT ${limit}`), [])
|
|
78
|
+
: await this.db.query(this.q(`SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE state = 'open' AND (accepted_at_ms > ? OR (accepted_at_ms = ? AND accept_id > ?)) ORDER BY accepted_at_ms, accept_id LIMIT ${limit}`, `SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE state = 'open' AND (accepted_at_ms > $1 OR (accepted_at_ms = $2 AND accept_id > $3)) ORDER BY accepted_at_ms, accept_id LIMIT ${limit}`), [after.acceptedAtMs, after.acceptedAtMs, after.acceptId]);
|
|
79
|
+
return rows.map(rowToAcceptance);
|
|
80
|
+
}
|
|
81
|
+
async listCrashed(sessionId) {
|
|
82
|
+
const { rows } = await this.db.query(this.q(`SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE session_id = ? AND state = 'converged' ORDER BY accepted_at_ms DESC, accept_id DESC LIMIT ${USER_INPUT_SESSION_READ_MAX}`, `SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE session_id = $1 AND state = 'converged' ORDER BY accepted_at_ms DESC, accept_id DESC LIMIT ${USER_INPUT_SESSION_READ_MAX}`), [sessionId]);
|
|
83
|
+
return rows.map(rowToAcceptance).reverse();
|
|
84
|
+
}
|
|
85
|
+
async listUnannounced(sessionId, limit) {
|
|
86
|
+
const n = assertPageLimit("listUnannounced", limit);
|
|
87
|
+
const { rows } = await this.db.query(this.q(`SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE session_id = ? AND state = 'converged' AND announced_at_ms IS NULL ORDER BY accepted_at_ms, accept_id LIMIT ${n}`, `SELECT ${COLUMNS} FROM ${USER_INPUT_ACCEPTANCE_TABLE} WHERE session_id = $1 AND state = 'converged' AND announced_at_ms IS NULL ORDER BY accepted_at_ms, accept_id LIMIT ${n}`), [sessionId]);
|
|
88
|
+
return rows.map(rowToAcceptance);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export class TiDBUserInputLedgerStore extends SqlUserInputLedgerStore {
|
|
92
|
+
constructor(pool) {
|
|
93
|
+
super(mysqlDriver(pool));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export class PgUserInputLedgerStore extends SqlUserInputLedgerStore {
|
|
97
|
+
constructor(pool) {
|
|
98
|
+
super(pgDriver(pool));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=user-input-ledger-sql.js.map
|
|
@@ -20,7 +20,7 @@ export function keyClosureClause(names, label) {
|
|
|
20
20
|
const TASK_BODY_DECLARED_KEYS = [
|
|
21
21
|
"a2aPeers", "acceptPartialRestore", "additionalDirectories", "additionalReadDirectories", "agents",
|
|
22
22
|
"appendSystemPrompt", "attachmentIds", "attachments", "capabilitiesNeeded", "cascade", "clientContext",
|
|
23
|
-
"compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "enableFork",
|
|
23
|
+
"compaction", "compactionModel", "council", "cwd", "debate", "deferTools", "deviceId", "disableSkills", "enableFork",
|
|
24
24
|
"excludeAllTools", "excludeTools", "fileHistory",
|
|
25
25
|
"finalVerification", "forwardSubagentEvents", "images", "inheritEnv", "interactiveTools", "jobId", "limits", "maxCostUsd",
|
|
26
26
|
"maxTokens", "mcpServers", "memory", "memoryCapture", "memoryWrite", "model", "objective", "oneShot", "outputRetries",
|
package/dist/run-local.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type AskRequest, type Brain, type TaskSpec } from "@sema-agent/core";
|
|
3
|
+
import { type UnattendedPolicyRefusal } from "./tool-approval.js";
|
|
3
4
|
import { type ApprovalBaselineConfigView, type DeploymentGovernanceConfigView } from "./deployment-governance.js";
|
|
4
5
|
import { type ServiceConfig } from "./config.js";
|
|
5
6
|
import { type Logger } from "./observability/logger.js";
|
|
@@ -105,6 +106,9 @@ export interface LocalApproverIo {
|
|
|
105
106
|
readonly askYesNo: (prompt: string, signal?: AbortSignal) => Promise<boolean>;
|
|
106
107
|
/** 非 TTY 臂的告知面(stderr)。 */
|
|
107
108
|
readonly printErr: (line: string) => void;
|
|
109
|
+
/** S-956:本席**没有人被问**就拒了一只 ask(无 TTY / 参数展示不出)的事实出口 —— 退出码的判据之一(见 {@link unfinishedStopOf}):
|
|
110
|
+
* 这一拒交给 core 的是部署政策拒(不停批、不记人拒),「活没干完」于是不再经 core 的人拒标记传出来,由本席自己说。 */
|
|
111
|
+
readonly noteRefusedWithoutPerson: () => void;
|
|
108
112
|
}
|
|
109
113
|
/**
|
|
110
114
|
* [ref] 件三:**onAsk 座**(§0 潜雷 / §5 锁 6)。
|
|
@@ -130,12 +134,21 @@ export interface LocalApproverIo {
|
|
|
130
134
|
* ask(core 把 `matchedAskRule` 刻意留在闸内、不上请求),所以无 TTY 拒句**列出**可能产门的全部旋钮,
|
|
131
135
|
* `APPROVAL_DENY` 也点名(它当场拒、不经本席 —— 运维排查「为什么这只工具不跑」时要在同一句里看到它)。
|
|
132
136
|
*
|
|
133
|
-
* 🔴 非 TTY
|
|
137
|
+
* 🔴 非 TTY 臂答**拒**(真 deny),**绝不答 `"unavailable"`**:后者被 core 读成「席位够不到」并**改道
|
|
134
138
|
* `suspendAsk`**(dist `core/hooks.js` 的 `approverUnavailable` 重投)。今天本腿无店 ⇒ 那条改道无处
|
|
135
139
|
* 可去(fail-closed 拒),但这一条仍是本席的契约:答案是「不批」,不是「找不到人」。
|
|
140
|
+
*
|
|
141
|
+
* 🔴 S-956:**谁拒的就记谁**。本席的拒分两种,交给 core 的形不同:
|
|
142
|
+
* · **人答 N**(TTY 臂)⇒ 裸 `false` —— core 契约里这就是「一个人拒了」(`human_refused` / `who.party:"person"`,模型读「用户不想继续,
|
|
143
|
+
* 停下来等他」,run 以 `haltedOnUserRejection` 停批);
|
|
144
|
+
* · **没有人被问**(无 TTY 臂 / 参数展示不出臂)⇒ 部署政策拒对象 {@link UnattendedPolicyRefusal}(经与协调器 deny 政策臂**同一只**构造
|
|
145
|
+
* `buildUnattendedPolicyRefusal`;core 7.37.1 `readApproverOutcomeObject` ⇒ `policy_refused` / `who.party:"none"`,模型读 core 的
|
|
146
|
+
* 策略句 + 本席的拒因,不停批)。修前两臂交裸 `false` ⇒ 机器的拒被记成人拒、模型被告知去等一个不存在的人。
|
|
147
|
+
* 两臂的拒都经 `io.noteRefusedWithoutPerson` 报一次 —— 「活没干完」(退 1)此前搭的是 core 人拒标记的便车,S-956 之后由本席的事实给。
|
|
136
148
|
*/
|
|
137
|
-
/** 本席的形:core `OnAsk` 的函数臂(`"allow"` / `"deny"` 两个字面臂本席从不用)
|
|
138
|
-
|
|
149
|
+
/** 本席的形:core `OnAsk` 的函数臂(`"allow"` / `"deny"` 两个字面臂本席从不用)—— 人的答是布尔,没有人被问的拒是部署政策拒对象;
|
|
150
|
+
* 结构上可赋给 `OnAsk`。 */
|
|
151
|
+
export type LocalApprover = (req: AskRequest, signal?: AbortSignal) => Promise<boolean | UnattendedPolicyRefusal>;
|
|
139
152
|
export declare function createLocalApprover(io: LocalApproverIo): LocalApprover;
|
|
140
153
|
export declare function promptYesNoOnTty(prompt: string, signal?: AbortSignal, streams?: {
|
|
141
154
|
input: NodeJS.ReadableStream;
|
|
@@ -143,8 +156,8 @@ export declare function promptYesNoOnTty(prompt: string, signal?: AbortSignal, s
|
|
|
143
156
|
}): Promise<boolean>;
|
|
144
157
|
/**
|
|
145
158
|
* Boot the engine in LOCAL mode, run ONE task to completion, print the result. Returns the process exit
|
|
146
|
-
* code (0 = completed its work, 1 = the task did not complete — including a `completed` run that a person stopped
|
|
147
|
-
* see {@link
|
|
159
|
+
* code (0 = completed its work, 1 = the task did not complete — including a `completed` run that a person stopped
|
|
160
|
+
* or whose approval no person could answer, see {@link unfinishedStopOf} — / a fatal error, 2 = bad usage / unmet env). Pure of
|
|
148
161
|
* `process.exit` so a test can assert the code; `main()` applies it.
|
|
149
162
|
*/
|
|
150
163
|
export declare function runLocal(argv: string[], deps?: RunLocalDeps): Promise<number>;
|