@sema-agent/server 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +5 -3
- package/dist/approval-hmac.js +1 -1
- package/dist/approval.d.ts +5 -37
- package/dist/approval.js +12 -112
- package/dist/auth-bridge.d.ts +0 -1
- package/dist/auth-bridge.js +0 -1
- package/dist/auth-keys.d.ts +1 -1
- package/dist/auth-keys.js +1 -1
- package/dist/bench/s1/run-firm.js +1 -1
- package/dist/boot/config-center.d.ts +1 -1
- package/dist/boot/config-center.js +3 -2
- package/dist/boot/execution-env.js +8 -1
- package/dist/boot/reapers.d.ts +1 -2
- package/dist/boot/reapers.js +4 -5
- package/dist/boot/resolve-spec.d.ts +1 -3
- package/dist/boot/resolve-spec.js +21 -35
- package/dist/boot/stores.js +1 -1
- package/dist/brain.js +3 -1
- package/dist/capabilities/center-prompts.d.ts +2 -3
- package/dist/capabilities/center-prompts.js +2 -3
- package/dist/capabilities/sandbox-file-send.d.ts +0 -2
- package/dist/capabilities/sandbox-file-send.js +2 -5
- package/dist/capabilities/scenarios.d.ts +0 -9
- package/dist/capabilities/scenarios.js +1 -20
- package/dist/capabilities/send-user-file-tool.js +3 -3
- package/dist/config-center/apply-effective.js +8 -8
- package/dist/config-center/types.d.ts +10 -3
- package/dist/config-provider.js +6 -11
- package/dist/config-types.d.ts +12 -26
- package/dist/config.js +95 -68
- package/dist/fleet/fleet-bus.js +25 -33
- package/dist/fleet-client.js +1 -1
- package/dist/http/active-run-conflict.d.ts +2 -0
- package/dist/http/active-run-conflict.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +2 -1
- package/dist/http/routes/approvals-assistant.js +10 -96
- package/dist/http/routes/capabilities.js +6 -5
- package/dist/http/routes/fleet.js +4 -1
- package/dist/http/routes/images.js +10 -4
- package/dist/http/routes/runs.js +34 -49
- package/dist/http/routes/session-sync.d.ts +1 -1
- package/dist/http/routes/session-sync.js +2 -1
- package/dist/http/routes/tasks.js +7 -7
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/server.d.ts +18 -7
- package/dist/http/server.js +25 -14
- package/dist/http/verify-rounds.d.ts +2 -2
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/images/bake-validate.d.ts +0 -22
- package/dist/images/bake-validate.js +0 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -5
- package/dist/lsp/e2b-bridge.d.ts +36 -3
- package/dist/lsp/e2b-bridge.js +94 -7
- package/dist/lsp/e2b-manager.d.ts +64 -1
- package/dist/lsp/e2b-manager.js +197 -28
- package/dist/lsp/manager.d.ts +71 -4
- package/dist/lsp/manager.js +149 -29
- package/dist/lsp/ws-transport.d.ts +1 -1
- package/dist/lsp/ws-transport.js +4 -1
- package/dist/main.js +5 -9
- package/dist/memory-scope.js +2 -2
- package/dist/observability/metrics.js +6 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +6 -4
- package/dist/orchestration/workflow-completion-inbox.js +6 -4
- package/dist/plugins/breaker-state-sql.d.ts +2 -3
- package/dist/plugins/image-index-sql.d.ts +0 -1
- package/dist/plugins/image-index-sql.js +0 -1
- package/dist/plugins/local-checkpoint-store.d.ts +1 -1
- package/dist/plugins/local-checkpoint-store.js +1 -1
- package/dist/plugins/memory-engine-pg.js +2 -1
- package/dist/plugins/memory-engine-tidb.js +1 -1
- package/dist/plugins/memory-engine-vector-util.d.ts +2 -3
- package/dist/plugins/memory-engine-vector-util.js +2 -3
- package/dist/plugins/memory-run-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.d.ts +1 -1
- package/dist/plugins/memory-session-policy-store.js +1 -1
- package/dist/plugins/memory-sync-store-pg.js +1 -1
- package/dist/plugins/pg-pool.js +8 -10
- package/dist/plugins/pg-rate-limiter.d.ts +1 -4
- package/dist/plugins/pg-rate-limiter.js +0 -1
- package/dist/plugins/roster-store-sql.d.ts +1 -3
- package/dist/plugins/roster-store-sql.js +3 -5
- package/dist/plugins/store-backend.d.ts +7 -17
- package/dist/plugins/store-backend.js +14 -30
- package/dist/plugins/tidb-pool.js +4 -28
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +1 -1
- package/dist/principal-jwt.js +2 -2
- package/dist/prompts-domain-validate.d.ts +4 -3
- package/dist/prompts-domain-validate.js +7 -8
- package/dist/router/route-orchestration.js +3 -3
- package/dist/run-local.js +5 -4
- package/dist/runs.js +8 -6
- package/dist/runtime-caps-resolver.d.ts +3 -2
- package/dist/runtime-caps-resolver.js +4 -4
- package/dist/runtime-governance.d.ts +4 -7
- package/dist/runtime-governance.js +12 -17
- package/dist/sealed-key.d.ts +1 -1
- package/dist/sealed-key.js +1 -1
- package/dist/security.d.ts +0 -5
- package/dist/security.js +8 -12
- package/dist/session-sync.d.ts +0 -2
- package/dist/session-sync.js +10 -17
- package/dist/spec-fields.js +6 -6
- package/dist/task-settings.js +7 -7
- package/dist/tool-approval.d.ts +3 -3
- package/dist/tool-approval.js +10 -10
- package/dist/trace/artifacts.d.ts +11 -0
- package/dist/trace/artifacts.js +8 -19
- package/dist/trace/ledger-sink.js +7 -1
- package/dist/trace/project.d.ts +17 -0
- package/dist/trace/project.js +55 -5
- package/package.json +2 -2
- package/dist/capabilities/oa-tools.d.ts +0 -15
- package/dist/capabilities/oa-tools.js +0 -54
- package/dist/plugins/approval-store-sql.d.ts +0 -116
- package/dist/plugins/approval-store-sql.js +0 -150
- package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
- package/dist/plugins/file-workflow-journal-store.js +0 -12
- package/dist/plugins/pg-approval-store.d.ts +0 -9
- package/dist/plugins/pg-approval-store.js +0 -9
- package/dist/plugins/pg-breaker-state.d.ts +0 -8
- package/dist/plugins/pg-breaker-state.js +0 -8
- package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
- package/dist/plugins/pg-checkpoint-store.js +0 -10
- package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/pg-file-snapshot-store.js +0 -8
- package/dist/plugins/pg-image-bake.d.ts +0 -12
- package/dist/plugins/pg-image-bake.js +0 -11
- package/dist/plugins/pg-image-index.d.ts +0 -12
- package/dist/plugins/pg-image-index.js +0 -11
- package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
- package/dist/plugins/pg-outcome-ledger.js +0 -11
- package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/pg-resume-anchor-store.js +0 -7
- package/dist/plugins/pg-run-store.d.ts +0 -9
- package/dist/plugins/pg-run-store.js +0 -9
- package/dist/plugins/pg-session-policy-store.d.ts +0 -7
- package/dist/plugins/pg-session-policy-store.js +0 -7
- package/dist/plugins/pg-session-store.d.ts +0 -12
- package/dist/plugins/pg-session-store.js +0 -12
- package/dist/plugins/pg-tool-result-store.d.ts +0 -9
- package/dist/plugins/pg-tool-result-store.js +0 -9
- package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-journal-store.js +0 -9
- package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-run-store.js +0 -9
- package/dist/plugins/tidb-approval-store.d.ts +0 -8
- package/dist/plugins/tidb-approval-store.js +0 -8
- package/dist/plugins/tidb-breaker-state.d.ts +0 -7
- package/dist/plugins/tidb-breaker-state.js +0 -7
- package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
- package/dist/plugins/tidb-checkpoint-store.js +0 -9
- package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/tidb-file-snapshot-store.js +0 -8
- package/dist/plugins/tidb-image-bake.d.ts +0 -12
- package/dist/plugins/tidb-image-bake.js +0 -11
- package/dist/plugins/tidb-image-index.d.ts +0 -12
- package/dist/plugins/tidb-image-index.js +0 -11
- package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
- package/dist/plugins/tidb-outcome-ledger.js +0 -12
- package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/tidb-resume-anchor-store.js +0 -7
- package/dist/plugins/tidb-run-store.d.ts +0 -10
- package/dist/plugins/tidb-run-store.js +0 -9
- package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
- package/dist/plugins/tidb-session-policy-store.js +0 -7
- package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
- package/dist/plugins/tidb-tool-result-store.js +0 -10
- package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/tidb-workflow-journal-store.js +0 -9
- package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
- package/dist/plugins/tidb-workflow-run-store.js +0 -10
- package/dist/plugins/workflow-journal-limits.d.ts +0 -12
- package/dist/plugins/workflow-journal-limits.js +0 -12
package/dist/tool-approval.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AskRequest } from "@sema-agent/core";
|
|
1
|
+
import { type AskRequest, type AskOutcome } from "@sema-agent/core";
|
|
2
2
|
/** A live approval frame delivered to whoever tails this run's stream. `type` IS the SSE event name (named-event
|
|
3
3
|
* convention, same as question). The shell renders `tool_approval` as the CC three-choice card and dismisses on
|
|
4
4
|
* `tool_approval_complete`. */
|
|
@@ -103,7 +103,7 @@ export declare class ToolApprovalCoordinator {
|
|
|
103
103
|
* approverUnavailable 回路把这只 ask 交回 suspendAsk 走 durable park(park 设施缺席的部署 core 自己
|
|
104
104
|
* fail-closed deny)。1.199 的「durable 部署不 wire onAsk」止血就此撤除:恒 wire,park 与 live 卡两全。
|
|
105
105
|
* Arrow property so it can be passed as `onAsk: coordinator.ask` with `this` bound. */
|
|
106
|
-
ask: (req: AskRequest, signal?: AbortSignal) => Promise<
|
|
106
|
+
ask: (req: AskRequest, signal?: AbortSignal) => Promise<AskOutcome>;
|
|
107
107
|
/** [1535] server 半场(审批链战役,[1539] 定谳断点①的修;[1546] HIGH-1 broker 形):宿主 sync
|
|
108
108
|
* streaming 腿在装配点以本闭包挂 `spec.onAsk`;core 继承链(parentConstraints 冻结
|
|
109
109
|
* `spec.onAsk ?? deps.onAsk`,1.294 起)把它逐 ancestor 冻给每个委派子代——子代(bg/嵌套孙代)的
|
|
@@ -115,7 +115,7 @@ export declare class ToolApprovalCoordinator {
|
|
|
115
115
|
owner: string | null;
|
|
116
116
|
taskId: string;
|
|
117
117
|
sessionId?: string;
|
|
118
|
-
}) => ((req: AskRequest, signal?: AbortSignal) => Promise<
|
|
118
|
+
}) => ((req: AskRequest, signal?: AbortSignal) => Promise<AskOutcome>);
|
|
119
119
|
/** 广播版裁决路径——`ask()`(ALS,恒单元素数组)与 `boundAsk`([1559]四多活集合,可能多元素)共用。
|
|
120
120
|
* 全体 `ctxs` 保证同一 (owner, sessionId)(streamKey 分组不变式;单元素数组平凡成立)。
|
|
121
121
|
*
|
package/dist/tool-approval.js
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
* and asks are turn-blocking by construction — the TTL bounds the human-side exposure instead.
|
|
42
42
|
*/
|
|
43
43
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
44
|
-
import { uuidv7
|
|
44
|
+
import { uuidv7 } from "@sema-agent/core";
|
|
45
45
|
import { redactDeep, redactSecrets } from "./trace/redact.js";
|
|
46
46
|
/** Size bound on the redacted args payload in a `tool_approval` frame (parity with question's MAX_QUESTIONS_BYTES).
|
|
47
47
|
* Over the cap ⇒ the frame still goes out WITHOUT args (`argsOmitted: true`) — unlike a question (undisplayable ⇒
|
|
@@ -53,17 +53,17 @@ const DEFAULT_APPROVAL_TTL_MS = 5 * 60_000;
|
|
|
53
53
|
/** Bound on the remembered allow-all grants (insertion-order evict — a long-lived worker must not grow unbounded). */
|
|
54
54
|
const MAX_ALLOW_SESSIONS = 4096;
|
|
55
55
|
/** The fs-write tool family the session allow-all collapses into ONE capability bucket (canonical space — a
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
const FS_WRITE_TOOLS = new Set(["Write", "Edit", "NotebookEdit"]
|
|
56
|
+
* 5.0.0 RB-476:折叠面退役,三名即 CC canonical(旧拼法在 core roster 层响亮 miss)。Mirrors core's
|
|
57
|
+
* PATH_WRITE_TOOLS + NotebookEdit (the same set `createFsWriteGatePolicy` gates); NOT exported by core's
|
|
58
|
+
* index, so the names are pinned here deliberately. */
|
|
59
|
+
const FS_WRITE_TOOLS = new Set(["Write", "Edit", "NotebookEdit"]);
|
|
60
60
|
/** The session allow-all CAPABILITY CATEGORY for a tool (修2): the fs-write family shares one bucket ("fs-write" —
|
|
61
61
|
* a yes-to-edits covers Write/Edit/NotebookEdit alike, CC 207 semantics); every other tool is its OWN bucket
|
|
62
62
|
* ("tool:<canonical>") so an allow_session on it never grants anything beyond that same tool — a low-risk ask must
|
|
63
63
|
* not become a session-wide write unlock. Canonical space in, category out. */
|
|
64
64
|
function sessionAllowCategory(toolName) {
|
|
65
|
-
|
|
66
|
-
return FS_WRITE_TOOLS.has(
|
|
65
|
+
// RAW(5.0.0):live 名恒 canonical;存量 grant 键当年也是 canonical 空间铸的,键空间不变。
|
|
66
|
+
return FS_WRITE_TOOLS.has(toolName) ? "fs-write" : `tool:${toolName}`;
|
|
67
67
|
}
|
|
68
68
|
/** The allow-all grant key (修2): owner + sessionId + capability category, JSON-tuple-encoded so no segment can
|
|
69
69
|
* ambiguate the boundary with the next (an owner or sessionId containing a separator can't forge another key).
|
|
@@ -396,9 +396,9 @@ export class ToolApprovalCoordinator {
|
|
|
396
396
|
// 方法顶注。
|
|
397
397
|
if (!emitSettled && lastOutcome === "expired")
|
|
398
398
|
return "unavailable";
|
|
399
|
-
// [1458]
|
|
400
|
-
//
|
|
401
|
-
//
|
|
399
|
+
// [1458] 编辑放行对象臂:core AskOutcome 公开类型已含 `{allow, updatedInput?}`([1462])——
|
|
400
|
+
// 宽铸退役,真类型直返(core BAGGAGE #17 严格化前提=对象臂 allow 恒严格 true,我方满足)。
|
|
401
|
+
// deny 恒返 false(falsy 纪律)。
|
|
402
402
|
if (settled.allowed && settled.updatedInput !== undefined) {
|
|
403
403
|
return { allow: true, updatedInput: settled.updatedInput };
|
|
404
404
|
}
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trace.artifacts (M2) — project "what the agent produced" from the durable event log.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic projection, no new write path: artifacts are DERIVED from the already-persisted
|
|
5
|
+
* tool_start/tool_end pairs (S2 log), so they exist for every historical run and cannot drift from execution.
|
|
6
|
+
* v1 produces two kinds:
|
|
7
|
+
* - `file` — a successful `Write` / `Edit` call (deduped per path; the summary counts edits).
|
|
8
|
+
* - `git-push` — a successful `bash` call whose command contains `git push` (ref = the parsed refspec tail).
|
|
9
|
+
* `diff` is declared in the wire schema but NOT produced in v1 (we don't capture patch bodies in the log) —
|
|
10
|
+
* consumers must treat kinds as an open set. Args in the log are already secret-redacted at persist time.
|
|
11
|
+
*/
|
|
1
12
|
import type { RunEvent } from "../plugins/store-contracts.js";
|
|
2
13
|
export interface TraceArtifact {
|
|
3
14
|
/** Stable within the task: `<taskId>:<n>` in first-seen order — safe as a UI key + dedupe handle. */
|
package/dist/trace/artifacts.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* trace.artifacts (M2) — project "what the agent produced" from the durable event log.
|
|
3
|
-
*
|
|
4
|
-
* Deterministic projection, no new write path: artifacts are DERIVED from the already-persisted
|
|
5
|
-
* tool_start/tool_end pairs (S2 log), so they exist for every historical run and cannot drift from execution.
|
|
6
|
-
* v1 produces two kinds:
|
|
7
|
-
* - `file` — a successful `Write` / `Edit` call (deduped per path; the summary counts edits).
|
|
8
|
-
* - `git-push` — a successful `bash` call whose command contains `git push` (ref = the parsed refspec tail).
|
|
9
|
-
* `diff` is declared in the wire schema but NOT produced in v1 (we don't capture patch bodies in the log) —
|
|
10
|
-
* consumers must treat kinds as an open set. Args in the log are already secret-redacted at persist time.
|
|
11
|
-
*/
|
|
12
|
-
import { canonicalToolName } from "@sema-agent/core";
|
|
13
1
|
/** Line count of a tool-arg string ("" = 0; no trailing-newline inflation). */
|
|
14
2
|
function lines(v) {
|
|
15
3
|
if (typeof v !== "string" || v.length === 0)
|
|
16
4
|
return 0;
|
|
17
5
|
return v.split("\n").length;
|
|
18
6
|
}
|
|
19
|
-
// Q6 (core 1.161) CC names
|
|
20
|
-
//
|
|
21
|
-
//
|
|
7
|
+
// Q6 (core 1.161) CC names。5.0.0 RB-476:折叠面退役——本投影跑在历史事件日志上,pre-rename 旧行
|
|
8
|
+
// (如 1.171 时代的 "MultiEdit")不再折到 canonical:旧名行按原名滑过 FILE_TOOLS 判定=不进 artifacts
|
|
9
|
+
// 归类(诚实历史,读面不再兜旧名;耐久面按 [2364] 令牺牲)。
|
|
22
10
|
const FILE_TOOLS = new Set(["Write", "Edit", "NotebookEdit"]);
|
|
23
11
|
const GIT_PUSH_RE = /\bgit\b[^;|&]*\bpush\b\s*(.*)/;
|
|
24
12
|
/** Project the durable event log into artifacts. Events must be in seq order (getEvents guarantees it). */
|
|
@@ -39,11 +27,12 @@ export function projectArtifacts(events, run) {
|
|
|
39
27
|
if (!start)
|
|
40
28
|
continue;
|
|
41
29
|
starts.delete(d.toolCallId); // each call contributes at most once
|
|
42
|
-
const toolName =
|
|
30
|
+
const toolName = start.toolName; // RAW(5.0.0):折叠面退役,历史旧名行按原名处理(见文件头注)
|
|
43
31
|
if (FILE_TOOLS.has(toolName)) {
|
|
44
|
-
// Write/Edit key the write target on `file_path
|
|
45
|
-
//
|
|
46
|
-
//
|
|
32
|
+
// Write/Edit key the write target on `file_path`. core 5.1.0([2390])收窄 schema 为 `file_path` 单形,
|
|
33
|
+
// 但**门读侧 over-read 保留**是同批 KEEP 裁定(fail-closed 方向)——这里是耐久历史行的读侧([2373] D 类),
|
|
34
|
+
// pre-1.169 emit/旧 durable checkpoint 仍带 `path`,回落臂是对自己旧数据的容忍,不是对上游新帧的兼容。
|
|
35
|
+
// 再回落 NotebookEdit 的 `notebook_path`(core 1.163)。三键全缺 → 非带路径调用。
|
|
47
36
|
const path = typeof start.args["file_path"] === "string" ? start.args["file_path"] : typeof start.args["path"] === "string" ? start.args["path"] : typeof start.args["notebook_path"] === "string" ? start.args["notebook_path"] : undefined;
|
|
48
37
|
if (!path)
|
|
49
38
|
continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { redactSecrets } from "./redact.js";
|
|
2
|
-
import { toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, workspaceChangedEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
2
|
+
import { toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, turnEndEventData, contextUsageEventData } from "./project.js";
|
|
3
3
|
import { NotifiedKeys, taskNotificationStreamKey } from "../orchestration/workflow-completion-inbox.js";
|
|
4
4
|
export function createLedgerSink(opts) {
|
|
5
5
|
const keys = opts.notifiedKeys ?? new NotifiedKeys();
|
|
@@ -101,6 +101,12 @@ export function createLedgerSink(opts) {
|
|
|
101
101
|
await flush();
|
|
102
102
|
await append("steering_injected", steeringInjectedEventData(ev));
|
|
103
103
|
break;
|
|
104
|
+
case "compaction_outcome":
|
|
105
|
+
// [2373]A-2/C-2:此前 durable 双腿零 case ⇒ 回放即消失(与 [1622] label / [1611] workflowRunId
|
|
106
|
+
// 同族)。共享 builder(§E1 脱敏)三腿同源;差集门 durable-append-arm-parity 看守。
|
|
107
|
+
await flush();
|
|
108
|
+
await append("compaction_outcome", compactionOutcomeEventData(ev));
|
|
109
|
+
break;
|
|
104
110
|
case "workspace_changed":
|
|
105
111
|
// [1559]二 core 裁定:design/99 §E13 旧臂,L1 门扩臂时首次撞见。durable 记账与其余内容型臂
|
|
106
112
|
// 同处置(轨迹回放时「何时 cd 到了哪」是合法历史事实,「不当持久态渲染」是 LIVE 消费端纪律,
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -235,6 +235,17 @@ export declare function steeringInjectedEventData(ev: {
|
|
|
235
235
|
eventId?: string;
|
|
236
236
|
parentToolCallId?: string;
|
|
237
237
|
}): Record<string, unknown>;
|
|
238
|
+
/** `compaction_outcome`(core 2.5.0 [2105]②:压缩非 compacted 结局——manual /compact 的 mooted/failed
|
|
239
|
+
* 两条真路)。[2373]A-2/C-2:此前它是**唯一**不走共享 builder、只在 live 腿 res.write 内联挑键的
|
|
240
|
+
* 内容帧 ⇒ 两条 durable 腿零 case、回放即消失、三方普查各自失明。收进共享白名单 builder,三腿同源。
|
|
241
|
+
* outcome/trigger=闭枚举 verbatim;reason=引擎说明文本(可能引用配置值)→ redactSecrets;identity 同款。 */
|
|
242
|
+
export declare function compactionOutcomeEventData(ev: {
|
|
243
|
+
outcome?: unknown;
|
|
244
|
+
trigger?: unknown;
|
|
245
|
+
reason?: unknown;
|
|
246
|
+
eventId?: unknown;
|
|
247
|
+
parentToolCallId?: unknown;
|
|
248
|
+
}): Record<string, unknown>;
|
|
238
249
|
/** The `workspace_changed` payload([1559]二 core 裁定:design/99 §E13 旧臂,L1 门本车首次撞见——非新增,
|
|
239
250
|
* 是本门扩 TaskEvent 臂集时第一次显式处置。语义 = bash `cd`/EnterWorktree 移动任务逻辑 cwd 后,在移动
|
|
240
251
|
* 它的工具结算后发一帧,给 shell/宿主 UI 一个路径提示行。cwd 与已展示的工具 args 同信任级[UNTRUSTED,
|
|
@@ -347,6 +358,12 @@ export declare function turnEndEventData(ev: {
|
|
|
347
358
|
* `in` 落回旧值,读端零伤)。`uncachedIn` 只在**能确认自己踩在新契约帧上**(`totalInputTokens` 在场)时
|
|
348
359
|
* 才发——旧帧的 `inputTokens` 是族依赖的旧语义,标成「uncachedIn(cache-MISS)」会是谎言。 */
|
|
349
360
|
export declare function toContractTokens(usage: unknown): TraceTurn["tokens"] | undefined;
|
|
361
|
+
/** [2373]C-13:tool_start/tool_end 的**共享挑键**——`projectEvents`(turns 面)与 `mapTraceEvent`
|
|
362
|
+
* (trace SSE 面)对同一账本行的字段选择逐字同构,此前两处独立手写,structured/totalChars/label
|
|
363
|
+
* 三次扩字段各改两遍(见两处对偶注)。收一份:两面各自包装键名皮(id/name/input vs 同名),
|
|
364
|
+
* 字段集合从此单点演进。 */
|
|
365
|
+
export declare function toolCallFieldsOf(d: Record<string, unknown>): Record<string, unknown>;
|
|
366
|
+
export declare function toolResultFieldsOf(d: Record<string, unknown>): Record<string, unknown>;
|
|
350
367
|
export interface ProjectOpts {
|
|
351
368
|
/** Prepend the user turn (the objective is not in the event log; the endpoint supplies it). */
|
|
352
369
|
objective?: string;
|
package/dist/trace/project.js
CHANGED
|
@@ -12,6 +12,8 @@ export async function appendModelUsageDelta(append, modelUsage, taskId) {
|
|
|
12
12
|
const delta = modelUsage?.drain(taskId);
|
|
13
13
|
if (delta) {
|
|
14
14
|
const tagged = {};
|
|
15
|
+
// [2373]A-4 记账(2026-08-02):usageBasis(与 TraceTurn.tokens.uncachedIn 同族)五端+黑盒仓零运行时读者——
|
|
16
|
+
// 3.0.0 cache 语义翻转的诚实位,契约正确但暂无消费;web/desktop 接账单面时它是现成的。
|
|
15
17
|
for (const [model, d] of Object.entries(delta))
|
|
16
18
|
tagged[model] = { ...d, usageBasis: "uncached-components-v1" };
|
|
17
19
|
await append("model_usage", { usage: tagged });
|
|
@@ -296,6 +298,19 @@ export function steeringInjectedEventData(ev) {
|
|
|
296
298
|
...identityFields(ev),
|
|
297
299
|
};
|
|
298
300
|
}
|
|
301
|
+
/** `compaction_outcome`(core 2.5.0 [2105]②:压缩非 compacted 结局——manual /compact 的 mooted/failed
|
|
302
|
+
* 两条真路)。[2373]A-2/C-2:此前它是**唯一**不走共享 builder、只在 live 腿 res.write 内联挑键的
|
|
303
|
+
* 内容帧 ⇒ 两条 durable 腿零 case、回放即消失、三方普查各自失明。收进共享白名单 builder,三腿同源。
|
|
304
|
+
* outcome/trigger=闭枚举 verbatim;reason=引擎说明文本(可能引用配置值)→ redactSecrets;identity 同款。 */
|
|
305
|
+
export function compactionOutcomeEventData(ev) {
|
|
306
|
+
// 形参照 contextUsageEventData 先例:全可选 unknown + 内部窄化,call site 免宽铸型(棘轮零新增)。
|
|
307
|
+
return {
|
|
308
|
+
outcome: String(ev.outcome),
|
|
309
|
+
trigger: String(ev.trigger),
|
|
310
|
+
...(ev.reason !== undefined ? { reason: redactSecrets(String(ev.reason)) } : {}),
|
|
311
|
+
...identityFields(ev),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
299
314
|
/** The `workspace_changed` payload([1559]二 core 裁定:design/99 §E13 旧臂,L1 门本车首次撞见——非新增,
|
|
300
315
|
* 是本门扩 TaskEvent 臂集时第一次显式处置。语义 = bash `cd`/EnterWorktree 移动任务逻辑 cwd 后,在移动
|
|
301
316
|
* 它的工具结算后发一帧,给 shell/宿主 UI 一个路径提示行。cwd 与已展示的工具 args 同信任级[UNTRUSTED,
|
|
@@ -395,6 +410,9 @@ export function compactedEventData(ev) {
|
|
|
395
410
|
* 挂那儿会是"另一次更早的测量顶着同一个名字")。去 turn_end 找是找不到的。
|
|
396
411
|
* 三个字段都是引擎派生的有限数标量 ⇒ 走 finite 门(与 compacted 的 1.289 标量同姿势),非有限即缺席。
|
|
397
412
|
*/
|
|
413
|
+
// [2373]A-3 记账(2026-08-02):SDK 有臂、五端零读点(webC Composer 读的是本端自己的 state.context,
|
|
414
|
+
// 非 wire 三键)——半死:契约面已就位、消费端一个都没接。头注下方的三条口径纪律警告的消费者还不存在;
|
|
415
|
+
// 谁要做压缩面板,这是真正需要的量,先上黑板认领。
|
|
398
416
|
export function contextUsageEventData(ev) {
|
|
399
417
|
const n = (v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined);
|
|
400
418
|
return {
|
|
@@ -442,6 +460,29 @@ export function toContractTokens(usage) {
|
|
|
442
460
|
...(u.cacheWriteTokens != null ? { cacheWrite: u.cacheWriteTokens } : {}),
|
|
443
461
|
};
|
|
444
462
|
}
|
|
463
|
+
/** [2373]C-13:tool_start/tool_end 的**共享挑键**——`projectEvents`(turns 面)与 `mapTraceEvent`
|
|
464
|
+
* (trace SSE 面)对同一账本行的字段选择逐字同构,此前两处独立手写,structured/totalChars/label
|
|
465
|
+
* 三次扩字段各改两遍(见两处对偶注)。收一份:两面各自包装键名皮(id/name/input vs 同名),
|
|
466
|
+
* 字段集合从此单点演进。 */
|
|
467
|
+
export function toolCallFieldsOf(d) {
|
|
468
|
+
return {
|
|
469
|
+
id: String(d.toolCallId ?? ""),
|
|
470
|
+
name: String(d.toolName ?? ""),
|
|
471
|
+
...(typeof d.label === "string" ? { label: d.label } : {}),
|
|
472
|
+
input: d.args,
|
|
473
|
+
...identityFields(d),
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
export function toolResultFieldsOf(d) {
|
|
477
|
+
return {
|
|
478
|
+
callId: String(d.toolCallId ?? ""),
|
|
479
|
+
...(typeof d.label === "string" ? { label: d.label } : {}),
|
|
480
|
+
...(d.truncated ? { truncated: true } : {}),
|
|
481
|
+
...(Number.isFinite(d.totalChars) ? { totalChars: d.totalChars } : {}),
|
|
482
|
+
...(d.structured !== undefined ? { structured: d.structured } : {}),
|
|
483
|
+
...identityFields(d),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
445
486
|
/** Project a per-task event slice (seq-ascending) into ordered `TraceTurn[]`. Pure + total (never throws). */
|
|
446
487
|
export function projectEvents(events, opts = {}) {
|
|
447
488
|
const turns = [];
|
|
@@ -470,14 +511,14 @@ export function projectEvents(events, opts = {}) {
|
|
|
470
511
|
break;
|
|
471
512
|
case "tool_start":
|
|
472
513
|
firstTs ??= ev.ts;
|
|
473
|
-
blocks.push({ type: "tool-call",
|
|
514
|
+
blocks.push({ type: "tool-call", ...toolCallFieldsOf(d) });
|
|
474
515
|
break;
|
|
475
516
|
case "tool_end": {
|
|
476
517
|
const callId = String(d.toolCallId ?? "");
|
|
477
518
|
// E1: output is read straight off the (already-redacted-at-append) event — no session join. Absent ⇒ undefined.
|
|
478
519
|
// `structured` (core 1.203 CC card) was redacted at APPEND (toolEndEventData) — passthrough on the read
|
|
479
520
|
// path so a cold-replay/turns consumer renders the same rich card the live wire carried (additive field).
|
|
480
|
-
blocks.push({ type: "tool-result",
|
|
521
|
+
blocks.push({ type: "tool-result", ...toolResultFieldsOf(d), output: d.output, ...(d.isError ? { isError: true } : {}) });
|
|
481
522
|
break;
|
|
482
523
|
}
|
|
483
524
|
case "prompt_assembled":
|
|
@@ -525,6 +566,8 @@ export function lastStep(latestEventType, status) {
|
|
|
525
566
|
/** A run record → the cheap list summary (contract 2.1). Token/cost/turns come from the terminal result.stats;
|
|
526
567
|
* scenario isn't denormalized on the run row (omitted — a later push-index/column refinement). */
|
|
527
568
|
export function taskSummary(run, latestEventType) {
|
|
569
|
+
// [2373]D-1(活 bug 修):core 2.0.0 起浮点 costUsd 族整删,stats 只有整数 costMicroUsd —— 旧读点
|
|
570
|
+
// (costUsd × 1e6)对现役 core 恒缺席,verbatim 透传才是唯一正确形。
|
|
528
571
|
const stats = (run.result?.stats ?? {});
|
|
529
572
|
const terminal = run.status !== "running";
|
|
530
573
|
return {
|
|
@@ -538,7 +581,7 @@ export function taskSummary(run, latestEventType) {
|
|
|
538
581
|
...(terminal ? { endedAt: run.updatedAt } : {}),
|
|
539
582
|
...(stats.turns !== undefined ? { turns: stats.turns } : {}),
|
|
540
583
|
...(stats.tokens !== undefined ? { tokens: stats.tokens } : {}),
|
|
541
|
-
...(stats.
|
|
584
|
+
...(stats.costMicroUsd !== undefined ? { costMicroUsd: stats.costMicroUsd } : {}),
|
|
542
585
|
lastStep: lastStep(latestEventType, run.status),
|
|
543
586
|
};
|
|
544
587
|
}
|
|
@@ -560,16 +603,23 @@ export function normalizeRunEventType(type) {
|
|
|
560
603
|
/** Map one durable event → a sema-registry SSE trace event (contract 2.3). null = not a client event. The
|
|
561
604
|
* durable log is block-grained, so each block ships as one "delta" the client appends (stream mode = "delta"). */
|
|
562
605
|
export function mapTraceEvent(type, seq, data) {
|
|
606
|
+
const mapped = mapTraceEventInner(type, seq, data);
|
|
607
|
+
if (mapped === null)
|
|
608
|
+
return null;
|
|
609
|
+
// [2373]C-12:data 里补 `type`(=event 名)双发(fleet 流同批;error 帧 arm 自带 type 保持原值)。
|
|
610
|
+
return { event: mapped.event, data: { type: mapped.event, ...mapped.data } };
|
|
611
|
+
}
|
|
612
|
+
function mapTraceEventInner(type, seq, data) {
|
|
563
613
|
switch (type) {
|
|
564
614
|
case "reasoning":
|
|
565
615
|
return { event: "block-thinking-delta", data: { seq, text: String(data.text ?? "") } };
|
|
566
616
|
case "text":
|
|
567
617
|
return { event: "block-content-delta", data: { seq, text: String(data.text ?? "") } };
|
|
568
618
|
case "tool_start":
|
|
569
|
-
return { event: "tool-call", data: { seq,
|
|
619
|
+
return { event: "tool-call", data: { seq, ...toolCallFieldsOf(data) } };
|
|
570
620
|
case "tool_end":
|
|
571
621
|
// E1: output/truncated are already redacted+bounded at append; pass through. E2: identity additive.
|
|
572
|
-
return { event: "tool-result", data: { seq,
|
|
622
|
+
return { event: "tool-result", data: { seq, ...toolResultFieldsOf(data), isError: Boolean(data.isError), ...(data.output !== undefined ? { output: data.output } : {}) } };
|
|
573
623
|
case "turn_end": {
|
|
574
624
|
const tokens = toContractTokens(data.usage);
|
|
575
625
|
return { event: "turn", data: { seq, ...(tokens ? { tokens } : {}) } };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "^
|
|
57
|
+
"@sema-agent/core": "^5.1.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.13.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { ToolSpec } from "@sema-agent/core";
|
|
2
|
-
/**
|
|
3
|
-
* OA scenario — tools are thin HTTP clients to the OA backend's existing APIs (read = /api/v1/*,
|
|
4
|
-
* write = /api/assets/*). The shared service token + the request's principal are forwarded so OA
|
|
5
|
-
* enforces THAT end-user's RBAC + audit (the service never holds per-user creds). Decoupled: the
|
|
6
|
-
* service knows no OA schema/DB — OA stays the source of truth (its /api/v1/meta is self-describing).
|
|
7
|
-
*/
|
|
8
|
-
export interface OaCtx {
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
token: string;
|
|
11
|
-
principal?: string;
|
|
12
|
-
}
|
|
13
|
-
export declare function oaTools(ctx: OaCtx): ToolSpec[];
|
|
14
|
-
export declare const oaPrompt: import("@sema-agent/core").PromptProvider;
|
|
15
|
-
//# sourceMappingURL=oa-tools.d.ts.map
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Type } from "typebox";
|
|
2
|
-
import { stablePrompt } from "./prompt.js";
|
|
3
|
-
async function oaFetch(ctx, path, init) {
|
|
4
|
-
if (!ctx.baseUrl || !ctx.token)
|
|
5
|
-
return "OA 后端未配置(OA_API_BASEURL / OA_SERVICE_TOKEN)";
|
|
6
|
-
try {
|
|
7
|
-
const res = await fetch(ctx.baseUrl + path, {
|
|
8
|
-
method: init?.method ?? "GET",
|
|
9
|
-
headers: {
|
|
10
|
-
"content-type": "application/json",
|
|
11
|
-
authorization: `Bearer ${ctx.token}`,
|
|
12
|
-
...(ctx.principal ? { "x-agent-principal": ctx.principal } : {}),
|
|
13
|
-
},
|
|
14
|
-
body: init?.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
15
|
-
});
|
|
16
|
-
const text = await res.text();
|
|
17
|
-
if (!res.ok)
|
|
18
|
-
return `OA ${res.status}: ${text.slice(0, 600)}`;
|
|
19
|
-
return text.slice(0, 60_000);
|
|
20
|
-
}
|
|
21
|
-
catch (e) {
|
|
22
|
-
return `调用 OA 失败:${e.message}`;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
const needWrite = (ctx) => (!ctx.principal ? "缺少身份(principal),无法代表用户写库;请让调用方带上 x-agent-principal。" : null);
|
|
26
|
-
export function oaTools(ctx) {
|
|
27
|
-
const obj = Type.Object({}, { additionalProperties: true });
|
|
28
|
-
return [
|
|
29
|
-
{ name: "oa_schema", effect: "read", description: "取 OA 数据结构(每类资产的字段/必填/条件必填 requiredWhen/联动 showWhen/枚举、生命周期、表名)。建/改前先调它,别凭记忆。", parameters: Type.Object({}), execute: () => oaFetch(ctx, "/api/v1/meta") },
|
|
30
|
-
{ name: "oa_alerts", effect: "read", description: "派生告警:GP 闲置(已注册未变现) / APP 未上架(已接广告未 LIVE)。", parameters: Type.Object({}), execute: () => oaFetch(ctx, "/api/v1/alerts") },
|
|
31
|
-
{ name: "oa_list", effect: "read", description: "列出某类资产。type∈gp/package/ad-platform/ad-unit/email/environment/proxy。可选 q 关键字。", parameters: Type.Object({ type: Type.String(), q: Type.Optional(Type.String()) }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/entities/${encodeURIComponent(x.type)}${x.q ? `?q=${encodeURIComponent(x.q)}` : ""}`); } },
|
|
32
|
-
{ name: "oa_get", effect: "read", description: "取某资产详情(敏感字段为掩码)。", parameters: Type.Object({ type: Type.String(), id: Type.String() }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/entities/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}`); } },
|
|
33
|
-
{ name: "oa_lookup", effect: "read", description: "约定字段值反查(同手机/证件=同一实物)。field: phone|idCardNo|bizLicenseNo。", parameters: Type.Object({ field: Type.String(), value: Type.String() }), execute: (a) => { const x = a; return oaFetch(ctx, `/api/v1/lookup?field=${encodeURIComponent(x.field)}&value=${encodeURIComponent(x.value)}`); } },
|
|
34
|
-
{ name: "oa_create", effect: "write", description: "创建实体(走 OA 校验+当前用户 RBAC+审计)。type + data(字段对象,参照 oa_schema)。失败会返回缺失/非法字段。", parameters: Type.Object({ type: Type.String(), data: obj }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
35
|
-
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}`, { method: "POST", body: x.data ?? {} }); } },
|
|
36
|
-
{ name: "oa_update", effect: "write", description: "改字段(敏感留空=保持原值;状态字段改不动,用 oa_transition)。", parameters: Type.Object({ type: Type.String(), id: Type.String(), data: obj }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
37
|
-
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}`, { method: "PATCH", body: x.data ?? {} }); } },
|
|
38
|
-
{ name: "oa_transition", effect: "write", description: "受控状态流转(先 oa_schema 看可用迁移;风险/弃用等需 note)。", parameters: Type.Object({ type: Type.String(), id: Type.String(), to: Type.String(), note: Type.Optional(Type.String()) }), execute: (a) => { const e = needWrite(ctx); if (e)
|
|
39
|
-
return e; const x = a; return oaFetch(ctx, `/api/assets/${encodeURIComponent(x.type)}/${encodeURIComponent(x.id)}/transition`, { method: "POST", body: { to: x.to, note: x.note ?? null } }); } },
|
|
40
|
-
];
|
|
41
|
-
}
|
|
42
|
-
// OA business prompt MOVED OUT — migration complete (design/29, 2026-06-05). Persona / 工作准则 / UI
|
|
43
|
-
// markers (::FORM:: / ::GOTO:: / ::SUGGEST:: / ::ISSUE_FORM::) / 分级确认 / 报bug流程 are OA's business,
|
|
44
|
-
// and OA now injects the WHOLE thing per request via `request.systemPrompt` (it holds the service token,
|
|
45
|
-
// content is stable per OA version → cacheable prefix; page context still rides `objective`). Keeping
|
|
46
|
-
// any of it here would just DUPLICATE OA's prompt, so the base is EMPTY — single source of truth = OA.
|
|
47
|
-
// The service stays business-agnostic: it provides the oa_* tools (referenced by NAME from OA's prompt;
|
|
48
|
-
// they carry their own descriptions) and nothing else. `stablePrompt` keeps STABLE→VARIABLE order —
|
|
49
|
-
// OA's systemPrompt is appended after this (empty) base, before core's <user_memory>.
|
|
50
|
-
// (::ISSUE_FORM:: is parsed/rendered + submitted entirely by OA's frontend via OA /api/ai/issue — the
|
|
51
|
-
// service never touches it; `open_gitea_issue` remains only as the unattended/automated fallback.)
|
|
52
|
-
const OA_SYSTEM = "";
|
|
53
|
-
export const oaPrompt = stablePrompt(OA_SYSTEM);
|
|
54
|
-
//# sourceMappingURL=oa-tools.js.map
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Durable approval store for the human-in-the-loop write gate (F4) — SINGLE-FILE DUAL-DIALECT
|
|
3
|
-
* (design/158 A12 定型半场). ONE implementation, TWO dialects; the historical `TiDBApprovalStore` /
|
|
4
|
-
* `PgApprovalStore` class names survive as thin ctor subclasses so every consumer (store-backend.ts,
|
|
5
|
-
* src/index.ts, the approval/audit/security-coverage suites) is untouched.
|
|
6
|
-
*
|
|
7
|
-
* When a gated tool call is about to run, a `pending` row is recorded here; the instance running the
|
|
8
|
-
* task polls it for a decision. Because the row is durable and shared, an operator's approve/deny can
|
|
9
|
-
* land on ANY instance — critical under async runs + a load balancer, where the deciding request and
|
|
10
|
-
* the waiting run are usually on different instances.
|
|
11
|
-
*
|
|
12
|
-
* Unlike the run/checkpoint PG store (whose schema is owned centrally by pg-pool.ts), this file also
|
|
13
|
-
* exports a self-contained PG schema (`PG_APPROVAL_SCHEMA` + `ensureSchema`) — the `approval` table is
|
|
14
|
-
* DISJOINT from the other PG stores' tables, so a CREATE IF NOT EXISTS here shadows nothing. Central
|
|
15
|
-
* aggregation into pg-pool.ts composes it alongside the others. Kept in lock-step with the TiDB store
|
|
16
|
-
* by the shared real-DB integration suite (test/pg-approval-store-integration.test.ts).
|
|
17
|
-
*
|
|
18
|
-
* ── Dialect deltas, kept EXPLICIT ────────────────────────────────────────────────────────────────────
|
|
19
|
-
* - `?` placeholders vs `$n`
|
|
20
|
-
* - null-safe scope compare: `<=>` vs `IS NOT DISTINCT FROM` (a NULL scope row — legacy/untenanted
|
|
21
|
-
* run — is matched by a NULL scope arg; one tenant's scope NEVER matches another's — the
|
|
22
|
-
* single-DB fleet read guard, defense-in-depth)
|
|
23
|
-
* - JSON binding: TiDB `JSON.stringify` verbatim vs PG `pgProtocolJsonStringify` (codex R4-H2: the
|
|
24
|
-
* persisted approval `args` is the operator's REVIEW surface and must agree byte-for-byte with the
|
|
25
|
-
* policy-executed args — a lossy/rewriting envelope would let an operator approve one payload while
|
|
26
|
-
* a different one runs)
|
|
27
|
-
* - affectedRows vs rowCount (via SqlDriver)
|
|
28
|
-
* - schema ownership: TiDB DDL lives in tidb-pool.ts SCHEMA_STATEMENTS; PG DDL is this file's own
|
|
29
|
-
* self-contained `PG_APPROVAL_SCHEMA` (see the PG twin's original header — unlike the other PG
|
|
30
|
-
* stores, `approval` is NOT centrally owned by pg-pool.ts's DDL array, only aggregated into its
|
|
31
|
-
* `ensureSchema` composition).
|
|
32
|
-
* - `getStatus` result shape: `RowDataPacket[]` (mysql2 typed query) vs a plain-record `pg` query —
|
|
33
|
-
* normalized to `SqlRow` by the shared driver, so both arms read the same field names.
|
|
34
|
-
*/
|
|
35
|
-
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
36
|
-
import type { Pool as PgPool, PoolClient } from "pg";
|
|
37
|
-
import { type SqlDriver } from "./sql-driver.js";
|
|
38
|
-
export type ApprovalStatus = "pending" | "approved" | "denied" | "expired";
|
|
39
|
-
export interface ApprovalRow {
|
|
40
|
-
id: string;
|
|
41
|
-
taskId: string | null;
|
|
42
|
-
sessionId: string | null;
|
|
43
|
-
owner: string | null;
|
|
44
|
-
/** Single-DB fleet scope guard (defense-in-depth): the run's tenant identity, owner-sourced. Reads filter on it. */
|
|
45
|
-
scope: string | null;
|
|
46
|
-
toolName: string;
|
|
47
|
-
args: unknown;
|
|
48
|
-
status: ApprovalStatus;
|
|
49
|
-
reason: string | null;
|
|
50
|
-
decidedBy: string | null;
|
|
51
|
-
createdAt: string;
|
|
52
|
-
decidedAt: string | null;
|
|
53
|
-
}
|
|
54
|
-
/** PG translation of the `approval` DDL in tidb-pool.ts SCHEMA_STATEMENTS (JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3),
|
|
55
|
-
* inline KEY→separate CREATE INDEX).
|
|
56
|
-
*
|
|
57
|
-
* SCHEMA POLICY: see the header of pg-pool.ts — the code is the single source of truth, schema changes are
|
|
58
|
-
* drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead. */
|
|
59
|
-
export declare const PG_APPROVAL_SCHEMA: string[];
|
|
60
|
-
/** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
|
|
61
|
-
export declare function ensureSchema(pool: PgPool | PoolClient): Promise<void>;
|
|
62
|
-
/** Dual-dialect durable approval store. See the file header for the dialect-delta ledger. */
|
|
63
|
-
export declare class SqlApprovalStore {
|
|
64
|
-
protected readonly db: SqlDriver;
|
|
65
|
-
constructor(db: SqlDriver);
|
|
66
|
-
/** Pick the dialect's SQL text. Both statements stay written out at the call site ON PURPOSE. */
|
|
67
|
-
private q;
|
|
68
|
-
/** JSON column binding: TiDB stores the string verbatim; PG goes through the LOSSLESS protocol envelope
|
|
69
|
-
* (codex R4-H2 — the approval `args` is the operator's review surface and must agree with the
|
|
70
|
-
* policy-executed args at the NUL position). */
|
|
71
|
-
private json;
|
|
72
|
-
createPending(id: string, scope: string | null, ctx: {
|
|
73
|
-
taskId?: string | null;
|
|
74
|
-
sessionId?: string | null;
|
|
75
|
-
owner?: string | null;
|
|
76
|
-
toolName: string;
|
|
77
|
-
args: unknown;
|
|
78
|
-
}): Promise<void>;
|
|
79
|
-
getStatus(id: string, scope: string | null): Promise<{
|
|
80
|
-
status: ApprovalStatus;
|
|
81
|
-
reason: string | null;
|
|
82
|
-
decidedBy: string | null;
|
|
83
|
-
} | undefined>;
|
|
84
|
-
/** Apply a terminal decision, but only to a still-pending row (CAS). Returns whether it applied. The scope
|
|
85
|
-
* guard (null-safe) is carried on the operator path too for consistency with the read paths. */
|
|
86
|
-
decide(id: string, scope: string | null, decision: "approved" | "denied" | "expired", reason: string | null, decidedBy: string | null): Promise<boolean>;
|
|
87
|
-
/** Pending approvals (operator queue), scoped to one tenant (single-DB fleet read guard, null-safe), optionally
|
|
88
|
-
* further filtered to one owner. */
|
|
89
|
-
listPending(scope: string | null, owner?: string): Promise<ApprovalRow[]>;
|
|
90
|
-
get(id: string, scope: string | null): Promise<ApprovalRow | undefined>;
|
|
91
|
-
/** Operator-only CROSS-tenant by-id read (no scope filter). The F4 operator queue serves/decides across
|
|
92
|
-
* tenants by design; the HTTP layer gates this to an operator and then re-binds decide() to the row's OWN
|
|
93
|
-
* scope. Tenants NEVER reach this — they use the scope-bound {@link get}. */
|
|
94
|
-
getById(id: string): Promise<ApprovalRow | undefined>;
|
|
95
|
-
/** Operator-only CROSS-tenant pending list (no scope filter) — the unfiltered operator queue across all
|
|
96
|
-
* tenants. Tenants use the scope-bound {@link listPending}; this is HTTP-gated to an operator.
|
|
97
|
-
* 🔴 identical SQL text on both dialects (no placeholders, nothing dialect-specific) — kept as ONE literal
|
|
98
|
-
* rather than a `q()` pair so a reader isn't misled into looking for a divergence that isn't there. */
|
|
99
|
-
listPendingAll(): Promise<ApprovalRow[]>;
|
|
100
|
-
/** Expire pending approvals older than `olderThanMs` (a never-answered operator request). Deliberately GLOBAL
|
|
101
|
-
* (NOT scope-filtered): staleness expiry is fleet-level maintenance that protects the worker regardless of
|
|
102
|
-
* tenant — a stale row in any scope must be reaped. Scope isolation applies to the per-tenant READ paths, not
|
|
103
|
-
* to this maintenance sweep. */
|
|
104
|
-
expireStale(olderThanMs: number): Promise<number>;
|
|
105
|
-
}
|
|
106
|
-
/** approval row → {@link ApprovalRow} — shared by BOTH dialects. design/158 S8 归位 / A12 收编. */
|
|
107
|
-
export declare function mapRow(r: Record<string, unknown>): ApprovalRow;
|
|
108
|
-
/** MySQL-protocol (TiDB) binding — historical class name + ctor shape preserved. */
|
|
109
|
-
export declare class TiDBApprovalStore extends SqlApprovalStore {
|
|
110
|
-
constructor(pool: MySqlPool);
|
|
111
|
-
}
|
|
112
|
-
/** PostgreSQL binding — historical class name + ctor shape preserved. */
|
|
113
|
-
export declare class PgApprovalStore extends SqlApprovalStore {
|
|
114
|
-
constructor(pool: PgPool);
|
|
115
|
-
}
|
|
116
|
-
//# sourceMappingURL=approval-store-sql.d.ts.map
|