@sema-agent/server 6.8.0 → 7.1.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/dist/boot/org-memory.d.ts +36 -0
- package/dist/boot/org-memory.js +76 -0
- package/dist/boot/resolve-spec.d.ts +6 -0
- package/dist/boot/resolve-spec.js +41 -11
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +11 -2
- package/dist/config-center/http-client.d.ts +1 -0
- package/dist/config-center/http-client.js +38 -14
- package/dist/config-types.d.ts +16 -1
- package/dist/config.js +18 -1
- package/dist/fleet/fleet-bus.d.ts +9 -0
- package/dist/fleet/fleet-bus.js +10 -0
- package/dist/http/routes/tasks.js +11 -3
- package/dist/http/server.js +8 -5
- package/dist/main.js +6 -1
- package/dist/memory-scope.d.ts +10 -1
- package/dist/memory-scope.js +22 -2
- package/dist/org-memory-admission.d.ts +96 -0
- package/dist/org-memory-admission.js +224 -0
- package/dist/question.d.ts +5 -0
- package/dist/question.js +7 -0
- package/dist/runs.d.ts +7 -0
- package/dist/runs.js +17 -3
- package/dist/runtime-governance.d.ts +14 -6
- package/dist/runtime-governance.js +27 -6
- package/dist/task-settings.d.ts +10 -8
- package/dist/task-settings.js +7 -19
- package/package.json +4 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/170 件A(#148 件3③)——org 记忆准入的装配点:把 org-memory-admission 模块接上 core 5.13.0
|
|
3
|
+
* 的两个 seam(`RunnerDeps.memoryScopeAdmission` / `RunnerDeps.deploymentMemoryScopes`)。
|
|
4
|
+
*
|
|
5
|
+
* 目录源三态(单选,授权面不做双源合并):
|
|
6
|
+
* · center 在场(configCenter 且非 dryRun)⇒ 远程腿:per-principal caps 响应的 `orgMemory` 段
|
|
7
|
+
* (fetchPrincipalCaps 已 schema 读+透传;段键缺席=旧 center 能力握手 ⇒ 目录记 section_absent 瞬时)。
|
|
8
|
+
* **独立 fetch,不复用 caps 缓存条目**(C3:org 授权段自带判别联合缓存,两张表的失败域互不干扰
|
|
9
|
+
* ——caps 腿的 deny 缓存/304 复用机制一旦共享,org 面的 gen 拒退与退避语义就会被 caps 语义污染)。
|
|
10
|
+
* 流量记账:每 principal 每 grant TTL(60s)至多多一拍 caps 拉取,有界。
|
|
11
|
+
* · 无 center 而 `MEMORY_ORG_DIRECTORY_JSON` 在场 ⇒ 单机腿:operator 自证静态表(此处装载=启动期,
|
|
12
|
+
* 坏表 parseOrgDirectoryStatic 直接 throw 拒启动——config 层因 A4 分层不校验,见 config.ts 注)。
|
|
13
|
+
* 两者都在 ⇒ center 胜,env 表忽略 + warn(授权面双源合并=语义地雷,显式拒)。
|
|
14
|
+
* · 都缺 ⇒ resolver 缺席:core 对 request-origin org scope 铸 `memory.admission_required`(fail-closed),
|
|
15
|
+
* deployment-origin 走 `deploymentMemoryScopes` 自证通道不受影响(单用户零迁移,v4 §0 伤害①)。
|
|
16
|
+
*
|
|
17
|
+
* 能力探测(C12,fail-loud):多租户(requirePrincipal)部署的 projects 登记簿里挂了 org 键 ——这些
|
|
18
|
+
* 键在多租户下按 request-origin 盖章(N2 部署形态维)——而目录源缺席(含 configCenter dryRun 形)
|
|
19
|
+
* ⇒ 启动报错:该部署的每个 projectId 请求都会在 prepare 期被整拒,「audit 在跑但恒拒」不是可运行
|
|
20
|
+
* 状态,响亮拒启动比静默全拒服务诚实(clay 三裁 [2687]:直接 BREAKING,不留兼容臂)。
|
|
21
|
+
*/
|
|
22
|
+
import type { RunnerDeps } from "@sema-agent/core";
|
|
23
|
+
import type { ServiceConfig } from "../config.js";
|
|
24
|
+
import type { Logger } from "../observability/logger.js";
|
|
25
|
+
import type { Metrics } from "../observability/metrics.js";
|
|
26
|
+
export interface OrgMemoryAdmissionWiring {
|
|
27
|
+
memoryScopeAdmission: RunnerDeps["memoryScopeAdmission"];
|
|
28
|
+
deploymentMemoryScopes: RunnerDeps["deploymentMemoryScopes"];
|
|
29
|
+
}
|
|
30
|
+
/** 依赖收窄到真实消费面(warn/inc 各一手)——接口隔离让测试用真形字面量,零铸形([2704] §1)。 */
|
|
31
|
+
export declare function createOrgMemoryAdmissionWiring(opts: {
|
|
32
|
+
config: ServiceConfig;
|
|
33
|
+
logger: Pick<Logger, "warn">;
|
|
34
|
+
metrics: Pick<Metrics, "inc">;
|
|
35
|
+
}): OrgMemoryAdmissionWiring;
|
|
36
|
+
//# sourceMappingURL=org-memory.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { fetchPrincipalCaps } from "../config-center/http-client.js";
|
|
2
|
+
import { createMemoryScopeAdmission, createOrgMemoryDirectory, parseOrgDirectoryStatic } from "../org-memory-admission.js";
|
|
3
|
+
const ORG_PREFIX = "org:";
|
|
4
|
+
/** 部署自证 org scope 集(core 准入门的 deployment origin 词表):env 钉的 MEMORY_SCOPE(org 形时)
|
|
5
|
+
* + **单用户部署**的 projects 登记簿 org 键(N2:requirePrincipal !== true 的部署无租户边界,operator
|
|
6
|
+
* 登记簿条目归 deployment)。多租户下登记簿 org 键**不入**此集——它们由 resolve-spec 按 request 盖章,
|
|
7
|
+
* 走目录判决。 */
|
|
8
|
+
function collectDeploymentOrgScopes(config) {
|
|
9
|
+
const scopes = new Set();
|
|
10
|
+
if (config.memoryScope !== undefined && config.memoryScope.startsWith(ORG_PREFIX))
|
|
11
|
+
scopes.add(config.memoryScope);
|
|
12
|
+
if (config.requirePrincipal !== true) {
|
|
13
|
+
for (const project of Object.values(config.projects)) {
|
|
14
|
+
for (const s of project.defaultScopes ?? [])
|
|
15
|
+
if (s.startsWith(ORG_PREFIX))
|
|
16
|
+
scopes.add(s);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...scopes];
|
|
20
|
+
}
|
|
21
|
+
/** 依赖收窄到真实消费面(warn/inc 各一手)——接口隔离让测试用真形字面量,零铸形([2704] §1)。 */
|
|
22
|
+
export function createOrgMemoryAdmissionWiring(opts) {
|
|
23
|
+
const { config, logger, metrics } = opts;
|
|
24
|
+
const deploymentMemoryScopes = collectDeploymentOrgScopes(config);
|
|
25
|
+
const centerLeg = config.configCenter !== undefined && !config.configCenter.dryRun ? config.configCenter : undefined;
|
|
26
|
+
const staticJson = config.memoryOrgDirectoryJson;
|
|
27
|
+
if (centerLeg !== undefined && staticJson !== undefined) {
|
|
28
|
+
logger.warn("org_memory_directory_env_ignored", {
|
|
29
|
+
reason: "MEMORY_ORG_DIRECTORY_JSON is set but a config-center is wired — the center directory wins; the env table is IGNORED (authorization faces never merge two sources)",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const directory = centerLeg !== undefined
|
|
33
|
+
? createOrgMemoryDirectory({
|
|
34
|
+
fetchSection: async (principal) => {
|
|
35
|
+
// etag 恒缺席 ⇒ fetchPrincipalCaps 永不走 304 臂(返回 null 的唯一途径是条件请求),
|
|
36
|
+
// 这里的 null 防御臂只为类型收口;C8 回声核与 schema 校验都在 fetchPrincipalCaps 内。
|
|
37
|
+
const r = await fetchPrincipalCaps(centerLeg.baseUrl, centerLeg.token, principal, undefined, fetch, centerLeg.worker);
|
|
38
|
+
if (r === null)
|
|
39
|
+
throw new Error("config-center returned 304 to a non-conditional org-directory fetch");
|
|
40
|
+
return r.orgMemory;
|
|
41
|
+
},
|
|
42
|
+
grantTtlMs: config.memoryOrgGrantTtlMs,
|
|
43
|
+
unavailableBackoffMs: config.memoryOrgUnavailableBackoffMs,
|
|
44
|
+
})
|
|
45
|
+
: staticJson !== undefined
|
|
46
|
+
? createOrgMemoryDirectory({
|
|
47
|
+
staticTable: parseOrgDirectoryStatic(staticJson), // 启动期 fail-loud(坏表拒启动)
|
|
48
|
+
grantTtlMs: config.memoryOrgGrantTtlMs,
|
|
49
|
+
unavailableBackoffMs: config.memoryOrgUnavailableBackoffMs,
|
|
50
|
+
})
|
|
51
|
+
: undefined;
|
|
52
|
+
// C12 能力探测:多租户 + 登记簿有 org 用面 + 目录源缺席 ⇒ 拒启动(见文件头注)。
|
|
53
|
+
if (directory === undefined && config.requirePrincipal === true) {
|
|
54
|
+
const orgProjects = Object.entries(config.projects)
|
|
55
|
+
.filter(([, p]) => (p.defaultScopes ?? []).some((s) => s.startsWith(ORG_PREFIX)))
|
|
56
|
+
.map(([id]) => id);
|
|
57
|
+
if (orgProjects.length > 0) {
|
|
58
|
+
throw new Error(`multi-tenant deployment has org: keys in projects[${orgProjects.join(", ")}].defaultScopes but NO org-memory directory source ` +
|
|
59
|
+
`(config-center absent/dry-run and MEMORY_ORG_DIRECTORY_JSON unset) — every request selecting these projects would be refused at prepare ` +
|
|
60
|
+
`(memory.admission_required, fail-closed). Wire a config-center or set MEMORY_ORG_DIRECTORY_JSON, or remove the org keys.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const memoryScopeAdmission = directory !== undefined
|
|
64
|
+
? createMemoryScopeAdmission(directory, {
|
|
65
|
+
mode: config.memoryOrgAdmissionMode,
|
|
66
|
+
// §5 观测:outcome 闭集 metric + 审计日志线(per-principal 归因走日志,principal 不入 metric label)。
|
|
67
|
+
onOutcome: (outcome, details) => {
|
|
68
|
+
metrics.inc("memory_admission_total", { outcome });
|
|
69
|
+
if (outcome !== "ok" || details !== undefined)
|
|
70
|
+
logger.warn("memory_admission_outcome", { outcome, ...details });
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
: undefined;
|
|
74
|
+
return { memoryScopeAdmission, deploymentMemoryScopes };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=org-memory.js.map
|
|
@@ -55,6 +55,12 @@ export interface ResolveSpecCtx {
|
|
|
55
55
|
root: string;
|
|
56
56
|
} | undefined;
|
|
57
57
|
durableEnabled: boolean;
|
|
58
|
+
/** #152 ([2703] 案二):活体 AskUserQuestion 面(QuestionCoordinator 的判决探针切面)。在场(=
|
|
59
|
+
* ASK_QUESTION_ENABLED)时 durable question 门按活流上下文分腿、spec 不再 stamp QUESTION_AWAITS_RESUME
|
|
60
|
+
* (否则 spec.onQuestion 恒遮蔽 RunnerDeps.onQuestion 的活人腿);缺席时行为与旧形逐字一致。 */
|
|
61
|
+
liveQuestionFace: {
|
|
62
|
+
hasLiveContext(): boolean;
|
|
63
|
+
} | undefined;
|
|
58
64
|
approvalExemptionStore: ReturnType<StoreBackend["approvalExemption"]> | undefined;
|
|
59
65
|
singleUserAutoAcceptBaseline: boolean;
|
|
60
66
|
checkpointStore: ReturnType<NonNullable<StoreBackend["checkpoint"]>> | undefined;
|
|
@@ -48,8 +48,27 @@ import { resolveRequestMcp } from "../task-mcp.js";
|
|
|
48
48
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, coercePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, providerDropsAppend, withPermissionMode } from "../task-settings.js";
|
|
49
49
|
import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
50
50
|
import { redactSecrets } from "../trace/redact.js";
|
|
51
|
+
/** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
|
|
52
|
+
* `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
|
|
53
|
+
* 活流腿(bg/SSE,coordinator.runWithContext 包裹,ALS 判)allow——工具执行落到 RunnerDeps.onQuestion
|
|
54
|
+
* 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade)ask——durable park
|
|
55
|
+
* 原语义逐字保留(此时执行只会拿到 headless 空答,park 才是把问题送到人面前的那条腿)。
|
|
56
|
+
* 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。 */
|
|
57
|
+
function durableQuestionPolicy(live) {
|
|
58
|
+
if (live === undefined)
|
|
59
|
+
return createDurableQuestionPolicy();
|
|
60
|
+
return {
|
|
61
|
+
check(req) {
|
|
62
|
+
if (req.toolName !== "AskUserQuestion")
|
|
63
|
+
return { action: "allow" };
|
|
64
|
+
return live.hasLiveContext()
|
|
65
|
+
? { action: "allow" }
|
|
66
|
+
: { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
51
70
|
export function createResolveSpec(ctx) {
|
|
52
|
-
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, } = ctx;
|
|
71
|
+
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, } = ctx;
|
|
53
72
|
// Deployment-owned mapping. Tools/prompt/skills come from the selected scenario (assembled once,
|
|
54
73
|
// bound per request); identity/session/policy stay server-side. Never taken from the request body.
|
|
55
74
|
// design/158 A8:从 createHttpServer 实参里提出来(原地占该字面量 1,093 行中的 967 行)——
|
|
@@ -677,7 +696,9 @@ export function createResolveSpec(ctx) {
|
|
|
677
696
|
// (`writeScope:null` — the engine materializes/reads but harvest commits nothing). Per-request (not a
|
|
678
697
|
// stored flag) → no new per-session state; the shell carries the toggle. RESUME re-runs resolveSpec
|
|
679
698
|
// from the persisted body, so a paused run stays paused across resume legs.
|
|
680
|
-
|
|
699
|
+
// 件A origin 盖章(#148 件3④):部署形态维随 requirePrincipal——多租户下登记簿种子(projectId
|
|
700
|
+
// 选定)按 request 盖章走 core 准入门,单用户一律 deployment(v4 §1 N2;零 org 键=整键缺席零迁移)。
|
|
701
|
+
memory: memoryEngine ? memorySpecForRequest(auth?.memoryScope, body.memoryWrite, s4DefaultScopes, { multiTenant: config.requirePrincipal === true }) : undefined,
|
|
681
702
|
// Scenario-provided capabilities (e.g. code-review = repo tools + reviewer subagents + prompt).
|
|
682
703
|
// RFC A2: the SelectEnvironment tool rides after the scenario's tools (spec.tools is ADDITIVE to core's
|
|
683
704
|
// built-in roster — prepare-task mounts first-party tools separately). Only when the image chain is live.
|
|
@@ -711,10 +732,11 @@ export function createResolveSpec(ctx) {
|
|
|
711
732
|
// operator with approval intent but no checkpointStore falls to `undefined` (core's UNGATED warning).
|
|
712
733
|
// Durable ask (TC-5.4, core 1.95): in durable mode an AskUserQuestion call suspends like an F4 gate —
|
|
713
734
|
// the question policy adjudicates it `ask`, the operator answers out-of-band, and the resume carries
|
|
714
|
-
// the QuestionAnswer (server.ts `body.answer` → onQuestion closure).
|
|
715
|
-
//
|
|
735
|
+
// the QuestionAnswer (server.ts `body.answer` → onQuestion closure). #152([2703] 案二): the question
|
|
736
|
+
// policy is LIVE-AWARE when the QuestionCoordinator is wired — a live-stream leg adjudicates `allow`
|
|
737
|
+
// (the live human answers over the stream) instead of unconditionally parking; see durableQuestionPolicy.
|
|
716
738
|
toolPolicy: durableEnabled
|
|
717
|
-
? combinePolicies(
|
|
739
|
+
? combinePolicies(durableQuestionPolicy(liveQuestionFace), createDurableAskPolicy({
|
|
718
740
|
requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
|
|
719
741
|
// The probe key is the CONTINUED session (auth.sessionId — the same id that keys the
|
|
720
742
|
// durable checkpoint /decide route). A fresh session (no body.sessionId) has no exemptions by
|
|
@@ -761,7 +783,11 @@ export function createResolveSpec(ctx) {
|
|
|
761
783
|
isCascade: body.cascade === true,
|
|
762
784
|
scope: encodeCheckpointScope(auth?.principal),
|
|
763
785
|
})),
|
|
764
|
-
|
|
786
|
+
// #152([2703] 案二):sentinel 只在活体面缺席时 stamp。core 取 `spec.onQuestion ?? deps.onQuestion`
|
|
787
|
+
// (spec 位恒赢),无条件 stamp 会把 ASK_QUESTION_ENABLED 部署的活人腿永久遮蔽成 durable park。
|
|
788
|
+
// 活体面在场 ⇒ 键缺席,RunnerDeps.onQuestion 的 coordinator 生效;park 与否由上面的判决门分腿。
|
|
789
|
+
// (suspend 侧的「未答 approve」拒绝不靠 sentinel:server.ts decide 路由 pre-CAS 400 守着。)
|
|
790
|
+
...(liveQuestionFace === undefined ? { onQuestion: QUESTION_AWAITS_RESUME } : {}),
|
|
765
791
|
}
|
|
766
792
|
: {}),
|
|
767
793
|
};
|
|
@@ -786,7 +812,13 @@ export function createResolveSpec(ctx) {
|
|
|
786
812
|
// per-task across suspend is a larger, separate decision that must cover the approval baseline too — tracked
|
|
787
813
|
// for center (adversarial-review HIGH-1), deliberately NOT a governance-only partial freeze here (that would
|
|
788
814
|
// be an inconsistent false-safety: approval would still be live).
|
|
789
|
-
|
|
815
|
+
// #153([2713] 分单②):MANUAL_MODE_SHELL_GATE 在此无条件施加(与 autonomy 同拍)——部署旋钮不再
|
|
816
|
+
// 依赖客户端 settings 在场性/模式臂/host lane(三重条件曾让 print/headless 腿与沙箱 lane 全静默失效)。
|
|
817
|
+
const governedBase = applyRuntimeGovernance(spec, {
|
|
818
|
+
autonomy: config.autonomy,
|
|
819
|
+
commandPolicy: config.commandPolicy,
|
|
820
|
+
...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
|
|
821
|
+
});
|
|
790
822
|
// Fold the client's per-request settings stamp onto the governed spec, TIGHTEN-ONLY (deny-wins) — AFTER
|
|
791
823
|
// the approval baseline + operator governance, so the order is deployment ⊇ operator ⊇ client (each can only
|
|
792
824
|
// narrow). tightenTaskSpec THROWS if the client settings would LOOSEN a stricter baseline → a 422 client error
|
|
@@ -864,10 +896,8 @@ export function createResolveSpec(ctx) {
|
|
|
864
896
|
// (deny 恒赢 —— 豁免/acceptDirs 越不过;集合取舍 core 成文,server 只透传 config 旋钮:
|
|
865
897
|
// 缺省 = core RECOMMENDED_SENSITIVE_PATTERNS,SENSITIVE_WRITE_PATTERNS 显式替换/off 关闭)。
|
|
866
898
|
...(config.sensitiveWritePatterns.length > 0 ? { sensitivePatterns: config.sensitiveWritePatterns } : {}),
|
|
867
|
-
// [1557]§四
|
|
868
|
-
//
|
|
869
|
-
// auto/acceptEdits 分支照旧不碰 TaskSpec.shellGate,零行为变化。
|
|
870
|
-
...(config.manualModeShellGate ? { shellGate: config.manualModeShellGate } : {}),
|
|
899
|
+
// [1557]§四 的 shellGate 旧家已随 #153 搬到 applyRuntimeGovernance(governance 层无条件施加,
|
|
900
|
+
// 见上方 governedBase)——wiring 不再承运该旋钮,FsWriteGateWiring.shellGate 字段一并拆除。
|
|
871
901
|
// [841]① / core 1.294 exemption seam:gate 产 ask 前查同一只 approval_exemption 店(与 decide
|
|
872
902
|
// remember="session" 的授予、ask 政策层探针同店同 canonical toolName 键空间)——「本会话不再询问」
|
|
873
903
|
// 对 fs-write 门同 turn 即时生效(父+继承子任务,ask 路径不再重入)。has() reject ⇒ core 按
|
|
@@ -49,6 +49,10 @@ export interface RunnerDepsCtx {
|
|
|
49
49
|
root: string;
|
|
50
50
|
} | undefined;
|
|
51
51
|
memorySyncRunner: MemorySyncRunner | undefined;
|
|
52
|
+
/** design/170 件A(#148 件3③):org 记忆准入两 seam 的装配产物(boot/org-memory.ts)。进**共享
|
|
53
|
+
* 基座**——主/sub 两 Runner 必须同源:委托子代的 prepare 同样跑准入(委托冻结的重判腿),漏挂
|
|
54
|
+
* subRunner=子代平面绕过目录判决。 */
|
|
55
|
+
orgMemoryAdmission: import("./org-memory.js").OrgMemoryAdmissionWiring;
|
|
52
56
|
toolResultStore: ToolResultStoreFull | undefined;
|
|
53
57
|
sessionPolicyStore: ReturnType<StoreBackend["sessionPolicy"]> | undefined;
|
|
54
58
|
runtimeCapsResolver: RunnerDeps["runtimeCapsResolver"];
|
|
@@ -68,7 +72,7 @@ export interface RunnerDepsCtx {
|
|
|
68
72
|
}
|
|
69
73
|
/** design/158 A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
|
|
70
74
|
* 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
|
|
71
|
-
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore">;
|
|
75
|
+
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes">;
|
|
72
76
|
/**
|
|
73
77
|
* design/158 A10 留档发现②(review 2026-07-29,[1543]§三族A 同源修补的延续):main runner 的
|
|
74
78
|
* `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
|
|
@@ -97,7 +101,7 @@ export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "models" | "roles" | "
|
|
|
97
101
|
* 属性,比再抽一层共享基座更强的同源保证),不重复收纳进这里。
|
|
98
102
|
*/
|
|
99
103
|
/** 基座真实消费的窄面(Pick)——subRunner 调用点(main.ts)只需凑这 13 个字段,不必造全量 ctx。 */
|
|
100
|
-
export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentHooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindowStore">;
|
|
104
|
+
export type SharedRunnerDepsCtx = Pick<RunnerDepsCtx, "config" | "brain" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "deploymentHooks" | "toolResultStore" | "sessionPolicyStore" | "usageWindowStore" | "orgMemoryAdmission">;
|
|
101
105
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
|
102
106
|
export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
|
|
103
107
|
//# sourceMappingURL=runner-deps.d.ts.map
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -49,6 +49,12 @@ export function createSharedRunnerDeps(ctx) {
|
|
|
49
49
|
// E6: operator-tightened session tool rules — core folds them into the ToolPolicy FIRST (subtract-only) for tasks
|
|
50
50
|
// carrying a sessionId (a delegated subagent has none → inherits no rules). Opt-in: undefined ⇒ no rules read.
|
|
51
51
|
sessionPolicyStore: ctx.sessionPolicyStore ? ctx.sessionPolicyStore : undefined,
|
|
52
|
+
// design/170 件A:org 记忆准入两 seam(core 5.13.0)。共享基座=主/sub 同源(委托子代 prepare 的
|
|
53
|
+
// 委托冻结重判腿同样过目录);resolver 缺席=core 对 request-origin org 铸 admission_required
|
|
54
|
+
// fail-closed,deployment 自证集空=键缺席(诚实缺席>空数组——core 按 Set 语义消费,空集与缺席
|
|
55
|
+
// 同义,但缺席让 deps 字面量不携带死键)。
|
|
56
|
+
memoryScopeAdmission: ctx.orgMemoryAdmission.memoryScopeAdmission ? ctx.orgMemoryAdmission.memoryScopeAdmission : undefined,
|
|
57
|
+
deploymentMemoryScopes: ctx.orgMemoryAdmission.deploymentMemoryScopes && ctx.orgMemoryAdmission.deploymentMemoryScopes.length > 0 ? ctx.orgMemoryAdmission.deploymentMemoryScopes : undefined,
|
|
52
58
|
// design/166-T1:治理窗两键**成对**且进共享基座——主/sub/hook 三 runner 全覆盖(漏挂 subRunner=
|
|
53
59
|
// 子代用量不进窗,治理可被 subagent 刷穿;双记不存在:core stats.nested 是旁路侧栏不折入
|
|
54
60
|
// stats.tokens,governance.commit 是 per-任务 delta 制)。store 只在 config.usageWindows 配置时
|
|
@@ -203,8 +209,11 @@ export function createRunnerDeps(ctx) {
|
|
|
203
209
|
// E23: the live-only inbound-elicitation seam. core invokes it only for servers that opted in via
|
|
204
210
|
// McpServerSpec.elicitation (default OFF) AND only when this is wired — both must hold (doubly fail-closed).
|
|
205
211
|
onElicit: elicitation ? elicitation.elicit : undefined,
|
|
206
|
-
// §4④: the live AskUserQuestion seam. core mounts the tool when onQuestion is present + routes each ask here
|
|
207
|
-
//
|
|
212
|
+
// §4④: the live AskUserQuestion seam. core mounts the tool when onQuestion is present + routes each ask here.
|
|
213
|
+
// #152([2703] 案二): with the coordinator wired the durable leg NO LONGER stamps spec.onQuestion
|
|
214
|
+
// (QUESTION_AWAITS_RESUME) — park-vs-live is decided per-leg by resolve-spec's live-aware question policy
|
|
215
|
+
// (live-stream leg → this coordinator; no-live-context leg → durable park). The sentinel is stamped only
|
|
216
|
+
// when the coordinator is absent (ASK_QUESTION_ENABLED off).
|
|
208
217
|
onQuestion: question ? question.question : undefined,
|
|
209
218
|
// [816]/[820]②: the live tool-approval seam (core `resolveAsk` — `spec.onAsk ?? deps.onAsk`). ALS-routed like
|
|
210
219
|
// onQuestion: a leg wrapped by the coordinator's runWithContext reaches the human.
|
|
@@ -38,6 +38,7 @@ export declare function fetchPrincipalCaps(baseUrl: string, token: string, princ
|
|
|
38
38
|
scenario?: ScenarioRuling;
|
|
39
39
|
execution?: ExecutionRuling;
|
|
40
40
|
executionDrift?: string;
|
|
41
|
+
orgMemory?: unknown;
|
|
41
42
|
etag?: string;
|
|
42
43
|
} | null>;
|
|
43
44
|
/** An HTTP error from a config-center fetch that carries the response status so callers can branch on it
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* facade re-exports every symbol below unchanged).
|
|
6
6
|
*/
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
|
-
import { readEffectiveWire } from "@sema-agent/registry-core";
|
|
8
|
+
import { readEffectiveWire, PrincipalCapsWire } from "@sema-agent/registry-core";
|
|
9
9
|
import { centerPromptsFromEffective } from "../prompts-domain-validate.js"; // #100 worker 腿 prompts 边界归一(中立叶)
|
|
10
10
|
/** GET the effective config (Bearer + ETag). null = 304 (unchanged). Throws on transport/HTTP error, and on a
|
|
11
11
|
* payload that is not an effective config at all(非对象 / version 非有限数 / gate 域坏形——[2281] 裁B)。
|
|
@@ -94,7 +94,22 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
94
94
|
}
|
|
95
95
|
if (!res.ok)
|
|
96
96
|
throw new Error(`config-center principal-caps HTTP ${res.status}`);
|
|
97
|
-
|
|
97
|
+
// 边界读=schema(clay 宪法 [2704] §2,同源锚=registry-core PrincipalCapsWire):治理面
|
|
98
|
+
// runtimeCaps/budget 严格+passthrough(值漂移整体拒 ⇒ 本函数 throw ⇒ resolver fail-close,
|
|
99
|
+
// 「outage 绝不静默 GRANT」同向);[565]/[580] fail-open 面与 orgMemory(C3 失败域隔离)在
|
|
100
|
+
// schema 里保持宽读,归一/校验仍在下方与目录腿(极性不因 schema 化翻转)。
|
|
101
|
+
const parsedBody = PrincipalCapsWire.safeParse(await res.json());
|
|
102
|
+
if (!parsedBody.success) {
|
|
103
|
+
throw new Error(`config-center principal-caps body failed schema validation: ${parsedBody.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
104
|
+
}
|
|
105
|
+
const body = parsedBody.data;
|
|
106
|
+
// design/170 件A C8(响应 principal 回声核,串租户防线一行):center 两条 ?principal= 腿的 body 都
|
|
107
|
+
// 回带 principal——在场且与请求不符 = 中间层/LB 把别人的 caps 视图递了过来,弃用整响应(throw ⇒
|
|
108
|
+
// caps 腿 fail-close、org 目录腿记 unavailable)。键缺席 = 旧 center 形,按无证据容忍(回声核只拦
|
|
109
|
+
// 「有回声且对不上」的确定性错递,不把旧形升级成故障)。
|
|
110
|
+
if (body.principal !== undefined && body.principal !== principal) {
|
|
111
|
+
throw new Error(`config-center principal-caps response echoes principal ${JSON.stringify(body.principal)} but ${JSON.stringify(principal)} was requested — discarding the whole response (cross-tenant delivery defense)`);
|
|
112
|
+
}
|
|
98
113
|
return {
|
|
99
114
|
runtimeCaps: body.runtimeCaps ?? null,
|
|
100
115
|
configured: Boolean(body.configured),
|
|
@@ -120,28 +135,37 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
120
135
|
// and a drifted `required` into silent non-enforcement. Now: drift ⇒ DROP the whole ruling (fail-open,
|
|
121
136
|
// gate passes) + `executionDrift` so the caller's audit line fires (the resolver routes it to onError).
|
|
122
137
|
...((() => {
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
// [2704] §1:unknown 面的窄化走运行时类型谓词(真检查),不走断言。
|
|
139
|
+
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
140
|
+
const exRaw = body.execution;
|
|
141
|
+
if (exRaw === undefined || exRaw === null)
|
|
125
142
|
return {};
|
|
126
|
-
if (
|
|
127
|
-
return { executionDrift: `execution
|
|
128
|
-
if (
|
|
129
|
-
return { executionDrift: `execution.
|
|
130
|
-
|
|
143
|
+
if (!isRecord(exRaw))
|
|
144
|
+
return { executionDrift: `execution is ${Array.isArray(exRaw) ? "array" : typeof exRaw}, expected object — ruling dropped (fail-open)` };
|
|
145
|
+
if (typeof exRaw.required !== "boolean")
|
|
146
|
+
return { executionDrift: `execution.required is ${typeof exRaw.required}, expected boolean — ruling dropped (fail-open)` };
|
|
147
|
+
if (!Array.isArray(exRaw.allowedLanes))
|
|
148
|
+
return { executionDrift: `execution.allowedLanes is ${typeof exRaw.allowedLanes}, expected string[] — ruling dropped (fail-open)` };
|
|
149
|
+
const execution = { required: exRaw.required, allowedLanes: exRaw.allowedLanes.filter((s) => typeof s === "string") };
|
|
131
150
|
// registry-core 0.10.1-0.10.2:execution.sessionMirror 宽读。observation-only 子面
|
|
132
151
|
// (server 非执法端,见 ExecutionRuling.sessionMirror 注),所以形状漂移只丢**本子面**并走 executionDrift
|
|
133
152
|
// 审计线(resolver 路由到 onError),**绝不**连带丢 lane ruling(那是真执法面,不能被观测子面拖垮)。
|
|
134
153
|
// required 缺省 false = registry-core zod 默认(便利态);engineUrl 必须非空 string。
|
|
135
|
-
if (
|
|
136
|
-
const sm =
|
|
137
|
-
|
|
138
|
-
if (smOk)
|
|
154
|
+
if (exRaw.sessionMirror !== undefined && exRaw.sessionMirror !== null) {
|
|
155
|
+
const sm = exRaw.sessionMirror;
|
|
156
|
+
if (isRecord(sm) && typeof sm.engineUrl === "string" && sm.engineUrl.length > 0 && (sm.required === undefined || typeof sm.required === "boolean")) {
|
|
139
157
|
execution.sessionMirror = { engineUrl: sm.engineUrl, required: sm.required === true };
|
|
140
|
-
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
141
160
|
return { execution, executionDrift: "execution.sessionMirror malformed — mirror observation dropped (fail-open; lane ruling kept)" };
|
|
161
|
+
}
|
|
142
162
|
}
|
|
143
163
|
return { execution };
|
|
144
164
|
})()),
|
|
165
|
+
// design/170 件A:org 记忆解析段**原值透传**(键在场保在场、缺席保缺席——缺席=旧 center 能力握手,
|
|
166
|
+
// org 目录腿据此判 section_absent 瞬时;形状校验是 org-memory-admission 模块用 PrincipalOrgMemoryWire
|
|
167
|
+
// 的事,本腿保持薄透传不预判)。
|
|
168
|
+
...("orgMemory" in body ? { orgMemory: body.orgMemory } : {}),
|
|
145
169
|
etag: res.headers.get("etag") ?? undefined,
|
|
146
170
|
};
|
|
147
171
|
}
|
package/dist/config-types.d.ts
CHANGED
|
@@ -205,6 +205,21 @@ export interface ServiceConfigFlat {
|
|
|
205
205
|
maxPushEntries?: number;
|
|
206
206
|
maxPullEntries?: number;
|
|
207
207
|
};
|
|
208
|
+
/** design/170 件A(clay 三裁 [2687]):org 记忆准入模式。`enforce`(默认)=判决即结果;`audit`=判决
|
|
209
|
+
* 照算但纯观察零行为变化(不整拒、不窄化 writeScope,只记 would-deny/would-narrow)——运维诊断位,
|
|
210
|
+
* 非发布步骤(audit-first 灰度步按裁2 免除)。env `MEMORY_ORG_ADMISSION_MODE`(enumEnv 二值)。 */
|
|
211
|
+
memoryOrgAdmissionMode: "audit" | "enforce";
|
|
212
|
+
/** 件A 单机形目录:env `MEMORY_ORG_DIRECTORY_JSON` 原文(`Record<principal, Record<org:scope, {write?}>>`)。
|
|
213
|
+
* config 解析期即整段校验(parseOrgDirectoryStatic,fail-loud);此处存**原文**(config=纯数据,
|
|
214
|
+
* Map 在装配点重建)。与 config-center 目录腿互斥的裁决在装配点(两者都在=center 腿胜,env 表忽略
|
|
215
|
+
* 并 warn——授权面不做双源合并)。 */
|
|
216
|
+
memoryOrgDirectoryJson?: string;
|
|
217
|
+
/** 件A 目录 granted(含负结果)缓存 TTL(ms)。新任务/新 resume 腿的准入判决滞后上限=此值(「TTL 即
|
|
218
|
+
* 有界 LKG」);与同仓保护面 grant TTL 对齐默认 60s。env `MEMORY_ORG_GRANT_TTL_MS`(numEnvBounded)。 */
|
|
219
|
+
memoryOrgGrantTtlMs: number;
|
|
220
|
+
/** 件A 目录 unavailable 退避窗(ms)——取数失败的退避语义(bound center hammering),不是策略负结果。
|
|
221
|
+
* 默认 10s。env `MEMORY_ORG_UNAVAILABLE_BACKOFF_MS`(numEnvBounded)。 */
|
|
222
|
+
memoryOrgUnavailableBackoffMs: number;
|
|
208
223
|
/** design/48 v1b + design/61: opt-in remote execution backend (PEER adapters, chosen by REMOTE_EXEC). Unset
|
|
209
224
|
* (OA/review deployments) → in-process stub env, zero behavior change. Secrets (apiKey/privateKey) ONLY from env.
|
|
210
225
|
* - `e2b` → per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
|
|
@@ -893,7 +908,7 @@ export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" |
|
|
|
893
908
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
894
909
|
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
895
910
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
896
|
-
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
911
|
+
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "memoryOrgAdmissionMode" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
897
912
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
|
898
913
|
* 函数不产出它,但它与 `autonomy` 是同一根治理棒的两半,归本组。 */
|
|
899
914
|
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
|
package/dist/config.js
CHANGED
|
@@ -987,6 +987,18 @@ function parseMemoryDomain(ctx) {
|
|
|
987
987
|
// design/158 B4 ② 的 D 家族四枚里,唯一落在 ServiceConfig 上的就是这枚(其余三枚只在使用点懒读,
|
|
988
988
|
// 预登记留在 orchestration 域 —— 顺序与改前一致:本枚的弃用通知仍先于那三枚)。
|
|
989
989
|
const projectMemoryEnabled = boolEnvWithLegacyNegated("PROJECT_MEMORY_ENABLED", "PROJECT_MEMORY_DISABLED", true);
|
|
990
|
+
// design/170 件A(clay 三裁 [2687]):org 记忆准入四旋钮。模式=enumEnv 二值非布尔(N9),默认
|
|
991
|
+
// enforce(裁2:audit-first 灰度步免除,audit 降级为运维诊断位);TTL/退避窗=numEnvBounded 启动期
|
|
992
|
+
// 报错(env-numeric-fail-loud 门,F6:optFinitePositiveEnv 的静默回默认恰是要消灭的形)。单机目录
|
|
993
|
+
// env 表在这里就整段校验(fail-loud 越早越好——一张半坏的授权表比拒绝启动更危险),config 上只存
|
|
994
|
+
// 原文(纯数据;Map 在装配点重建)。
|
|
995
|
+
const memoryOrgAdmissionMode = enumEnv("MEMORY_ORG_ADMISSION_MODE", "enforce", ["audit", "enforce"]);
|
|
996
|
+
// 原文存 config(纯数据);整段 fail-loud 校验(parseOrgDirectoryStatic)在装配点——它传递值引
|
|
997
|
+
// security.js(assertPrincipalShape),而 base config 层禁值引 auth 模块(design/158 A4 分层),
|
|
998
|
+
// 校验不能放这里。装配点仍在启动期,坏表照样拒启动。
|
|
999
|
+
const memoryOrgDirectoryJson = process.env.MEMORY_ORG_DIRECTORY_JSON || undefined;
|
|
1000
|
+
const memoryOrgGrantTtlMs = numEnvBounded("MEMORY_ORG_GRANT_TTL_MS", "60000", 1000, 3_600_000);
|
|
1001
|
+
const memoryOrgUnavailableBackoffMs = numEnvBounded("MEMORY_ORG_UNAVAILABLE_BACKOFF_MS", "10000", 500, 600_000);
|
|
990
1002
|
return {
|
|
991
1003
|
memoryEngineEnabled,
|
|
992
1004
|
memoryEngineDir: process.env.MEMORY_ENGINE_DIR || undefined,
|
|
@@ -1000,6 +1012,10 @@ function parseMemoryDomain(ctx) {
|
|
|
1000
1012
|
memoryEngineBackend,
|
|
1001
1013
|
memoryScope, // hoisted above (the 142-S2.5-W1 sync-scope default consumes it)
|
|
1002
1014
|
...(memorySync ? { memorySync } : {}),
|
|
1015
|
+
memoryOrgAdmissionMode,
|
|
1016
|
+
...(memoryOrgDirectoryJson !== undefined ? { memoryOrgDirectoryJson } : {}),
|
|
1017
|
+
memoryOrgGrantTtlMs,
|
|
1018
|
+
memoryOrgUnavailableBackoffMs,
|
|
1003
1019
|
projectMemoryEnabled, // design/113 C4 opt-out, flipped positive in design/158 B4 (3.0.0 起 legacy PROJECT_MEMORY_DISABLED = fail-loud 墓碑)
|
|
1004
1020
|
syncImportLeaseStaleSec: Math.max(0, numEnv("SYNC_IMPORT_LEASE_STALE_SEC", "600")),
|
|
1005
1021
|
};
|
|
@@ -1362,7 +1378,8 @@ const APPROVAL_GROUP_KEYS = [
|
|
|
1362
1378
|
];
|
|
1363
1379
|
const MEMORY_GROUP_KEYS = [
|
|
1364
1380
|
"memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
|
|
1365
|
-
"memorySync", "
|
|
1381
|
+
"memorySync", "memoryOrgAdmissionMode", "memoryOrgDirectoryJson", "memoryOrgGrantTtlMs", "memoryOrgUnavailableBackoffMs",
|
|
1382
|
+
"projectMemoryEnabled", "syncImportLeaseStaleSec",
|
|
1366
1383
|
];
|
|
1367
1384
|
const AUTH_GROUP_KEYS = [
|
|
1368
1385
|
"authToken", "authTokens", "allowUnauthedWrites", "corsOrigins", "principalHeader", "requirePrincipal", "autonomy",
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import type { TaskNotificationPayload } from "@sema-agent/core";
|
|
2
|
+
/** [2687-cli] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
3
|
+
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
4
|
+
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
5
|
+
* agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
|
|
6
|
+
* 终态映射折成 `failed`。**两侧都正向枚举**(新 task_type/新 status 默认不铸行=安全方向)。
|
|
7
|
+
* 与 core `isDelegatedAgentTerminal`(task-notification.js,未从包根导出)语义同源——core 根导出后
|
|
8
|
+
* 换装 import 删本镜像(提货件已上板)。 */
|
|
9
|
+
export declare function isFleetAgentTerminalNotification(n: Pick<TaskNotificationPayload, "task_type" | "status">): boolean;
|
|
1
10
|
/** The fleet vocabulary the shell renders (MF-Fleet `FleetTask.status`). The service maps its run/workflow status
|
|
2
11
|
* onto this neutral set; `awaiting approval` = a needs-review/plan-approval park, `waiting` = a durable suspend.
|
|
3
12
|
*
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -22,6 +22,16 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { EventEmitter } from "node:events";
|
|
24
24
|
import { redactSecrets } from "../trace/redact.js";
|
|
25
|
+
/** [2687-cli] 幽灵行案的单源判别:一条 `task_notification` 只有在 **agent 族 × 终态** 时才允许打
|
|
26
|
+
* `onChildTerminal`(fleet「subagent 树」只渲 agent 子代)。`background_bash`/`monitor`/`external`
|
|
27
|
+
* 不属 agent fleet 树——它们此前每条都打,fleet-bus 的「无 tick 无 claim」臂给 b\* 与 m\* handle 合成
|
|
28
|
+
* agent 形瞬态终帧,壳终态留存池把这帧渲 60s = footer 幽灵子行;monitor 的 `status:"event"` 还被
|
|
29
|
+
* 终态映射折成 `failed`。**两侧都正向枚举**(新 task_type/新 status 默认不铸行=安全方向)。
|
|
30
|
+
* 与 core `isDelegatedAgentTerminal`(task-notification.js,未从包根导出)语义同源——core 根导出后
|
|
31
|
+
* 换装 import 删本镜像(提货件已上板)。 */
|
|
32
|
+
export function isFleetAgentTerminalNotification(n) {
|
|
33
|
+
return n.task_type === "background_agent" && (n.status === "completed" || n.status === "failed" || n.status === "killed" || n.status === "cancelled");
|
|
34
|
+
}
|
|
25
35
|
/**
|
|
26
36
|
* The process-local fleet aggregation bus. Holds the current active set + fans out deltas to SSE subscribers.
|
|
27
37
|
* Upserts MERGE (a partial delta patches the existing row), so a publisher can emit just the field that changed
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runWithVerification, runCascade, uuidv7 } from "@sema-agent/core";
|
|
2
2
|
import { markChildrenStoppedByUserOnAbort, resumeAtHttpStatus, stripCheckpointToken, HEARTBEAT_MS } from "../../runs.js";
|
|
3
3
|
import { withPrincipal } from "../../observability/principal-context.js";
|
|
4
|
-
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../../fleet/fleet-bus.js";
|
|
4
|
+
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../../fleet/fleet-bus.js";
|
|
5
5
|
import { defaultSubagentTailBus, projectTailFrame } from "../../fleet/subagent-tail-bus.js";
|
|
6
6
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../../orchestration/workflow-completion-inbox.js";
|
|
7
7
|
import { createLedgerSink } from "../../trace/ledger-sink.js";
|
|
@@ -448,7 +448,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
448
448
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
449
449
|
}
|
|
450
450
|
// id-domain alias: flip by payload.sessionId (= the tick's uuid domain), fallback task_id.
|
|
451
|
-
|
|
451
|
+
// [2687-cli] 幽灵行案:只对 agent 族终态打(isFleetAgentTerminalNotification 单源判别,
|
|
452
|
+
// 病灶链见其 doc 注);bash/monitor 的通知帧/park 面照走。孪生:runs.ts bg 腿、http/server.ts resume 腿。
|
|
453
|
+
const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
|
|
452
454
|
// 🔴 对抗评审 2026-07-11(HIGH,「下一 turn 0 帧」最强根因):the teardown WINDOW — after the
|
|
453
455
|
// client disconnects (the user killed the turn → ac.abort → core reaps the bg child → THIS
|
|
454
456
|
// notification fires) but BEFORE the outer finally flips `syncLegLive=false` (several awaits sit
|
|
@@ -1075,7 +1077,13 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
1075
1077
|
if (resumeAtStatus) {
|
|
1076
1078
|
if (durableTaskId && deps.runStore)
|
|
1077
1079
|
await deps.runStore.setTerminal(durableTaskId, "failed", stripCheckpointToken(result), result.errorMessage ?? null);
|
|
1078
|
-
|
|
1080
|
+
// #148 件4: the transient org-memory refusal carries core's retry hint — forward it as
|
|
1081
|
+
// retryAfterSec (seconds, ceil; family shape = usage.window_exhausted). ONLY on the transient
|
|
1082
|
+
// code: a wait hint on a terminal 4xx would be a directional lie.
|
|
1083
|
+
const retryAfterSec = result.errorCode === "memory.admission_required" && result.retryAfterMs !== undefined
|
|
1084
|
+
? Math.max(1, Math.ceil(result.retryAfterMs / 1000))
|
|
1085
|
+
: undefined;
|
|
1086
|
+
return { status: resumeAtStatus, body: { errorCode: result.errorCode, error: result.errorMessage ?? "resume-at failed", ...(retryAfterSec !== undefined ? { retryAfterSec } : {}) } };
|
|
1079
1087
|
}
|
|
1080
1088
|
// Durable suspend on the sync path: PARK it as an async run + return a pollable taskId, NEVER the
|
|
1081
1089
|
// capability token. The submitter switches to GET /v1/runs/:id. Cost is recorded
|
package/dist/http/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { runInBackground, evictIfConflict, stripCheckpointToken, TurnAnchorCaptu
|
|
|
16
16
|
import { looksLikeJwt } from "@sema-agent/registry-core/api/auth-bridge";
|
|
17
17
|
import {} from "../orchestration/workflow-agent-steer.js";
|
|
18
18
|
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKey, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
19
|
-
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals } from "../fleet/fleet-bus.js";
|
|
19
|
+
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
|
|
20
20
|
import { defaultSubagentTailBus, projectTailFrame } from "../fleet/subagent-tail-bus.js";
|
|
21
21
|
import {} from "../observability/rate-limit.js";
|
|
22
22
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
@@ -1372,9 +1372,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1372
1372
|
if (deps.config.remoteExec)
|
|
1373
1373
|
for (const name of HAND_TOOL_NAMES)
|
|
1374
1374
|
availableTools.add(name);
|
|
1375
|
-
// AskUserQuestion (durable ask, TC-5.4) is ALSO not in spec.tools — core mounts it from `onQuestion
|
|
1376
|
-
// the
|
|
1377
|
-
// this it 422s on resume exactly like a hand tool would. Gated on
|
|
1375
|
+
// AskUserQuestion (durable ask, TC-5.4) is ALSO not in spec.tools — core mounts it from `onQuestion`
|
|
1376
|
+
// (#152: the QUESTION_AWAITS_RESUME sentinel when no live coordinator is wired, else RunnerDeps.onQuestion's
|
|
1377
|
+
// coordinator serves the seat). Without this it 422s on resume exactly like a hand tool would. Gated on
|
|
1378
|
+
// checkpointStore = durable mode is active.
|
|
1378
1379
|
if (deps.checkpointStore)
|
|
1379
1380
|
availableTools.add("AskUserQuestion");
|
|
1380
1381
|
// Workflow (codex round-2 on [1245]/[1248]②): `run_workflow` is ALSO mounted at the runner, never in
|
|
@@ -1775,7 +1776,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1775
1776
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
1776
1777
|
}
|
|
1777
1778
|
// id-domain alias: flip by payload.sessionId (= the tick's uuid domain), fallback task_id.
|
|
1778
|
-
|
|
1779
|
+
// [2687-cli] 幽灵行案:只对 agent 族终态打(isFleetAgentTerminalNotification 单源判别,
|
|
1780
|
+
// 病灶链见其 doc 注);bash/monitor 的通知帧/park 面照走。孪生:runs.ts bg 腿、routes/tasks.ts sync 腿。
|
|
1781
|
+
const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
|
|
1779
1782
|
const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
|
|
1780
1783
|
// diagnosability (rationale in runs.ts twin).
|
|
1781
1784
|
deps.logger?.info?.("task_notification_observed", { route: "resume", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
|
package/dist/main.js
CHANGED
|
@@ -46,6 +46,7 @@ import { createWorkflowOrchestration } from "./boot/workflow-orchestration.js";
|
|
|
46
46
|
import { createLiveCoordinators } from "./boot/coordinators.js";
|
|
47
47
|
import { createRuntimeCaps } from "./boot/runtime-caps.js";
|
|
48
48
|
import { createRunnerDeps, createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
49
|
+
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
49
50
|
import { createSessionFaces } from "./boot/session-faces.js";
|
|
50
51
|
import { createLeaderFace } from "./boot/leader.js";
|
|
51
52
|
import { installShutdownHandlers } from "./boot/shutdown.js";
|
|
@@ -183,13 +184,15 @@ async function main() {
|
|
|
183
184
|
const { elicitation, question, toolApproval, durableEnabled, sendUserFileEmitter, sendFileLedger, sendUserFileToolSpec } = createLiveCoordinators({ config, logger, backend, sendUserFileTaskEnvs });
|
|
184
185
|
// design/158 A10:per-principal caps 段搬到 src/boot/runtime-caps.ts(逐字)。
|
|
185
186
|
const { principalCaps, centerRuntimeCapsResolver, runtimeCapsResolver } = createRuntimeCaps({ config, logger });
|
|
187
|
+
// design/170 件A(#148 件3③):org 记忆准入装配(目录源三态选择+C12 能力探测,坏配置在此拒启动)。
|
|
188
|
+
const orgMemoryAdmission = createOrgMemoryAdmissionWiring({ config, logger, metrics });
|
|
186
189
|
// design/158 A10:RunnerDeps 装配段搬到 src/boot/runner-deps.ts(逐字;runStore 晚绑改取值,见该文件头注)。
|
|
187
190
|
const runnerDeps = createRunnerDeps({
|
|
188
191
|
config, logger, metrics, localRoot, promptSource: configCenter.promptSource, rosterStore, backgroundAgentStore, mailboxStore, usageWindowStore, brain,
|
|
189
192
|
pricing, tracer, outcomeSink, elicitation, question, toolApproval, sessionStore, memoryEngine,
|
|
190
193
|
memorySyncRunner, toolResultStore, sessionPolicyStore, runtimeCapsResolver, fileSnapshotStore,
|
|
191
194
|
executionEnvFactory, lspManager, fleetBus, deploymentHooks, workflowRunStore, workflowJournalStore,
|
|
192
|
-
workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion,
|
|
195
|
+
workflowAgentRegistry, workflowNotifyGate, workflowCompletionInbox, deliverWorkflowCompletion, orgMemoryAdmission,
|
|
193
196
|
getRunStore: () => runStore,
|
|
194
197
|
});
|
|
195
198
|
const runner = new Runner(runnerDeps);
|
|
@@ -330,6 +333,7 @@ async function main() {
|
|
|
330
333
|
toolResultStore,
|
|
331
334
|
sessionPolicyStore,
|
|
332
335
|
usageWindowStore,
|
|
336
|
+
orgMemoryAdmission,
|
|
333
337
|
}),
|
|
334
338
|
// ── 以下为 subRunner 差异键(不在共享基座;逐个有因)──────────────────────────────────────
|
|
335
339
|
sessionStore: subRunnerSessions, // 子代转录=私有短 TTL fork 路由店,生命周期异于宿主 durable 店
|
|
@@ -870,6 +874,7 @@ async function main() {
|
|
|
870
874
|
selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore,
|
|
871
875
|
singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage,
|
|
872
876
|
sessionEnvSelection,
|
|
877
|
+
liveQuestionFace: question, // #152:活体问答面在场 ⇒ durable question 门按活流分腿(见 ResolveSpecCtx 注)
|
|
873
878
|
});
|
|
874
879
|
const server = createHttpServer({ runner, config, resolveSpec, stores, coordinators, seams, observability, governance, deployment, knobs });
|
|
875
880
|
// D-D SLA-timer: wire the server's deny-sweep into the reaper holder declared above (the reaper is defined
|