@sema-agent/server 7.118.0 → 7.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +139 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +26 -13
- package/deploy/sema-up/README.md +2 -0
- package/deploy/sema-up/chart/templates/minio.yaml +2 -1
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
- package/deploy/sema-up/chart/templates/server.yaml +1 -0
- package/deploy/sema-up/chart/values.yaml +7 -1
- package/deploy/sema-up/cluster-up.sh +20 -8
- package/deploy/sema-up/kube-up.sh +29 -16
- package/deploy/sema-up/lib/buckets.sh +21 -0
- package/deploy/sema-up/lib/health.sh +33 -0
- package/deploy/sema-up/sema-up.sh +14 -9
- package/deploy/sema-up/smoke.sh +16 -6
- package/deploy/sema-up/stack/compose.yaml +1 -1
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
- package/dist/approval-ask-audit-store.js +2 -7
- package/dist/approval-card.d.ts +5 -5
- package/dist/approval.d.ts +51 -5
- package/dist/approval.js +76 -13
- package/dist/audit.d.ts +12 -1
- package/dist/audit.js +62 -2
- package/dist/auto-mode-face.d.ts +2 -3
- package/dist/bake-runner/main.js +1 -6
- package/dist/bake-runner/protocol.d.ts +18 -0
- package/dist/bake-runner/protocol.js +22 -2
- package/dist/bake-runner/runner.d.ts +12 -8
- package/dist/bake-runner/runner.js +5 -9
- package/dist/boot/config-center.js +6 -6
- package/dist/boot/coordinators.js +4 -1
- package/dist/boot/device-lane.d.ts +3 -3
- package/dist/boot/device-lane.js +1 -2
- package/dist/boot/execution-env.js +3 -2
- package/dist/boot/fatal-exit.d.ts +7 -1
- package/dist/boot/fatal-exit.js +2 -1
- package/dist/boot/leader.d.ts +4 -4
- package/dist/boot/leader.js +1 -2
- package/dist/boot/parked-revive-gate.d.ts +5 -1
- package/dist/boot/parked-revive-gate.js +8 -2
- package/dist/boot/resolve-spec.js +7 -4
- package/dist/boot/runner-deps.d.ts +4 -3
- package/dist/boot/runner-deps.js +7 -3
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/boot/stage-07-capability-layer.js +5 -1
- package/dist/boot/stage-10-http-server.js +3 -2
- package/dist/boot/stores.js +12 -7
- package/dist/capabilities/builtin-tools.d.ts +31 -8
- package/dist/capabilities/builtin-tools.js +22 -1
- package/dist/capabilities/center-plugins.d.ts +2 -0
- package/dist/capabilities/center-plugins.js +24 -6
- package/dist/capabilities/code-review-council.js +8 -11
- package/dist/capabilities/scenarios.d.ts +42 -13
- package/dist/capabilities/scenarios.js +36 -9
- package/dist/capabilities/send-user-file-tool.d.ts +6 -3
- package/dist/capabilities/send-user-file-tool.js +1 -1
- package/dist/config-catalog.js +5 -5
- package/dist/config-lkg.d.ts +33 -5
- package/dist/config-lkg.js +55 -11
- package/dist/config-types.d.ts +8 -1
- package/dist/config.js +11 -11
- package/dist/deployment-governance.d.ts +8 -3
- package/dist/deployment-governance.js +7 -4
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +4 -0
- package/dist/device-ws-hub.js +17 -9
- package/dist/device-ws-protocol.d.ts +15 -0
- package/dist/host-decision.d.ts +52 -27
- package/dist/host-decision.js +14 -7
- package/dist/http/admission.js +6 -6
- package/dist/http/resume-legs.js +6 -1
- package/dist/http/routes/approvals-assistant.d.ts +25 -3
- package/dist/http/routes/approvals-assistant.js +9 -5
- package/dist/http/routes/capabilities.js +5 -3
- package/dist/http/routes/devices.js +3 -3
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +13 -6
- package/dist/http/routes/runs.js +11 -5
- package/dist/http/routes/sessions.js +20 -16
- package/dist/http/wire-types.d.ts +12 -13
- package/dist/images/manifest.d.ts +7 -1
- package/dist/images/manifest.js +18 -16
- package/dist/list-entry-display.d.ts +14 -0
- package/dist/list-entry-display.js +14 -0
- package/dist/memory-store-refusal.d.ts +7 -12
- package/dist/memory-store-refusal.js +4 -5
- package/dist/namespaced-rule-name.d.ts +4 -3
- package/dist/namespaced-rule-name.js +2 -6
- package/dist/observability/fail-open.d.ts +9 -1
- package/dist/observability/fail-open.js +9 -1
- package/dist/operator-ask.d.ts +7 -6
- package/dist/plugins/approval-exemption-store.d.ts +4 -0
- package/dist/plugins/approval-exemption-store.js +1 -0
- package/dist/plugins/blob-backend.d.ts +13 -0
- package/dist/plugins/blob-backend.js +26 -0
- package/dist/plugins/local-session-store.d.ts +78 -47
- package/dist/plugins/local-session-store.js +152 -122
- package/dist/plugins/one-time-migrations.d.ts +14 -0
- package/dist/plugins/one-time-migrations.js +90 -3
- package/dist/plugins/permission-rule-store-file.js +2 -6
- package/dist/plugins/permission-rule-store-sql.d.ts +64 -6
- package/dist/plugins/permission-rule-store-sql.js +166 -59
- package/dist/plugins/remote-env-device.d.ts +12 -1
- package/dist/plugins/remote-env-device.js +13 -5
- package/dist/plugins/scheduler-support.d.ts +8 -4
- package/dist/plugins/scheduler-support.js +5 -5
- package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
- package/dist/plugins/snapshot-bucket-presence.js +40 -0
- package/dist/plugins/store-backend.d.ts +17 -6
- package/dist/plugins/store-backend.js +46 -16
- package/dist/plugins/store-open-wait.d.ts +33 -0
- package/dist/plugins/store-open-wait.js +73 -0
- package/dist/rule-entry-list.d.ts +0 -1
- package/dist/rule-entry-list.js +5 -10
- package/dist/rules-consent.d.ts +12 -3
- package/dist/rules-consent.js +5 -4
- package/dist/run-local.js +6 -3
- package/dist/security.d.ts +5 -3
- package/dist/security.js +4 -0
- package/dist/sema-config-dir.d.ts +19 -0
- package/dist/sema-config-dir.js +33 -0
- package/dist/session-policy-wire.js +10 -14
- package/dist/spec-fields.d.ts +7 -2
- package/dist/store-posture.d.ts +14 -6
- package/dist/store-posture.js +7 -4
- package/dist/task-settings.d.ts +30 -58
- package/dist/task-settings.js +6 -18
- package/dist/tool-approval.d.ts +97 -43
- package/dist/tool-approval.js +120 -31
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +16 -9
- package/dist/trace/redact.d.ts +6 -0
- package/dist/trace/redact.js +9 -9
- package/dist/trace/self-minted-wire.d.ts +15 -1
- package/dist/trace/self-minted-wire.js +2 -0
- package/dist/user-input-ledger.d.ts +19 -15
- package/dist/user-input-ledger.js +12 -20
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
- package/package.json +2 -2
|
@@ -1,19 +1,42 @@
|
|
|
1
|
-
import type { SubagentSpawnContext, ToolSpec } from "@sema-agent/core";
|
|
1
|
+
import type { SubagentSpawnContext, SubagentToolOptions, ToolSpec } from "@sema-agent/core";
|
|
2
2
|
/** Current time — the model has no clock; OA/most tasks need it. No side effects. CC-parity tool name `Now`
|
|
3
3
|
* (PascalCase, core 1.176 first-party rename wrap-up — the SERVICE owns this one; `nowTool` is not a core tool).
|
|
4
4
|
* effect:"read" so it never suspends → no durable-resume rename strand (no pending-tool checkpoint to canonicalize). */
|
|
5
5
|
export declare function nowTool(): ToolSpec;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* 委派子代逐次注入的**一组**工具:`tools` 是这组**可能**注入的全部工具对象,`forSpawn` 只在这些对象里选「这一次 spawn 拿不拿」(缺席 = 每次都拿)。
|
|
8
|
+
* 名册与工厂读的是同一张 `tools` —— 工厂只能交出名册里那几只对象,结构上交不出名册外的东西(core 7.46.0 #1432 的「声明不说谎」在本仓由形状保证,
|
|
9
|
+
* 不靠两张表对拍)。
|
|
10
|
+
*/
|
|
11
|
+
export interface SubagentExtraToolSet {
|
|
12
|
+
readonly tools: readonly ToolSpec[];
|
|
13
|
+
readonly forSpawn?: (ctx: SubagentSpawnContext) => boolean;
|
|
14
|
+
}
|
|
15
|
+
/** 委派工具上与逐次注入相关的两键(core `SubagentToolOptions` 的 `extraTools` / `extraToolNames`):工厂(本仓的恒同步)+ 它的名册。 */
|
|
16
|
+
export interface SubagentExtraToolOptions {
|
|
17
|
+
readonly extraTools: (ctx: SubagentSpawnContext) => ToolSpec[];
|
|
18
|
+
readonly extraToolNames: NonNullable<SubagentToolOptions["extraToolNames"]>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* #1110(core 7.32.0)—— 委派子代的**逐次注入**:`Now` + 部署的 extra tools(今天 = SendUserFile)。
|
|
8
22
|
*
|
|
9
23
|
* 为什么 `Now` 走这条通道:core 7.32.0 起子代按**身份**继承场景层 bundle 的工具(`assembleCodeTools` 交给 Agent 的
|
|
10
24
|
* `inheritedTools` ∩ 父 run 真挂载),而 `Now` 是本仓在 bundle **旁边**组的宿主工具(scenarios.ts),宿主工具**从不**被继承
|
|
11
25
|
* (core:继承整张 `spec.tools` 会把宿主工具带过宿主自己的逐次门)—— 逐次注入是它到达子代的唯一通道。一个工厂、两个产地:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
26
|
+
* 部署那一组的门(只读内建排除)在它自己的 `forSpawn` 里,这里只做合成。
|
|
27
|
+
*
|
|
28
|
+
* core 7.46.0 #1432 —— **工厂与名册一个来源**:返回委派工具的两键,`extraTools`(每次 spawn 交出 `forSpawn` 选中的那几只)与
|
|
29
|
+
* `extraToolNames`(全部可能交出的那几只的名字 / 别名 / 内容来源,从同一批 `ToolSpec` 读出)。名册在场 ⇒ 引擎重新能列出子代的工具池:
|
|
30
|
+
* 定义级 / 工具级 / `TaskSpec.agents` 清单里的退役拼法与大小写孪生回到开跑前拒(`config.agent.unknown_tool`),记忆内容来源读者按声明读这几只。
|
|
31
|
+
* 修前(core 7.32.0–7.45.x)工厂恒在场 ⇒ 池不可静态知 ⇒ 那一族拼法只告不拒、委派结果一律按「外部」读。
|
|
32
|
+
* 工厂**不抛**:各组的 `tools` 是装配期就建好的对象;部署 `forSpawn` 的抛由 core 收成「这一 spawn 什么都不继承 + 模型面披露句」(onExtraToolsError 记 warn)。
|
|
33
|
+
* 一种拼法出现在两只工具上(部署工具与 `Now` 撞名 / 别名撞)⇒ core 在建委派工具处抛 `config.extra_tool_names`(`shape`),不静默取其一。
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSubagentExtraTools(deployment?: SubagentExtraToolSet): SubagentExtraToolOptions;
|
|
36
|
+
/**
|
|
37
|
+
* `onExtraToolsError` 收到的错误 → 运维行 `subagent_extra_tools_error` 的字段:句子,加上错误**自带**的码与 `detail.reason`(在场才铸)。
|
|
38
|
+
* core 7.46.0 #1432 被拒的 spawn(产物不在名册里)带 `config.extra_tool_names` + `undeclared_name` / `undeclared_origin`;部署工厂自己抛的错多半两位都不带。
|
|
39
|
+
* 本仓不按码分支、不重铸 —— 只把错误说的话原样过境到运维面(修前只记句子,码与原因词丢在这一跳)。
|
|
17
40
|
*/
|
|
18
|
-
export declare function
|
|
41
|
+
export declare function extraToolsErrorFields(err: unknown): Record<string, unknown>;
|
|
19
42
|
//# sourceMappingURL=builtin-tools.d.ts.map
|
|
@@ -9,6 +9,27 @@ export function nowTool() {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export function createSubagentExtraTools(deployment) {
|
|
12
|
-
|
|
12
|
+
const sets = [{ tools: [nowTool()] }, ...(deployment !== undefined ? [deployment] : [])];
|
|
13
|
+
return {
|
|
14
|
+
extraTools: (ctx) => sets.flatMap((s) => (s.forSpawn === undefined || s.forSpawn(ctx) ? [...s.tools] : [])),
|
|
15
|
+
extraToolNames: sets.flatMap((s) => s.tools).map((t) => ({
|
|
16
|
+
name: t.name,
|
|
17
|
+
...(t.aliases !== undefined ? { aliases: [...t.aliases] } : {}),
|
|
18
|
+
...(t.contentOrigin !== undefined ? { contentOrigin: t.contentOrigin } : {}),
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function extraToolsErrorFields(err) {
|
|
23
|
+
const fields = { err: err instanceof Error ? err.message : String(err) };
|
|
24
|
+
if (err === null || typeof err !== "object")
|
|
25
|
+
return fields;
|
|
26
|
+
const code = Reflect.get(err, "code");
|
|
27
|
+
if (typeof code === "string")
|
|
28
|
+
fields.code = code;
|
|
29
|
+
const detail = Reflect.get(err, "detail");
|
|
30
|
+
const reason = detail !== null && typeof detail === "object" ? Reflect.get(detail, "reason") : undefined;
|
|
31
|
+
if (typeof reason === "string")
|
|
32
|
+
fields.reason = reason;
|
|
33
|
+
return fields;
|
|
13
34
|
}
|
|
14
35
|
//# sourceMappingURL=builtin-tools.js.map
|
|
@@ -97,6 +97,8 @@ export declare function materializablePlugins(eff: {
|
|
|
97
97
|
url: string;
|
|
98
98
|
sha?: string;
|
|
99
99
|
}>;
|
|
100
|
+
/** 总入口(条款 3/7/8):逐条物化 + 装载 + 让位合并。materialize 盘位 = `<cacheRoot>/<name>/live`
|
|
101
|
+
* (成功后原子 rename 替换,失败保留旧盘位 = LKG)。 */
|
|
100
102
|
export declare function applyCenterPlugins(baseline: LoadedSkill[], eff: {
|
|
101
103
|
plugins?: unknown;
|
|
102
104
|
}, opts: {
|
|
@@ -108,21 +108,39 @@ export function resolvePluginSkillsRoot(cloneRoot) {
|
|
|
108
108
|
}
|
|
109
109
|
return { ok: true, dir: candidate };
|
|
110
110
|
}
|
|
111
|
+
const PLUGINS_DOMAIN_FAILURE_NAME = "(plugins domain)";
|
|
111
112
|
function declaredPlugins(eff) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
const domain = eff.plugins;
|
|
114
|
+
if (domain === undefined)
|
|
115
|
+
return { ok: true, decls: [] };
|
|
116
|
+
if (Array.isArray(domain))
|
|
117
|
+
return { ok: true, decls: domain };
|
|
118
|
+
if (typeof domain === "object" && domain !== null) {
|
|
119
|
+
const inner = domain.plugins;
|
|
120
|
+
if (Array.isArray(inner))
|
|
121
|
+
return { ok: true, decls: inner };
|
|
122
|
+
return { ok: false, reason: `plugins domain shape rejected: expected an array or { plugins: [...] }, got an object whose plugins member is ${wireTypeWord(inner)}` };
|
|
123
|
+
}
|
|
124
|
+
return { ok: false, reason: `plugins domain shape rejected: expected an array or { plugins: [...] }, got ${wireTypeWord(domain)}` };
|
|
116
125
|
}
|
|
117
126
|
export function materializablePlugins(eff, allowHosts) {
|
|
118
|
-
|
|
127
|
+
const shape = declaredPlugins(eff);
|
|
128
|
+
if (!shape.ok)
|
|
129
|
+
return [];
|
|
130
|
+
return shape.decls.flatMap((decl) => {
|
|
119
131
|
const v = validatePluginDecl(decl, allowHosts);
|
|
120
132
|
return v.ok ? [{ name: v.name, url: v.url, ...(v.sha !== undefined ? { sha: v.sha } : {}) }] : [];
|
|
121
133
|
});
|
|
122
134
|
}
|
|
123
135
|
export async function applyCenterPlugins(baseline, eff, opts) {
|
|
124
|
-
const
|
|
136
|
+
const shape = declaredPlugins(eff);
|
|
125
137
|
const out = { skills: [...baseline], failures: [], letGo: [], lkgUsed: [] };
|
|
138
|
+
if (!shape.ok) {
|
|
139
|
+
opts.logger?.warn("center_plugins_domain_rejected", { reason: shape.reason });
|
|
140
|
+
out.failures.push({ plugin: PLUGINS_DOMAIN_FAILURE_NAME, reason: shape.reason });
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
const declared = shape.decls;
|
|
126
144
|
if (declared.length === 0)
|
|
127
145
|
return out;
|
|
128
146
|
const runGit = opts.runGit ?? (async (args) => {
|
|
@@ -35,8 +35,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
|
|
|
35
35
|
parameters: Type.Object({}),
|
|
36
36
|
execute: async (_args, ctx) => {
|
|
37
37
|
const active = pickLenses(opts.lensCount);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
38
|
+
const outcomes = await runLensReviews(subRunner, repoTools, objective, ctx.signal, active);
|
|
39
|
+
const lenses = outcomes.filter((o) => o.ok);
|
|
40
|
+
const failedLenses = outcomes.flatMap((o) => (o.ok ? [] : [{ lens: o.lens, reason: o.reason }]));
|
|
41
|
+
const findings = outcomes.map((o) => `### ${o.lens} reviewer\n${o.ok ? o.text : `(lens errored: ${o.reason})`}`).join("\n\n");
|
|
40
42
|
const converge = await runConvergence(subRunner, repoTools, objective, findings, opts, ctx.signal, active);
|
|
41
43
|
const totalTokens = lenses.reduce((s, l) => s + l.tokens, 0) + converge.tokens;
|
|
42
44
|
const totalTurns = lenses.reduce((s, l) => s + l.turns, 0) + converge.turns;
|
|
@@ -46,10 +48,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
|
|
|
46
48
|
opts.metrics?.inc("council_runs_total", { mode });
|
|
47
49
|
opts.metrics?.inc("task_tokens_total", {}, totalTokens);
|
|
48
50
|
opts.metrics?.observe("council_tokens", totalTokens);
|
|
49
|
-
opts.logger?.info("council_done", { mode, lenses: lenses.length, totalTokens, totalTurns });
|
|
51
|
+
opts.logger?.info("council_done", { mode, lenses: lenses.length, failedLenses, totalTokens, totalTurns });
|
|
50
52
|
return {
|
|
51
53
|
content: converge.review,
|
|
52
|
-
details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })) } },
|
|
54
|
+
details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })), failedLenses } },
|
|
53
55
|
};
|
|
54
56
|
},
|
|
55
57
|
};
|
|
@@ -65,16 +67,11 @@ async function runLensReviews(subRunner, repoTools, objective, signal, lenses) {
|
|
|
65
67
|
enableBlockedReport: false,
|
|
66
68
|
signal,
|
|
67
69
|
});
|
|
68
|
-
return { lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
|
|
70
|
+
return { ok: true, lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
|
|
69
71
|
}));
|
|
70
72
|
return settled.map((s, i) => s.status === "fulfilled"
|
|
71
73
|
? s.value
|
|
72
|
-
: {
|
|
73
|
-
lens: lenses[i].name,
|
|
74
|
-
text: `(lens errored: ${s.reason instanceof Error ? s.reason.message : String(s.reason)})`,
|
|
75
|
-
tokens: 0,
|
|
76
|
-
turns: 0,
|
|
77
|
-
});
|
|
74
|
+
: { ok: false, lens: lenses[i].name, reason: s.reason instanceof Error ? s.reason.message : String(s.reason) });
|
|
78
75
|
}
|
|
79
76
|
async function runConvergence(subRunner, repoTools, objective, findings, opts, signal, lenses) {
|
|
80
77
|
if (opts.debate) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type PromptProvider, type Runner, type SkillSpec, type
|
|
1
|
+
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type PromptProvider, type Runner, type SkillSpec, type TaskListStore, type ToolSpec, type WebSearchConfig } from "@sema-agent/core";
|
|
2
2
|
import type { Metrics } from "../observability/metrics.js";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
|
+
import { type SubagentExtraToolSet } from "./builtin-tools.js";
|
|
4
5
|
import { type RepoReadClient } from "./repo-tools.js";
|
|
5
6
|
import { type LoadedSkill } from "./skills.js";
|
|
6
7
|
import { type ScenarioHands } from "./hands-lane.js";
|
|
@@ -90,12 +91,13 @@ export interface ScenarioDeps {
|
|
|
90
91
|
* core 1.287 `SubagentToolOptions.extraTools` seam wire-through — per-SPAWN dynamic tool injection into the
|
|
91
92
|
* delegated (Agent-tool) child roster. Deployment-injected per-task tools (today: SendUserFile — its instance is
|
|
92
93
|
* execute-time late-bound via ctx.taskId → TaskEnvRegistry, so ONE spec serves every spawn) cannot ride the
|
|
93
|
-
* ASSEMBLY-time static `subagent.tools` pool;
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
94
|
+
* ASSEMBLY-time static `subagent.tools` pool; core evaluates the factory at each spawn and merges its product into the
|
|
95
|
+
* pool, then it goes through core's `resolveToolSubset` whitelist like any other tool (agent-def allowTools/denyTools
|
|
96
|
+
* still govern — no extra gate needed on our side). A SET, not a bare factory (core 7.46.0 #1432): its `tools` are both
|
|
97
|
+
* what a spawn can be handed and the roster the delegation tool declares (`extraToolNames`), so the factory cannot hand
|
|
98
|
+
* out a tool the roster does not name. Absent ⇒ the delegation tool injects `Now` alone.
|
|
97
99
|
*/
|
|
98
|
-
subagentExtraTools?:
|
|
100
|
+
subagentExtraTools?: SubagentExtraToolSet;
|
|
99
101
|
/**
|
|
100
102
|
* core 1.364 durable background agents([ref] S1,[ref] 提货单②写半场):后台子代 `a*` 行的
|
|
101
103
|
* durable 执行记录 store。**必须与 `RunnerDeps.backgroundAgentStore` 同实例**(读写双点配对,engine
|
|
@@ -174,7 +176,12 @@ export declare function semaIdentityProvider(): PromptProvider;
|
|
|
174
176
|
*/
|
|
175
177
|
export declare function codePromptProvider(brand: boolean): PromptProvider;
|
|
176
178
|
export declare function buildScenarios(deps: ScenarioDeps): Record<string, Scenario>;
|
|
177
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* Resolve the scenario bundle for a name. `undefined` = no preference ⇒ the built-in `default`; any OTHER name must be
|
|
181
|
+
* registered — an unregistered one is a caller bug and THROWS (it never falls back to `default`: a restricted principal's
|
|
182
|
+
* bundle must not silently widen). Request paths get their name from {@link gateScenarioRequest}, whose result is always
|
|
183
|
+
* registered, so the throw is unreachable from them; it is the loud arm for a direct caller that skipped the gate.
|
|
184
|
+
*/
|
|
178
185
|
export declare function selectScenario(scenarios: Record<string, Scenario>, name: string | undefined): Scenario;
|
|
179
186
|
/** web 渲染契约(定稿形状;knobs 现在没有=整字段省略)。 */
|
|
180
187
|
export interface ScenarioDetail {
|
|
@@ -259,16 +266,38 @@ export interface ScenarioRuling {
|
|
|
259
266
|
/** 纯场景名集合(principal/组名不上 wire);空 = 该 principal 无任何指派 = 无治理。 */
|
|
260
267
|
allowlist: string[];
|
|
261
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* 一个场景名的**来源**(闭集:新增一词 ⇒ {@link danglingScenarioRefusal} 的穷尽 switch 编译红)。
|
|
271
|
+
* · `ruling` —— 中心按主体指派的场景(`ScenarioRuling.scenario`);
|
|
272
|
+
* · `deployment` —— 部署自己写的缺省(`DEFAULT_SCENARIO` / config `defaultScenario`)。
|
|
273
|
+
* 请求体显式给的名字不在此列:那是调用方的话,不在册 ⇒ 400 `scenario_unknown`(见 {@link gateScenarioRequest})。
|
|
274
|
+
*/
|
|
275
|
+
export type ScenarioNameSource = "ruling" | "deployment";
|
|
276
|
+
/**
|
|
277
|
+
* 🔴 **悬空场景名的判据与拒句 —— 全仓唯一属主。**
|
|
278
|
+
*
|
|
279
|
+
* 指派 / 部署缺省指向不在册的名字,此前一路被折成 `default`(工具 / 提示 / skills / mcp 整包跟着换,零 warn):受限场景的主体被
|
|
280
|
+
* 悄悄放进更宽的 default。本函数把那条回退链换成两条出路,**没有第三条**:
|
|
281
|
+
* · `deployment` 悬空 ⇒ 起服拒启(`boot/stage-07-capability-layer.ts` 在 center 场景 overlay 之后调它、`throw new Error`);
|
|
282
|
+
* · `ruling` 悬空 ⇒ 该 run 503 `state.scenario_assignment_unknown`({@link gateScenarioRequest} 调它);
|
|
283
|
+
* · `deployment` 悬空到得了 run(只有自己装配 `resolveSpec`、没经过起服的嵌入方)⇒ 同码同判据同一句话 —— 起服拒启与这一条是同一个答案。
|
|
284
|
+
*
|
|
285
|
+
* 返回 `undefined` = 在册;否则是**拒句**(点名名字与来源、给在册词表、说怎么修)。判据按**自有键**(`known` 是场景表的
|
|
286
|
+
* `Object.keys`,`constructor` / `__proto__` 不是场景名)。
|
|
287
|
+
*/
|
|
288
|
+
export declare function danglingScenarioRefusal(name: string, source: ScenarioNameSource, known: readonly string[]): string | undefined;
|
|
262
289
|
/**
|
|
263
290
|
* resolveSpec 的执法入口(指派为默认、body.scenario 可换但限 allowlist)。返回本请求的
|
|
264
|
-
*
|
|
291
|
+
* **在册**生效场景名 —— 返回值一定在 `known` 里:下游直接按它取场景,没有「未知名 → default」的回退臂。
|
|
265
292
|
*
|
|
266
|
-
* - body.scenario
|
|
267
|
-
* ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空
|
|
268
|
-
* 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
|
|
293
|
+
* - body.scenario 显式给出:不在册 ⇒ typed 400 `scenario_unknown`(调用方的错);allowlist 非空且集外 ⇒ typed 拒绝
|
|
294
|
+
* (400 `scenario_not_allowed`,附 allowlist ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空
|
|
295
|
+
* (无 ruling / 该 principal 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
|
|
269
296
|
* 不收紧,恒不比没有治理时更宽;caps 故障不把用户锁死在 default——场景切换不是权限放大面,fail-open
|
|
270
297
|
* 到现状,与 workflows 面的 fail-closed 是刻意的 posture 差异,见 runtime-caps-resolver)。
|
|
271
|
-
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario
|
|
298
|
+
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。链上的名字不在册 ⇒ 503
|
|
299
|
+
* `state.scenario_assignment_unknown`(部署的错 / 中心的错,不是调用方的错),**不**折 default
|
|
300
|
+
* ({@link danglingScenarioRefusal})。`principal` 只进那一行运维日志,不参与判定。
|
|
272
301
|
*/
|
|
273
|
-
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger): string;
|
|
302
|
+
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger, principal?: string): string;
|
|
274
303
|
//# sourceMappingURL=scenarios.d.ts.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
|
-
import { HttpError } from "../security.js";
|
|
3
|
-
import { createSubagentExtraTools, nowTool } from "./builtin-tools.js";
|
|
2
|
+
import { HttpError, createCodedHttpError } from "../security.js";
|
|
3
|
+
import { createSubagentExtraTools, extraToolsErrorFields, nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
5
5
|
import { createCouncilTool, clampRounds } from "./code-review-council.js";
|
|
6
6
|
import { stablePrompt } from "./prompt.js";
|
|
@@ -70,9 +70,9 @@ export function buildScenarios(deps) {
|
|
|
70
70
|
...(deps.checkpointStore ? { checkpointStore: deps.checkpointStore } : {}),
|
|
71
71
|
...(deps.ensureChildSessionDurable ? { ensureChildSessionDurable: deps.ensureChildSessionDurable } : {}),
|
|
72
72
|
},
|
|
73
|
-
|
|
73
|
+
...createSubagentExtraTools(deps.subagentExtraTools),
|
|
74
74
|
...(deps.logger
|
|
75
|
-
? { onExtraToolsError: (err, info) => deps.logger?.warn?.("subagent_extra_tools_error", { ...info,
|
|
75
|
+
? { onExtraToolsError: (err, info) => deps.logger?.warn?.("subagent_extra_tools_error", { ...info, ...extraToolsErrorFields(err) }) }
|
|
76
76
|
: {}),
|
|
77
77
|
},
|
|
78
78
|
...(webSearch ? { webSearch } : {}),
|
|
@@ -120,8 +120,12 @@ export function buildScenarios(deps) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
export function selectScenario(scenarios, name) {
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
if (name === undefined)
|
|
124
|
+
return scenarios.default;
|
|
125
|
+
const picked = Object.hasOwn(scenarios, name) ? scenarios[name] : undefined;
|
|
126
|
+
if (picked === undefined)
|
|
127
|
+
throw new Error(`scenario "${name}" is not registered — resolve a request's scenario through gateScenarioRequest, which only returns registered names`);
|
|
128
|
+
return picked;
|
|
125
129
|
}
|
|
126
130
|
function availabilityFields(deps, toolset) {
|
|
127
131
|
const verdict = scenarioAvailability(deps, toolset);
|
|
@@ -295,11 +299,28 @@ export function mergeUserSkills(scenarioSkills, userSkills, logger) {
|
|
|
295
299
|
logger?.warn("user_skills_dropped", { reason: "name reserved by scenario (or duplicate)", names: dropped });
|
|
296
300
|
return out;
|
|
297
301
|
}
|
|
298
|
-
|
|
302
|
+
function registeredScenariosClause(known) {
|
|
303
|
+
return `this deployment's scenarios are: ${known.join(", ")}`;
|
|
304
|
+
}
|
|
305
|
+
export function danglingScenarioRefusal(name, source, known) {
|
|
306
|
+
if (known.includes(name))
|
|
307
|
+
return undefined;
|
|
308
|
+
switch (source) {
|
|
309
|
+
case "ruling":
|
|
310
|
+
return `the scenario assigned to this principal, "${name}", is not a scenario of this deployment — ${registeredScenariosClause(known)}; nothing ran — re-point the assignment (or add that scenario to this deployment) and retry`;
|
|
311
|
+
case "deployment":
|
|
312
|
+
return `DEFAULT_SCENARIO "${name}" does not name a scenario of this deployment — ${registeredScenariosClause(known)}; set DEFAULT_SCENARIO to one of them`;
|
|
313
|
+
default: {
|
|
314
|
+
const unhandled = source;
|
|
315
|
+
return unhandled;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known, logger, principal) {
|
|
299
320
|
if (typeof bodyScenario === "string") {
|
|
300
321
|
const requested = bodyScenario;
|
|
301
322
|
if (!known.includes(requested)) {
|
|
302
|
-
throw new HttpError(400, `unknown scenario "${bodyScenario}" —
|
|
323
|
+
throw new HttpError(400, `unknown scenario "${bodyScenario}" — ${registeredScenariosClause(known)}`, {
|
|
303
324
|
code: "scenario_unknown",
|
|
304
325
|
extra: { known: [...known] },
|
|
305
326
|
});
|
|
@@ -313,6 +334,12 @@ export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known
|
|
|
313
334
|
}
|
|
314
335
|
return requested;
|
|
315
336
|
}
|
|
316
|
-
|
|
337
|
+
const [name, source] = ruling?.scenario != null ? [ruling.scenario, "ruling"] : [defaultScenario, "deployment"];
|
|
338
|
+
const refusal = danglingScenarioRefusal(name, source, known);
|
|
339
|
+
if (refusal !== undefined) {
|
|
340
|
+
logger?.warn("scenario_assignment_unknown", { scenario: name, source, ...(principal !== undefined ? { principal } : {}) });
|
|
341
|
+
throw createCodedHttpError(503, "state.scenario_assignment_unknown", refusal, { detail: { scenario: name, source } });
|
|
342
|
+
}
|
|
343
|
+
return name;
|
|
317
344
|
}
|
|
318
345
|
//# sourceMappingURL=scenarios.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ToolSpec } from "@sema-agent/core";
|
|
2
2
|
import type { IssuedFileLink } from "../plugins/send-user-file.js";
|
|
3
|
+
import type { SubagentExtraToolSet } from "./builtin-tools.js";
|
|
3
4
|
/** The frame the web/shell renders as a file card. Rides the durable events tail (replayable). */
|
|
4
5
|
export interface FileLinkFrame {
|
|
5
6
|
type: "file_link";
|
|
@@ -44,7 +45,9 @@ export declare function sendUserFileTool(deps: {
|
|
|
44
45
|
}): ToolSpec;
|
|
45
46
|
export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
46
47
|
/**
|
|
47
|
-
*
|
|
48
|
+
* The deployment's group for the delegated-child injection (`createSubagentExtraTools` in `builtin-tools.ts` composes it with
|
|
49
|
+
* `Now` into the delegation tool's `extraTools` factory AND its `extraToolNames` roster — one list, read twice): SendUserFile,
|
|
50
|
+
* handed to every spawn except a read-only builtin agent's.
|
|
48
51
|
*
|
|
49
52
|
* F2 (MED, codex; a product judgment of this server, kept): read-only builtin agents (Explore/Plan) must NOT get
|
|
50
53
|
* SendUserFile — a read-only agent that can read sensitive files AND mint public links off them breaks the read-only
|
|
@@ -59,5 +62,5 @@ export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
|
59
62
|
*/
|
|
60
63
|
export declare function subagentSendUserFileExtraTools(deps: {
|
|
61
64
|
spec: ToolSpec;
|
|
62
|
-
}):
|
|
65
|
+
}): SubagentExtraToolSet;
|
|
63
66
|
//# sourceMappingURL=send-user-file-tool.d.ts.map
|
|
@@ -94,6 +94,6 @@ export function sendUserFileTool(deps) {
|
|
|
94
94
|
export const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
95
95
|
const READONLY_BUILTIN_AGENT_TYPES = new Set(builtinAgentDefinitions().map((d) => d.name));
|
|
96
96
|
export function subagentSendUserFileExtraTools(deps) {
|
|
97
|
-
return (ctx) => (ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType)
|
|
97
|
+
return { tools: [deps.spec], forSpawn: (ctx) => !(ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType)) };
|
|
98
98
|
}
|
|
99
99
|
//# sourceMappingURL=send-user-file-tool.js.map
|
package/dist/config-catalog.js
CHANGED
|
@@ -94,7 +94,7 @@ export const CONFIG_CATALOG = [
|
|
|
94
94
|
r("CONFIG_CATALOG_ENABLED", "auth", "boolean", "本目录端点 GET /v1/config/catalog 的旋钮(operator-only 面;合规部署可关 ⇒ 路径 404 + 能力位翻假)", { staticDefault: "true", danger: SEC, configKey: "configCatalogEnabled" }),
|
|
95
95
|
r("CONFIG_LKG_DURABLE", "integrations", "boolean", "LKG 跨重启 handoff(严格 'true' 才开:重启先用 LKG 起服,tier 候选借此落地)", { staticDefault: "false" }),
|
|
96
96
|
r("CONFIG_LKG_ENABLED", "integrations", "boolean", "last-known-good 配置盘缓存读写开关(仅远程 provider 有意义)", { staticDefault: "true" }),
|
|
97
|
-
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径", { derivedDefaultNote: "缺席 ⇒ SEMA_CONFIG_DIR
|
|
97
|
+
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径(设了 ⇒ 派生读拒行:这条路径与它的真实落点各一行,按路径段串匹配 —— 任意深度出现即拒、大小写不敏感,用户仓里同段串的路径一并读不到;空串拒启)", { derivedDefaultNote: "缺席 ⇒ 配置根(SEMA_CONFIG_DIR,缺省 ~/.sema)下按 worker + 凭证身份分箱派生", configKey: "configLkgPath" }),
|
|
98
98
|
r("CONFIG_LOCAL_DIR", "integrations", "path", "本地配置源根(config.d/<domain>.json);兼作数据根回落链一环", { configKey: "configLocalDir", danger: DATA }),
|
|
99
99
|
r("CONFIG_PROVIDER", "integrations", "enum", "配置源双模选择(闭集,逐字比较;认不出的词 ⇒ 拒启,S-841)", { enumValues: CONFIG_PROVIDER_WORDS, derivedDefaultNote: "缺席:SEMA_REGISTRY_URL 在场 ⇒ remote,否则纯 env(无管道)", configKey: "configProvider" }),
|
|
100
100
|
r("CONFIG_REQUIRE_ROSTER", "integrations", "boolean", "远程 registry 非 dryRun 时布防 ready 门:roster 未落地 ⇒ 计费提交 503 + /health ready:false(严格 'true')", { staticDefault: "false" }),
|
|
@@ -104,7 +104,7 @@ export const CONFIG_CATALOG = [
|
|
|
104
104
|
r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
|
|
105
105
|
r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: STORE_BACKENDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
|
|
106
106
|
r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
|
|
107
|
-
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
|
|
107
|
+
r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回);必须是本部署在册的场景名(内建 ∪ 配置中心声明的),不在册 ⇒ 拒启", { staticDefault: "code", configKey: "defaultScenario" }),
|
|
108
108
|
r("DELEGATION_MAX_CONCURRENT", "orchestration", "number", "委派入口并发帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 20)" }),
|
|
109
109
|
r("DELEGATION_MAX_PER_SESSION", "orchestration", "number", "委派入口每会话累计帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 200)" }),
|
|
110
110
|
r("DEVICE_DELIVERY_TIMEOUT_MS", "orchestration", "number", "device 车道等在途槽上限(ms;到期 device.busy)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 10s" }),
|
|
@@ -391,7 +391,7 @@ export const CONFIG_CATALOG = [
|
|
|
391
391
|
r("SELF_ORCHESTRATION_MODELS", "orchestration", "csv", "workflow agent({modelName}) 显式词表(恒赢;缺席单用户=目录键全集/多租户 fail-closed)", { configKey: "selfOrchestrationModels" }),
|
|
392
392
|
r("SELF_ORCHESTRATION_WORKER_ISOLATION", "orchestration", "boolean", "workflow 脚本跑 worker_thread(堆帽+terminate 的 DoS 隔离)", { staticDefault: "false", danger: SEC, configKey: "selfOrchestrationWorkerIsolation" }),
|
|
393
393
|
r("SEMA_APT_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 apt 源地址(注入沙箱 pkg-source hook)"),
|
|
394
|
-
r("SEMA_CONFIG_DIR", "integrations", "path", "~/.sema 配置目录覆写(LKG/skill 缓存/scheduler
|
|
394
|
+
r("SEMA_CONFIG_DIR", "integrations", "path", "~/.sema 配置目录覆写(LKG/skill 缓存/scheduler 店同门;设了 ⇒ 起服时建出目录,交引擎在根下同护配置文件,能不能用由引擎判;空串拒启)", { derivedDefaultNote: "缺席 ⇒ ~/.sema", configKey: "agentConfigDir" }),
|
|
395
395
|
r("SEMA_FAILOPEN_PROBE", "observability", "path", "F 类 fail-open 命中的探针文件路径(在场时逐次追加 fail-open 行;缺席只走计数+一次性 warn)"),
|
|
396
396
|
r("SEMA_NODE_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 node 分发镜像地址"),
|
|
397
397
|
r("SEMA_NPM_REGISTRY", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 npm registry 地址"),
|
|
@@ -411,7 +411,7 @@ export const CONFIG_CATALOG = [
|
|
|
411
411
|
r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
|
|
412
412
|
r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
|
|
413
413
|
r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
|
|
414
|
-
r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto
|
|
414
|
+
r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=配了该引擎的坐标就用那只库、没配就是内存;配了而此刻连不上 ⇒ 在 STORE_LOCK_WAIT_MS 窗内重试、到窗拒启,绝不落内存;回显=boot 解析后的真引擎)", { enumValues: SESSION_BACKENDS, danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
|
|
415
415
|
r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
|
|
416
416
|
r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
|
|
417
417
|
r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
|
|
@@ -431,7 +431,7 @@ export const CONFIG_CATALOG = [
|
|
|
431
431
|
r("SSH_PORT", "orchestration", "number", "ssh 端口", { derivedDefaultNote: "缺席 ⇒ 22" }),
|
|
432
432
|
r("SSH_PRIVATE_KEY", "orchestration", "secret", "ssh 私钥(adapter 持有,绝不进 workspace)", { danger: SEC }),
|
|
433
433
|
r("SSH_USER", "orchestration", "string", "ssh 用户(必需件)"),
|
|
434
|
-
r("STORE_LOCK_WAIT_MS", "store", "number", "local 数据根 LOCK
|
|
434
|
+
r("STORE_LOCK_WAIT_MS", "store", "number", "store 启动期的有界等待窗(ms):local 数据根 LOCK 撞上活持锁人,或已配置的 SQL 库此刻连不上 / 建表失败;0=当场拒;到窗仍开不了 ⇒ 拒启(local 保持原码,永不放行第二个写者;SQL 点名后端与 host:port,永不退成内存)", { staticDefault: "60000", danger: DATA, configKey: "storeLockWaitMs" }),
|
|
435
435
|
r("STORE_PROBE_INTERVAL_MS", "store", "number", "/health 活体 store 后台探针间隔(ms;0=显式关;仅 SQL 后端挂环)", { staticDefault: "15000", configKey: "storeProbeIntervalMs" }),
|
|
436
436
|
r("STREAM_APPROVAL_ADHOC_GRACE_MS", "approval", "number", "流内审批 adhoc 腿窗后宽限(ms;须 window+grace < orphanTtl,破坏形拒启)", { staticDefault: "60000" }),
|
|
437
437
|
r("STREAM_APPROVAL_ADMIT_MAX_PER_OWNER", "approval", "number", "流内审批 per-owner 未决帽", { staticDefault: "256" }),
|
package/dist/config-lkg.d.ts
CHANGED
|
@@ -11,11 +11,37 @@ export interface PersistedLkg {
|
|
|
11
11
|
/** skill 正文的 content-addressed 盘缓存目录(LKG 的姊妹面):LKG 只含 skills MANIFEST(正文按
|
|
12
12
|
* hash 另拉)——黑洞中心下 LKG-boot 会在 skill 正文拉取上再挂数秒(live 实测 +5.3s)。正文按
|
|
13
13
|
* `<dir>/<sha-hex>` 落盘,读侧**必验 hash**(content-addressed 的全部意义;坏文件=忽略重拉)。 */
|
|
14
|
-
export declare function defaultSkillCacheDir(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export declare function defaultSkillCacheDir(config: {
|
|
15
|
+
readonly agentConfigDir?: string;
|
|
16
|
+
}): string;
|
|
17
|
+
/** 默认路径(scheduler store 同门:配置根 = 配置席 `agentConfigDir` → ~/.sema,{@link configRootOf}),按「worker + 凭证身份」分箱。`CONFIG_LKG_PATH`
|
|
18
|
+
* 覆写(`config.configLkgPath`,读法 {@link lkgPathOverrideOf};单一路径 ⇒ 不分箱,信封的 worker + identity 核仍守读回);`CONFIG_LKG_ENABLED=false` 关闭(读写双关)。
|
|
17
19
|
* 🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是**fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
|
|
18
|
-
export declare function defaultLkgPath(
|
|
20
|
+
export declare function defaultLkgPath(config: {
|
|
21
|
+
readonly agentConfigDir?: string;
|
|
22
|
+
}, worker: string | undefined, identity: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* `CONFIG_LKG_PATH`(LKG 落盘路径覆写)的**唯一读法**:没设 ⇒ `undefined`(LKG 落在配置根下按箱派生,{@link defaultLkgPath});设了 ⇒ 绝对路径
|
|
25
|
+
* (相对值按进程 cwd 解,与文件被打开时同一个解法);空串 ⇒ 拒(起服拒):空串解出来是进程工作目录本身 —— 一条落盘路径不该是一个目录,
|
|
26
|
+
* 由它派生的读拒行({@link lkgReadDenyRows})会把整个工作目录读拒掉。`loadConfig` 调它铸 `configLkgPath`,读者只读那一席。
|
|
27
|
+
*/
|
|
28
|
+
export declare function lkgPathOverrideOf(raw: string | undefined): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* LKG(含凭据)实际所在位置的**读拒行** —— 一条规则:读拒从 LKG 落在哪派生,不写死位置。
|
|
31
|
+
* · 没覆写 ⇒ LKG 落在配置根下(`config-lkg-<箱>.json`):引擎内建表的 `~/.sema/config-lkg-*.json` 行在家目录与声明的根(`agentConfigDir`,
|
|
32
|
+
* 根经链接时引擎连目标一起守)两处守它 ⇒ 不另加行(加了就是同一个文件两只判据,`READ_DENY_BUILTIN_EXCLUDE` 删那一行也删不掉它);
|
|
33
|
+
* · 覆写 ⇒ LKG 在配置根外,引擎那一行够不着 ⇒ 这里派生行:它的**两种拼法**各一行 —— 覆写写下的绝对路径,与文件真正落的地方(路径经符号链接的
|
|
34
|
+
* 目录时,写进去的文件在链接目标下;只守拼写的话读真实路径就绕过了 —— 车内复审 [high])。两者相同 ⇒ 一行。大小写不敏感(与内建行同读法)。
|
|
35
|
+
* 拒句与内建行同一句(`sensitive-path read deny list (pattern …)`)。
|
|
36
|
+
* **行的射程**(引擎读拒表的读法,不是本函数选的):行是路径段串 —— 引擎去掉开头的 `/`、不锚到根,这串段在任意深度出现即拒(大小写不敏感),
|
|
37
|
+
* 所以挡的不止 LKG 那一个文件:用户仓里同段串的路径(`/app/config-lkg.json` ⇒ `…/app/config-lkg.json`)一并读不到、Grep 结果里也不出现。
|
|
38
|
+
* 挡多了优于不挡;引擎提供锚定单文件的行之前如此(随包文案 MIGRATION / 目录行同句)。
|
|
39
|
+
* 读者:部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats` 的 `readDenyPatterns` 席 = 运维的行 ∪ 本函数的行)—— 每次铸席现解一次链接。
|
|
40
|
+
*/
|
|
41
|
+
export declare function lkgReadDenyRows(configLkgPath: string | undefined): {
|
|
42
|
+
pattern: string;
|
|
43
|
+
caseSensitive: boolean;
|
|
44
|
+
}[];
|
|
19
45
|
/** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
|
|
20
46
|
* 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
|
|
21
47
|
export declare function saveLkg(path: string, worker: string | undefined, identity: string, effective: Record<string, unknown>, etag: string | undefined): Promise<void>;
|
|
@@ -31,7 +57,9 @@ export declare function loadLkg(path: string, worker: string | undefined, identi
|
|
|
31
57
|
reason: string;
|
|
32
58
|
}>;
|
|
33
59
|
/** 7.98.0 及以前的默认 LKG 路径(按当时的写法逐字保留 —— 这是盘上已有文件的名字,不是现行分箱规则)。 */
|
|
34
|
-
export declare function legacyV1LkgPath(
|
|
60
|
+
export declare function legacyV1LkgPath(config: {
|
|
61
|
+
readonly agentConfigDir?: string;
|
|
62
|
+
}, worker: string | undefined): string;
|
|
35
63
|
export type LegacyLkgMigration = {
|
|
36
64
|
readonly outcome: "none";
|
|
37
65
|
}
|
package/dist/config-lkg.js
CHANGED
|
@@ -1,15 +1,60 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
1
|
+
import { promises as fs, lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { basename, join, dirname, resolve } from "node:path";
|
|
4
3
|
import { randomBytes } from "node:crypto";
|
|
5
4
|
import { validatePromptsDomain } from "./prompts-domain-validate.js";
|
|
6
5
|
import { centerStateBin, pullTokenIdentity } from "./center-credential.js";
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { configRootOf } from "./sema-config-dir.js";
|
|
7
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
8
|
+
export function defaultSkillCacheDir(config) {
|
|
9
|
+
return join(configRootOf(config), "skill-cache");
|
|
9
10
|
}
|
|
10
|
-
export function defaultLkgPath(worker, identity) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export function defaultLkgPath(config, worker, identity) {
|
|
12
|
+
return join(configRootOf(config), `config-lkg-${centerStateBin(worker, identity)}.json`);
|
|
13
|
+
}
|
|
14
|
+
export function lkgPathOverrideOf(raw) {
|
|
15
|
+
if (raw === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (raw === "") {
|
|
18
|
+
throw new Error('env CONFIG_LKG_PATH="" is empty — refusing to start: an empty path resolves to the working directory itself, which is no file the last-known-good ' +
|
|
19
|
+
"config can be kept in. Unset the variable to keep the LKG below the configuration root (SEMA_CONFIG_DIR, default ~/.sema), or set it to the file path you mean.");
|
|
20
|
+
}
|
|
21
|
+
return resolve(raw);
|
|
22
|
+
}
|
|
23
|
+
export function lkgReadDenyRows(configLkgPath) {
|
|
24
|
+
if (configLkgPath === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
return [...new Set([configLkgPath, landedPathOf(configLkgPath)])].map((pattern) => ({ pattern, caseSensitive: false }));
|
|
27
|
+
}
|
|
28
|
+
function landedPathOf(p) {
|
|
29
|
+
const rest = [];
|
|
30
|
+
let landed = p;
|
|
31
|
+
let unresolved;
|
|
32
|
+
for (let head = p;; head = dirname(head)) {
|
|
33
|
+
try {
|
|
34
|
+
landed = join(realpathSync(head), ...rest);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
if (unresolved === undefined && head !== p && segmentPresent(head))
|
|
39
|
+
unresolved = head;
|
|
40
|
+
if (dirname(head) === head)
|
|
41
|
+
break;
|
|
42
|
+
rest.unshift(basename(head));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (unresolved !== undefined)
|
|
46
|
+
recordFailOpen("server.config-lkg.landed-path-unresolved", `segment=${unresolved}`);
|
|
47
|
+
return landed;
|
|
48
|
+
}
|
|
49
|
+
function segmentPresent(p) {
|
|
50
|
+
try {
|
|
51
|
+
lstatSync(p);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
const code = e.code;
|
|
56
|
+
return code !== "ENOENT" && code !== "ENOTDIR";
|
|
57
|
+
}
|
|
13
58
|
}
|
|
14
59
|
export async function saveLkg(path, worker, identity, effective, etag) {
|
|
15
60
|
const body = { formatVersion: 2, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
|
|
@@ -79,10 +124,9 @@ function lkgPayloadProblem(effective, etag) {
|
|
|
79
124
|
return "prompts face fails re-validation";
|
|
80
125
|
return undefined;
|
|
81
126
|
}
|
|
82
|
-
export function legacyV1LkgPath(worker) {
|
|
83
|
-
const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
|
|
127
|
+
export function legacyV1LkgPath(config, worker) {
|
|
84
128
|
const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
|
|
85
|
-
return join(
|
|
129
|
+
return join(configRootOf(config), `config-lkg-${key}.json`);
|
|
86
130
|
}
|
|
87
131
|
export async function migrateV1Lkg(o) {
|
|
88
132
|
const refuse = (why) => ({
|
package/dist/config-types.d.ts
CHANGED
|
@@ -1994,6 +1994,13 @@ export interface ServiceConfigFlat {
|
|
|
1994
1994
|
* `READ_DENY_BUILTIN_EXCLUDE`=逗号分隔;不在表里的行名 / 空串**启动期响亮拒**。
|
|
1995
1995
|
* 🔴 不变量(判据帖 G5b):删内建**绝不**削弱任务自己显式声明的 deny —— 那是另一层(additions)。 */
|
|
1996
1996
|
readDenyBuiltinExclude?: readonly string[];
|
|
1997
|
+
/** core 7.46.0 #1419:部署**声明**的配置根(`SEMA_CONFIG_DIR`,绝对路径;起服的供给动作建出目录,能不能用由引擎判)—— 交引擎 `RunnerDeps.agentConfigDir`
|
|
1998
|
+
* 与守卫集 `createSensitivePathPolicy` 的同名入参:内建读拒表里以 `.sema` 开头的行在这个根下同样拒,写进根下要问一次。缺席 = 没声明
|
|
1999
|
+
* ⇒ 引擎读字面 `~/.sema`(与 LKG / skill 缓存 / scheduler 店的缺省落点同一处)。读法单一属主 `sema-config-dir.ts`。 */
|
|
2000
|
+
agentConfigDir?: string;
|
|
2001
|
+
/** LKG 落盘路径的覆写(`CONFIG_LKG_PATH`,绝对路径;单一读法 `config-lkg.ts` `lkgPathOverrideOf`)。缺席 = LKG 落在配置根下按箱派生。
|
|
2002
|
+
* 读者:LKG 的落盘 / 读回(`boot/config-center.ts`)与读拒行(部署姿态座席族 —— LKG 在哪,读拒就守哪,`lkgReadDenyRows`)。 */
|
|
2003
|
+
configLkgPath?: string;
|
|
1997
2004
|
/** Structured log level. */
|
|
1998
2005
|
logLevel: "debug" | "info" | "warn" | "error";
|
|
1999
2006
|
/** Per-principal request cap per minute on task/run endpoints. 0 disables. */
|
|
@@ -2127,7 +2134,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
|
|
|
2127
2134
|
* config 这一层它只是一句声明。 */
|
|
2128
2135
|
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "authBridgeIssuer" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
|
|
2129
2136
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
2130
|
-
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "requireExecutionLane" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "lspEnvAllow" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
2137
|
+
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "remoteExecHomeDir" | "requireExecutionLane" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "lspEnvAllow" | "imageBakes" | "readFace" | "readFaceSource" | "taskWriteFaceEscalation" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "agentConfigDir" | "configLkgPath">;
|
|
2131
2138
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
2132
2139
|
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "drainSecondSignalMinGapMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "engineOrphanLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
2133
2140
|
/** 组:observability(可观测)。 */
|