@sema-agent/server 7.90.0 → 7.91.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +1 -1
- package/dist/adoption/plan.js +11 -0
- package/dist/boot/runner-deps.d.ts +6 -0
- package/dist/boot/runner-deps.js +11 -0
- package/dist/boot/session-faces.d.ts +6 -0
- package/dist/boot/session-faces.js +10 -2
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +1 -0
- package/dist/boot/stage-06-runners.d.ts +1 -0
- package/dist/boot/stage-06-runners.js +2 -2
- package/dist/boot/stage-07-capability-layer.d.ts +1 -0
- package/dist/boot/stage-08-reapers.d.ts +1 -0
- package/dist/boot/stage-09-leader.d.ts +1 -0
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +1 -0
- package/dist/boot/stage-10-http-server.js +1 -0
- package/dist/boot/stores.d.ts +2 -0
- package/dist/boot/stores.js +11 -3
- package/dist/cross-session-settings.d.ts +6 -2
- package/dist/http/routes/capabilities.js +4 -1
- package/dist/http/routes/rules.d.ts +11 -0
- package/dist/http/routes/rules.js +54 -5
- package/dist/http/routes/sessions-list.js +6 -1
- package/dist/http/server.d.ts +12 -0
- package/dist/peer-directory.d.ts +114 -0
- package/dist/peer-directory.js +136 -0
- package/dist/permission-rule-vocab.d.ts +27 -1
- package/dist/permission-rule-vocab.js +8 -0
- package/dist/plugins/mailbox-recipient-lifecycle.d.ts +86 -0
- package/dist/plugins/mailbox-recipient-lifecycle.js +10 -0
- package/dist/plugins/mailbox-store-sql.d.ts +18 -2
- package/dist/plugins/mailbox-store-sql.js +77 -1
- package/dist/plugins/store-contracts.d.ts +36 -1
- package/dist/plugins/store-contracts.js +10 -0
- package/dist/rules-consent.d.ts +74 -1
- package/dist/rules-consent.js +81 -1
- package/dist/trace/core-keyset-guard.d.ts +10 -4
- package/dist/trace/project.d.ts +16 -0
- package/dist/trace/project.js +13 -1
- package/dist/trace/redact.js +129 -18
- package/dist/trace/sema-provenance.d.ts +7 -6
- package/package.json +3 -3
package/USAGE.md
CHANGED
|
@@ -1075,7 +1075,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
1075
1075
|
| `POST /v1/approvals/<sessionId>/decide` `{"decision":"approve"|"deny","reason":"…"}` | 批/否并恢复挂起任务(CAS,重复决议 409);**仅 operator**(非 operator → 403)。5.0.0 起旧轮询腿 `POST /v1/approvals/<id>` 已退役 |
|
|
1076
1076
|
| `GET /v1/agents/roster` | 后台 agent **名册**(7.29 起,需 durable background-agent store,否则 501 `capability.background_agent_store_required`):调用方自己 scope 的 `a…` 行,新→旧。行只带 `handle`/`name`/`agentType`/`status`/`spawnedAt`/`updatedAt`/`settledAt`(content-free;子代产物正文走 `GET /v1/runs/<id>/subagents/<handle>/output`)。查询:`?limit=`(1..100,默认 20)、`?before=`(上一页的 `nextBefore` 游标)、`?status=`(running\|parked\|completed\|failed\|killed)、`?session=`(按会话树窄化)、`?scope=`(**仅** service-token/单用户形;带 principal 的调用方锁定自己)。回体 `{agents,total,nextBefore?,truncated?}`;`truncated:true` = 租户行数超过名册窗(500),`total` 此时是窗内数。非属主的行**不出现也不计数**(无存在性 oracle) |
|
|
1077
1077
|
| `GET /v1/capabilities/scenarios/<name>` | 单个场景的只读详情:工具面、提示词概览、**本部署现在跑不跑得动**(见下) |
|
|
1078
|
-
| `POST /v1/admin/drain` `{"reason":"…"}` | **停机因由显式声明**(7.35.0+;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 = 谁都不是 operator ⇒ 恒 403)
|
|
1078
|
+
| `POST /v1/admin/drain` `{"reason":"…"}` | **停机因由显式声明**(7.35.0+;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 = 谁都不是 operator ⇒ 恒 403)。设计给编排层(k8s `preStop` / systemd `ExecStop` 等)在发 SIGTERM **之前**调用声明因由;⚠️ **如实**:本仓四条消费树(server/cli/sdk/web-admin)与编排层脚本树 `sema-deploy` 里对它零调用,`sema-comms audits/.md:213` N-18 已把它书面登记为 declined(cli 判定这是 operator 面,不是壳自动打的口)—— 今天它是给**运维手动 curl**(或运维自己写的编排脚本)用的口,不是任何一个内置壳自动调用的。调用后 server 记进程内 `drainState.reason`,draining 期由 `/health` 的 `drainReason` 与 503 `draining` 体的 `reason` 透出(未声明 = 两面键缺席)。声明是**覆盖式**、无 TTL、随进程重启清零 ⇒ 每次停机流程内都要重新声明。`reason` 非空、trim 后 ≤256 字符,坏值 400。无 drain 面装配(嵌入宿主 / 测试)上声明无处可落 ⇒ 501 `capability.drain_state_required`。🔴 **写口是特权的,读口不是**:`/health` 免鉴权(见下一行)且缺省全网卡监听 —— 任何能连到这个端口的人都读得到你写的那句话。**别在 reason 里写工单号、内部主机名或任何内部标识**。 |
|
|
1079
1079
|
| `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`,空名单=谁都不是)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,domainExists,managedNow,effectiveNow}}`;顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 路径 404 + 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
|
|
1080
1080
|
| `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:空 `OPERATOR_PRINCIPALS` ⇒ 恒 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
|
|
1081
1081
|
| `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
|
package/dist/adoption/plan.js
CHANGED
|
@@ -256,6 +256,17 @@ export const REBIND_LEGS = [
|
|
|
256
256
|
residualKey: ["handle"],
|
|
257
257
|
why: "信箱行的 scope 分区。`scope_key`(字节列)与 `scope`(可读列)存的是同一个值,必须一起改",
|
|
258
258
|
},
|
|
259
|
+
{
|
|
260
|
+
leg: "mailbox_tombstone#scope_key",
|
|
261
|
+
table: "mailbox_tombstone",
|
|
262
|
+
kind: "bulk-rebind",
|
|
263
|
+
action: "bucket-rebind",
|
|
264
|
+
columns: ["scope_key"],
|
|
265
|
+
matchColumn: "scope_key",
|
|
266
|
+
encoding: "verbatim",
|
|
267
|
+
residualKey: ["handle"],
|
|
268
|
+
why: "S-72 收件人墓碑(预删除态)。**必须与 `mailbox` 同迁**:漏迁它 = 收编后新身份下的会话盒**永久静默拒收**(append 撞一条属于旧身份的墓碑),而那正是墓碑这个机制唯一的失效方向;本表无 `scope` 可读列(与 mailbox_message 同形)",
|
|
269
|
+
},
|
|
259
270
|
{
|
|
260
271
|
leg: "mailbox_message#scope_key",
|
|
261
272
|
table: "mailbox_message",
|
|
@@ -27,6 +27,7 @@ import type { QuestionCoordinator } from "../question.js";
|
|
|
27
27
|
import type { ToolApprovalCoordinator } from "../tool-approval.js";
|
|
28
28
|
import type { MemoryBackend } from "@sema-agent/core";
|
|
29
29
|
import type { MemorySyncRunner } from "../memory-sync-client.js";
|
|
30
|
+
import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
|
|
30
31
|
/**
|
|
31
32
|
* `RunnerDeps.onAsk` 的装配([ref] 件4:从内联闭包提为具名导出,**纯 testability 重构、零行为变化**)。
|
|
32
33
|
*
|
|
@@ -86,6 +87,11 @@ export interface RunnerDepsCtx {
|
|
|
86
87
|
rosterStore: RunnerDeps["rosterStore"];
|
|
87
88
|
backgroundAgentStore: RunnerDeps["backgroundAgentStore"];
|
|
88
89
|
mailboxStore: RunnerDeps["mailboxStore"];
|
|
90
|
+
/**
|
|
91
|
+
* [ref]:mailbox 的**收件人生命周期面**(预删除态)。与 `mailboxStore` **成对**(`boot/stores.ts` 同段铸,
|
|
92
|
+
* 两键同在场/同缺席);是 peer 目录席三个合取项之一,也是会话删除级联落墓碑的那一口。
|
|
93
|
+
*/
|
|
94
|
+
mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
|
|
89
95
|
/** [ref]-T1:治理窗账本店(USAGE_WINDOWS 配置时才在场;与 config.usageWindows 成对进 deps)。 */
|
|
90
96
|
usageWindowStore: RunnerDeps["usageWindowStore"];
|
|
91
97
|
brain: ReturnType<typeof createBrain>;
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -13,6 +13,7 @@ import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js"
|
|
|
13
13
|
import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
|
|
14
14
|
import { workflowModelAllowlistFor } from "../task-workflow.js";
|
|
15
15
|
import { buildCrossSessionInboundLayers, crossSessionDialogExpiryOf } from "../cross-session-settings.js";
|
|
16
|
+
import { createPeerDirectory } from "../peer-directory.js";
|
|
16
17
|
export function createRunnerDepsOnAsk(toolApproval) {
|
|
17
18
|
if (!toolApproval)
|
|
18
19
|
return undefined;
|
|
@@ -101,6 +102,7 @@ export function createSharedRunnerDeps(ctx) {
|
|
|
101
102
|
}
|
|
102
103
|
export function createRunnerDeps(ctx) {
|
|
103
104
|
const { config, logger, metrics, localRoot, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine, sessionCaptureRecords, memorySyncRunner, runtimeCapsResolver, fileHistoryStore, permissionRuleStore, fleetBus, workflowRunStore, workflowJournalStore, workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, getRunStore, sessionShellGateExplicitlyOff, } = ctx;
|
|
105
|
+
const peerSessionLister = sessionStore.listSessions?.bind(sessionStore);
|
|
104
106
|
const memoryEngineIncident = createMemoryEngineIncidentSink({ logger, metrics });
|
|
105
107
|
const sharedRunnerDeps = createSharedRunnerDeps(ctx);
|
|
106
108
|
const workflowModelAllowlist = workflowModelAllowlistFor(config);
|
|
@@ -156,6 +158,15 @@ export function createRunnerDeps(ctx) {
|
|
|
156
158
|
: {};
|
|
157
159
|
const runnerDeps = {
|
|
158
160
|
...sharedRunnerDeps,
|
|
161
|
+
peerDirectory: peerSessionLister !== undefined && ctx.mailboxStore !== undefined && ctx.mailboxRecipientLifecycle !== undefined
|
|
162
|
+
? createPeerDirectory({
|
|
163
|
+
lister: peerSessionLister,
|
|
164
|
+
lifecycle: ctx.mailboxRecipientLifecycle,
|
|
165
|
+
requirePrincipal: config.requirePrincipal,
|
|
166
|
+
crossSessionInbound: buildCrossSessionInboundLayers(config),
|
|
167
|
+
logger,
|
|
168
|
+
})
|
|
169
|
+
: undefined,
|
|
159
170
|
onTaskOutcome: outcomeSink
|
|
160
171
|
? (o) => {
|
|
161
172
|
metrics.inc("task_outcomes_total", { status: o.status, green: String(o.oracle?.green ?? "unknown") });
|
|
@@ -9,6 +9,8 @@ import type { OwnerAwareSessionStore } from "../security.js";
|
|
|
9
9
|
import type { DeviceStore } from "../device-store.js";
|
|
10
10
|
import { SessionWatchRegistry } from "../session-watch.js";
|
|
11
11
|
import type { TaskListLane } from "./task-list-lane.js";
|
|
12
|
+
import { type RunnerDeps } from "@sema-agent/core";
|
|
13
|
+
import type { MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
|
|
12
14
|
export interface SessionFacesCtx {
|
|
13
15
|
config: ServiceConfig;
|
|
14
16
|
logger: Logger;
|
|
@@ -31,6 +33,10 @@ export interface SessionFacesCtx {
|
|
|
31
33
|
deviceStore: DeviceStore | undefined;
|
|
32
34
|
/** per-session cwd/shellEnv 登记簿的清除口(E21 级联的一条腿,codex R1-F1)。缺席 = 装配未接。 */
|
|
33
35
|
dropSessionScoped: ((sid: string) => void) | undefined;
|
|
36
|
+
/** [ref]:会话盒(`session.<id>`)的信箱 + 它的收件人生命周期面(E21 级联的一条腿)。**成对**在场
|
|
37
|
+
* (`boot/stores.ts` 同段铸);缺席 = 本部署无耐久 mailbox ⇒ 无箱可退役。 */
|
|
38
|
+
mailboxStore: RunnerDeps["mailboxStore"];
|
|
39
|
+
mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
|
|
34
40
|
}
|
|
35
41
|
export declare function createSessionFaces(ctx: SessionFacesCtx): {
|
|
36
42
|
ownerAware: OwnerAwareSessionStore;
|
|
@@ -4,9 +4,9 @@ import { PlanCacheProbe } from "../plan-cache-probe.js";
|
|
|
4
4
|
import { purgeScratchpadDir } from "../env-facts.js";
|
|
5
5
|
import { setLeafAdvanceListener } from "../session-leaf-bus.js";
|
|
6
6
|
import { SessionWatchRegistry, posIntEnv } from "../session-watch.js";
|
|
7
|
-
import { defaultTaskRegistry } from "@sema-agent/core";
|
|
7
|
+
import { defaultTaskRegistry, peerSessionBoxHandle } from "@sema-agent/core";
|
|
8
8
|
export function createSessionFaces(ctx) {
|
|
9
|
-
const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, } = ctx;
|
|
9
|
+
const { config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore, resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore, workflowCompletionInbox, taskListLane, deviceStore, dropSessionScoped, mailboxStore, mailboxRecipientLifecycle, } = ctx;
|
|
10
10
|
const mysqlPool = backend?.mysqlPool();
|
|
11
11
|
const auditPgPool = backend?.pgPool();
|
|
12
12
|
const auditLocalRoot = backend?.kind === "local" ? (config.localDataRoot ?? localRoot) : undefined;
|
|
@@ -79,6 +79,12 @@ export function createSessionFaces(ctx) {
|
|
|
79
79
|
}
|
|
80
80
|
if (workflowCompletionInbox)
|
|
81
81
|
await workflowCompletionInbox.purge(sessionId, owner ?? null).catch((err) => logger.warn("workflow_completion_inbox_purge_failed", { sessionId, err: String(err) }));
|
|
82
|
+
const peerBox = mailboxStore !== undefined && mailboxRecipientLifecycle !== undefined
|
|
83
|
+
? { store: mailboxStore, lifecycle: mailboxRecipientLifecycle, scope: owner ?? "default", handle: peerSessionBoxHandle(sessionId) }
|
|
84
|
+
: undefined;
|
|
85
|
+
if (peerBox !== undefined) {
|
|
86
|
+
await peerBox.lifecycle.retireRecipient(peerBox.scope, peerBox.handle, Date.now());
|
|
87
|
+
}
|
|
82
88
|
await purgeScratchpadDir(config.localDataRoot ?? localRoot, sessionId);
|
|
83
89
|
try {
|
|
84
90
|
const reaped = defaultTaskRegistry.reapSessionBackground(sessionId, owner ?? "default");
|
|
@@ -88,6 +94,8 @@ export function createSessionFaces(ctx) {
|
|
|
88
94
|
catch (err) {
|
|
89
95
|
logger.warn("session_background_reap_failed", { sessionId, err: String(err) });
|
|
90
96
|
}
|
|
97
|
+
if (peerBox !== undefined)
|
|
98
|
+
await peerBox.store.drop(peerBox.scope, peerBox.handle);
|
|
91
99
|
const historyDeleted = await ownerAware.deleteSession(sessionId, owner);
|
|
92
100
|
sessionWatchRegistry?.dropSession(sessionId);
|
|
93
101
|
return { deleted: historyDeleted || runs.removed > 0 };
|
|
@@ -4,6 +4,7 @@ export declare function runStage03(ctx: Awaited<ReturnType<typeof runStage02>>):
|
|
|
4
4
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
5
5
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
6
6
|
instanceId: string;
|
|
7
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
7
8
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
8
9
|
memoryEngine: {
|
|
9
10
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -2,10 +2,10 @@ import { uuidv7 } from "@sema-agent/core";
|
|
|
2
2
|
import { openStores } from "./stores.js";
|
|
3
3
|
export async function runStage03(ctx) {
|
|
4
4
|
const { config, localRoot, logger, metrics } = ctx;
|
|
5
|
-
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
5
|
+
const { backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, taskAttachmentStore, mailboxStore, mailboxRecipientLifecycle, memoryExportBackend, memorySyncRunner, sessionStore, breakerState, usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts, } = await openStores({ config, logger, metrics, localRoot });
|
|
6
6
|
const instanceId = uuidv7();
|
|
7
7
|
const sessionCaptureRecords = backend?.sessionCaptureRecords();
|
|
8
8
|
const sessionCaptureRecordStore = sessionCaptureRecords?.();
|
|
9
|
-
return { ...ctx, backend, backgroundAgentStore, breakerState, instanceId, mailboxStore, memoryEngine, memoryExportBackend, memoryPosture, memorySyncCursors, memorySyncRunner, rosterStore, sessionCaptureRecordStore, sessionCaptureRecords, sessionStore, sqlEngineFacts, storeBackendDegraded, taskAttachmentStore, taskListLane, usageWindowStore };
|
|
9
|
+
return { ...ctx, backend, backgroundAgentStore, breakerState, instanceId, mailboxRecipientLifecycle, mailboxStore, memoryEngine, memoryExportBackend, memoryPosture, memorySyncCursors, memorySyncRunner, rosterStore, sessionCaptureRecordStore, sessionCaptureRecords, sessionStore, sqlEngineFacts, storeBackendDegraded, taskAttachmentStore, taskListLane, usageWindowStore };
|
|
10
10
|
}
|
|
11
11
|
//# sourceMappingURL=stage-03-store-backend.js.map
|
|
@@ -41,6 +41,7 @@ export declare function runStage04(ctx: Awaited<ReturnType<typeof runStage03>>):
|
|
|
41
41
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
42
42
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
43
43
|
instanceId: string;
|
|
44
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
44
45
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
45
46
|
memoryEngine: {
|
|
46
47
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -54,6 +54,7 @@ export declare function runStage05(ctx: Awaited<ReturnType<typeof runStage04>>):
|
|
|
54
54
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
55
55
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
56
56
|
instanceId: string;
|
|
57
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
57
58
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
58
59
|
memoryEngine: {
|
|
59
60
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -115,6 +115,7 @@ export declare function runStage06(ctx: Awaited<ReturnType<typeof runStage05>>):
|
|
|
115
115
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
116
116
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
117
117
|
instanceId: string;
|
|
118
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
118
119
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
119
120
|
memoryEngine: {
|
|
120
121
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -21,7 +21,7 @@ import { createGovernanceSeams } from "./governance-seams.js";
|
|
|
21
21
|
import { createSharedMemoryScopeAuthorizer } from "../shared-memory-scope-authorizer.js";
|
|
22
22
|
import { parkedAskRedeem, runStore } from "./late-binds.js";
|
|
23
23
|
export function runStage06(ctx) {
|
|
24
|
-
const { backend, backgroundAgentStore, brain, config, configCenter, deploymentHooks, executionEnvFactory, fileHistoryStore, localRoot, logger, lspManager, mailboxStore, memoryEngine, memorySyncRunner, metrics, perSessionCwd, permissionRuleProvider, pricing, rosterStore, ruleConsent, sendUserFileTaskEnvs, sessionCaptureRecordStore, sessionCaptureRecords, sessionPolicyStore, sessionStore, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore } = ctx;
|
|
24
|
+
const { backend, backgroundAgentStore, brain, config, configCenter, deploymentHooks, executionEnvFactory, fileHistoryStore, localRoot, logger, lspManager, mailboxRecipientLifecycle, mailboxStore, memoryEngine, memorySyncRunner, metrics, perSessionCwd, permissionRuleProvider, pricing, rosterStore, ruleConsent, sendUserFileTaskEnvs, sessionCaptureRecordStore, sessionCaptureRecords, sessionPolicyStore, sessionStore, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore } = ctx;
|
|
25
25
|
const { sqlWorkflowRunStore, workflowNotifyJournal, workflowCompletionInbox, deliverWorkflowCompletion, workflowNotifyGate, fleetBus, workflowRunStore, workflowJournalStore, outcomeSink, workflowRecoverOpts, workflowAgentRegistry, subagentSteerRegistry, workflowAgentSessionIndex, } = createWorkflowOrchestration({ config, logger, metrics, localRoot, backend, getRunStore: () => runStore });
|
|
26
26
|
const { elicitation, question, toolApproval, durableEnabled, streamApprovalGate, approvalAskAudit, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({
|
|
27
27
|
config, logger, backend, sendUserFileTaskEnvs, ruleConsent,
|
|
@@ -116,7 +116,7 @@ export function runStage06(ctx) {
|
|
|
116
116
|
hands: commitHands,
|
|
117
117
|
mcpRevocations,
|
|
118
118
|
sessionShellGateExplicitlyOff: sessionShellGateRegistry.explicitlyOff,
|
|
119
|
-
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
119
|
+
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, mailboxRecipientLifecycle, usageWindowStore, brain,
|
|
120
120
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
121
121
|
sessionCaptureRecords,
|
|
122
122
|
memorySyncRunner, toolResultStore: runnerOffloadStore, checkpointStore, sessionPolicyStore, runtimeCapsResolver, fileHistoryStore,
|
|
@@ -130,6 +130,7 @@ export declare function runStage07(ctx: Awaited<ReturnType<typeof runStage06>>):
|
|
|
130
130
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
131
131
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
132
132
|
instanceId: string;
|
|
133
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
133
134
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
134
135
|
memoryEngine: {
|
|
135
136
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -145,6 +145,7 @@ export declare function runStage08(ctx: Awaited<ReturnType<typeof runStage07>>):
|
|
|
145
145
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
146
146
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
147
147
|
instanceId: string;
|
|
148
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
148
149
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
149
150
|
memoryEngine: {
|
|
150
151
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -177,6 +177,7 @@ export declare function runStage09(ctx: Awaited<ReturnType<typeof runStage08>>):
|
|
|
177
177
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
178
178
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
179
179
|
instanceId: string;
|
|
180
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
180
181
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
181
182
|
memoryEngine: {
|
|
182
183
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -6,13 +6,13 @@ import { createLeaderFace } from "./leader.js";
|
|
|
6
6
|
import { assertStaticWiringConsistent } from "../http/routes/diagnostics.js";
|
|
7
7
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
8
8
|
export function runStage09(ctx) {
|
|
9
|
-
const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryStore, governanceSeams, localRoot, logger, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
|
|
9
|
+
const { approvalExemptionStore, backend, brain, checkpointStore, config, configCenter, deviceLane, dropSessionScoped, durableEnabled, executionEnvFactory, fileHistoryStore, governanceSeams, localRoot, logger, mailboxRecipientLifecycle, mailboxStore, metrics, pricing, resumeAnchorStore, runStore, runnerDeps, sessionPolicyStore, sessionStore, streamApprovalGate, taskAttachmentStore, taskListLane, toolResultStore, tracer, usageWindowStore, workflowCompletionInbox } = ctx;
|
|
10
10
|
const { ownerAware, sessionAudit, sessionWatchRegistry, purgeSession, instrumentDegenerate, planCacheProbe } = createSessionFaces({
|
|
11
11
|
deviceStore: deviceLane?.store,
|
|
12
12
|
dropSessionScoped,
|
|
13
13
|
config, logger, metrics, localRoot, backend, sessionStore, runStore, checkpointStore, toolResultStore,
|
|
14
14
|
resumeAnchorStore, approvalExemptionStore, sessionPolicyStore, taskAttachmentStore, fileHistoryStore,
|
|
15
|
-
workflowCompletionInbox, taskListLane,
|
|
15
|
+
workflowCompletionInbox, taskListLane, mailboxStore, mailboxRecipientLifecycle,
|
|
16
16
|
});
|
|
17
17
|
const leaderEndpoint = createLeaderFace({ config, logger, brain, pricing, executionEnvFactory, toolResultStore, sessionStore, checkpointStore, governanceSeams, backend, tracer, usageWindowStore, promptSource: configCenter.promptSource, onError: runnerDeps.onError });
|
|
18
18
|
const drainState = { draining: false };
|
|
@@ -177,6 +177,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
|
|
|
177
177
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
178
178
|
breakerState: import("../plugins/breaker-state-sql.js").TiDBBreakerState | import("../plugins/breaker-state-sql.js").PgBreakerState | undefined;
|
|
179
179
|
instanceId: string;
|
|
180
|
+
mailboxRecipientLifecycle: import("../plugins/mailbox-recipient-lifecycle.js").MailboxRecipientLifecycle | undefined;
|
|
180
181
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
181
182
|
memoryEngine: {
|
|
182
183
|
backend: import("@sema-agent/core").MemoryBackend;
|
|
@@ -37,6 +37,7 @@ export async function runStage10(ctx) {
|
|
|
37
37
|
taskAttachmentStore: taskAttachmentStore ? taskAttachmentStore : undefined,
|
|
38
38
|
backgroundAgentStore: backgroundAgentStore ? backgroundAgentStore : undefined,
|
|
39
39
|
sessionStorage: ownerAware,
|
|
40
|
+
peerDirectory: runnerDeps.peerDirectory,
|
|
40
41
|
workflowRunStore: workflowRunStore ? workflowRunStore : undefined,
|
|
41
42
|
workflowAgentSessionIndex: workflowAgentSessionIndex ? workflowAgentSessionIndex : undefined,
|
|
42
43
|
workflowJournalStore: workflowJournalStore ? workflowJournalStore : undefined,
|
package/dist/boot/stores.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type MemorySyncRunner } from "../memory-sync-client.js";
|
|
|
4
4
|
import type { Logger } from "../observability/logger.js";
|
|
5
5
|
import type { Metrics } from "../observability/metrics.js";
|
|
6
6
|
import { type MemorySyncStore } from "../plugins/memory-sync-store-pg.js";
|
|
7
|
+
import { type MailboxRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
|
|
7
8
|
import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js";
|
|
8
9
|
import { type StoreBackend } from "../plugins/store-backend.js";
|
|
9
10
|
export interface OpenStoresCtx {
|
|
@@ -22,6 +23,7 @@ export declare function openStores(ctx: OpenStoresCtx): Promise<{
|
|
|
22
23
|
memorySyncCursors: MemorySyncStore | undefined;
|
|
23
24
|
rosterStore: import("@sema-agent/core").RosterStore | undefined;
|
|
24
25
|
backgroundAgentStore: import("@sema-agent/core").BackgroundAgentStore | undefined;
|
|
26
|
+
mailboxRecipientLifecycle: MailboxRecipientLifecycle | undefined;
|
|
25
27
|
taskAttachmentStore: TaskAttachmentStore | undefined;
|
|
26
28
|
mailboxStore: import("@sema-agent/core").MailboxStore | undefined;
|
|
27
29
|
memoryExportBackend: MemoryBackend | undefined;
|
package/dist/boot/stores.js
CHANGED
|
@@ -10,6 +10,7 @@ import { TiDBMemoryHistoryStore, TiDBMemorySyncStore, ensureTiDBMemoryHistorySch
|
|
|
10
10
|
import { MinioBlobBackend } from "../plugins/blob-backend.js";
|
|
11
11
|
import { PgBackgroundAgentStore, TiDBBackgroundAgentStore, ensurePgBackgroundAgentSchema, ensureTiDBBackgroundAgentSchema } from "../plugins/background-agent-store-sql.js";
|
|
12
12
|
import { PgMailboxStore, TiDBMailboxStore, ensurePgMailboxSchema, ensureTiDBMailboxSchema } from "../plugins/mailbox-store-sql.js";
|
|
13
|
+
import { createDropOnlyRecipientLifecycle } from "../plugins/mailbox-recipient-lifecycle.js";
|
|
13
14
|
import { PgUsageWindowStore, TiDBUsageWindowStore, ensurePgUsageWindowSchema, ensureTiDBUsageWindowSchema } from "../plugins/usage-window-store-sql.js";
|
|
14
15
|
import { PgRosterStore, TiDBRosterStore, ensurePgRosterSchema, ensureTiDBRosterSchema } from "../plugins/roster-store-sql.js";
|
|
15
16
|
import { LocalTaskAttachmentStore } from "../plugins/local-task-attachment-store.js";
|
|
@@ -285,27 +286,34 @@ export async function openStores(ctx) {
|
|
|
285
286
|
logger.info("task_attachment_store_enabled", { backend: pgPool ? "pg+minio" : mysqlPool ? "tidb+minio" : "file", maxBytes: config.attachmentMaxBytes });
|
|
286
287
|
}
|
|
287
288
|
let mailboxStore;
|
|
289
|
+
let mailboxRecipientLifecycle;
|
|
288
290
|
{
|
|
289
291
|
const mysqlPool = backend?.mysqlPool?.();
|
|
290
292
|
const pgPool = backend?.pgPool?.();
|
|
291
293
|
if (pgPool) {
|
|
292
294
|
await ensurePgMailboxSchema(async (text, params) => pgPool.query(text, params));
|
|
293
|
-
|
|
295
|
+
const store = new PgMailboxStore(pgPool, logger);
|
|
296
|
+
mailboxStore = store;
|
|
297
|
+
mailboxRecipientLifecycle = store;
|
|
294
298
|
}
|
|
295
299
|
else if (mysqlPool) {
|
|
296
300
|
await ensureTiDBMailboxSchema(mysqlPool);
|
|
297
|
-
|
|
301
|
+
const store = new TiDBMailboxStore(mysqlPool, logger);
|
|
302
|
+
mailboxStore = store;
|
|
303
|
+
mailboxRecipientLifecycle = store;
|
|
298
304
|
}
|
|
299
305
|
else if (backend?.kind === "local") {
|
|
300
306
|
mailboxStore = new FileMailboxStore(config.localDataRoot ?? localRoot, {
|
|
301
307
|
onCorruptRead: (info) => logger.warn("mailbox_corrupt_read", { path: info.path, reason: info.reason }),
|
|
302
308
|
});
|
|
309
|
+
mailboxRecipientLifecycle = createDropOnlyRecipientLifecycle(mailboxStore);
|
|
303
310
|
}
|
|
304
311
|
if (mailboxStore) {
|
|
305
312
|
const laneVerdict = mailboxCrossProcessMountVerdict(mailboxStore);
|
|
306
313
|
logger.info("mailbox_store_enabled", {
|
|
307
314
|
backend: pgPool ? "pg" : mysqlPool ? "tidb" : "file",
|
|
308
315
|
crossProcessSafe: mailboxStore.crossProcessSafe,
|
|
316
|
+
recipientTombstones: pgPool !== undefined || mysqlPool !== undefined,
|
|
309
317
|
peerLaneMountable: laneVerdict.ok,
|
|
310
318
|
...(laneVerdict.ok ? {} : { peerLaneRefusal: laneVerdict.code }),
|
|
311
319
|
});
|
|
@@ -424,7 +432,7 @@ export async function openStores(ctx) {
|
|
|
424
432
|
.startRefresh()
|
|
425
433
|
: undefined;
|
|
426
434
|
return {
|
|
427
|
-
backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore,
|
|
435
|
+
backend, storeBackendDegraded, memoryEngine, memorySyncCursors, rosterStore, backgroundAgentStore, mailboxRecipientLifecycle,
|
|
428
436
|
taskAttachmentStore, mailboxStore, memoryExportBackend, memorySyncRunner, sessionStore, breakerState,
|
|
429
437
|
usageWindowStore, memoryPosture, taskListLane, sqlEngineFacts,
|
|
430
438
|
};
|
|
@@ -28,8 +28,12 @@
|
|
|
28
28
|
* ### 另一件如实:整条车道今日未挂
|
|
29
29
|
*
|
|
30
30
|
* 两个座位在引擎侧都**以 `RunnerDeps.peerDirectory` 在场为前提**(座缺席 ⇒ 跨会话车道整条不挂,
|
|
31
|
-
* 每个面与 pre-385 构建逐字节相同)
|
|
32
|
-
*
|
|
31
|
+
* 每个面与 pre-385 构建逐字节相同)。
|
|
32
|
+
*
|
|
33
|
+
* ✅ **7.91.0:目录席已接线**([ref],`boot/runner-deps.ts` 的三合取:会话枚举面 ∧ mailbox 店 ∧ 收件人生命周期面)
|
|
34
|
+
* —— 旧文字写的是「另一件欠账 …… 就位待命而非生效中」,如实订正:本文件铸的 managed 层在**有席的部署**上
|
|
35
|
+
* 真的在治理 drain 点的判决了;缺任一合取项的部署(典型 = 无耐久 mailbox 的单机形)上车道整条不挂,层仍是
|
|
36
|
+
* 就位待命。当初那句「目录席到货那天零改动即生效」已兑现:本文件一个字节未改。
|
|
33
37
|
*/
|
|
34
38
|
import type { CrossSessionDialogExpiry, CrossSessionInboundSetting, CrossSessionInboundSettingLayers } from "@sema-agent/core";
|
|
35
39
|
/** 引擎层形的层名 —— 从 core 的类型上取,**引擎加一层 = 本文件编译错**(下面两处闭集都锁在这个联合上)。 */
|
|
@@ -59,6 +59,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
59
59
|
const { fault: autoModeFault, ...autoMode } = await judgeAutoModeArming(deps.autoModeFace, { requestedMode, principal: gatedPrincipal(req, deps.config), entitlementSource, localDeny: deps.config.permissionsDisableAutoMode });
|
|
60
60
|
if (autoModeFault !== undefined)
|
|
61
61
|
deps.logger?.warn("auto_mode_face_resolver_fault", { fault: autoModeFault });
|
|
62
|
+
const rulesLaneReachable = Boolean(deps.ruleConsent) && gatedPrincipal(req, deps.config) !== undefined;
|
|
62
63
|
sendJson(res, 200, {
|
|
63
64
|
...deps.capabilities,
|
|
64
65
|
asyncRuns: Boolean(deps.runStore),
|
|
@@ -91,7 +92,8 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
91
92
|
memoryConsolidationDriver: Boolean(deps.memoryConsolidation) && deps.config.operatorPrincipals.length > 0,
|
|
92
93
|
memoryOptOutGrant: Boolean(deps.memoryOptOutGrant) && deps.config.operatorPrincipals.length > 0,
|
|
93
94
|
outcomeLedger: Boolean(deps.outcomeSink?.summary) && (deps.config.requirePrincipal !== true || deps.config.operatorPrincipals.length > 0),
|
|
94
|
-
permissionRules:
|
|
95
|
+
permissionRules: rulesLaneReachable,
|
|
96
|
+
permissionRulesWrite: rulesLaneReachable,
|
|
95
97
|
permissionRulesRevoke: Boolean(deps.ruleConsent),
|
|
96
98
|
respondFreeFormRules: true,
|
|
97
99
|
respondBatchRuleOffers: true,
|
|
@@ -150,6 +152,7 @@ export async function handleCapabilities(req, res, match, ctx) {
|
|
|
150
152
|
sideQuery: true,
|
|
151
153
|
agentRoster: Boolean(deps.backgroundAgentStore),
|
|
152
154
|
retention: retentionAdvertisement(deps.config),
|
|
155
|
+
peerLane: Boolean(deps.peerDirectory),
|
|
153
156
|
sessionList: Boolean(deps.sessionStorage?.listSessions ?? deps.runStore?.listSessions),
|
|
154
157
|
sessionSearch: Boolean(deps.sessionStorage?.listSessions ?? deps.runStore?.listSessions),
|
|
155
158
|
sessionFork: Boolean(deps.sessionStorage?.fork),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* [ref] 车二 + [ref] §2 —— **规则车道的 wire 面**([ref] §7 的导入半场 + [ref] 的撤销半场)。
|
|
3
3
|
*
|
|
4
|
+
* POST /v1/rules → 写一条**收紧**规则(deny/ask)进 durable 分区,单步、无票仪式
|
|
4
5
|
* POST /v1/rules/cc-import/prepare → 预览 + 一张**一次性、principal 绑定、有期、载荷绑定**的票
|
|
5
6
|
* POST /v1/rules/cc-import/redeem → 原子消费票 → confirm → 批量兑付进店
|
|
6
7
|
* GET /v1/rules → 列出自己名下**活着**的规则(keyset 分页,游标绑 rev)
|
|
@@ -11,6 +12,11 @@
|
|
|
11
12
|
* 因此**导入两口**只认已验明的 principal,连 `explicitOperatorOk` 的旁路都不给:一个 operator 想给
|
|
12
13
|
* 自己导规则,用他自己的 principal 走同一条路即可(那时他就是租户)。
|
|
13
14
|
*
|
|
15
|
+
* 🔴 **`POST /v1/rules` 是撤销口的对偶,不是导入口的简写**(S-511):它写的只有**收紧**方向
|
|
16
|
+
* (deny/ask),而收紧不需要同意仪式 —— 与撤销为什么不需要是同一条理由(core `removePersistedRule`
|
|
17
|
+
* 头注逐字:「a host may narrow on a user's behalf, it may not widen」)。放宽(allow)仍然只有两条路:
|
|
18
|
+
* 一张卡上的人点头,或一次显式的 settings 导入。方向表在 `permission-rule-vocab.ts`,不在本文件。
|
|
19
|
+
*
|
|
14
20
|
* 🔴 **撤销面两口多一条 operator 超范围域,而这不是上面那句话的例外,是它的对偶**([ref] §2 设计题
|
|
15
21
|
* 一裁定):`?principal=` / body 里的 `principal` 让 operator 读**或收回**任一租户的规则。方向决定授权:
|
|
16
22
|
* 收回是**收紧**(core 的 `removePersistedRule` 头注逐字:「a host may narrow on a user's behalf, it may
|
|
@@ -55,6 +61,11 @@ export declare const RULES_ROUTES: readonly [{
|
|
|
55
61
|
readonly id: "rules-list";
|
|
56
62
|
readonly path: "/v1/rules";
|
|
57
63
|
readonly methods: readonly ["GET"];
|
|
64
|
+
}, {
|
|
65
|
+
readonly id: "rules-write";
|
|
66
|
+
readonly path: "/v1/rules";
|
|
67
|
+
readonly credentialGated: readonly ["POST"];
|
|
68
|
+
readonly methods: readonly ["POST"];
|
|
58
69
|
}, {
|
|
59
70
|
readonly id: "rules-revoke";
|
|
60
71
|
readonly path: "/v1/rules";
|
|
@@ -3,11 +3,11 @@ import { sendJson, sendError, httpErrorCode } from "../send.js";
|
|
|
3
3
|
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
4
4
|
import { assertNever } from "../route-ctx.js";
|
|
5
5
|
import { createLogger } from "../../observability/logger.js";
|
|
6
|
-
import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, serializeRuleScope } from "../../rules-consent.js";
|
|
6
|
+
import { RULE_IMPORT_RETRY_AFTER_SEC, MAX_RULE_SCOPE_CHARS, parseRuleScope, sameWireRuleIdentity, serializeRuleScope } from "../../rules-consent.js";
|
|
7
7
|
import { CONTROL_AND_BIDI_CHARS } from "../../text-bidi.js";
|
|
8
8
|
import { HttpError } from "../../security.js";
|
|
9
9
|
import { MAX_RULE_TEXT_CHARS, RULE_BEHAVIORS } from "@sema-agent/core";
|
|
10
|
-
import { PERSISTED_RULE_MATCHES, isPersistedRuleTool } from "../../permission-rule-vocab.js";
|
|
10
|
+
import { PERSISTED_RULE_MATCHES, isPersistedRuleTool, isDirectWritableRuleBehavior } from "../../permission-rule-vocab.js";
|
|
11
11
|
const logger = createLogger();
|
|
12
12
|
export const RULES_CC_IMPORT_PREPARE_PATH = "/v1/rules/cc-import/prepare";
|
|
13
13
|
export const RULES_CC_IMPORT_REDEEM_PATH = "/v1/rules/cc-import/redeem";
|
|
@@ -58,7 +58,7 @@ const LOCAL_IMPORT_ROW_SHAPE_PIN = true;
|
|
|
58
58
|
void LOCAL_IMPORT_ROW_SHAPE_PIN;
|
|
59
59
|
const LocalImportPrepareBodySchema = z.object({ rows: z.array(LocalImportRowSchema).min(1) }).strict();
|
|
60
60
|
const LocalImportRedeemBodySchema = z.object({ ticket: TicketStringSchema }).strict();
|
|
61
|
-
const
|
|
61
|
+
const RuleIdentityBodySchema = z
|
|
62
62
|
.object({
|
|
63
63
|
behavior: z.enum(RULE_BEHAVIORS),
|
|
64
64
|
rule: z.string().min(1).max(1024),
|
|
@@ -118,6 +118,10 @@ export async function handleRules(req, res, match, ctx) {
|
|
|
118
118
|
await handleRuleRevoke(res, rawBody, principal, lane, deps.config.operatorPrincipals);
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
|
+
if (match.decl.id === "rules-write") {
|
|
122
|
+
await handleRuleWrite(res, rawBody, principal, lane, deps.config.operatorPrincipals);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
121
125
|
if (match.decl.id === "rules-local-import-prepare") {
|
|
122
126
|
const parsed = LocalImportPrepareBodySchema.safeParse(rawBody);
|
|
123
127
|
if (!parsed.success) {
|
|
@@ -242,8 +246,52 @@ async function handleRuleList(res, search, caller, lane, operatorPrincipals) {
|
|
|
242
246
|
: undefined;
|
|
243
247
|
sendJson(res, 200, { rules: page, rev: listing.rev, ...(nextCursor !== undefined ? { nextCursor } : {}) });
|
|
244
248
|
}
|
|
249
|
+
async function handleRuleWrite(res, rawBody, caller, lane, operatorPrincipals) {
|
|
250
|
+
const parsed = RuleIdentityBodySchema.safeParse(rawBody);
|
|
251
|
+
if (!parsed.success) {
|
|
252
|
+
sendError(res, 400, "request.body_shape", "body must be {behavior, rule, scope} with an optional principal");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const behavior = parsed.data.behavior;
|
|
256
|
+
if (!isDirectWritableRuleBehavior(behavior)) {
|
|
257
|
+
sendError(res, 400, "request.field_invalid", "this endpoint writes standing refusals only: behavior must be 'deny' or 'ask' — a standing approval is minted by answering a permission question or by importing settings, never by this call");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const scope = parseRuleScope(parsed.data.scope);
|
|
261
|
+
if (scope === undefined) {
|
|
262
|
+
sendError(res, 400, "request.body_shape", "scope must be 'global' or 'project:<root>' with a non-empty root");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const target = resolveTargetPrincipal(parsed.data.principal, caller, operatorPrincipals);
|
|
266
|
+
if (!target.ok) {
|
|
267
|
+
sendError(res, 403, "auth.operator_only", "writing another principal's rules is an operator-only action");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const written = await lane.writeRule({ principal: target.principal, behavior, rule: parsed.data.rule, scope });
|
|
271
|
+
switch (written.status) {
|
|
272
|
+
case "persisted":
|
|
273
|
+
case "no-op":
|
|
274
|
+
sendJson(res, 200, { status: written.status, rev: written.rev, rule: written.row });
|
|
275
|
+
return;
|
|
276
|
+
case "rejected":
|
|
277
|
+
logger.info("rule write refused", { reason: written.reason, detail: written.detail });
|
|
278
|
+
sendError(res, 400, "request.field_invalid", "this rule was not written — `reason` names which check refused it and `detail` says why", {
|
|
279
|
+
reason: written.reason,
|
|
280
|
+
detail: written.detail,
|
|
281
|
+
});
|
|
282
|
+
return;
|
|
283
|
+
case "indeterminate":
|
|
284
|
+
logger.info("rule write indeterminate", { detail: written.detail });
|
|
285
|
+
sendError(res, 503, "state.rule_write_failed", "this write could not be confirmed as a standing rule — retry, then reconcile with GET /v1/rules");
|
|
286
|
+
return;
|
|
287
|
+
default: {
|
|
288
|
+
const unknown = written;
|
|
289
|
+
throw new Error(`unknown rule write outcome: ${JSON.stringify(unknown)}`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
245
293
|
async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals) {
|
|
246
|
-
const parsed =
|
|
294
|
+
const parsed = RuleIdentityBodySchema.safeParse(rawBody);
|
|
247
295
|
if (!parsed.success) {
|
|
248
296
|
sendError(res, 400, "request.body_shape", "body must be {behavior, rule, scope} with an optional principal");
|
|
249
297
|
return;
|
|
@@ -264,7 +312,7 @@ async function handleRuleRevoke(res, rawBody, caller, lane, operatorPrincipals)
|
|
|
264
312
|
sendJson(res, 200, { status: "removed", rev: outcome.rev, stillLive: outcome.stillLive });
|
|
265
313
|
return;
|
|
266
314
|
case "no-op": {
|
|
267
|
-
const stillLive = (await lane.listRules(target.principal)).rows.some((r) => r.rule
|
|
315
|
+
const stillLive = (await lane.listRules(target.principal)).rows.some((r) => sameWireRuleIdentity(r, { behavior: parsed.data.behavior, rule: parsed.data.rule, scope: serializeRuleScope(scope) }));
|
|
268
316
|
sendJson(res, 200, { status: "no-op", rev: outcome.rev, stillLive });
|
|
269
317
|
return;
|
|
270
318
|
}
|
|
@@ -280,6 +328,7 @@ export const RULES_ROUTES = [
|
|
|
280
328
|
{ id: "rules-local-import-prepare", path: RULES_LOCAL_IMPORT_PREPARE_PATH, methods: ["POST"] },
|
|
281
329
|
{ id: "rules-local-import-redeem", path: RULES_LOCAL_IMPORT_REDEEM_PATH, methods: ["POST"] },
|
|
282
330
|
{ id: "rules-list", path: RULES_PATH, methods: ["GET"] },
|
|
331
|
+
{ id: "rules-write", path: RULES_PATH, credentialGated: ["POST"], methods: ["POST"] },
|
|
283
332
|
{ id: "rules-revoke", path: RULES_PATH, credentialGated: ["DELETE"], methods: ["DELETE"] },
|
|
284
333
|
{ prefix: "/v1/rules/cc-import/", credentialGated: ["POST"] },
|
|
285
334
|
{ prefix: "/v1/rules/local-import/", credentialGated: ["POST"] },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isUuidV7, legacyUnownedAmnestyOpen } from "../../security.js";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
3
|
import { filterFingerprint } from "../cursor-fingerprint.js";
|
|
4
|
+
import { sessionTempoOfRunStatus } from "../../plugins/store-contracts.js";
|
|
4
5
|
import { assertNever } from "../route-ctx.js";
|
|
5
6
|
export async function handleSessionsList(req, res, match, ctx) {
|
|
6
7
|
const { deps } = ctx;
|
|
@@ -120,7 +121,11 @@ async function handleSessionList(res, lister, query, forceOwner, includeUnowned
|
|
|
120
121
|
const page = hasMore ? rows.slice(0, limit) : rows;
|
|
121
122
|
const last = page[page.length - 1];
|
|
122
123
|
const nextCursor = hasMore && last ? encodeSessionCursor({ lastActivityAt: last.lastActivityAt, sessionId: last.sessionId }, f) : undefined;
|
|
123
|
-
|
|
124
|
+
const sessions = page.map((r) => {
|
|
125
|
+
const tempo = sessionTempoOfRunStatus(r.lastStatus);
|
|
126
|
+
return tempo === undefined ? r : { ...r, tempo };
|
|
127
|
+
});
|
|
128
|
+
sendJson(res, 200, { sessions, ...(nextCursor ? { nextCursor } : {}) });
|
|
124
129
|
}
|
|
125
130
|
export const SESSIONS_LIST_ROUTES = [
|
|
126
131
|
{ id: "sessions-list", path: "/v1/sessions", methods: ["GET"] },
|
package/dist/http/server.d.ts
CHANGED
|
@@ -163,6 +163,18 @@ export interface ServiceStoreDeps {
|
|
|
163
163
|
* `task_run` runs ledger. When it carries `listSessions`, GET /v1/sessions sources sessions from the session
|
|
164
164
|
* store (task_run only joins for run preview); `fork`/`deleteSession` enable the fork/delete routes. */
|
|
165
165
|
sessionStorage?: OwnerAwareSessionStore;
|
|
166
|
+
/**
|
|
167
|
+
* [ref] —— 跨会话车道的**发现席**(core `RunnerDeps.peerDirectory`),与 `RunnerDeps` 侧**同一个对象**
|
|
168
|
+
* (`boot/stage-10-http-server.ts` 直接递 `runnerDeps.peerDirectory`)。
|
|
169
|
+
*
|
|
170
|
+
* 🔴 HTTP 面**今天不读它的内容**:它在这里的唯一作用是让 `GET /v1/capabilities.peerLane` 那一位
|
|
171
|
+
* 「说 yes ⟺ 车道真的挂上了」成为**同一个值**的读取,而不是把那条三合取的装配谓词在路由里**重写一遍**
|
|
172
|
+
* (重写的那一份必然漂 —— 本仓能力位族的既有病,见 `routes/capabilities.ts` 里 `agentRoster` 的同款注)。
|
|
173
|
+
* ⚠️ 精确说法是**值** iff,不是键 present-iff:那一位与同族的 `sessionList` 一样是 `Boolean(...)`,
|
|
174
|
+
* 本席缺席时键仍在、值为 `false`(`test/capabilities-http.test.ts` 的桩部署那一格逐字钉着它)。
|
|
175
|
+
* 「键缺席」只发生在**老版本 worker** 上,那是版本事实,不是本部署的车道事实。
|
|
176
|
+
*/
|
|
177
|
+
peerDirectory?: import("@sema-agent/core").PeerDirectory;
|
|
166
178
|
/** S8 workflow-run store ([ref]/98): enables GET /v1/workflows (list) + /v1/workflows/:id (detail).
|
|
167
179
|
* Set when SELF_ORCHESTRATION_ENABLED. Live progress (/v1/workflows/:id/stream) is in-process
|
|
168
180
|
* (subscribeWorkflow) and works whenever self-orchestration is on, store or not. Runs are owner-gated by
|