@sema-agent/server 7.123.0 → 7.124.1
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 +138 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +53 -51
- package/dist/approval-ask-audit-store.d.ts +27 -32
- package/dist/approval-ask-audit-store.js +10 -9
- package/dist/approval-crash-convergence.d.ts +2 -2
- package/dist/approval-crash-convergence.js +5 -5
- package/dist/approval-deny-reasons.d.ts +163 -51
- package/dist/approval-deny-reasons.js +79 -8
- package/dist/approval-policy-settlement.d.ts +4 -1
- package/dist/approval-policy-settlement.js +3 -2
- package/dist/approval-reconciler.d.ts +3 -3
- package/dist/approval-reconciler.js +10 -10
- package/dist/approval.d.ts +1 -1
- package/dist/approval.js +1 -1
- package/dist/audit.js +1 -1
- package/dist/auth-bridge.d.ts +8 -8
- package/dist/auth-bridge.js +6 -6
- package/dist/auth-keys.d.ts +2 -2
- package/dist/auto-mode-face.d.ts +1 -1
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/execution-env.js +1 -0
- package/dist/boot/leader.d.ts +1 -1
- package/dist/boot/memory-consolidation.d.ts +4 -4
- package/dist/boot/org-memory.d.ts +3 -3
- package/dist/boot/org-memory.js +5 -5
- package/dist/boot/resolve-spec.js +7 -7
- package/dist/boot/runner-deps.d.ts +1 -1
- package/dist/boot/runtime-caps.d.ts +1 -1
- package/dist/boot/runtime-caps.js +10 -10
- package/dist/boot/shutdown.d.ts +2 -2
- package/dist/boot/shutdown.js +2 -2
- package/dist/boot/side-query-lane.d.ts +3 -3
- package/dist/boot/{stage-02-registry-pull.d.ts → stage-02-web-admin-pull.d.ts} +2 -2
- package/dist/boot/{stage-02-registry-pull.js → stage-02-web-admin-pull.js} +4 -4
- package/dist/boot/stage-03-store-backend.d.ts +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +6 -6
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +6 -5
- package/dist/boot/stage-08-reapers.d.ts +3 -3
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +3 -3
- package/dist/boot/stage-09-leader.js +7 -7
- package/dist/boot/stage-10-http-server.d.ts +3 -3
- package/dist/boot/stage-10-http-server.js +19 -19
- package/dist/boot/unpriced-ceiling-verdict.d.ts +1 -1
- package/dist/boot/{config-center.d.ts → web-admin.d.ts} +11 -11
- package/dist/boot/{config-center.js → web-admin.js} +38 -22
- package/dist/boot/webfetch-summarize-lane.d.ts +2 -2
- package/dist/brain.d.ts +1 -1
- package/dist/brain.js +1 -1
- package/dist/budget.d.ts +3 -3
- package/dist/canonical-json.d.ts +2 -2
- package/dist/capabilities/center-plugins.d.ts +1 -1
- package/dist/capabilities/center-wiring.d.ts +12 -5
- package/dist/capabilities/center-wiring.js +11 -2
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/scenarios.js +2 -2
- package/dist/capabilities/team.d.ts +1 -1
- package/dist/capabilities/tool-defer.d.ts +1 -1
- package/dist/center-credential-renewer.d.ts +1 -1
- package/dist/center-credential-renewer.js +7 -7
- package/dist/center-credential.d.ts +9 -9
- package/dist/center-credential.js +6 -6
- package/dist/config-catalog.d.ts +7 -10
- package/dist/config-catalog.js +15 -15
- package/dist/config-d-migration.d.ts +32 -0
- package/dist/config-d-migration.js +59 -0
- package/dist/config-invariants.d.ts +14 -4
- package/dist/config-invariants.js +31 -2
- package/dist/config-lkg.d.ts +26 -6
- package/dist/config-lkg.js +43 -14
- package/dist/config-provider.d.ts +23 -19
- package/dist/config-provider.js +24 -26
- package/dist/config-types.d.ts +52 -51
- package/dist/config.d.ts +9 -46
- package/dist/config.js +33 -92
- package/dist/execution-lane-caps.d.ts +1 -1
- package/dist/fleet/drain-entry.d.ts +1 -1
- package/dist/fleet/engine-signal.d.ts +8 -8
- package/dist/fleet/fleet-bus.d.ts +12 -3
- package/dist/fleet/fleet-bus.js +13 -9
- package/dist/fleet/serving-lease.d.ts +3 -3
- package/dist/fleet/serving-lease.js +1 -1
- package/dist/fleet-client.d.ts +7 -7
- package/dist/fleet-client.js +5 -4
- package/dist/fleet-lease.d.ts +3 -3
- package/dist/fleet-lease.js +2 -2
- package/dist/host-lsp-manager.d.ts +1 -1
- package/dist/hosted-posture.d.ts +4 -4
- package/dist/http/admission.js +4 -4
- package/dist/http/checkpoint-ctx.d.ts +20 -3
- package/dist/http/checkpoint-ctx.js +13 -5
- package/dist/http/dispatch.js +2 -2
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/health-self-report.d.ts +4 -4
- package/dist/http/health-self-report.js +3 -3
- package/dist/http/idempotency.d.ts +1 -1
- package/dist/http/resume-legs.d.ts +4 -3
- package/dist/http/resume-legs.js +31 -18
- package/dist/http/route-ctx.d.ts +8 -0
- package/dist/http/routes/admin-config-refresh.d.ts +1 -1
- package/dist/http/routes/admin-config-refresh.js +1 -1
- package/dist/http/routes/admin-drain.d.ts +1 -1
- package/dist/http/routes/admin-drain.js +5 -4
- package/dist/http/routes/approvals-assistant.js +4 -4
- package/dist/http/routes/capabilities.js +5 -5
- package/dist/http/routes/config-catalog.js +2 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/runs.js +8 -4
- package/dist/http/routes/sessions-list.js +2 -1
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/side-query.js +3 -3
- package/dist/http/routes/tasks.js +3 -2
- package/dist/http/run-meta.d.ts +1 -1
- package/dist/http/run-meta.js +2 -2
- package/dist/http/server.d.ts +7 -7
- package/dist/http/server.js +1 -1
- package/dist/http/stored-body-replay.js +3 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/leader/wire.d.ts +2 -2
- package/dist/leader/wire.js +2 -1
- package/dist/main.js +1 -1
- package/dist/mcp-tool-faces.d.ts +2 -2
- package/dist/model-entry-refusal.d.ts +3 -3
- package/dist/model-entry-refusal.js +4 -4
- package/dist/model-provider.d.ts +2 -2
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-route-url.d.ts +1 -1
- package/dist/model-route-url.js +2 -1
- package/dist/model-select.d.ts +24 -21
- package/dist/model-select.js +22 -17
- package/dist/observability/metrics.d.ts +1 -1
- package/dist/org-memory-admission.d.ts +1 -1
- package/dist/plugins/approval-ask-store-file.d.ts +4 -2
- package/dist/plugins/approval-ask-store-file.js +3 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +4 -3
- package/dist/plugins/approval-ask-store-memory.js +10 -4
- package/dist/plugins/approval-ask-store-sql.d.ts +16 -2
- package/dist/plugins/approval-ask-store-sql.js +20 -13
- package/dist/plugins/caching-session-store.d.ts +0 -10
- package/dist/plugins/caching-session-store.js +0 -3
- package/dist/plugins/file-run-store.js +9 -3
- package/dist/plugins/leader-run-store-sql.d.ts +3 -2
- package/dist/plugins/leader-run-store-sql.js +3 -2
- package/dist/plugins/local-session-store.d.ts +20 -27
- package/dist/plugins/local-session-store.js +19 -17
- package/dist/plugins/memory-run-store.js +9 -3
- package/dist/plugins/one-time-migrations.js +26 -2
- package/dist/plugins/scheduler-support.d.ts +26 -7
- package/dist/plugins/scheduler-support.js +96 -53
- package/dist/plugins/store-backend.js +1 -1
- package/dist/plugins/store-contracts.d.ts +20 -4
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -1
- package/dist/posture-source.d.ts +1 -1
- package/dist/read-face-posture.d.ts +3 -3
- package/dist/read-face-posture.js +1 -1
- package/dist/run-local.js +6 -3
- package/dist/runtime-caps-resolver.d.ts +4 -4
- package/dist/runtime-caps-resolver.js +2 -2
- package/dist/runtime-governance.d.ts +5 -5
- package/dist/sealed-key.d.ts +4 -4
- package/dist/sealed-key.js +3 -2
- package/dist/security.d.ts +3 -3
- package/dist/service-version.d.ts +1 -1
- package/dist/service-version.js +5 -0
- package/dist/session-titler.d.ts +0 -11
- package/dist/session-titler.js +3 -5
- package/dist/shared-memory-scope-authorizer.d.ts +4 -4
- package/dist/task-a2a.d.ts +1 -1
- package/dist/tool-approval.d.ts +43 -4
- package/dist/tool-approval.js +147 -98
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +3 -3
- package/dist/trace/redact.d.ts +2 -23
- package/dist/trace/redact.js +17 -15
- package/dist/trace/self-minted-wire.d.ts +134 -3
- package/dist/trace/self-minted-wire.js +46 -9
- package/dist/url-refusal.d.ts +35 -0
- package/dist/url-refusal.js +15 -0
- package/dist/user-authored-text.d.ts +31 -0
- package/dist/user-authored-text.js +18 -0
- package/dist/{config-center → web-admin-client}/apply-effective.d.ts +13 -70
- package/dist/{config-center → web-admin-client}/apply-effective.js +100 -119
- package/dist/{config-center → web-admin-client}/apply-ledger.d.ts +2 -2
- package/dist/{config-center → web-admin-client}/center-request.d.ts +7 -7
- package/dist/{config-center → web-admin-client}/center-request.js +5 -4
- package/dist/{config-center → web-admin-client}/effective-keys.d.ts +1 -3
- package/dist/{config-center → web-admin-client}/effective-keys.js +0 -9
- package/dist/{config-center → web-admin-client}/facade.d.ts +6 -6
- package/dist/{config-center → web-admin-client}/facade.js +2 -2
- package/dist/{config-center → web-admin-client}/http-client.d.ts +6 -10
- package/dist/{config-center → web-admin-client}/http-client.js +12 -12
- package/dist/{config-center → web-admin-client}/pull-state.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/read-face.js +4 -4
- package/dist/{config-center → web-admin-client}/read-warnings.d.ts +23 -16
- package/dist/{config-center → web-admin-client}/read-warnings.js +33 -23
- package/dist/{config-center → web-admin-client}/restart-signal.d.ts +5 -3
- package/dist/{config-center → web-admin-client}/restart-signal.js +3 -4
- package/dist/{config-center → web-admin-client}/skills-mcp.d.ts +1 -1
- package/dist/{config-center → web-admin-client}/skills-mcp.js +13 -13
- package/dist/{config-center → web-admin-client}/stage-limits.d.ts +1 -7
- package/dist/{config-center → web-admin-client}/stage-limits.js +6 -7
- package/dist/{config-center → web-admin-client}/types.d.ts +12 -39
- package/package.json +2 -2
- package/skills/find-skills.md +2 -2
- package/dist/url-shape.d.ts +0 -31
- package/dist/url-shape.js +0 -41
- /package/dist/{config-center → web-admin-client}/apply-ledger.js +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/hot-keys-registry.js +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/mcp-revocation.js +0 -0
- /package/dist/{config-center → web-admin-client}/pull-state.js +0 -0
- /package/dist/{config-center → web-admin-client}/read-face.d.ts +0 -0
- /package/dist/{config-center → web-admin-client}/types.js +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AskState } from "../approval-ask-machine.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SettleCause } from "../approval-deny-reasons.js";
|
|
3
|
+
import type { AskCallCoordinate, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, DecideResult, EnsureAskResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
|
|
3
4
|
/** 数据根下的目录名。**按店命名**,与兄弟 File 店同规(`approval-nonces` / `approval-ask-audit` /
|
|
4
5
|
* `approval-exemptions` / `resume-anchors` / `checkpoint-ctx` / …)—— 首版写的是族名 `approvals`,而那
|
|
5
6
|
* 正是读者会去找**持久 park 行**的地方(它们其实在 `checkpoint-ctx`),且持久数据面的名字按硬 breaking
|
|
@@ -61,6 +62,7 @@ type JournalCall = {
|
|
|
61
62
|
askId: string;
|
|
62
63
|
batchId: string;
|
|
63
64
|
intent: JournalV1ClaimIntent;
|
|
65
|
+
cause?: SettleCause;
|
|
64
66
|
}
|
|
65
67
|
/** NP-2 的部署政策拒(`claimTerminal(…, "refuse")`)—— 新动词而不是老动词带新意图,理由见 {@link JOURNAL_V1_CLAIM_INTENTS}。 */
|
|
66
68
|
| {
|
|
@@ -162,7 +164,7 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
|
|
|
162
164
|
* 「先编码、编码成功才一次性提交」同一条原子性)。
|
|
163
165
|
*/
|
|
164
166
|
decideAsk(askId: string, batchId: string, decision: DecideAskInput): Promise<DecideResult>;
|
|
165
|
-
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent): Promise<AskTerminalClaimOutcome>;
|
|
167
|
+
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
|
|
166
168
|
expireAsk(askId: string, batchId: string): Promise<ExpireResult>;
|
|
167
169
|
bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
|
|
168
170
|
abortBatch(batchId: string): Promise<string[]>;
|
|
@@ -182,7 +182,7 @@ export class FileApprovalAskStore {
|
|
|
182
182
|
case "decideAsk":
|
|
183
183
|
return this.core.decideAsk(rec.askId, rec.batchId, rec.decision);
|
|
184
184
|
case "claimTerminal":
|
|
185
|
-
return this.core.claimTerminal(rec.askId, rec.batchId, rec.intent);
|
|
185
|
+
return this.core.claimTerminal(rec.askId, rec.batchId, rec.intent, rec.cause !== undefined ? { cause: rec.cause } : undefined);
|
|
186
186
|
case "claimTerminalRefuse":
|
|
187
187
|
return this.core.claimTerminal(rec.askId, rec.batchId, "refuse");
|
|
188
188
|
case "expireAsk":
|
|
@@ -213,11 +213,11 @@ export class FileApprovalAskStore {
|
|
|
213
213
|
const journalled = decision.updatedInput === undefined ? decision : { ...decision, updatedInput: encodeDecideUpdatedInput(decision.updatedInput) };
|
|
214
214
|
return this.write({ op: "decideAsk", askId, batchId, decision: journalled }, (c) => this.core.decideAsk(c.askId, c.batchId, c.decision));
|
|
215
215
|
}
|
|
216
|
-
async claimTerminal(askId, batchId, intent) {
|
|
216
|
+
async claimTerminal(askId, batchId, intent, opts) {
|
|
217
217
|
switch (intent) {
|
|
218
218
|
case "expire":
|
|
219
219
|
case "cancel":
|
|
220
|
-
return this.write({ op: "claimTerminal", askId, batchId, intent }, (c) => this.core.claimTerminal(c.askId, c.batchId, c.intent));
|
|
220
|
+
return this.write({ op: "claimTerminal", askId, batchId, intent, ...(opts?.cause !== undefined ? { cause: opts.cause } : {}) }, (c) => this.core.claimTerminal(c.askId, c.batchId, c.intent, c.cause !== undefined ? { cause: c.cause } : undefined));
|
|
221
221
|
case "refuse":
|
|
222
222
|
return this.write({ op: "claimTerminalRefuse", askId, batchId }, (c) => this.core.claimTerminal(c.askId, c.batchId, "refuse"));
|
|
223
223
|
default: {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - resolveProvisional 故意不经 canAskTransition(同 SQL twin 头注:这是版本化补偿的例外通道)。
|
|
26
26
|
*/
|
|
27
27
|
import { type AskState, type BatchState } from "../approval-ask-machine.js";
|
|
28
|
-
import type { AskCallCoordinate, AskDecision, AskRow, AskTerminalClaimIntent, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, EnsureAskResult, DecideResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
|
|
28
|
+
import type { AskCallCoordinate, AskDecision, AskRow, AskTerminalClaimIntent, AskTerminalClaimOpts, AskTerminalClaimOutcome, AskTransitionPatch, ApprovalAskStore, BatchRow, BindGateInput, BindResult, DecideAskInput, EnsureAskResult, DecideResult, ExpireResult, NewAskRow } from "./approval-ask-store-sql.js";
|
|
29
29
|
/**
|
|
30
30
|
* [ref] 的 `updatedInput` **入店编码**——两 twin 共用的那一只(本函数是唯一真源)。
|
|
31
31
|
*
|
|
@@ -88,8 +88,9 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
|
|
|
88
88
|
* 论证);await 后读到的行 ≥ CAS 线性化点,而非 pending 态无回边 ⇒ 败方读数自足(与 SQL 侧同一条
|
|
89
89
|
* 单调性论证)。
|
|
90
90
|
*/
|
|
91
|
-
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent): Promise<AskTerminalClaimOutcome>;
|
|
92
|
-
expireAsk(
|
|
91
|
+
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
|
|
92
|
+
/** `cause`(可选)= 转 park 的触发成因,赢下 PARKING 的同一步落进 `deniedReason`(SQL twin 同一条 UPDATE 的镜像;接口上的 `expireAsk` 不带它,只有 `claimTerminal(expire, { cause })` 带)。 */
|
|
93
|
+
expireAsk(askId: string, batchId: string, cause?: AskTerminalClaimOpts["cause"]): Promise<ExpireResult>;
|
|
93
94
|
bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
|
|
94
95
|
deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
|
|
95
96
|
abortBatch(batchId: string): Promise<string[]>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { canAskTransition } from "../approval-ask-machine.js";
|
|
2
|
+
import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
|
|
2
3
|
import { askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./approval-ask-store-sql.js";
|
|
3
4
|
function applyPatch(row, patch) {
|
|
4
5
|
if (patch.decision !== undefined)
|
|
@@ -152,10 +153,10 @@ export class InMemoryApprovalAskStore {
|
|
|
152
153
|
ask.updatedAtMs = now;
|
|
153
154
|
return { ok: true, row: { ...ask } };
|
|
154
155
|
}
|
|
155
|
-
async claimTerminal(askId, batchId, intent) {
|
|
156
|
+
async claimTerminal(askId, batchId, intent, opts) {
|
|
156
157
|
switch (intent) {
|
|
157
158
|
case "expire": {
|
|
158
|
-
const res = await this.expireAsk(askId, batchId);
|
|
159
|
+
const res = await this.expireAsk(askId, batchId, opts?.cause);
|
|
159
160
|
if (res.won)
|
|
160
161
|
return { claimed: true, voidedSiblings: res.voidedSiblings };
|
|
161
162
|
break;
|
|
@@ -164,7 +165,7 @@ export class InMemoryApprovalAskStore {
|
|
|
164
165
|
const scoped = this.asks.get(askId);
|
|
165
166
|
if (scoped !== undefined && scoped.batchId !== batchId)
|
|
166
167
|
return { claimed: false, current: { state: "absent" } };
|
|
167
|
-
const won = await this.transitionAsk(askId, "STREAM_PENDING", "VOID", { updatedAtMs: this.now() });
|
|
168
|
+
const won = await this.transitionAsk(askId, "STREAM_PENDING", "VOID", { updatedAtMs: this.now(), ...(opts?.cause !== undefined ? { deniedReason: opts.cause } : {}) });
|
|
168
169
|
if (won)
|
|
169
170
|
return { claimed: true, voidedSiblings: [] };
|
|
170
171
|
break;
|
|
@@ -195,7 +196,7 @@ export class InMemoryApprovalAskStore {
|
|
|
195
196
|
const cur = this.asks.get(askId);
|
|
196
197
|
return { claimed: false, current: projectAskTerminalClaimTruth(cur !== undefined && cur.batchId === batchId ? { ...cur } : null) };
|
|
197
198
|
}
|
|
198
|
-
async expireAsk(askId, batchId) {
|
|
199
|
+
async expireAsk(askId, batchId, cause) {
|
|
199
200
|
const now = this.now();
|
|
200
201
|
const batch = this.batches.get(batchId);
|
|
201
202
|
if (!batch)
|
|
@@ -204,6 +205,8 @@ export class InMemoryApprovalAskStore {
|
|
|
204
205
|
if (!ask || ask.state !== "STREAM_PENDING" || ask.batchId !== batchId)
|
|
205
206
|
return { won: false, voidedSiblings: [] };
|
|
206
207
|
ask.state = "PARKING";
|
|
208
|
+
if (cause !== undefined)
|
|
209
|
+
ask.deniedReason = cause;
|
|
207
210
|
ask.rev += 1;
|
|
208
211
|
ask.updatedAtMs = now;
|
|
209
212
|
if (batch.state === "OPEN") {
|
|
@@ -215,6 +218,7 @@ export class InMemoryApprovalAskStore {
|
|
|
215
218
|
for (const [id, r] of this.asks) {
|
|
216
219
|
if (r.batchId === batchId && r.state === "STREAM_PENDING" && id !== askId) {
|
|
217
220
|
r.state = "VOID";
|
|
221
|
+
r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN;
|
|
218
222
|
r.rev += 1;
|
|
219
223
|
r.updatedAtMs = now;
|
|
220
224
|
voided.push(id);
|
|
@@ -247,6 +251,7 @@ export class InMemoryApprovalAskStore {
|
|
|
247
251
|
for (const [id, r] of this.asks) {
|
|
248
252
|
if (r.batchId === batchId && r.state === "PARKING" && id !== askId) {
|
|
249
253
|
r.state = "VOID";
|
|
254
|
+
r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE;
|
|
250
255
|
r.rev += 1;
|
|
251
256
|
r.updatedAtMs = now;
|
|
252
257
|
voidedSiblings.push(id);
|
|
@@ -274,6 +279,7 @@ export class InMemoryApprovalAskStore {
|
|
|
274
279
|
for (const [id, r] of this.asks) {
|
|
275
280
|
if (r.batchId === batchId && r.state === "STREAM_PENDING") {
|
|
276
281
|
r.state = "VOID";
|
|
282
|
+
r.deniedReason = APPROVAL_ENDING_CAUSES.BATCH_ABORTED;
|
|
277
283
|
r.rev += 1;
|
|
278
284
|
r.updatedAtMs = now;
|
|
279
285
|
voided.push(id);
|
|
@@ -42,6 +42,7 @@ import type { PgQueryFn } from "./pg-query.js";
|
|
|
42
42
|
import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
|
|
43
43
|
import { type AskState, type BatchState } from "../approval-ask-machine.js";
|
|
44
44
|
import type { HostSettledBy } from "../host-decision.js";
|
|
45
|
+
import { type AskRowCause, type SettleCause } from "../approval-deny-reasons.js";
|
|
45
46
|
import { type IndexSpec } from "./ensure-index.js";
|
|
46
47
|
export type AskDecision = "approve" | "deny";
|
|
47
48
|
export interface AskRow {
|
|
@@ -317,6 +318,15 @@ export interface ExpireResult {
|
|
|
317
318
|
* WHERE 谓词 {@link pendingClaimWhere} 兑现,不是把不同 verb 塞进一个函数。
|
|
318
319
|
*/
|
|
319
320
|
export type AskTerminalClaimIntent = "expire" | "cancel" | "refuse";
|
|
321
|
+
/**
|
|
322
|
+
* 终局 claim 的可选实参:`cause` —— **赢下 `cancel`(VOID)/ `expire`(PARKING)时写进这一行 `denied_reason` 列的成因词**(决断口 410 / 晚答 404 / 跨副本的败方读数读同一列,
|
|
323
|
+
* 所以这一行自己记着「为什么」,读面不靠任何进程内的东西)。`cancel` 写终态成因({@link SettleCause} 里的 `run_cancelled` / `run_exited`),
|
|
324
|
+
* `expire` 写转 park 的触发成因({@link ParkTriggerCause}:`ttl_expired` / `broadcast_lost` / `stream_disconnected` / `undeliverable` / `shutdown_before_park`);缺席 ⇒ 列保持 NULL(读面缺席即缺席)。
|
|
325
|
+
* `refuse` 不读它(落的是一条决议,来源位 `policy`,不是结局成因);`expire` 撤兄弟时店自己写 `batch_sibling_withdrawn`(那是这条事务本身的事实)。
|
|
326
|
+
*/
|
|
327
|
+
export interface AskTerminalClaimOpts {
|
|
328
|
+
readonly cause?: SettleCause;
|
|
329
|
+
}
|
|
320
330
|
/**
|
|
321
331
|
* `refuse` 意图落行的决议(NP-2)—— **部署政策拒**:deny + 结算来源 `policy`。来源词的型取 core 的 `HostSettledBy`
|
|
322
332
|
* (core 改词 / 退词 ⇒ 这里 tsc 红);协调器交给 core 的那一只(`tool-approval.ts` 的 `UnattendedPolicyRefusal`)读的是
|
|
@@ -353,6 +363,10 @@ export type AskTerminalClaimCurrent = {
|
|
|
353
363
|
/** S-558:赢家决议的**结算来源**(语义与两态读法见 {@link AskRow.settledBy})。**必填**(不是可选)——
|
|
354
364
|
* 可选会让投影漏挑这一列时静默编译过,而漏挑的后果恰是本批要修的病:CAS 输家把赢家的策略拒读成人拒。 */
|
|
355
365
|
settledBy: HostSettledBy | null;
|
|
366
|
+
/** 行上记着的成因,经问账行成因的唯一读口 `causeOfAskRow` 读出(`kind` 说它在这一行上的意思:VOID / DENIED = 终态成因,PARKING / PARKED = 转 park 的触发成因);
|
|
367
|
+
* **只在读得出时在场**(没写 / 与 state 不配 ⇒ 缺席;DECIDED 的败方读数逐字不变)。CAS 输家据它按**行上那个成因**结算(而不是把别人撤的卡读成一次拒绝);
|
|
368
|
+
* 投影的唯一函数是 {@link projectAskTerminalClaimTruth},三 twin 不各自挑列,原始列值不出店层。 */
|
|
369
|
+
cause?: AskRowCause;
|
|
356
370
|
};
|
|
357
371
|
/**
|
|
358
372
|
* 单次原子往返的答案(语义律的唯一规范陈述点 = core `checkpoint-store.ts` `claimTerminal` 契约 JSDoc,
|
|
@@ -424,7 +438,7 @@ export interface ApprovalAskStore {
|
|
|
424
438
|
* `expireAsk`/`transitionAsk`(InMemory,单线程天然原子)——两向都成立的判据是**没有第二套谓词**。
|
|
425
439
|
* 方法**必备**(本仓自有三形全实现,无 core 那侧的 presence 探测问题)。
|
|
426
440
|
*/
|
|
427
|
-
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent): Promise<AskTerminalClaimOutcome>;
|
|
441
|
+
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
|
|
428
442
|
expireAsk(askId: string, batchId: string): Promise<ExpireResult>;
|
|
429
443
|
bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
|
|
430
444
|
abortBatch(batchId: string): Promise<string[]>;
|
|
@@ -624,7 +638,7 @@ export declare class SqlApprovalAskStore implements ApprovalAskStore {
|
|
|
624
638
|
* 在状态机上无回边 ⇒ 锁定读读到的恒 ≥ 线性化点,且 DECIDED 决议不可变 —— 答案自足。
|
|
625
639
|
* · 本事务在输臂上除批行 rev 自增外零写,rollback 无痕。
|
|
626
640
|
*/
|
|
627
|
-
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent): Promise<AskTerminalClaimOutcome>;
|
|
641
|
+
claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
|
|
628
642
|
/** [ref] 起 = {@link claimTerminal}(expire 意图)的薄投影 —— 律 2「在 claimTerminal 之上实现」的
|
|
629
643
|
* SQL 半场落笔处:谓词/事务/兄弟撤卡全在 claim 里,这里零第二套写路径。败方读数按既有返回形丢弃
|
|
630
644
|
* (`ExpireResult` 不携真相 —— 要真相的调用方直接用 claimTerminal;对账收敛器的 orphan 腿输了本就
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
2
2
|
import { canAskTransition } from "../approval-ask-machine.js";
|
|
3
|
+
import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
|
|
3
4
|
import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
|
|
4
5
|
import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
|
|
5
6
|
import { ensureIndexes } from "./ensure-index.js";
|
|
@@ -70,12 +71,14 @@ export function claimTargetState(intent) {
|
|
|
70
71
|
export function projectAskTerminalClaimTruth(row) {
|
|
71
72
|
if (row === null || row.state === "STREAM_PENDING")
|
|
72
73
|
return { state: "absent" };
|
|
74
|
+
const cause = causeOfAskRow(row);
|
|
73
75
|
return {
|
|
74
76
|
state: row.state,
|
|
75
77
|
rev: row.rev,
|
|
76
78
|
decision: row.decision,
|
|
77
79
|
...(row.updatedInput !== undefined ? { updatedInput: row.updatedInput } : {}),
|
|
78
80
|
settledBy: row.settledBy,
|
|
81
|
+
...(cause !== undefined ? { cause } : {}),
|
|
79
82
|
};
|
|
80
83
|
}
|
|
81
84
|
export function askRowOnCall(row, call) {
|
|
@@ -501,7 +504,7 @@ export class SqlApprovalAskStore {
|
|
|
501
504
|
throw new Error(`decideAsk: row vanished after commit (askId=${askId})`);
|
|
502
505
|
return { ok: true, row };
|
|
503
506
|
}
|
|
504
|
-
async claimTerminal(askId, batchId, intent) {
|
|
507
|
+
async claimTerminal(askId, batchId, intent, opts) {
|
|
505
508
|
const to = claimTargetState(intent);
|
|
506
509
|
if (!canAskTransition("STREAM_PENDING", to)) {
|
|
507
510
|
throw new Error(`approval-ask-store.claimTerminal: illegal terminal claim STREAM_PENDING -> ${to} (askId=${askId})`);
|
|
@@ -522,12 +525,15 @@ export class SqlApprovalAskStore {
|
|
|
522
525
|
}
|
|
523
526
|
const askRes = intent === "refuse"
|
|
524
527
|
? await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, decision = ${ph(dialect, 2)}, settled_by = ${ph(dialect, 3)}, decided_at_ms = ${ph(dialect, 4)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 5)} WHERE ${pendingClaimWhere(dialect, 6)}`, [to, POLICY_REFUSAL_DECISION.decision, POLICY_REFUSAL_DECISION.settledBy, now, now, askId, batchId])
|
|
525
|
-
:
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
528
|
+
: (intent === "cancel" || intent === "expire") && opts?.cause !== undefined
|
|
529
|
+
?
|
|
530
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, denied_reason = ${ph(dialect, 2)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 3)} WHERE ${pendingClaimWhere(dialect, 4)}`, [to, opts.cause, now, askId, batchId])
|
|
531
|
+
: await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state = ${ph(dialect, 1)}, rev = rev + 1, updated_at_ms = ${ph(dialect, 2)} WHERE ${pendingClaimWhere(dialect, 3)}`, [
|
|
532
|
+
to,
|
|
533
|
+
now,
|
|
534
|
+
askId,
|
|
535
|
+
batchId,
|
|
536
|
+
]);
|
|
531
537
|
if (askRes.affected !== 1) {
|
|
532
538
|
const sel = await conn.query(`SELECT ${askCols(dialect)} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ${ph(dialect, 1)} AND batch_id = ${ph(dialect, 2)} FOR UPDATE`, [askId, batchId]);
|
|
533
539
|
await conn.rollback();
|
|
@@ -540,7 +546,7 @@ export class SqlApprovalAskStore {
|
|
|
540
546
|
await conn.query(this.q(`UPDATE ${APPROVAL_BATCH_TABLE} SET state='ROUTING_UNBOUND', rev=rev+1, updated_at_ms=? WHERE batch_id=? AND state='OPEN'`, `UPDATE ${APPROVAL_BATCH_TABLE} SET state='ROUTING_UNBOUND', rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='OPEN'`), [now, batchId]);
|
|
541
547
|
let voidedSiblings;
|
|
542
548
|
if (this.db.dialect === "pg") {
|
|
543
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId]);
|
|
549
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=$4, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId, APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN]);
|
|
544
550
|
voidedSiblings = res.rows.map((r) => String(r.ask_id));
|
|
545
551
|
}
|
|
546
552
|
else {
|
|
@@ -548,7 +554,8 @@ export class SqlApprovalAskStore {
|
|
|
548
554
|
voidedSiblings = sel.rows.map((r) => String(r.ask_id));
|
|
549
555
|
if (voidedSiblings.length > 0) {
|
|
550
556
|
const inList = voidedSiblings.map(() => "?").join(", ");
|
|
551
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [
|
|
557
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [
|
|
558
|
+
APPROVAL_ENDING_CAUSES.BATCH_SIBLING_WITHDRAWN,
|
|
552
559
|
now,
|
|
553
560
|
...voidedSiblings,
|
|
554
561
|
]);
|
|
@@ -593,7 +600,7 @@ export class SqlApprovalAskStore {
|
|
|
593
600
|
}
|
|
594
601
|
let voidedSiblings;
|
|
595
602
|
if (this.db.dialect === "pg") {
|
|
596
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='PARKING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId]);
|
|
603
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=$4, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='PARKING' AND ask_id<>$3 RETURNING ask_id`, [now, batchId, askId, APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE]);
|
|
597
604
|
voidedSiblings = res.rows.map((r) => String(r.ask_id));
|
|
598
605
|
}
|
|
599
606
|
else {
|
|
@@ -601,7 +608,7 @@ export class SqlApprovalAskStore {
|
|
|
601
608
|
voidedSiblings = sel.rows.map((r) => String(r.ask_id));
|
|
602
609
|
if (voidedSiblings.length > 0) {
|
|
603
610
|
const inList = voidedSiblings.map(() => "?").join(", ");
|
|
604
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='PARKING'`, [now, ...voidedSiblings]);
|
|
611
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='PARKING'`, [APPROVAL_ENDING_CAUSES.BATCH_BOUND_ELSEWHERE, now, ...voidedSiblings]);
|
|
605
612
|
}
|
|
606
613
|
}
|
|
607
614
|
await conn.commit();
|
|
@@ -631,7 +638,7 @@ export class SqlApprovalAskStore {
|
|
|
631
638
|
}
|
|
632
639
|
let voided;
|
|
633
640
|
if (this.db.dialect === "pg") {
|
|
634
|
-
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' RETURNING ask_id`, [now, batchId]);
|
|
641
|
+
const res = await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=$3, rev=rev+1, updated_at_ms=$1 WHERE batch_id=$2 AND state='STREAM_PENDING' RETURNING ask_id`, [now, batchId, APPROVAL_ENDING_CAUSES.BATCH_ABORTED]);
|
|
635
642
|
voided = res.rows.map((r) => String(r.ask_id));
|
|
636
643
|
}
|
|
637
644
|
else {
|
|
@@ -639,7 +646,7 @@ export class SqlApprovalAskStore {
|
|
|
639
646
|
voided = sel.rows.map((r) => String(r.ask_id));
|
|
640
647
|
if (voided.length > 0) {
|
|
641
648
|
const inList = voided.map(() => "?").join(", ");
|
|
642
|
-
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [now, ...voided]);
|
|
649
|
+
await conn.query(`UPDATE ${APPROVAL_ASK_TABLE} SET state='VOID', denied_reason=?, rev=rev+1, updated_at_ms=? WHERE ask_id IN (${inList}) AND state='STREAM_PENDING'`, [APPROVAL_ENDING_CAUSES.BATCH_ABORTED, now, ...voided]);
|
|
643
650
|
}
|
|
644
651
|
}
|
|
645
652
|
await conn.commit();
|
|
@@ -57,16 +57,6 @@ export declare class CachingSessionStore implements SessionStore {
|
|
|
57
57
|
}) => Promise<SessionListItem[]>;
|
|
58
58
|
readonly fork?: (sourceId: string, owner: string | null) => Promise<string | null>;
|
|
59
59
|
readonly deleteSession?: (sessionId: string, owner: string | null) => Promise<boolean>;
|
|
60
|
-
/** K-5c (core 1.155 SessionStore.noteTaskRun seam) — core's Runner calls it at runTask START to record the latest
|
|
61
|
-
* run per session (→ listSessions `lastRunId`). MUST be forwarded: the local backend's Runner store is THIS cache
|
|
62
|
-
* wrapper, so without forwarding `noteTaskRun?.()` silently no-ops and lastRunId stays null (review BLOCKER). Pure
|
|
63
|
-
* pass-through to the inner store's in-mem map — no cache interaction. (TiDB/PG don't implement it → not bound.)
|
|
64
|
-
* Return type matches core's `SessionStore.noteTaskRun?(): void | Promise<void>` (review: don't narrow to void).
|
|
65
|
-
* [ref](core 5.65 增量批):第三参 = engine runId。绑定用的是 `.bind(inner)`(整函数),运行期本就
|
|
66
|
-
* 三参全转 —— 这里把**声明**对齐到 core 的三参形,免得读这行类型的人以为缝上只有两参(fork-routing
|
|
67
|
-
* 的两参形正是在运行期真丢过第三参的那个病,同批修)。内店(LocalSessionStore)今日仍是两参消费,
|
|
68
|
-
* runId 落地候 sessions 列表 wire 键的 spec 节拍(见本批提货报告件1b)。 */
|
|
69
|
-
readonly noteTaskRun?: (sessionId: string, taskId: string, runId?: string) => void | Promise<void>;
|
|
70
60
|
/** Write-once auto-title (SQL twins + local sidecar carry it; pure pass-through). */
|
|
71
61
|
readonly setTitleIfNull?: (sessionId: string, title: string) => Promise<boolean>;
|
|
72
62
|
readonly probeTitle?: (sessionId: string) => Promise<"none" | "untitled" | "titled">;
|
|
@@ -11,7 +11,6 @@ export class CachingSessionStore {
|
|
|
11
11
|
listSessions;
|
|
12
12
|
fork;
|
|
13
13
|
deleteSession;
|
|
14
|
-
noteTaskRun;
|
|
15
14
|
setTitleIfNull;
|
|
16
15
|
probeTitle;
|
|
17
16
|
readInstructionSource;
|
|
@@ -62,8 +61,6 @@ export class CachingSessionStore {
|
|
|
62
61
|
this.recordInstructionSource = ownerAware.recordInstructionSource.bind(inner);
|
|
63
62
|
if (ownerAware.fork)
|
|
64
63
|
this.fork = ownerAware.fork.bind(inner);
|
|
65
|
-
if (ownerAware.noteTaskRun)
|
|
66
|
-
this.noteTaskRun = ownerAware.noteTaskRun.bind(inner);
|
|
67
64
|
if (ownerAware.getLeafId)
|
|
68
65
|
this.getLeafId = ownerAware.getLeafId.bind(inner);
|
|
69
66
|
if (ownerAware.getHead)
|
|
@@ -499,9 +499,15 @@ export class FileRunStore {
|
|
|
499
499
|
const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
500
500
|
const latest = sorted[0];
|
|
501
501
|
const latestRunId = latest.result?.runId;
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
const
|
|
502
|
+
let last = -Infinity;
|
|
503
|
+
let first = Infinity;
|
|
504
|
+
for (const r of arr) {
|
|
505
|
+
const t = r.createdAt.getTime();
|
|
506
|
+
if (t > last)
|
|
507
|
+
last = t;
|
|
508
|
+
if (t < first)
|
|
509
|
+
first = t;
|
|
510
|
+
}
|
|
505
511
|
summaries.push({
|
|
506
512
|
sessionId,
|
|
507
513
|
owner: latest.owner,
|
|
@@ -12,8 +12,9 @@ export interface LeaderRunRecord {
|
|
|
12
12
|
status: LeaderRunStatus;
|
|
13
13
|
/** BL-4 属主(null = 匿名/单租)。GET 的 404 门读它。 */
|
|
14
14
|
owner: string | null;
|
|
15
|
-
/** 运维列表面的可读预览(**不是**完整 objective —— 名字即语义,见 DDL 列注)
|
|
16
|
-
|
|
15
|
+
/** 运维列表面的可读预览(**不是**完整 objective —— 名字即语义,见 DDL 列注)。一个事实位,与 run 行同形:`""` = 建过、剥完没有用户正文;
|
|
16
|
+
* `null` = 没记录(列 NULL:更早版本 / 手改留下的行;本版写点只写 `""` 或正文)。 */
|
|
17
|
+
objectivePreview: string | null;
|
|
17
18
|
result?: unknown;
|
|
18
19
|
error?: string;
|
|
19
20
|
startedAtMs: number;
|
|
@@ -4,6 +4,7 @@ import { isDupKeyError } from "./sql-errors.js";
|
|
|
4
4
|
import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
|
|
5
5
|
import { decodeNullableJsonColumn, foldJsonColumn, jsonColumnText, readNonNullJsonValueColumn } from "./sql-json-column.js";
|
|
6
6
|
import { ensureIndexes } from "./ensure-index.js";
|
|
7
|
+
import { buildObjectivePreview } from "../user-authored-text.js";
|
|
7
8
|
export const LEADER_RUN_TABLE = "leader_run";
|
|
8
9
|
const LEADER_RUN_STATUSES = new Set(["running", "completed", "failed", "needs_human"]);
|
|
9
10
|
export const LEADER_OBJECTIVE_PREVIEW_CHARS = 160;
|
|
@@ -86,7 +87,7 @@ function mapRow(r) {
|
|
|
86
87
|
id,
|
|
87
88
|
status: statusOf(r.status, id),
|
|
88
89
|
owner: r.owner == null ? null : String(r.owner),
|
|
89
|
-
objectivePreview: r.objective_preview == null ?
|
|
90
|
+
objectivePreview: r.objective_preview == null ? null : String(r.objective_preview),
|
|
90
91
|
...(result === undefined ? {} : { result }),
|
|
91
92
|
...(error === undefined ? {} : { error }),
|
|
92
93
|
startedAtMs: Number(r.started_at_ms),
|
|
@@ -104,7 +105,7 @@ export class SqlLeaderRunStore {
|
|
|
104
105
|
async createRun(input) {
|
|
105
106
|
const id = uuidv7();
|
|
106
107
|
const now = Date.now();
|
|
107
|
-
const preview = input.objective
|
|
108
|
+
const preview = buildObjectivePreview(input.objective, LEADER_OBJECTIVE_PREVIEW_CHARS);
|
|
108
109
|
const params = [id, input.idemKey, input.owner, preview, "running", now];
|
|
109
110
|
try {
|
|
110
111
|
await this.db.query(this.q(`INSERT INTO ${LEADER_RUN_TABLE} (leader_run_id, idem_key, owner, objective_preview, status, started_at_ms) VALUES (?,?,?,?,?,?)`, `INSERT INTO ${LEADER_RUN_TABLE} (leader_run_id, idem_key, owner, objective_preview, status, started_at_ms) VALUES ($1,$2,$3,$4,$5,$6)`), params);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FileSessionRepo, type AcquiredSession, type SessionStore, type SessionRepo, type SessionTreeEntry } from "@sema-agent/core";
|
|
2
|
-
import type { SessionSummary } from "./store-contracts.js";
|
|
2
|
+
import type { SessionRunRollupSource, SessionSummary } from "./store-contracts.js";
|
|
3
3
|
import { type Logger } from "../observability/logger.js";
|
|
4
4
|
import { type SessionFace } from "../session-incarnation.js";
|
|
5
5
|
import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
@@ -14,6 +14,7 @@ import { type StagingHandle } from "../session-sync-kernel.js";
|
|
|
14
14
|
export declare function createLocalSessionRepo(root: string): FileSessionRepo;
|
|
15
15
|
export declare class LocalSessionStore implements SessionStore {
|
|
16
16
|
private readonly repo;
|
|
17
|
+
private readonly runs;
|
|
17
18
|
/** [ref] 车1:托管留存声明 —— **诚实的 none**。本店的行没有任何按期删除的腿(留存 lane 的三方法只实现在
|
|
18
19
|
* SQL 聚合店上),所以 locked policy + 本店 ⇒ core 的 `assertRetentionCapability` 拒启,这是契约的
|
|
19
20
|
* **正确行为**不是缺口(要留存治理先上 DB 后端;设计稿 §1「不做清单」逐字)。缺席也会被读成 none,
|
|
@@ -27,17 +28,6 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
27
28
|
* shows the wrong "most recent" — TOC-review #10). In-memory only (no sidecar) when `dataDir` is absent (the
|
|
28
29
|
* process-local `InMemorySessionRepo` case, where the sessions themselves are lost on restart anyway). */
|
|
29
30
|
private readonly lastActivity;
|
|
30
|
-
/** K-5c (core 1.155 SessionStore.noteTaskRun seam): the latest task run per session, recorded by core's Runner at
|
|
31
|
-
* runTask START (so a running/suspended run surfaces too) → projected as `lastRunId` on listSessions. The cloud
|
|
32
|
-
* TiDB/PG twins derive it from `task_run`; the local backend has no runs ledger, so this is an in-memory map.
|
|
33
|
-
* ⚠️ IN-MEMORY BEST-EFFORT (honest degrade, NOT sidecar-persisted — review Q6): it covers the PRIMARY /resume use
|
|
34
|
-
* case (re-attach to a RUNNING run — recorded at the run's start, the process can't have restarted while that run
|
|
35
|
-
* is still live), and is LOST on a process restart. That's acceptable: a session whose last run predates a restart
|
|
36
|
-
* has only TERMINAL runs (no live tail to re-attach), so `lastRunId` falls back to null and the shell uses its
|
|
37
|
-
* no-anchor path — same posture as the other local degrades here (runCount:0, lastStatus:""). Persisting it would
|
|
38
|
-
* need a sidecar-format change (the #10 clobber area); deferred until a real local-file-backed need. */
|
|
39
|
-
/** K-5c/[ref]/[ref]:per-session 最近一次 run 的 {taskId, runId?}(noteTaskRun 席;runId omit ⇒ 清座)。 */
|
|
40
|
-
private readonly lastTaskRun;
|
|
41
31
|
/** 2c P1d-β — live staged-import handles keyed by stagingId, so a local staging's in-memory buffer is readable via
|
|
42
32
|
* readStagedEntries (the durable twins re-read the staged session_event rows; local has no such rows). */
|
|
43
33
|
private readonly stagings;
|
|
@@ -81,12 +71,14 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
81
71
|
/** 认领行(`session_owner_claimed`)的出口 —— 一次性迁移 M10 的认领臂专用(窗口见 `plugins/one-time-migrations.ts` 登记表)。 */
|
|
82
72
|
private readonly log;
|
|
83
73
|
/** @param repo the core SessionRepo this store projects into the §0.5 SessionStore shape.
|
|
74
|
+
* @param runs the run store whose per-session projection is the ONLY source of a row's `runCount` / `lastStatus` / `lastRunId` /
|
|
75
|
+
* `lastTaskRunId` (LocalBackend passes its `FileRunStore`). Required — a run-less local session store does not exist.
|
|
84
76
|
* @param dataDir the SAME data dir the file-backed repo persists under (LocalBackend passes `fileBackend.root`).
|
|
85
77
|
* When given, lastActivity is persisted to `<dataDir>/session-last-activity.json` so it survives a
|
|
86
78
|
* restart (the file-backed deployment); omit it for the in-memory deployment (nothing survives).
|
|
87
79
|
* @param opts.log the `session_owner_claimed` line's sink (LocalBackend passes a logger at the deployment's LOG_LEVEL;
|
|
88
80
|
* absent ⇒ an info-level JSON-line logger — a claim is never silent). */
|
|
89
|
-
constructor(repo: SessionRepo, dataDir?: string, opts?: {
|
|
81
|
+
constructor(repo: SessionRepo, runs: SessionRunRollupSource, dataDir?: string, opts?: {
|
|
90
82
|
log?: Pick<Logger, "info">;
|
|
91
83
|
});
|
|
92
84
|
/**
|
|
@@ -211,9 +203,11 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
211
203
|
* - `firstActivityAt` — the session's `createdAt` (repo metadata).
|
|
212
204
|
* - `lastActivityAt` — the touch marker (falls back to `createdAt`); the keyset sort key.
|
|
213
205
|
* - `objectivePreview`— the last USER-text entry, synthesized from the session tree (no run ledger).
|
|
214
|
-
* - `runCount`/`lastStatus` —
|
|
215
|
-
* LEFT-JOINs `task_run` for
|
|
216
|
-
*
|
|
206
|
+
* - `runCount`/`lastStatus`/`lastRunId`/`lastTaskRunId` — S-1371:read from the run store handed in (`runs`; the TiDB
|
|
207
|
+
* twin LEFT-JOINs `task_run` for the same values, the run store's own `listSessions` is that
|
|
208
|
+
* join's same-semantics projection — latest run by `created_at DESC, task_id DESC`, count of all,
|
|
209
|
+
* `lastTaskRunId` = that run's `result.runId`). One call per list (not per row); a session with no
|
|
210
|
+
* run row ⇒ `0 / "" / null` (TiDB's null→"" mapping); a run-store read failure throws (never `0 / ""`).
|
|
217
211
|
*
|
|
218
212
|
* Keyset matches the contract: STRICTLY older than the cursor, ties broken by id DESC; `limit` is honored after
|
|
219
213
|
* the in-memory sort. Cheap (a local single-user store holds few sessions). Errors reading one session's entries
|
|
@@ -230,17 +224,16 @@ export declare class LocalSessionStore implements SessionStore {
|
|
|
230
224
|
limit: number;
|
|
231
225
|
q?: string;
|
|
232
226
|
}): Promise<SessionSummary[]>;
|
|
233
|
-
/**
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
noteTaskRun(sessionId: string, taskId: string, runId?: string): void;
|
|
227
|
+
/** S-1371:run 店里每个会话的 run 投影(按 sessionId 索引)。
|
|
228
|
+
* **一次读**:run 店的 `listSessions` 本来就按会话聚合全账(无会话过滤口),这里取全量再按会话仓的 id 查表 —— 比逐会话 N 次读便宜,
|
|
229
|
+
* 也不在会话店里另存一份 run 账。读失败 ⇒ 抛(不吞成 `0 / ""`:那会把「读不到」说成「没跑过」)。 */
|
|
230
|
+
private runRollups;
|
|
231
|
+
/**
|
|
232
|
+
* 一行里 run 身份与计数的**唯一出处**(S-1371,会话行四位一源):四位全读 {@link runRollups} 的投影(与 SQL 双生的 `task_run` rn=1 JOIN 同口径);
|
|
233
|
+
* 该会话没有 run 行 ⇒ `0` / `""` / `null`、`lastTaskRunId` 缺席(TiDB 的 `COALESCE(…,0)` / null→"" 映射)。在途的最新一条:`lastRunId` 在场(run 行建出来就有)、
|
|
234
|
+
* `lastTaskRunId` 缺席(`result` 在终局与 park 时才写)。
|
|
235
|
+
*/
|
|
236
|
+
private runProjectionOf;
|
|
244
237
|
/** Synthesize a session's objective preview = the most-recent USER text message (the objective the run last saw).
|
|
245
238
|
* Reads the session's entries through the repo; any read error degrades to null (never fails the list). */
|
|
246
239
|
private previewOf;
|
|
@@ -12,9 +12,11 @@ import { carriedIncarnation, mintSessionIncarnation, sessionFaceFromRow } from "
|
|
|
12
12
|
import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
|
|
13
13
|
import { noteReplayInSessionOpenScope, openSessionForItself } from "../session-records-skipped-notice.js";
|
|
14
14
|
import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
|
|
15
|
+
import { buildObjectivePreview } from "../user-authored-text.js";
|
|
15
16
|
import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, } from "../session-sync-kernel.js";
|
|
16
17
|
const ownerEq = (a, b) => (a ?? null) === (b ?? null);
|
|
17
18
|
const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
|
|
19
|
+
const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
|
|
18
20
|
function userText(entry) {
|
|
19
21
|
if (entry.type !== "message")
|
|
20
22
|
return null;
|
|
@@ -23,14 +25,12 @@ function userText(entry) {
|
|
|
23
25
|
return null;
|
|
24
26
|
const c = msg.content;
|
|
25
27
|
if (typeof c === "string")
|
|
26
|
-
return c
|
|
28
|
+
return c;
|
|
27
29
|
if (Array.isArray(c)) {
|
|
28
|
-
|
|
30
|
+
return c
|
|
29
31
|
.filter((p) => !!p && typeof p === "object" && p.type === "text")
|
|
30
32
|
.map((p) => p.text)
|
|
31
|
-
.join(" ")
|
|
32
|
-
.trim();
|
|
33
|
-
return text || null;
|
|
33
|
+
.join(" ");
|
|
34
34
|
}
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
@@ -143,10 +143,10 @@ export function createLocalSessionRepo(root) {
|
|
|
143
143
|
}
|
|
144
144
|
export class LocalSessionStore {
|
|
145
145
|
repo;
|
|
146
|
+
runs;
|
|
146
147
|
retention = UNMANAGED_RETENTION;
|
|
147
148
|
pending = new Map();
|
|
148
149
|
lastActivity = new Map();
|
|
149
|
-
lastTaskRun = new Map();
|
|
150
150
|
stagings = new Map();
|
|
151
151
|
loaded;
|
|
152
152
|
flushTimer;
|
|
@@ -159,8 +159,9 @@ export class LocalSessionStore {
|
|
|
159
159
|
instructionSources;
|
|
160
160
|
meta;
|
|
161
161
|
log;
|
|
162
|
-
constructor(repo, dataDir, opts) {
|
|
162
|
+
constructor(repo, runs, dataDir, opts) {
|
|
163
163
|
this.repo = repo;
|
|
164
|
+
this.runs = runs;
|
|
164
165
|
this.sidecarPath = dataDir ? join(dataDir, "session-last-activity.json") : undefined;
|
|
165
166
|
this.log = opts?.log ?? createLogger();
|
|
166
167
|
const sidecar = (file, which, read, strict = false) => new SidecarMap(dataDir ? resolve(join(dataDir, file)) : undefined, `${file} (the local ${which} sidecar)`, read, (path, e) => this.refuseSidecar(path, `LocalSessionStore ${which} sidecar`, e, which), (e) => this.warnSidecar("write", e, which), strict);
|
|
@@ -403,6 +404,7 @@ export class LocalSessionStore {
|
|
|
403
404
|
await this.titles.ensureLoaded();
|
|
404
405
|
await this.meta.ensureLoaded();
|
|
405
406
|
const metas = await this.repo.list();
|
|
407
|
+
const rollups = await this.runRollups();
|
|
406
408
|
const all = [];
|
|
407
409
|
for (const meta of metas) {
|
|
408
410
|
const cell = this.meta.state.entries.get(meta.id);
|
|
@@ -411,17 +413,13 @@ export class LocalSessionStore {
|
|
|
411
413
|
continue;
|
|
412
414
|
const firstActivityAt = meta.createdAt || new Date(0).toISOString();
|
|
413
415
|
const lastActivityAt = this.lastActivity.get(meta.id) ?? firstActivityAt;
|
|
414
|
-
const lastRun = this.lastTaskRun.get(meta.id);
|
|
415
416
|
all.push({
|
|
416
417
|
sessionId: meta.id,
|
|
417
418
|
owner,
|
|
418
419
|
lastActivityAt,
|
|
419
420
|
firstActivityAt,
|
|
420
|
-
runCount: 0,
|
|
421
421
|
objectivePreview: await this.previewOf(meta),
|
|
422
|
-
|
|
423
|
-
lastRunId: lastRun?.taskId ?? null,
|
|
424
|
-
...(lastRun?.runId !== undefined ? { lastTaskRunId: lastRun.runId } : {}),
|
|
422
|
+
...this.runProjectionOf(meta.id, rollups),
|
|
425
423
|
title: this.titles.state.entries.get(meta.id) ?? null,
|
|
426
424
|
});
|
|
427
425
|
}
|
|
@@ -436,8 +434,12 @@ export class LocalSessionStore {
|
|
|
436
434
|
: filtered;
|
|
437
435
|
return after.slice(0, opts.limit);
|
|
438
436
|
}
|
|
439
|
-
|
|
440
|
-
this.
|
|
437
|
+
async runRollups() {
|
|
438
|
+
const rows = await this.runs.listSessions({ limit: Number.MAX_SAFE_INTEGER });
|
|
439
|
+
return new Map(rows.map((r) => [r.sessionId, { runCount: r.runCount, lastStatus: r.lastStatus, lastRunId: r.lastRunId, ...(r.lastTaskRunId !== undefined ? { lastTaskRunId: r.lastTaskRunId } : {}) }]));
|
|
440
|
+
}
|
|
441
|
+
runProjectionOf(sessionId, rollups) {
|
|
442
|
+
return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null };
|
|
441
443
|
}
|
|
442
444
|
async previewOf(meta) {
|
|
443
445
|
try {
|
|
@@ -445,8 +447,9 @@ export class LocalSessionStore {
|
|
|
445
447
|
const entries = await session.getEntries();
|
|
446
448
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
447
449
|
const text = userText(entries[i]);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
+
const preview = text === null ? "" : buildObjectivePreview(text, SESSION_OBJECTIVE_PREVIEW_CHARS);
|
|
451
|
+
if (preview !== "")
|
|
452
|
+
return preview;
|
|
450
453
|
}
|
|
451
454
|
return null;
|
|
452
455
|
}
|
|
@@ -541,7 +544,6 @@ export class LocalSessionStore {
|
|
|
541
544
|
}
|
|
542
545
|
await this.dropMeta(sessionId);
|
|
543
546
|
this.lastActivity.delete(sessionId);
|
|
544
|
-
this.lastTaskRun.delete(sessionId);
|
|
545
547
|
for (const map of [this.titles, this.instructionSources]) {
|
|
546
548
|
await map.ensureLoaded();
|
|
547
549
|
if (map.state.entries.delete(sessionId))
|
|
@@ -224,9 +224,15 @@ export class MemoryRunStore {
|
|
|
224
224
|
const sorted = [...arr].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime() || (a.taskId < b.taskId ? 1 : a.taskId > b.taskId ? -1 : 0));
|
|
225
225
|
const latest = sorted[0];
|
|
226
226
|
const latestRunId = latest.result?.runId;
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
227
|
+
let last = -Infinity;
|
|
228
|
+
let first = Infinity;
|
|
229
|
+
for (const r of arr) {
|
|
230
|
+
const t = r.createdAt.getTime();
|
|
231
|
+
if (t > last)
|
|
232
|
+
last = t;
|
|
233
|
+
if (t < first)
|
|
234
|
+
first = t;
|
|
235
|
+
}
|
|
230
236
|
summaries.push({
|
|
231
237
|
sessionId,
|
|
232
238
|
owner: latest.owner,
|