@sema-agent/server 7.68.0 → 7.70.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/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/USAGE.md +122 -9
- package/dist/adoption/plan.js +11 -0
- package/dist/approval-ask-machine.d.ts +1 -1
- package/dist/approval-card.d.ts +27 -0
- package/dist/approval-card.js +11 -0
- package/dist/approval-reconciler.d.ts +0 -54
- package/dist/approval-reconciler.js +2 -2
- package/dist/approval.js +5 -4
- package/dist/boot/config-center.d.ts +6 -7
- package/dist/boot/config-center.js +31 -8
- package/dist/boot/leader.js +1 -1
- package/dist/boot/parked-revive-gate.js +10 -9
- package/dist/boot/runner-deps.d.ts +35 -4
- package/dist/boot/runner-deps.js +2 -6
- package/dist/boot/workflow-orchestration.d.ts +2 -0
- package/dist/boot/workflow-orchestration.js +10 -2
- package/dist/budget.js +3 -2
- package/dist/config-catalog.d.ts +31 -2
- package/dist/config-catalog.js +61 -24
- package/dist/config-center/apply-effective.d.ts +8 -1
- package/dist/config-center/apply-effective.js +23 -9
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/read-face.d.ts +30 -1
- package/dist/config-center/read-face.js +33 -15
- package/dist/config-center/restart-signal.js +2 -1
- package/dist/config-center/types.d.ts +20 -5
- package/dist/config-provider.js +1 -0
- package/dist/config-types.d.ts +17 -2
- package/dist/config.d.ts +0 -21
- package/dist/config.js +19 -6
- package/dist/http/route-ctx.d.ts +39 -1
- package/dist/http/routes/approvals-assistant.js +4 -12
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/diagnostics.js +4 -0
- package/dist/http/routes/runs.js +2 -3
- package/dist/http/routes/tasks.js +22 -18
- package/dist/http/routes/workflows.js +15 -7
- package/dist/http/server.d.ts +10 -2
- package/dist/http/server.js +74 -26
- package/dist/leader/wire.d.ts +9 -2
- package/dist/leader/wire.js +6 -6
- package/dist/main.js +8 -7
- package/dist/model-compat.d.ts +81 -0
- package/dist/model-compat.js +52 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/metrics.d.ts +0 -6
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/permission-rule-events.d.ts +20 -0
- package/dist/observability/permission-rule-events.js +28 -0
- package/dist/observability/run-terminal-log.d.ts +9 -19
- package/dist/observability/run-terminal-log.js +183 -32
- package/dist/observability/tool-trace.d.ts +12 -0
- package/dist/observability/tool-trace.js +5 -1
- package/dist/operator-ask.d.ts +46 -0
- package/dist/operator-ask.js +4 -0
- package/dist/orchestration/workflow-agent-session-index.d.ts +96 -0
- package/dist/orchestration/workflow-agent-session-index.js +146 -0
- package/dist/orchestration/workflow-journal-entry.d.ts +78 -0
- package/dist/orchestration/workflow-journal-entry.js +20 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +43 -1
- package/dist/orchestration/workflow-notify-journal.js +43 -3
- package/dist/parked-decide.d.ts +115 -5
- package/dist/parked-decide.js +94 -26
- package/dist/plugins/pg-pool.js +8 -0
- package/dist/plugins/store-backend.d.ts +7 -1
- package/dist/plugins/store-backend.js +3 -1
- package/dist/plugins/tidb-pool.js +8 -0
- package/dist/plugins/workflow-journal-store-sql.d.ts +1 -1
- package/dist/plugins/workflow-journal-store-sql.js +5 -5
- package/dist/plugins/workflow-run-store-sql.d.ts +30 -0
- package/dist/plugins/workflow-run-store-sql.js +40 -0
- package/dist/posture-source.d.ts +23 -3
- package/dist/posture-source.js +1 -1
- package/dist/read-face-posture.d.ts +8 -1
- package/dist/read-face-posture.js +1 -0
- package/dist/router/route-orchestration.js +2 -1
- package/dist/run-cancel-context.d.ts +50 -1
- package/dist/run-cancel-context.js +22 -0
- package/dist/run-local.d.ts +14 -6
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +0 -51
- package/dist/runs.js +40 -23
- package/dist/runtime-governance.js +5 -4
- package/dist/tool-approval.d.ts +31 -5
- package/dist/tool-approval.js +5 -4
- package/dist/trace/core-keyset-guard.d.ts +9 -5
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +1 -0
- package/dist/trace/ledger-events.d.ts +17 -1
- package/dist/trace/ledger-events.js +6 -1
- package/dist/trace/ledger-sink.d.ts +10 -8
- package/dist/trace/ledger-sink.js +2 -1
- package/dist/trace/project.d.ts +3 -2
- package/dist/trace/redact.d.ts +28 -0
- package/dist/trace/redact.js +50 -27
- package/package.json +4 -4
package/dist/parked-decide.js
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
import { defaultSubagentTailBus } from "./fleet/subagent-tail-bus.js";
|
|
2
2
|
import { backgroundScopesForCheckpointScope, decodeCheckpointScope } from "./security.js";
|
|
3
|
+
export async function findParkOrigin(sources, cp) {
|
|
4
|
+
if (sources.agentStore !== undefined) {
|
|
5
|
+
let match;
|
|
6
|
+
try {
|
|
7
|
+
match = await findParkedAgentForCheckpoint(sources.agentStore, cp);
|
|
8
|
+
}
|
|
9
|
+
catch (e) {
|
|
10
|
+
quiet(sources.warn, "parked_decide_probe_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
11
|
+
}
|
|
12
|
+
if (match !== undefined)
|
|
13
|
+
return { kind: "background", handle: match.handle, row: match.row };
|
|
14
|
+
}
|
|
15
|
+
const index = sources.workflowIndex;
|
|
16
|
+
const runStore = sources.workflowRunStore;
|
|
17
|
+
if (index === undefined || runStore === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const row = await index.findByAgentSession(cp.sessionId);
|
|
21
|
+
if (row === null)
|
|
22
|
+
return undefined;
|
|
23
|
+
const run = await runStore.get(row.runId);
|
|
24
|
+
if (run === null)
|
|
25
|
+
return undefined;
|
|
26
|
+
const agent = (run.agents ?? []).find((a) => a.sessionId === cp.sessionId && a.status === "parked");
|
|
27
|
+
if (agent === undefined || agent.parkedCheckpointToken !== cp.token)
|
|
28
|
+
return undefined;
|
|
29
|
+
return { kind: "workflow", runId: row.runId, run, agent };
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
quiet(sources.warn, "workflow_park_probe_failed", { error: e instanceof Error ? e.message : String(e) });
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function quiet(warn, event, fields) {
|
|
37
|
+
try {
|
|
38
|
+
warn?.(event, fields);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
3
43
|
export async function findParkedAgentForCheckpoint(agentStore, cp) {
|
|
4
44
|
for (const scope of backgroundScopesForCheckpointScope(cp.scope)) {
|
|
5
45
|
const parked = await agentStore.listByScope(scope, { status: "parked" });
|
|
@@ -25,21 +65,61 @@ function mintReviveArgs(row, knownAgentTypes) {
|
|
|
25
65
|
...(row.agentType !== undefined && knownAgentTypes.has(row.agentType) ? { subagent_type: row.agentType } : {}),
|
|
26
66
|
};
|
|
27
67
|
}
|
|
28
|
-
export
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
export function buildParkedResumeOutcome(req) {
|
|
69
|
+
const pa = req.pendingAction;
|
|
70
|
+
const persistedCallId = pa?.kind === "tool_approval" ? (pa.toolCallId ?? "") : "";
|
|
71
|
+
const persistedHash = pa?.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
|
|
72
|
+
return {
|
|
73
|
+
gate: "policy_ask",
|
|
74
|
+
boundCallId: req.binding?.boundCallId ?? persistedCallId,
|
|
75
|
+
boundInputHash: req.binding?.boundInputHash ?? persistedHash,
|
|
76
|
+
decision: req.decision === "approve" ? "allow" : "deny",
|
|
77
|
+
...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
78
|
+
...(req.answer !== undefined ? { answer: req.answer } : {}),
|
|
79
|
+
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
|
80
|
+
hostDecision: req.hostDecision,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function planWorkflowParkedDecide(origin, req) {
|
|
84
|
+
if (req.grantRemember !== undefined) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
deny: {
|
|
88
|
+
status: 400,
|
|
89
|
+
body: {
|
|
90
|
+
error: "remember:\"session\" is not supported for a parked WORKFLOW agent: this lane's 200 means the decision was handed to the host's next turn, not that the engine committed it — an exemption granted here could outlive a decision the engine later rejects. Re-issue the decide without `remember`.",
|
|
91
|
+
errorCode: "decide.workflow_remember_unsupported",
|
|
92
|
+
runId: origin.runId,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
32
96
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
97
|
+
const hostSessionId = origin.run.originatingSessionId;
|
|
98
|
+
if (hostSessionId === undefined || hostSessionId === "") {
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
deny: {
|
|
102
|
+
status: 409,
|
|
103
|
+
body: {
|
|
104
|
+
error: "this parked workflow agent's run carries no originating session — the deployment has nowhere to deliver the decision (a directly-started workflow, not a Workflow tool call)",
|
|
105
|
+
errorCode: "decide.workflow_host_unknown",
|
|
106
|
+
runId: origin.runId,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
40
110
|
}
|
|
41
|
-
|
|
42
|
-
|
|
111
|
+
const verdict = req.decision === "approve" ? "APPROVED" : "DENIED";
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
hostSessionId,
|
|
115
|
+
runId: origin.runId,
|
|
116
|
+
outcome: buildParkedResumeOutcome(req),
|
|
117
|
+
message: `The human approval that workflow run "${origin.runId}" parked on (agent callKey "${origin.agent.callKey}") has been ${verdict}. ` +
|
|
118
|
+
`Resume that run now by calling Workflow with resumeFromRunId: "${origin.runId}" — the decision is already held by the deployment ` +
|
|
119
|
+
`and will be applied to the parked agent; completed steps replay from the journal. Do not re-issue the original call.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export async function decideParkedAgent(deps, req, match) {
|
|
43
123
|
const row = match.row;
|
|
44
124
|
const rememberRoot = req.grantRemember !== undefined ? (row.rootSessionId ?? row.parentSessionId) : undefined;
|
|
45
125
|
if (req.grantRemember !== undefined && rememberRoot === undefined) {
|
|
@@ -81,19 +161,7 @@ export async function decideParkedAgent(deps, req) {
|
|
|
81
161
|
catch {
|
|
82
162
|
}
|
|
83
163
|
}
|
|
84
|
-
const
|
|
85
|
-
const persistedCallId = pa?.kind === "tool_approval" ? (pa.toolCallId ?? "") : "";
|
|
86
|
-
const persistedHash = pa?.kind === "tool_approval" ? (pa.boundInputHash ?? "") : "";
|
|
87
|
-
const outcome = {
|
|
88
|
-
gate: "policy_ask",
|
|
89
|
-
boundCallId: req.binding?.boundCallId ?? persistedCallId,
|
|
90
|
-
boundInputHash: req.binding?.boundInputHash ?? persistedHash,
|
|
91
|
-
decision: req.decision === "approve" ? "allow" : "deny",
|
|
92
|
-
...(req.decision === "approve" && req.binding?.updatedInput !== undefined ? { updatedInput: req.binding.updatedInput } : {}),
|
|
93
|
-
...(req.answer !== undefined ? { answer: req.answer } : {}),
|
|
94
|
-
...(req.reason !== undefined ? { reason: req.reason } : {}),
|
|
95
|
-
hostDecision: req.hostDecision,
|
|
96
|
-
};
|
|
164
|
+
const outcome = buildParkedResumeOutcome(req);
|
|
97
165
|
const ctx = {
|
|
98
166
|
toolCallId: `drv-${ticket.claimId}`,
|
|
99
167
|
reviveClaim: {
|
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -266,6 +266,14 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
266
266
|
)`,
|
|
267
267
|
`CREATE INDEX IF NOT EXISTS idx_wfrun_scope_created ON workflow_run (scope, created_at_ms)`,
|
|
268
268
|
`CREATE INDEX IF NOT EXISTS idx_wfrun_scope_status ON workflow_run (scope, status)`,
|
|
269
|
+
`CREATE TABLE IF NOT EXISTS workflow_agent_session (
|
|
270
|
+
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
271
|
+
run_id VARCHAR(191) COLLATE "C" NOT NULL,
|
|
272
|
+
agent_key VARCHAR(191) COLLATE "C" NOT NULL,
|
|
273
|
+
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
274
|
+
PRIMARY KEY (session_id)
|
|
275
|
+
)`,
|
|
276
|
+
`CREATE INDEX IF NOT EXISTS idx_wfagentsess_run ON workflow_agent_session (run_id)`,
|
|
269
277
|
`CREATE TABLE IF NOT EXISTS workflow_completion_inbox (
|
|
270
278
|
seq BIGSERIAL,
|
|
271
279
|
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -71,7 +71,8 @@ export type ServiceFileHistoryStore = FileHistoryStore & {
|
|
|
71
71
|
/** SVC-2 (core CORE-7) WorkflowJournalStore — core's interface PLUS the service `deleteByRun` (GC extra). tidb/pg =
|
|
72
72
|
* durable SQL twins (cross-replica resume journal); local = the service {@link FileWorkflowJournalStore}
|
|
73
73
|
* (single-box but RESTART-durable crash-safe JSONL, replacing core's InMemory). The shared equivalence suite keeps
|
|
74
|
-
* all three byte-matched (scope guard / idempotent-per-ordinal / oversize
|
|
74
|
+
* all three byte-matched (scope guard / idempotent-per-ordinal / oversize **refusal** —— S-194 / core 7.12.0
|
|
75
|
+
* [ref] 起两臂一律抛 `workflow.journal_oversize`,判长口径归 core `assertJournalEntryFits` 单一属主). */
|
|
75
76
|
export type ServiceWorkflowJournalStore = WorkflowJournalStore & {
|
|
76
77
|
/** codex R1-H2(journal 读面界):分页+单行字节门的投影读——SQL 端 LIMIT/OFFSET 定行数、LENGTH 门
|
|
77
78
|
* 定单行(超 maxResultBytes 的行 resultJson=null 只回 resultBytes,读面标 truncated)。HTTP 读面
|
|
@@ -238,6 +239,11 @@ export interface StoreBackend {
|
|
|
238
239
|
/** 1.108 review fix (lens③ HIGH): the notify-JOURNAL twin — third leg of the same axis (a SQL run store +
|
|
239
240
|
* inbox with a replica-LOCAL File journal stranded a dead replica's un-acked notify forever). */
|
|
240
241
|
notifyJournal?(): import("../orchestration/workflow-notify-journal.js").WorkflowNotifyJournalStore;
|
|
242
|
+
/** S-185 车CM:workflow 出身 park 的**有界 join** 索引(子会话 id → 它所属的 workflow run)。
|
|
243
|
+
* 与 `workflowRun`/`completionInbox`/`notifyJournal` 同一条轴 —— SQL 后端 = 跨副本双生;`local` 车道
|
|
244
|
+
* 缺席**是一个真答案**:那里的索引家在 run 店同一间屋子里(`LocalWorkflowAgentSessionIndex`,文件账本
|
|
245
|
+
* 或纯内存),不需要经过 backend。契约与单写者纪律的属主 = orchestration/workflow-agent-session-index.ts。 */
|
|
246
|
+
workflowAgentSessionIndex?(): import("../orchestration/workflow-agent-session-index.js").WorkflowAgentSessionIndex;
|
|
241
247
|
/** Cloud-only stores below are OPTIONAL: the `local` (in-memory/file) backend omits them (it has no core
|
|
242
248
|
* in-memory twin carrying the service extras, and these are fleet/multi-replica concepts), so a local HTTP
|
|
243
249
|
* service degrades EXACTLY like today's no-DB env (the consumers gate on `backend?.X?.()`). TiDB/PG provide all. */
|
|
@@ -4,7 +4,7 @@ import { AdoptionError, FileStorageBackend, FileSessionRepo } from "@sema-agent/
|
|
|
4
4
|
import { FileWorkflowJournalStore } from "@sema-agent/core";
|
|
5
5
|
import { TiDBOutcomeLedger, PgOutcomeLedger } from "./outcome-ledger-sql.js";
|
|
6
6
|
import { FileOutcomeSink } from "./file-outcome-sink.js";
|
|
7
|
-
import { TiDBWorkflowRunStore, TiDBWorkflowCompletionInbox, TiDBWorkflowNotifyJournalStore, PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore } from "./workflow-run-store-sql.js";
|
|
7
|
+
import { TiDBWorkflowRunStore, TiDBWorkflowCompletionInbox, TiDBWorkflowNotifyJournalStore, TiDBWorkflowAgentSessionIndex, PgWorkflowRunStore, PgWorkflowCompletionInbox, PgWorkflowNotifyJournalStore, PgWorkflowAgentSessionIndex } from "./workflow-run-store-sql.js";
|
|
8
8
|
import { FileRunStore, RunStoreStrictHydrateError } from "./file-run-store.js";
|
|
9
9
|
import { LocalSessionStore } from "./local-session-store.js";
|
|
10
10
|
import { LocalCheckpointStore } from "./local-checkpoint-store.js";
|
|
@@ -145,6 +145,7 @@ class TiDBBackend {
|
|
|
145
145
|
workflowRun(onWarn) { return new TiDBWorkflowRunStore(this.pool, onWarn); }
|
|
146
146
|
completionInbox(onWarn) { return new TiDBWorkflowCompletionInbox(this.pool, onWarn); }
|
|
147
147
|
notifyJournal() { return new TiDBWorkflowNotifyJournalStore(this.pool); }
|
|
148
|
+
workflowAgentSessionIndex() { return new TiDBWorkflowAgentSessionIndex(this.pool); }
|
|
148
149
|
sharedMemoryStore(authorizer) { return new TiDBSharedMemoryStore(this.pool, authorizer); }
|
|
149
150
|
checkpoint(logger) { return new TiDBCheckpointStore(this.pool, logger); }
|
|
150
151
|
toolResult() { return new TiDBToolResultStore(this.pool); }
|
|
@@ -191,6 +192,7 @@ class PgBackend {
|
|
|
191
192
|
workflowRun(onWarn) { return new PgWorkflowRunStore(this.pool, onWarn); }
|
|
192
193
|
completionInbox(onWarn) { return new PgWorkflowCompletionInbox(this.pool, onWarn); }
|
|
193
194
|
notifyJournal() { return new PgWorkflowNotifyJournalStore(this.pool); }
|
|
195
|
+
workflowAgentSessionIndex() { return new PgWorkflowAgentSessionIndex(this.pool); }
|
|
194
196
|
sharedMemoryStore(authorizer) { return new PgSharedMemoryStore(this.pool, authorizer); }
|
|
195
197
|
checkpoint(logger) { return new PgCheckpointStore(this.pool, logger); }
|
|
196
198
|
toolResult() { return new PgToolResultStore(this.pool); }
|
|
@@ -446,6 +446,14 @@ export const SCHEMA_STATEMENTS = [
|
|
|
446
446
|
PRIMARY KEY (id),
|
|
447
447
|
KEY idx_wfrun_scope_created (scope, created_at_ms),
|
|
448
448
|
KEY idx_wfrun_scope_status (scope, status)
|
|
449
|
+
) COLLATE utf8mb4_bin`,
|
|
450
|
+
`CREATE TABLE IF NOT EXISTS workflow_agent_session (
|
|
451
|
+
session_id VARCHAR(190) NOT NULL,
|
|
452
|
+
run_id VARCHAR(191) NOT NULL,
|
|
453
|
+
agent_key VARCHAR(191) NOT NULL,
|
|
454
|
+
scope VARCHAR(190) NOT NULL,
|
|
455
|
+
PRIMARY KEY (session_id),
|
|
456
|
+
KEY idx_wfagentsess_run (run_id)
|
|
449
457
|
) COLLATE utf8mb4_bin`,
|
|
450
458
|
`CREATE TABLE IF NOT EXISTS workflow_completion_inbox (
|
|
451
459
|
seq BIGINT NOT NULL AUTO_INCREMENT,
|
|
@@ -57,7 +57,7 @@ export declare class SqlWorkflowJournalStore implements WorkflowJournalStore {
|
|
|
57
57
|
/** Record one agent's result. Idempotent per (run_id, ordinal): the PK upsert OVERWRITES (last-write-wins), so a
|
|
58
58
|
* resumed run re-appending its replayed prefix verbatim leaves no duplicate. callKeyOrdinal is the SOLE ordinal
|
|
59
59
|
* source (core's parser — a malformed key collapses to 0, matching InMemory). Throws on a REAL failure
|
|
60
|
-
* (LOAD-BEARING)
|
|
60
|
+
* (LOAD-BEARING) — an oversize entry included, on BOTH arms (S-194 / core 7.12.0 [ref]; see below). */
|
|
61
61
|
append(runId: string, scope: string, entry: WorkflowJournalEntry): Promise<void>;
|
|
62
62
|
/** Entries for `runId` IF its recorded scope === `scope`, ASCENDING by ordinal; otherwise EMPTY. CORE-9 audit
|
|
63
63
|
* BLOCKER: scope in the WHERE (twin of WorkflowRunStore) — the LLM controls `resumeFromRunId`, so a cross-scope
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { callKeyOrdinal } from "@sema-agent/core";
|
|
2
|
-
import {
|
|
2
|
+
import { assertJournalEntryFits } from "@sema-agent/core";
|
|
3
|
+
import { journalEntryOfRow, journalRowPayloadOf } from "../orchestration/workflow-journal-entry.js";
|
|
3
4
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
4
5
|
const DEFAULT_RESUME_CLAIM_TTL_MS = 60 * 60 * 1000;
|
|
5
6
|
export class SqlWorkflowJournalStore {
|
|
@@ -13,16 +14,15 @@ export class SqlWorkflowJournalStore {
|
|
|
13
14
|
return this.db.dialect === "tidb" ? tidb : pg;
|
|
14
15
|
}
|
|
15
16
|
async append(runId, scope, entry) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return;
|
|
17
|
+
assertJournalEntryFits(entry);
|
|
18
|
+
const serialized = journalRowPayloadOf(entry);
|
|
19
19
|
await this.db.query(this.q("INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at_ms) VALUES (?,?,?,?,?,?) " +
|
|
20
20
|
"ON DUPLICATE KEY UPDATE call_key = VALUES(call_key), result = VALUES(result)", "INSERT INTO workflow_journal (run_id, ordinal, scope, call_key, result, created_at_ms) VALUES ($1,$2,$3,$4,$5,$6) " +
|
|
21
21
|
"ON CONFLICT (run_id, ordinal) DO UPDATE SET call_key = EXCLUDED.call_key, result = EXCLUDED.result"), [runId, callKeyOrdinal(entry.callKey), scope, entry.callKey, serialized, Date.now()]);
|
|
22
22
|
}
|
|
23
23
|
async load(runId, scope) {
|
|
24
24
|
const { rows } = await this.db.query(this.q("SELECT call_key, result FROM workflow_journal WHERE run_id = ? AND scope = ? ORDER BY ordinal ASC", "SELECT call_key, result FROM workflow_journal WHERE run_id = $1 AND scope = $2 ORDER BY ordinal ASC"), [runId, scope]);
|
|
25
|
-
return rows.map((r) => (
|
|
25
|
+
return rows.map((r) => journalEntryOfRow(String(r.call_key), String(r.result)));
|
|
26
26
|
}
|
|
27
27
|
locator(runId, _scope) {
|
|
28
28
|
return `/v1/workflows/${runId}/journal`;
|
|
@@ -59,6 +59,7 @@ import type { Pool as PgPool } from "pg";
|
|
|
59
59
|
import { summarizeWorkflowRun, type WorkflowRunStore } from "@sema-agent/core";
|
|
60
60
|
import { type WorkflowCompletionInbox, type WorkflowCompletionInboxEntry } from "../orchestration/workflow-completion-inbox.js";
|
|
61
61
|
import type { WorkflowNotifyJournalStore, WorkflowNotifyJournalEntry } from "../orchestration/workflow-notify-journal.js";
|
|
62
|
+
import type { WorkflowAgentSessionIndex, WorkflowAgentSessionRow } from "../orchestration/workflow-agent-session-index.js";
|
|
62
63
|
import { type SqlDriver } from "./sql-driver.js";
|
|
63
64
|
type WorkflowRun = Parameters<WorkflowRunStore["put"]>[1];
|
|
64
65
|
/** Row cap guard: a run blob beyond this is NOT stored on update (the prior revision stays) — TiDB's
|
|
@@ -153,6 +154,29 @@ export declare class SqlWorkflowNotifyJournalStore implements WorkflowNotifyJour
|
|
|
153
154
|
reapAcked(before: number): Promise<number>;
|
|
154
155
|
get(runId: string): Promise<WorkflowNotifyJournalEntry | null>;
|
|
155
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* S-185 车CM —— **workflow 出身 park 的有界 join 索引** SQL 双生(契约与纪律的唯一属主 =
|
|
159
|
+
* {@link import("../orchestration/workflow-agent-session-index.js").WorkflowAgentSessionIndex} 的文件头,
|
|
160
|
+
* 这里只记方言差)。行是 `WorkflowRun` 的派生投影,写者只有一个(Journaling 装饰器的写观察点)。
|
|
161
|
+
*
|
|
162
|
+
* ── 方言差,保持显式 ──────────────────────────────────────────────────────────────────────────
|
|
163
|
+
* - `?` 占位 vs `$n`,且 `syncRun` 的删除腿在 PG 上要按 `params.length` 记位(与 `listByScope` 同因)。
|
|
164
|
+
* - 「删掉这条 run 里已不在 rows 里的旧行」:TiDB `session_id NOT IN (?,?,…)`(动态占位数)vs
|
|
165
|
+
* PG `session_id <> ALL($n::text[])`(数组绑定)—— 两边同一件事,语句结构不同(reap 的批删同先例)。
|
|
166
|
+
* - upsert:TiDB `REPLACE INTO`(整行替换)vs PG `INSERT … ON CONFLICT (session_id) DO UPDATE`。
|
|
167
|
+
* - schema 属主同店内其余表:TiDB DDL 在 tidb-pool.ts,PG DDL 在 pg-pool.ts(店不建表)。
|
|
168
|
+
*
|
|
169
|
+
* 🔴 **写序**:先 upsert 再删。反过来(先删后 upsert)会在两条语句之间留出一个**索引行不存在**的窗口,
|
|
170
|
+
* 而这期间进来的 `/decide` 会诚实 miss ⇒ 一次本可受理的批准被退回。先 upsert 的最坏形是窗口内多一条
|
|
171
|
+
* 旧行(读口按 session_id 精确查,取 run 后还要逐字比对赎回键 ⇒ 多余的行只会 miss,不会错配)。
|
|
172
|
+
*/
|
|
173
|
+
export declare class SqlWorkflowAgentSessionIndex implements WorkflowAgentSessionIndex {
|
|
174
|
+
private readonly db;
|
|
175
|
+
constructor(db: SqlDriver);
|
|
176
|
+
private q;
|
|
177
|
+
syncRun(runId: string, rows: readonly WorkflowAgentSessionRow[]): Promise<void>;
|
|
178
|
+
findByAgentSession(sessionId: string): Promise<WorkflowAgentSessionRow | null>;
|
|
179
|
+
}
|
|
156
180
|
/** MySQL-protocol (TiDB) bindings — historical class names preserved; `onWarn` is an ADDITIVE optional 2nd
|
|
157
181
|
* ctor arg (existing 1-arg call sites are unaffected — see {@link SqlWorkflowRunStore}'s C5 trace channel). */
|
|
158
182
|
export declare class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
|
|
@@ -175,5 +199,11 @@ export declare class PgWorkflowCompletionInbox extends SqlWorkflowCompletionInbo
|
|
|
175
199
|
export declare class PgWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStore {
|
|
176
200
|
constructor(pool: PgPool);
|
|
177
201
|
}
|
|
202
|
+
export declare class TiDBWorkflowAgentSessionIndex extends SqlWorkflowAgentSessionIndex {
|
|
203
|
+
constructor(pool: MySqlPool);
|
|
204
|
+
}
|
|
205
|
+
export declare class PgWorkflowAgentSessionIndex extends SqlWorkflowAgentSessionIndex {
|
|
206
|
+
constructor(pool: PgPool);
|
|
207
|
+
}
|
|
178
208
|
export {};
|
|
179
209
|
//# sourceMappingURL=workflow-run-store-sql.d.ts.map
|
|
@@ -277,6 +277,36 @@ export class SqlWorkflowNotifyJournalStore {
|
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
|
+
export class SqlWorkflowAgentSessionIndex {
|
|
281
|
+
db;
|
|
282
|
+
constructor(db) {
|
|
283
|
+
this.db = db;
|
|
284
|
+
}
|
|
285
|
+
q(tidb, pg) {
|
|
286
|
+
return this.db.dialect === "tidb" ? tidb : pg;
|
|
287
|
+
}
|
|
288
|
+
async syncRun(runId, rows) {
|
|
289
|
+
for (const r of rows) {
|
|
290
|
+
await this.db.query(this.q("REPLACE INTO workflow_agent_session (session_id, run_id, agent_key, scope) VALUES (?,?,?,?)", `INSERT INTO workflow_agent_session (session_id, run_id, agent_key, scope) VALUES ($1,$2,$3,$4)
|
|
291
|
+
ON CONFLICT (session_id) DO UPDATE SET run_id = EXCLUDED.run_id, agent_key = EXCLUDED.agent_key, scope = EXCLUDED.scope`), [r.sessionId, r.runId, r.agentKey, r.scope]);
|
|
292
|
+
}
|
|
293
|
+
const keep = rows.map((r) => r.sessionId);
|
|
294
|
+
if (this.db.dialect === "tidb") {
|
|
295
|
+
const holes = keep.map(() => "?").join(",");
|
|
296
|
+
await this.db.query(`DELETE FROM workflow_agent_session WHERE run_id = ?${keep.length > 0 ? ` AND session_id NOT IN (${holes})` : ""}`, [runId, ...keep]);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
await this.db.query(`DELETE FROM workflow_agent_session WHERE run_id = $1${keep.length > 0 ? " AND session_id <> ALL($2::text[])" : ""}`, keep.length > 0 ? [runId, keep] : [runId]);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async findByAgentSession(sessionId) {
|
|
303
|
+
const { rows } = await this.db.query(this.q("SELECT session_id, run_id, agent_key, scope FROM workflow_agent_session WHERE session_id = ?", "SELECT session_id, run_id, agent_key, scope FROM workflow_agent_session WHERE session_id = $1"), [sessionId]);
|
|
304
|
+
const r = rows[0];
|
|
305
|
+
if (!r)
|
|
306
|
+
return null;
|
|
307
|
+
return { sessionId: String(r.session_id), runId: String(r.run_id), agentKey: String(r.agent_key), scope: String(r.scope) };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
280
310
|
export class TiDBWorkflowRunStore extends SqlWorkflowRunStore {
|
|
281
311
|
constructor(pool, onWarn) {
|
|
282
312
|
super(mysqlDriver(pool), onWarn);
|
|
@@ -307,4 +337,14 @@ export class PgWorkflowNotifyJournalStore extends SqlWorkflowNotifyJournalStore
|
|
|
307
337
|
super(pgDriver(pool));
|
|
308
338
|
}
|
|
309
339
|
}
|
|
340
|
+
export class TiDBWorkflowAgentSessionIndex extends SqlWorkflowAgentSessionIndex {
|
|
341
|
+
constructor(pool) {
|
|
342
|
+
super(mysqlDriver(pool));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
export class PgWorkflowAgentSessionIndex extends SqlWorkflowAgentSessionIndex {
|
|
346
|
+
constructor(pool) {
|
|
347
|
+
super(pgDriver(pool));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
310
350
|
//# sourceMappingURL=workflow-run-store-sql.js.map
|
package/dist/posture-source.d.ts
CHANGED
|
@@ -13,8 +13,16 @@
|
|
|
13
13
|
* `env 显式` > `center 发布` > `posture 派生` > `引擎/服务端默认`。
|
|
14
14
|
*
|
|
15
15
|
* ## 定界
|
|
16
|
-
*
|
|
17
|
-
* ({@link PostureKnobFacts.postureMeaning})
|
|
16
|
+
* 本模块只说**来源**。值本身、以及**两条键专属臂**的句子——「这台机器凭什么派生出这个值」
|
|
17
|
+
* ({@link PostureKnobFacts.postureMeaning})与「组织发布的值在这台机器上怎么生效」
|
|
18
|
+
* ({@link PostureKnobFacts.centerMeaning})——由每个键自己给。那两句话是键专属的事实,共享它们只会
|
|
19
|
+
* 得到一句谁都不认的空话,或者更坏:一句**对某些键是假的**话。
|
|
20
|
+
*
|
|
21
|
+
* 🔴 `centerMeaning` 是 G-7(test [ref])的修复面,成因是一次真的过期断言:这一臂原本硬编码
|
|
22
|
+
* 「applied from the config-center (**restart-to-apply**)」,而 S-174(core 7.10.0 [ref])之后
|
|
23
|
+
* `readFace.face` 是**热换席**(`Runner.swapDeps({readFace})`,下一拍 refresh 当场换进全部 Runner,
|
|
24
|
+
* `read-face` 重启片也不再为它签理由)——运维照那句话去滚动重启一片 fleet,重启的是一个已经生效的变更。
|
|
25
|
+
* 生效时机是**键**的事实,不是四词表的属性,所以它跟 `postureMeaning` 一样归键。
|
|
18
26
|
*/
|
|
19
27
|
/** 旋钮取值来源的闭集词表(顺序 = 优先序)。 */
|
|
20
28
|
export declare const POSTURE_SOURCE_WORDS: readonly ["env", "center", "posture", "engine-default"];
|
|
@@ -25,13 +33,25 @@ export interface PostureKnobFacts {
|
|
|
25
33
|
/** 这台机器上占住这个键的 env 变量名(指路句要逐字点它,运维才知道去改哪个)。 */
|
|
26
34
|
readonly knob: string;
|
|
27
35
|
readonly source: PostureSource;
|
|
36
|
+
/**
|
|
37
|
+
* `center` 那一臂**专属**的一句话:组织发布的这个值在这台机器上**怎么生效**(热换席?等重启?
|
|
38
|
+
* 还是这个键根本没有 center 腿?),以及怎么把这个键取回来。只在 `source === "center"` 时被读到。
|
|
39
|
+
*
|
|
40
|
+
* 🔴 为什么归键而不是共享(G-7 的成因):生效时机是键的事实。同一句「restart-to-apply」对
|
|
41
|
+
* `readFace.face` 是假的(S-174 热换席),对三个 S-178 旋钮则连触发条件都没有(`config.ts` 的
|
|
42
|
+
* `postureSourceOfKnob` / `postureSourceOfNumEnv` 只产 env/posture/engine-default,center 写不到
|
|
43
|
+
* 它们)—— 一句共享的硬编码必然对其中一类说谎。
|
|
44
|
+
*/
|
|
45
|
+
readonly centerMeaning: string;
|
|
28
46
|
/**
|
|
29
47
|
* `posture` 那一臂**专属**的一句话:这台机器为什么派生出了这个值,以及怎么把它钉回去。
|
|
30
48
|
* 只在 `source === "posture"` 时被读到 —— 别的臂的句子是通用的(见 {@link postureSourceNote})。
|
|
31
49
|
*/
|
|
32
50
|
readonly postureMeaning: string;
|
|
33
51
|
}
|
|
34
|
-
/** 运维指路句 —— **穷举 switch,无缺省臂**:四词闭集加一个成员这里就编译红。
|
|
52
|
+
/** 运维指路句 —— **穷举 switch,无缺省臂**:四词闭集加一个成员这里就编译红。
|
|
53
|
+
* 两条**通用**臂(`env` / `engine-default`)的句子在这里写死并点名旋钮;两条**键专属**臂
|
|
54
|
+
* (`center` / `posture`)原样取键给的那句话(理由见 {@link PostureKnobFacts} 的两个字段注)。 */
|
|
35
55
|
export declare function postureSourceNote(facts: PostureKnobFacts): string;
|
|
36
56
|
/**
|
|
37
57
|
* 这个值是**运维显式表态**的吗?
|
package/dist/posture-source.js
CHANGED
|
@@ -4,7 +4,7 @@ export function postureSourceNote(facts) {
|
|
|
4
4
|
case "env":
|
|
5
5
|
return `pinned by this machine's ${facts.knob} env var (deployment sovereignty — it wins over any published value for this key)`;
|
|
6
6
|
case "center":
|
|
7
|
-
return
|
|
7
|
+
return facts.centerMeaning;
|
|
8
8
|
case "posture":
|
|
9
9
|
return facts.postureMeaning;
|
|
10
10
|
case "engine-default":
|
|
@@ -46,7 +46,14 @@ export interface ReadFacePostureFacts {
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function readFaceForPosture(singleUserTurnkey: boolean): ReadFacePostureFacts;
|
|
48
48
|
/** 本档的运维指路句 —— 骨架由共享件 {@link postureSourceNote} 给(四词穷举在那里,加词即编译红);
|
|
49
|
-
*
|
|
49
|
+
* 本函数提供两条**键专属**臂(`center` / `posture`)的事实句。
|
|
50
|
+
*
|
|
51
|
+
* 🔴 `center` 那句话的事实来源是 S-174(core 7.10.0 [ref]):`RunnerDeps.readFace` 进了 core 的可换席
|
|
52
|
+
* 闭集 `SWAPPABLE_DEP_SEATS` ⇒ 组织发布/撤回一个档位,**下一拍 refresh 当场换进全部 Runner**,
|
|
53
|
+
* `config-center/restart-signal.ts` 的 `read-face` 片也不再为它签重启理由(判据同源 =
|
|
54
|
+
* `CENTER_READ_FACE_LIVE_KEYS`,由 core 的 `isSwappableDepSeat` 派生)。本句此前逐字写着
|
|
55
|
+
* 「(restart-to-apply)」—— G-7 抓获的那条过期断言,方向恰好是「让运维为一个已生效的变更重启 fleet」。
|
|
56
|
+
* ⚠️ 只说 `face` 这一席:三个 deny 键仍是 boot-baked,那半场的话不在这条读面上(它们没有来源位)。 */
|
|
50
57
|
export declare function readFaceSourceNote(source: ReadFaceSource): string;
|
|
51
58
|
/** operator 诊断面的投影(`GET /v1/diagnostics/wiring`)。`face:null` = 本部署没钉档,引擎默认接管。 */
|
|
52
59
|
export declare function buildReadFaceWiring(facts: ReadFacePostureFacts): {
|
|
@@ -7,6 +7,7 @@ export function readFaceSourceNote(source) {
|
|
|
7
7
|
return postureSourceNote({
|
|
8
8
|
knob: "READ_FACE",
|
|
9
9
|
source,
|
|
10
|
+
centerMeaning: "published by the config-center and applied HOT — the containment FACE is a swappable engine seat (core 7.10.0), so the next refresh (or POST /v1/admin/config/refresh) swaps it into every Runner with NO restart; set READ_FACE on this machine to take the key back",
|
|
10
11
|
postureMeaning: "derived from the single-user turnkey posture (REQUIRE_PRINCIPAL is not set): reads outside the workspace roots are allowed by default — set READ_FACE=roots to pin the containing posture back",
|
|
11
12
|
});
|
|
12
13
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {} from "@sema-agent/core";
|
|
2
|
+
import { operatorAsk } from "../operator-ask.js";
|
|
2
3
|
export function routeOrchestration(s) {
|
|
3
4
|
if (s.explicitMode)
|
|
4
5
|
return { mode: s.explicitMode, reason: `explicit:${s.explicitMode}` };
|
|
@@ -40,7 +41,7 @@ export function isIsolatedExecEnv(provider, runtimeClass) {
|
|
|
40
41
|
}
|
|
41
42
|
const WRITE_HAND_TOOLS = new Set(["Edit", "Write", "NotebookEdit"]);
|
|
42
43
|
export function supPostureOverrides() {
|
|
43
|
-
const ask = (toolName) => ({
|
|
44
|
+
const ask = (toolName) => operatorAsk(`supervisor routing(capability-danger:${toolName})`, `${toolName} gated by supervisor routing (capability-danger task)`);
|
|
44
45
|
const writeGate = {
|
|
45
46
|
check: (req) => (WRITE_HAND_TOOLS.has(req.toolName) ? ask(req.toolName) : { action: "allow" }),
|
|
46
47
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunCancelContext } from "./plugins/store-contracts.js";
|
|
1
|
+
import type { PersistedTaskResult, RunCancelContext } from "./plugins/store-contracts.js";
|
|
2
2
|
/**
|
|
3
3
|
* @param taskId 这条 run 的账本 id(turn 活性登记的键)。
|
|
4
4
|
* @param startedAtMs 这条 run 起跑的墙钟时刻(epoch ms):取消腿手上有行就用行的 `createdAt`,
|
|
@@ -10,4 +10,53 @@ import type { RunCancelContext } from "./plugins/store-contracts.js";
|
|
|
10
10
|
* 断言「我看过现场,什么都没有」,而事实是「我什么都没看见」)。
|
|
11
11
|
*/
|
|
12
12
|
export declare function buildCancelContext(taskId: string, startedAtMs: number, nowMs?: number): RunCancelContext | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* 🔴 **用户取消的机器码 —— 全仓单源**(合并重扫 [5])。
|
|
15
|
+
*
|
|
16
|
+
* run 行的 `errorCode` 列、因由臂上的 `terminal.code`、`tasks_total{status}` 的 metrics 标签、fleet 行的
|
|
17
|
+
* 终态词,是**同一个词**;同一个词就该同一个来源。此前仓里是「一个词两只常量 + 十余处字面量」
|
|
18
|
+
* (`approval-reconciler.ts` 另有一份 `CANCELLED_ERROR_CODE`),改它的值会让 recordDone / fleet 标签 /
|
|
19
|
+
* reconciler 的 VOID 判据 / cancel 端点的幂等判据静默分叉。
|
|
20
|
+
*
|
|
21
|
+
* 机器门(新写者当场红):`test/cancel-terminal-single-owner.test.ts` 的 G2/G3。
|
|
22
|
+
*/
|
|
23
|
+
export declare const CANCELLED_CODE = "cancelled";
|
|
24
|
+
/**
|
|
25
|
+
* S-104 —— **取消终局的唯一铸点**。三条用户取消腿(sync stream 的断连支与 cancel-verb 支、durable bg 腿)
|
|
26
|
+
* 都从这里出结果,于是「一次取消在账本/wire 上长什么样」只有一份口径。
|
|
27
|
+
*
|
|
28
|
+
* ## 两种到达形,一条规则
|
|
29
|
+
* `engineResult` 在场与否 = 「引擎有没有给出终局」,不是两条规则:
|
|
30
|
+
* · **在场**(现行常态):**跑起来之后**的 `signal.aborted` 被 core 当成流内事件优雅收尾 —— 流照常
|
|
31
|
+
* 发 `done`,`terminal` 是一条**不带码**的 `failed`。这里在**它**之上补标,不另造一份结果(另造会把
|
|
32
|
+
* 引擎已经算出的 `stats` / `result` / `editedFiles` 全丢掉)。
|
|
33
|
+
* · **缺席**:根本没读到 done —— 客户端断连后迭代器早收,或**开跑之前**就已经立起的旗
|
|
34
|
+
* (亲核 core 7.11.2 `dist/core/runner/runtask.js:623-626`:`runLocked` 入口对已 aborted 的 signal
|
|
35
|
+
* 抛 `AbortError`)⇒ 合成一条诚实的取消终局。
|
|
36
|
+
*
|
|
37
|
+
* ## 「只补空座」纪律(与 core 的 `amendTerminal` 同规),而且**补码与补快照同进同退**
|
|
38
|
+
* · 引擎给的终局**不是无码的 failed**(它完成了 / 阻塞了 / 自己带了码)⇒ **整只原样返回**。
|
|
39
|
+
* 这一条是本车自查轮逮住的自造缺陷:首版只让**补码**受这条纪律管、快照却无条件挂,于是「取消动词
|
|
40
|
+
* 到了、但活其实已经干完了」会产出一条 `completed` 却带着取消便签的终局 —— 契约 §5.3 逐字写着
|
|
41
|
+
* 「只在用户取消形上铸」,两个方向的读者都会读错。一次与 abort 赛跑的真失败(`brain.network`)同理:
|
|
42
|
+
* 贴成 `cancelled` 会把故障统计洗白。
|
|
43
|
+
* · `cancelContext`:已经有快照就不重铸(重铸会把「上一条引擎事件多久以前」重新计时)。
|
|
44
|
+
*
|
|
45
|
+
* ## 为什么不判「这条 failed 是不是 abort 形」
|
|
46
|
+
* 判据在**调用点**:调用点问的是「本腿这次是不是被取消动词打中了」(`cancelCtrl.signal.aborted` /
|
|
47
|
+
* `cancelledViaVerb`),那是 server 自己的事实。让本函数去嗅 `terminal.message` 的字面就是再造事实
|
|
48
|
+
* —— [ref] 二那次事故的同一个形。
|
|
49
|
+
*/
|
|
50
|
+
export declare function mintCancelledResult(args: {
|
|
51
|
+
/** 这条 run 的账本 id(活性登记的键;账本不在场时给空串,快照按「证不出」处理)。 */
|
|
52
|
+
taskId: string;
|
|
53
|
+
/** 会话 id(合成形要写进结果;引擎给了结果时不用)。 */
|
|
54
|
+
sessionId: string;
|
|
55
|
+
/** 本腿起跑的墙钟时刻(epoch ms)。语义与缺席纪律见 {@link buildCancelContext}。 */
|
|
56
|
+
startedAtMs: number;
|
|
57
|
+
/** 合成形的失败文案(引擎给了终局时不用)。 */
|
|
58
|
+
message: string;
|
|
59
|
+
/** 引擎给出的终局(在场 ⇒ 在它之上补标)。 */
|
|
60
|
+
engineResult?: PersistedTaskResult;
|
|
61
|
+
}): PersistedTaskResult;
|
|
13
62
|
//# sourceMappingURL=run-cancel-context.d.ts.map
|
|
@@ -12,4 +12,26 @@ export function buildCancelContext(taskId, startedAtMs, nowMs = Date.now()) {
|
|
|
12
12
|
...(elapsedMs !== undefined ? { elapsedMs } : {}),
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
+
export const CANCELLED_CODE = "cancelled";
|
|
16
|
+
export function mintCancelledResult(args) {
|
|
17
|
+
const ctx = args.taskId === "" ? undefined : buildCancelContext(args.taskId, args.startedAtMs);
|
|
18
|
+
const engine = args.engineResult;
|
|
19
|
+
if (engine !== undefined) {
|
|
20
|
+
if (engine.terminal.kind !== "failed" || engine.terminal.code !== undefined)
|
|
21
|
+
return engine;
|
|
22
|
+
return {
|
|
23
|
+
...engine,
|
|
24
|
+
terminal: { ...engine.terminal, code: CANCELLED_CODE },
|
|
25
|
+
...(engine.cancelContext === undefined && ctx !== undefined ? { cancelContext: ctx } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
taskId: args.taskId,
|
|
30
|
+
sessionId: args.sessionId,
|
|
31
|
+
terminal: { kind: "failed", code: CANCELLED_CODE, message: args.message },
|
|
32
|
+
result: "",
|
|
33
|
+
stats: { turns: 0, tokens: 0 },
|
|
34
|
+
...(ctx ? { cancelContext: ctx } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
15
37
|
//# sourceMappingURL=run-cancel-context.js.map
|
package/dist/run-local.d.ts
CHANGED
|
@@ -96,17 +96,25 @@ export interface LocalApproverIo {
|
|
|
96
96
|
/**
|
|
97
97
|
* [ref] 件三:**onAsk 座**(§0 潜雷 / §5 锁 6)。
|
|
98
98
|
*
|
|
99
|
-
* 🔴
|
|
100
|
-
* `AUTONOMY=ask` ⇒ `shellGate:"always"` ⇒ Bash 进
|
|
101
|
-
* shell 调用都 durable park 成一张永远没人 resume
|
|
102
|
-
*
|
|
103
|
-
* 早退(prepare-task dist 亲读),ask
|
|
99
|
+
* 🔴 为什么这个座位是接治理链的**同窗必做项**(成因,留档):彼时 run-local 的主 runner 带着
|
|
100
|
+
* checkpointStore(file backend),于是 `AUTONOMY=ask` ⇒ `shellGate:"always"` ⇒ Bash 进
|
|
101
|
+
* `irreversibleTools` ⇒ park lane effective ⇒ **每一次 shell 调用都 durable park 成一张永远没人 resume
|
|
102
|
+
* 的 checkpoint**,任务挂起、CLI 退 1。一次性 CLI 里没有任何赎回腿,那不是「等人来批」,是卡死。
|
|
103
|
+
* core 的 `isLiveApproverSeat(onAsk)` 为真时 `suspendAsk` 早退(prepare-task dist 亲读),ask 于是落回
|
|
104
|
+
* 本回调 —— 把 park 换成「问人 / 当场拒」的正当接法。
|
|
105
|
+
*
|
|
106
|
+
* 🔴 **本席不是那颗雷的唯一防线,也从来不该是**(B-060 批 codex 交叉复审 [medium] 的教训):它只挡
|
|
107
|
+
* **审批**那条车道;core 的**平台/资源停驻**(`suspendForPlatformLimit`)由「店在不在」一位武装,
|
|
108
|
+
* 压根不经过 onAsk。所以自 B-060 起本腿的结构性防线是**整条腿不接 checkpointStore**(装配点在
|
|
109
|
+
* `createSharedRunnerDeps` 调用处,理由全文在那里),本席退回它本来的职责:**给 TTY 前的人一次真的
|
|
110
|
+
* 问答**。两道并存不是冗余 —— 一道管「谁来答」,一道管「答不了时会不会留下无人兑付的行」。
|
|
104
111
|
*
|
|
105
112
|
* ⛔ **不在 toolPolicy 层做 ask→deny 映射**:core 自己就有 headless auto-deny 的语义与文案,在策略层
|
|
106
113
|
* 复刻一份 = 同源谎(两处判据日后各改各的)。裁决归 core,座位归这里。
|
|
107
114
|
*
|
|
108
115
|
* 🔴 非 TTY 臂答 `false`(真 deny),**绝不答 `"unavailable"`**:后者被 core 读成「席位够不到」并**改道
|
|
109
|
-
* `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投)
|
|
116
|
+
* `suspendAsk`**(dist `core/hooks.js:296` 的 `approverUnavailable` 重投)。今天本腿无店 ⇒ 那条改道无处
|
|
117
|
+
* 可去(fail-closed 拒),但这一条仍是本席的契约:答案是「不批」,不是「找不到人」。
|
|
110
118
|
*/
|
|
111
119
|
export declare function createLocalApprover(io: LocalApproverIo): OnAsk;
|
|
112
120
|
/** 默认的 TTY 问人器:一问一答,回答写 **stderr**(stdout 是任务结果的专用管道,别污染它)。
|
package/dist/run-local.js
CHANGED
|
@@ -190,7 +190,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
190
190
|
config.mcpServers = resolveMcpServers(effective.mcp, logger);
|
|
191
191
|
if (effective?.a2a)
|
|
192
192
|
config.a2aPeers = resolveA2aPeers(effective.a2a, logger);
|
|
193
|
-
applyCenterReadFace(config, r.effective, logger);
|
|
193
|
+
applyCenterReadFace(config, r.effective, logger, { phase: "boot" });
|
|
194
194
|
logger.info("config_loaded", { source: configProvider.kind, version: r.effective.version });
|
|
195
195
|
}
|
|
196
196
|
}
|
|
@@ -387,6 +387,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
387
387
|
...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
|
|
388
388
|
},
|
|
389
389
|
toolResultStore: fileBackend.toolResultStore,
|
|
390
|
+
checkpointStore: undefined,
|
|
390
391
|
sessionPolicyStore: fileBackend.sessionPolicyStore,
|
|
391
392
|
usageWindowStore: config.usageWindows ? fileBackend.usageWindowStore : undefined,
|
|
392
393
|
orgMemoryAdmission,
|
|
@@ -397,7 +398,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
397
398
|
...sharedRunnerDeps,
|
|
398
399
|
sessionStore,
|
|
399
400
|
...(memoryEngine ? { memoryBackend: memoryEngine.backend, memoryEngineDir: memoryEngine.root } : {}),
|
|
400
|
-
checkpointStore: fileBackend.checkpointStore,
|
|
401
401
|
onError: engineOnError,
|
|
402
402
|
onAsk,
|
|
403
403
|
...(config.projectMemoryEnabled
|