@sema-agent/server 7.122.0 → 7.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +209 -0
- package/README.md +7 -2
- package/README.zh-CN.md +5 -2
- package/USAGE.md +76 -56
- 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.d.ts +1 -1
- package/dist/audit.js +53 -23
- 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 +13 -9
- package/dist/auto-mode-face.js +8 -7
- 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} +39 -20
- 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 +16 -16
- 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 +34 -11
- package/dist/config-lkg.js +76 -48
- package/dist/config-provider.d.ts +26 -21
- package/dist/config-provider.js +25 -27
- package/dist/config-types.d.ts +59 -50
- package/dist/config.d.ts +17 -54
- package/dist/config.js +99 -181
- 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 +61 -30
- package/dist/fleet/engine-signal.js +29 -5
- 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 +15 -13
- package/dist/fleet-client.js +9 -7
- 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 +7 -16
- package/dist/http/durable-run-leg.js +2 -1
- package/dist/http/health-self-report.d.ts +7 -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 +6 -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 +2 -5
- 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.d.ts +4 -3
- package/dist/http/stored-body-replay.js +5 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/key-resolver.d.ts +1 -1
- package/dist/leader/leader.d.ts +7 -6
- package/dist/leader/leader.js +3 -1
- package/dist/leader/run-entry.d.ts +2 -2
- package/dist/leader/run-entry.js +3 -3
- package/dist/leader/wire.d.ts +4 -4
- 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 +20 -11
- package/dist/model-entry-refusal.js +22 -17
- package/dist/model-provider.d.ts +2 -2
- package/dist/model-route-endpoint.d.ts +22 -2
- package/dist/model-route-endpoint.js +9 -3
- package/dist/model-route-gap.d.ts +103 -0
- package/dist/model-route-gap.js +80 -0
- 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/namespaced-rule-name.d.ts +16 -30
- package/dist/namespaced-rule-name.js +2 -20
- package/dist/observability/fail-open.d.ts +0 -4
- package/dist/observability/fail-open.js +0 -4
- 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 +51 -0
- package/dist/plugins/scheduler-support.d.ts +26 -7
- package/dist/plugins/scheduler-support.js +96 -53
- package/dist/plugins/session-policy-store-sql.js +1 -2
- 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/plugins/workflow-run-store-sql.d.ts +4 -2
- 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/rule-entry-list.d.ts +3 -13
- package/dist/rule-entry-list.js +1 -12
- package/dist/run-local.js +10 -7
- package/dist/runtime-caps-resolver.d.ts +4 -4
- package/dist/runtime-caps-resolver.js +2 -2
- package/dist/runtime-governance.d.ts +36 -13
- package/dist/runtime-governance.js +7 -2
- 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-policy-wire.d.ts +47 -49
- package/dist/session-policy-wire.js +41 -36
- package/dist/session-sync.d.ts +5 -1
- package/dist/session-sync.js +2 -1
- 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/spec-fields.d.ts +4 -4
- package/dist/spec-fields.js +1 -4
- package/dist/task-a2a.d.ts +1 -1
- package/dist/task-settings.d.ts +4 -5
- package/dist/task-settings.js +5 -8
- 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 +1 -6
- package/dist/trace/self-minted-wire.d.ts +147 -3
- package/dist/trace/self-minted-wire.js +50 -9
- package/dist/unattended-permission-mode.d.ts +3 -3
- package/dist/unattended-permission-mode.js +2 -2
- 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 +104 -121
- 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 +15 -15
- 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/web-admin-client/read-warnings.d.ts +137 -0
- package/dist/web-admin-client/read-warnings.js +168 -0
- 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 +19 -47
- package/package.json +3 -3
- package/skills/find-skills.md +2 -2
- package/dist/config-center/read-warnings.d.ts +0 -109
- package/dist/config-center/read-warnings.js +0 -110
- 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,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,
|
|
@@ -162,6 +162,57 @@ export const ONE_TIME_MIGRATIONS = [
|
|
|
162
162
|
"`AUTONOMY=plan` 的部署上按体里的原词重放(运维的 plan 不再被按段重钳)—— 升级前把这些停驻的 run 决掉 / 取消,或升级窗内设 `AUTONOMY=read-only`",
|
|
163
163
|
reportKeys: [],
|
|
164
164
|
},
|
|
165
|
+
{
|
|
166
|
+
id: "M13",
|
|
167
|
+
introducedIn: "7.123.0",
|
|
168
|
+
releasedOn: "2026-10-10",
|
|
169
|
+
removableAfter: "2026-11-09",
|
|
170
|
+
removal: "删 `config-lkg.ts` 的 formatVersion 2 读回迁移:`migrateV2Lkg` / `migrateDroppedIdentityRow` / `RETIRED_CATALOG_COORDINATE` 与其上的窗注;`loadLkg` 的 2 臂" +
|
|
171
|
+
"(版本判据里的 `2` 与 `formatVersion === 2 ? migrateV2Lkg(…)` 那一支 —— 2 回到「unknown formatVersion」拒)、返回型的 `migrated` 位与 JSDoc 里说它的那一行、" +
|
|
172
|
+
"文件头注「2 读回时一次性迁移」那一句;`boot/web-admin.ts` 读 `read.migrated` 打 `config_lkg_migrated` 的那一行(formatVersion 1 迁移的同名事件那一行留);" +
|
|
173
|
+
"`migrateV1Lkg` 里不带 v1 的 etag 那一句旁注对 `migrateV2Lkg` 的引用(不带 etag 的规则本身留 —— v1 载荷同样没有完整的被丢条目记录);契约 §A.1d " +
|
|
174
|
+
"`droppedEntries` 一节「升级窗(如实)」那一条与 §A.1d `[config.model_refused_name_minted]` 那一条里「LKG 载体 `droppedIdentities` 读回时一次性迁移」那半句;手写 formatVersion 2 信封的测试夹具" +
|
|
175
|
+
"(`test/config-lkg.test.ts`、`test/config-hot/batch1-version-ordinal.test.ts` 改写成 3;`test/models-dropped-entries-s1295.test.ts` 的旧 LKG 迁移格改钉 2 读回不可用)",
|
|
176
|
+
afterRemoval: "既有响亮臂 `config_lkg_unusable`(`loadLkg` 对 2 答「unknown formatVersion 2」):2 读回 ⇒ 不可用,中心可达照拉(整拉,读腿铸出完整载体,新 LKG 按 3 覆盖)、" +
|
|
177
|
+
"断着则 env 兜底,下一份干净候选重写 —— 与回滚到 7.122.0 读 3 同一种处置",
|
|
178
|
+
reportKeys: [],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "M14",
|
|
182
|
+
introducedIn: "7.99.0",
|
|
183
|
+
releasedOn: "2026-09-24",
|
|
184
|
+
removableAfter: "2026-10-24",
|
|
185
|
+
removal: "删 `config-lkg.ts` 的 formatVersion 1 迁移:`legacyV1LkgPath` / `migrateV1Lkg` 与其上的窗注(`loadLkg` 的 1 臂今天已是拒句,不动);" +
|
|
186
|
+
"`boot/web-admin.ts` `bootFromLkg` 里调用 `migrateV1Lkg` 的那一段(含它打 `config_lkg_migrated` / 推不出身份时响亮拒的那两行);" +
|
|
187
|
+
"`migrateV1Lkg` 的测试格(`test/config-lkg.test.ts` v1 迁移组)",
|
|
188
|
+
afterRemoval: "既有响亮臂 `config_lkg_unusable`(`loadLkg` 对 1 答「pre-identity LKG (formatVersion 1) — not read back …」):旧默认路径上的 v1 文件不再被迁进箱、" +
|
|
189
|
+
"原样留在盘上;中心可达照拉(整拉,新 LKG 按现行箱名落盘)、断着则 env 兜底",
|
|
190
|
+
reportKeys: [],
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "M15",
|
|
194
|
+
introducedIn: "7.124.0",
|
|
195
|
+
releasedOn: "2026-10-11",
|
|
196
|
+
removableAfter: "2026-11-10",
|
|
197
|
+
removal: "删 `config-lkg.ts` 的载荷迁移:`migrateLkgPayload` 与其上的窗注、`loadLkg` 里调它的那一段(载荷照旧过 `lkgPayloadProblem`)、`LkgReadMigration.receipts`" +
|
|
198
|
+
"(信封 2 → 3 的 M13 若已先删,`LkgReadMigration` 与 `migrated` 位一并删)、对 `@sema-agent/settings-schema/one-time-migrations` 的 import;`loadLkg` 的版本判据收成" +
|
|
199
|
+
"只认 4(3 回到「unknown formatVersion」拒);`boot/web-admin.ts` 读回后按 4 写回的那一段;文件头注 formatVersion 4 一段里「每次读回都过迁移」那一句;" +
|
|
200
|
+
"`test/ss11-stored-migrations.test.ts` 的 M15 组(4 原样读回那一格留)",
|
|
201
|
+
afterRemoval: "既有响亮臂 `config_lkg_unusable`(`loadLkg` 对 3 答「unknown formatVersion 3」):旧载荷的 LKG 读回 ⇒ 不可用,中心可达照拉(整拉,新 LKG 按 4 覆盖)、" +
|
|
202
|
+
"断着则 env 兜底 —— 与回滚到 7.123.0 读 4 同一种处置",
|
|
203
|
+
reportKeys: [],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "M16",
|
|
207
|
+
introducedIn: "7.124.0",
|
|
208
|
+
releasedOn: "2026-10-11",
|
|
209
|
+
removableAfter: "2026-11-10",
|
|
210
|
+
removal: "删 `config-d-migration.ts` 整只文件与两个入口的调用(`boot/web-admin.ts` 建本地配置源之后、`run-local.ts` 建配置源之后各一行 + import);" +
|
|
211
|
+
"`test/ss11-stored-migrations.test.ts` 的 M16 组;部署文档里「起服自动迁 config.d」那一句(运维仍可手跑契约命令行,直到契约删族)",
|
|
212
|
+
afterRemoval: "契约读腿的墓碑(永久):config.d 里还带旧形的域文档 —— `runtime.json` 带事实 ⇒ `RetiredDomainError` 整份读不出;`atModelAllowlist` / `models.default` ⇒ " +
|
|
213
|
+
"models 域降级、rosters 整份读不出 —— 拒句点名迁移 id;出路是运维手跑契约命令行(契约删族之后只剩手改)",
|
|
214
|
+
reportKeys: [],
|
|
215
|
+
},
|
|
165
216
|
];
|
|
166
217
|
export function migrationWindowOpen(window, today) {
|
|
167
218
|
return window.removableAfter === null || today <= window.removableAfter;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SchedulerError } from "@sema-agent/core";
|
|
2
2
|
import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledTaskId, ScheduledTaskSummary } from "@sema-agent/core";
|
|
3
|
-
import type { SchedulerRecord } from "@sema-agent/settings-schema/scheduler-store";
|
|
3
|
+
import type { SchedulerParseWarning, SchedulerRecord } from "@sema-agent/settings-schema/scheduler-store";
|
|
4
4
|
export type { SchedulerRecord };
|
|
5
5
|
/** core `Result<T,E>` shape (harness/types) — `{ok:true,value} | {ok:false,error}`. */
|
|
6
6
|
type Result<T, E> = {
|
|
@@ -31,23 +31,42 @@ export declare function validateIntent(intent: ScheduledIntent, caps: Caps, nowM
|
|
|
31
31
|
export declare function defaultSchedulerStorePath(config: {
|
|
32
32
|
readonly agentConfigDir?: string;
|
|
33
33
|
}): string;
|
|
34
|
+
/** 调度店读写的日志口(logger 的子集)。 */
|
|
35
|
+
export interface SchedulerStoreLog {
|
|
36
|
+
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
37
|
+
info(msg: string, meta?: Record<string, unknown>): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 一次读的告警 → 日志(settings-schema 11 的调度店读口恒交回告警,没有静默模式)。告警是闭集,穷举;**不带记录原文**(`malformed-record`
|
|
41
|
+
* 的 `record` 可能是任意内容 —— 只报下标):
|
|
42
|
+
* · `unreadable-file` ⇒ warn(整份文件不是 JSON 数组:读成零条,写者一律不写 —— 见 `mutateSchedulerStore`);
|
|
43
|
+
* · `malformed-record` ⇒ warn(这一条读不懂、被跳过;下一个写者落盘时它就没了);
|
|
44
|
+
* · `stale-session-record` ⇒ info(会话链上过期 ≥ 7 天的记录,读时丢、下一个写者落盘);
|
|
45
|
+
* · `one-time-migration` ⇒ info(SS-M3 的读时改写收据:durable 记录换上新键,下一个写者落盘)。
|
|
46
|
+
*/
|
|
47
|
+
export declare function logSchedulerWarnings(log: SchedulerStoreLog, path: string, warnings: readonly SchedulerParseWarning[]): void;
|
|
34
48
|
/**
|
|
35
|
-
* File-backed SchedulerCapability (durable across daemon restarts). Store = a single JSON file
|
|
36
|
-
*
|
|
37
|
-
*
|
|
49
|
+
* File-backed SchedulerCapability (durable across daemon restarts). Store = a single JSON file shared with the resident shell
|
|
50
|
+
* daemon. Every WRITE goes through the contract's ONE mutation primitive `mutateSchedulerStore` (optimistic witness + atomic
|
|
51
|
+
* write; a naked load → save is the lost-update race against the daemon's fire / reap writers). A store file that is not a JSON
|
|
52
|
+
* array is `unreadable_file`: nothing is written over it (the caller gets an `io` error). Reads (`list`) use the contract's read
|
|
53
|
+
* port; every read's warnings go to the log ({@link logSchedulerWarnings}). `now` injectable for tests.
|
|
38
54
|
*/
|
|
39
55
|
export declare class FileSchedulerBackend implements SchedulerCapability {
|
|
40
56
|
readonly schedulerCapabilities: Caps;
|
|
41
57
|
private readonly storePath;
|
|
42
58
|
private readonly now;
|
|
43
|
-
|
|
59
|
+
private readonly log;
|
|
60
|
+
/** `storePath` 必填:路径是装配层从配置定的事实(`SCHEDULER_STORE_PATH` ?? {@link defaultSchedulerStorePath}),本类不自己找缺省。
|
|
61
|
+
* `log` 必填:店的每次读都交回告警(读不懂的文件 / 被跳过的记录 / 读时改写),没有不出声的读法。 */
|
|
44
62
|
constructor(opts: {
|
|
45
63
|
storePath: string;
|
|
64
|
+
log: SchedulerStoreLog;
|
|
46
65
|
caps?: Partial<Caps>;
|
|
47
66
|
now?: () => number;
|
|
48
67
|
});
|
|
49
|
-
|
|
50
|
-
private
|
|
68
|
+
/** 一次读改写(契约的唯一写原语)。`fn` 必须纯、可重放(并发写者落地时整段重放);返回 `null` = 不写。拿不到结果的三形各成一个 `io` 错。 */
|
|
69
|
+
private mutate;
|
|
51
70
|
schedule(intent: ScheduledIntent, ctx: SchedulerContext): Promise<Result<{
|
|
52
71
|
id: ScheduledTaskId;
|
|
53
72
|
}, SchedulerError>>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { randomBytes, createHash } from "node:crypto";
|
|
3
3
|
import { SchedulerError, isValidCronExpr } from "@sema-agent/core";
|
|
4
|
-
import { loadSchedulerStore,
|
|
4
|
+
import { loadSchedulerStore, mutateSchedulerStore } from "@sema-agent/settings-schema/node";
|
|
5
5
|
import { configRootOf } from "../sema-config-dir.js";
|
|
6
6
|
const ok = (value) => ({ ok: true, value });
|
|
7
7
|
const err = (error) => ({ ok: false, error });
|
|
@@ -62,20 +62,67 @@ function computeNextRunMs(when, nowMs) {
|
|
|
62
62
|
export function defaultSchedulerStorePath(config) {
|
|
63
63
|
return join(configRootOf(config), "scheduled_tasks.json");
|
|
64
64
|
}
|
|
65
|
+
export function logSchedulerWarnings(log, path, warnings) {
|
|
66
|
+
for (const w of warnings) {
|
|
67
|
+
switch (w.kind) {
|
|
68
|
+
case "unreadable-file":
|
|
69
|
+
log.warn("scheduler_store_unreadable", { path, why: w.why, note: "the scheduled-task store is not a JSON array — it reads as zero records and no writer overwrites it; fix or remove the file" });
|
|
70
|
+
break;
|
|
71
|
+
case "malformed-record":
|
|
72
|
+
log.warn("scheduler_store_record_malformed", { path, index: w.index, note: "this record does not read and is skipped (the next write drops it)" });
|
|
73
|
+
break;
|
|
74
|
+
case "stale-session-record":
|
|
75
|
+
log.info("scheduler_store_record_stale", { path, index: w.index, id: w.id, note: "a session-chained record whose next run is ≥ 7 days past — dropped on read (the next write persists the drop)" });
|
|
76
|
+
break;
|
|
77
|
+
case "one-time-migration":
|
|
78
|
+
log.info("scheduler_store_record_migrated", { path, index: w.index, migration: w.receipt.migration, appliedAs: w.receipt.appliedAs, note: "settings-schema one-time migration rewrote this record on read (the next write persists it)" });
|
|
79
|
+
break;
|
|
80
|
+
default: {
|
|
81
|
+
const unhandled = w;
|
|
82
|
+
throw new Error(`scheduler store: unknown read warning ${JSON.stringify(unhandled)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
65
87
|
export class FileSchedulerBackend {
|
|
66
88
|
schedulerCapabilities;
|
|
67
89
|
storePath;
|
|
68
90
|
now;
|
|
91
|
+
log;
|
|
69
92
|
constructor(opts) {
|
|
70
93
|
this.schedulerCapabilities = { ...DEFAULT_SCHEDULER_CAPS, ...(opts.caps ?? {}) };
|
|
71
94
|
this.storePath = opts.storePath;
|
|
95
|
+
this.log = opts.log;
|
|
72
96
|
this.now = opts.now ?? (() => Date.now());
|
|
73
97
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
mutate(fn, what) {
|
|
99
|
+
let result;
|
|
100
|
+
let produced = false;
|
|
101
|
+
let mutation;
|
|
102
|
+
try {
|
|
103
|
+
mutation = mutateSchedulerStore(this.storePath, (records) => {
|
|
104
|
+
const r = fn(records);
|
|
105
|
+
result = r.result;
|
|
106
|
+
produced = true;
|
|
107
|
+
return r.next;
|
|
108
|
+
}, { nowMs: this.now() });
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
return err(new SchedulerError("io", `failed to ${what}`, e));
|
|
112
|
+
}
|
|
113
|
+
logSchedulerWarnings(this.log, this.storePath, mutation.warnings);
|
|
114
|
+
if (mutation.ok || mutation.reason === "aborted")
|
|
115
|
+
return produced ? ok(result) : err(new SchedulerError("io", `failed to ${what}`));
|
|
116
|
+
switch (mutation.reason) {
|
|
117
|
+
case "unreadable_file":
|
|
118
|
+
return err(new SchedulerError("io", `failed to ${what}: the scheduled-task store file is not a JSON array — nothing was written over it`));
|
|
119
|
+
case "conflict_exhausted":
|
|
120
|
+
return err(new SchedulerError("io", `failed to ${what}: concurrent writers kept changing the store — nothing was written`));
|
|
121
|
+
default: {
|
|
122
|
+
const unhandled = mutation;
|
|
123
|
+
throw new Error(`scheduler store: unknown mutation outcome ${JSON.stringify(unhandled)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
79
126
|
}
|
|
80
127
|
async schedule(intent, ctx) {
|
|
81
128
|
const nowMs = this.now();
|
|
@@ -88,60 +135,56 @@ export class FileSchedulerBackend {
|
|
|
88
135
|
if (intent.lifetime === "session" && this.schedulerCapabilities.supportsSessionLifetime === false) {
|
|
89
136
|
return err(new SchedulerError("invalid_schedule", 'lifetime:"session" is not supported on this deployment (no resident daemon reaps session-lifetime records — the row would be an orphan)'));
|
|
90
137
|
}
|
|
91
|
-
const records = this.load();
|
|
92
138
|
const key = dedupKey(ctx.scope, intent, ctx.sessionId);
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
records
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.save(records);
|
|
122
|
-
}
|
|
123
|
-
catch (e) {
|
|
124
|
-
return err(new SchedulerError("io", "failed to persist scheduled task", e));
|
|
125
|
-
}
|
|
126
|
-
return ok({ id });
|
|
139
|
+
const decided = this.mutate((records) => {
|
|
140
|
+
const existingIdx = records.findIndex((r) => {
|
|
141
|
+
const rr = r;
|
|
142
|
+
return r.scope === ctx.scope && dedupKey(r.scope, { when: r.when, label: r.label, lifetime: rr.lifetime }, r.sessionId) === key;
|
|
143
|
+
});
|
|
144
|
+
const scopeCount = records.filter((r) => r.scope === ctx.scope).length;
|
|
145
|
+
if (existingIdx < 0 && scopeCount >= this.schedulerCapabilities.maxScheduledPerScope) {
|
|
146
|
+
return { next: null, result: err(new SchedulerError("limit_exceeded", `scope at cap (${this.schedulerCapabilities.maxScheduledPerScope})`)) };
|
|
147
|
+
}
|
|
148
|
+
const id = (existingIdx >= 0 ? records[existingIdx].id : randomBytes(12).toString("base64url"));
|
|
149
|
+
const rec = {
|
|
150
|
+
id,
|
|
151
|
+
scope: ctx.scope,
|
|
152
|
+
...(ctx.principal ? { principal: ctx.principal } : {}),
|
|
153
|
+
...(ctx.sessionId ? { sessionId: ctx.sessionId } : {}),
|
|
154
|
+
prompt: intent.prompt,
|
|
155
|
+
when: intent.when,
|
|
156
|
+
...(intent.label ? { label: intent.label } : {}),
|
|
157
|
+
...(intent.mode ? { mode: intent.mode } : {}),
|
|
158
|
+
...(intent.lifetime ? { lifetime: intent.lifetime } : {}),
|
|
159
|
+
...(intent.recurring !== undefined ? { recurring: intent.recurring } : {}),
|
|
160
|
+
createdMs: nowMs,
|
|
161
|
+
...(computeNextRunMs(intent.when, nowMs) !== undefined ? { nextRunMs: computeNextRunMs(intent.when, nowMs) } : {}),
|
|
162
|
+
};
|
|
163
|
+
const next = existingIdx >= 0 ? records.map((r, i) => (i === existingIdx ? rec : r)) : [...records, rec];
|
|
164
|
+
return { next, result: ok({ id }) };
|
|
165
|
+
}, "persist scheduled task");
|
|
166
|
+
return decided.ok ? decided.value : decided;
|
|
127
167
|
}
|
|
128
168
|
async cancel(id, ctx) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
169
|
+
const decided = this.mutate((records) => {
|
|
170
|
+
const idx = records.findIndex((r) => r.id === id);
|
|
171
|
+
if (idx < 0 || records[idx].scope !== ctx.scope) {
|
|
172
|
+
return { next: null, result: err(new SchedulerError("not_found", `no scheduled task ${id} in your scope (wrong id, already cancelled, or not yours)`)) };
|
|
173
|
+
}
|
|
174
|
+
return { next: records.filter((_, i) => i !== idx), result: ok(undefined) };
|
|
175
|
+
}, "persist cancellation");
|
|
176
|
+
return decided.ok ? decided.value : decided;
|
|
177
|
+
}
|
|
178
|
+
async list(ctx) {
|
|
179
|
+
let read;
|
|
135
180
|
try {
|
|
136
|
-
this.
|
|
181
|
+
read = loadSchedulerStore(this.storePath, { nowMs: this.now() });
|
|
137
182
|
}
|
|
138
183
|
catch (e) {
|
|
139
|
-
return err(new SchedulerError("io", "failed to
|
|
184
|
+
return err(new SchedulerError("io", "failed to read the scheduled-task store", e));
|
|
140
185
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
async list(ctx) {
|
|
144
|
-
const summaries = this.load()
|
|
186
|
+
logSchedulerWarnings(this.log, this.storePath, read.warnings);
|
|
187
|
+
const summaries = read.records
|
|
145
188
|
.filter((r) => r.scope === ctx.scope)
|
|
146
189
|
.map((r) => ({
|
|
147
190
|
id: r.id,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { assertSessionRuleNamesCurrent, loosenReasons, SessionPolicyError, normalizeRules, stripRev } from "@sema-agent/core";
|
|
3
|
-
import {
|
|
3
|
+
import { readSessionPermissionRules, describeSessionPermissionRulesProblem } from "../session-policy-wire.js";
|
|
4
4
|
import { decodeJsonColumn, isJsonColumnBodyProblem, jsonColumnText } from "./sql-json-column.js";
|
|
5
5
|
import { pgHasUnstorable } from "./pg-safe-json.js";
|
|
6
6
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
@@ -105,7 +105,6 @@ export class SqlSessionPolicyStore {
|
|
|
105
105
|
}
|
|
106
106
|
const clean = normalizeRules(stripRev(rules));
|
|
107
107
|
assertSessionRuleNamesCurrent(clean);
|
|
108
|
-
assertSessionRuleNamesReachable(clean);
|
|
109
108
|
if (prior && opts?.operator !== true) {
|
|
110
109
|
const loosens = loosenReasons(prior, clean);
|
|
111
110
|
if (loosens.length > 0) {
|
|
@@ -242,12 +242,12 @@ class LocalBackend {
|
|
|
242
242
|
throw new Error(`DB_BACKEND=local cannot open data dir ${root}: ${e instanceof Error ? e.message : String(e)}` +
|
|
243
243
|
(tail === undefined ? "" : ` ${tail}`));
|
|
244
244
|
}
|
|
245
|
-
this.sessionStore = new LocalSessionStore(createLocalSessionRepo(this.fileBackend.root), this.fileBackend.root, { log: createLogger(config.logLevel) });
|
|
246
245
|
const corruptReadWarn = (msg, fields) => this.discloseWarn(msg, fields);
|
|
247
246
|
this.runStore = new FileRunStore(this.fileBackend.root, {
|
|
248
247
|
error: (msg, fields) => void (this.onError ?? this.onWarn)?.(msg, fields),
|
|
249
248
|
strictHydrate: config.runStoreStrictHydrate,
|
|
250
249
|
});
|
|
250
|
+
this.sessionStore = new LocalSessionStore(createLocalSessionRepo(this.fileBackend.root), this.runStore, this.fileBackend.root, { log: createLogger(config.logLevel) });
|
|
251
251
|
this.resumeAnchorStore = new FileResumeAnchorStore(this.fileBackend.root);
|
|
252
252
|
this.approvalExemptionStore = new FileApprovalExemptionStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
253
253
|
this.approvalNonceStore = new FileApprovalNonceStore(this.fileBackend.root, { warn: corruptReadWarn });
|
|
@@ -216,7 +216,7 @@ export interface RunRecord {
|
|
|
216
216
|
* 被取消了没有」看终局码;判「一条还在跑的 run 上有没有人按过取消」才看这一位。 */
|
|
217
217
|
cancelRequested: boolean;
|
|
218
218
|
/** First ~120 chars of the objective (secret-redacted at write) — the list's "what is this task" glance.
|
|
219
|
-
* null
|
|
219
|
+
* 一个事实位:`""` = 建过预览、剥完没有用户正文(只有注入块 / 空 objective);null = 没记录(更早版本建的行 / 列前的行)。 */
|
|
220
220
|
objectivePreview: string | null;
|
|
221
221
|
createdAt: string;
|
|
222
222
|
updatedAt: string;
|
|
@@ -337,9 +337,8 @@ export declare function isParkedRunStatus(status: RunRecord["status"]): boolean;
|
|
|
337
337
|
* 两个不同的忙闲。
|
|
338
338
|
*
|
|
339
339
|
* 🔴 **未知词 / 无读数 ⇒ 词缺席(`undefined`),不是 `idle`**(诚实缺席,不是静默兜底):
|
|
340
|
-
* · `lastStatus === ""`
|
|
341
|
-
*
|
|
342
|
-
* 两者都不是「我知道它闲着」,所以都不许铸出 `idle` 这个断言;
|
|
340
|
+
* · `lastStatus === ""` 的真来源:这条会话**还没有任何 run**(SQL 双生与 local 会话店同;local 会话店的 run 店是必填位,没有「说不出 run 状态」的装配形)。
|
|
341
|
+
* 它不是「我知道它闲着」,所以不许铸出 `idle` 这个断言;
|
|
343
342
|
* · 读不懂的词(滚动升级里新副本写入的新状态词经裸 cast 读到本进程)同判 —— 与
|
|
344
343
|
* {@link isTerminalRunStatus} 族的保守方向一致。
|
|
345
344
|
* · **缺席怎么渲染是 core 的辖域,本仓一个字都不复述**(S-514-JU:此处原写着「core 对缺席渲染成 `idle`」
|
|
@@ -407,6 +406,23 @@ export interface SessionSummary {
|
|
|
407
406
|
* titled OR the lister face has no session_meta (the legacy task_run aggregate / memory twins — honest degrade). */
|
|
408
407
|
title: string | null;
|
|
409
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* S-1371 —— 会话**枚举面**读 run 账里「每会话的 run 投影」的窄读面:`runCount` / `lastStatus` / `lastRunId` / `lastTaskRunId` 四位,
|
|
411
|
+
* 同一次读、同一条「最新一条」的口径。
|
|
412
|
+
*
|
|
413
|
+
* 为什么是 run 店自己的 `listSessions`、不另造一只读口:这四位在 SQL 双生里就是 `task_run` 的 `rn=1` JOIN(计数、最新一条的状态与 task_id、
|
|
414
|
+
* 最新一条 `result.runId`),在文件 / 内存 run 店里就是 `listSessions` 的同口径聚合(`created_at DESC, task_id DESC` 取最新一条、数全部)——
|
|
415
|
+
* 它们本来就是**同一个问题的同一份答案**。local 会话店(会话身份来自会话仓、不是 run 账)读这一只,一行里就不会出现两个来源各答一遍
|
|
416
|
+
* 「最新一条是谁」(此前 `runCount` / `lastStatus` 读 run 店、`lastRunId` / `lastTaskRunId` 读进程内座:重启后同一行「有终态词、`lastRunId` 为 null」);
|
|
417
|
+
* 不另造的另一半理由:再造一只 `listBySession` 就要在三个孪生里各落一遍并重新证一遍同口径。
|
|
418
|
+
*
|
|
419
|
+
* 只收四位:`Pick` 把读面钉死在它真用的键上(其余键 —— 预览 / 属主 / 时刻 —— 在会话仓那边另有来源,不许从这里混读)。
|
|
420
|
+
*/
|
|
421
|
+
export interface SessionRunRollupSource {
|
|
422
|
+
listSessions(opts: {
|
|
423
|
+
limit: number;
|
|
424
|
+
}): Promise<ReadonlyArray<Pick<SessionSummary, "sessionId" | "runCount" | "lastStatus" | "lastRunId" | "lastTaskRunId">>>;
|
|
425
|
+
}
|
|
410
426
|
/**
|
|
411
427
|
* S-297 —— 「这个会话**最近一条腿**是哪一条 run」的窄读面(`latestRunForSession` 的回值)。
|
|
412
428
|
*
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
* 换的时候要核对的等价点:本文件的 `normalize()` 统一形状 + `max` 截断 +
|
|
41
41
|
* `setWebSearchBadPayloadObserver` 观测器,core 的 adapter 都没有,得在外层补回来。
|
|
42
42
|
*
|
|
43
|
-
* ③ **四级优先级语义未定**(env /
|
|
43
|
+
* ③ **四级优先级语义未定**(env / web-admin / per-scenario / per-request 谁压谁)。
|
|
44
44
|
* 🔴 三问里「谁被伤」这条决定了默认值:一个**只配了模型 key** 的既有部署,若新语义让某一级
|
|
45
45
|
* 能自行打开搜索,它就在升级后**凭空长出一条出网工具** —— 那是安全姿态变更,不是特性。
|
|
46
46
|
* ⇒ 语义定稿前,任何新的开启通道**默认 OFF**;开启必须是部署方的显式动作。
|