@sema-agent/server 7.27.0 → 7.28.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +4 -4
- package/dist/boot/resolve-spec.js +5 -3
- package/dist/capabilities/center-prompts.js +22 -3
- package/dist/capabilities/scenario-alias.d.ts +27 -0
- package/dist/capabilities/scenario-alias.js +61 -0
- package/dist/capabilities/scenarios.d.ts +6 -5
- package/dist/capabilities/scenarios.js +54 -20
- package/dist/capabilities/skills.d.ts +3 -1
- package/dist/capabilities/skills.js +6 -2
- package/dist/capabilities/team.d.ts +37 -7
- package/dist/capabilities/team.js +40 -8
- package/dist/config-center/skills-mcp.d.ts +2 -1
- package/dist/config-center/skills-mcp.js +7 -3
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/main.js +5 -4
- package/dist/run-local.js +9 -6
- package/dist/task-mcp.js +8 -1
- package/dist/task-workflow.d.ts +1 -1
- package/dist/task-workflow.js +1 -1
- package/package.json +2 -2
package/USAGE.md
CHANGED
|
@@ -282,7 +282,7 @@ FLEET_ADVERTISE_ADDRESS=http://<本机可达IP>:8090 # 可选:设了才启 flee
|
|
|
282
282
|
- **灰度姿势**(配置控制面 AI 建议):先 `SEMA_REGISTRY_DRY_RUN=true` 起一轮,看日志 `sema_registry_dry_run`(中心给的 models/roles/teams + 会否覆盖 default、per-model apiKeyEnv)对得上 env 再去掉该 flag 真正 apply。
|
|
283
283
|
- 拉取**只读、只取逻辑配置**(模型名册/角色/团队);密钥/网关仍在本服务 env(中心只发 env-**名** 引用,不发密钥值)。models/roles 改动需重启生效,teams 60s 热刷。回滚=去掉 `SEMA_REGISTRY_URL` 即纯 env。
|
|
284
284
|
- **仅 `/v1/tasks`(同步)+ `/v1/runs`(异步)**——`/v1/tasks/stream` 不支持(多次尝试非单流,400)。与 `verify` **互斥**(同时给 → 400)。
|
|
285
|
-
- 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `
|
|
285
|
+
- 成本上界 = 任务的 `maxCostUsd`(防冷重跑税)。**三条 core 警示**:① 每档**冷重跑**重付输入成本(便宜档常过才划算);② 门收到**未脱敏**输出(自定义门转发外部 verifier 要脱敏);③ **写工具会跑 N 次**——**只用于只读/幂等任务**(每次升级整任务重跑)。开放式任务(找全 bug/文笔)没有可判定 oracle、级联会空转,那种用 `discuss`(广度对抗)而非级联(深度阶梯)。
|
|
286
286
|
|
|
287
287
|
**可选 — AskUserQuestion 的裁量窗(问活人的三个帽子)**
|
|
288
288
|
```bash
|
|
@@ -510,7 +510,7 @@ SEMA_PARENT_PID=$$ # 壳把自己的 pid 传给它 spawn 出来的引擎
|
|
|
510
510
|
| `sessionId` | — | 续聊:带上次返回的 `sessionId`,服务端自动 wake 历史 |
|
|
511
511
|
| `images` | — | 图文输入 `[{data,mimeType}|{url}]`(模型需支持 vision) |
|
|
512
512
|
| `attachmentIds` | — | D-1 通用文件上传(1.289+):先 `POST /v1/attachments?name=…`(raw body,content-type=mime)拿句柄,提交时引用 ≤16 个;文件物化到执行环境工作目录 `attachments/` 下,objective 尾部自动追加文件清单(内容不进会话流)。单文件缺省 ≤32 MiB(`ATTACHMENT_MAX_BYTES`);可配 mime 白名单(`ATTACHMENT_MIME_ALLOWLIST` CSV,缺省不限);上传后未引用的按 `ATTACHMENT_UNBOUND_TTL_MS`(缺省 24h)回收。**云形态(tidb/pg)字节本体存对象存储——MinIO 必配**(`MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY`,与快照 lane 同一组变量),未配则附件面 501;local 形走本地文件店。 |
|
|
513
|
-
| `scenario` | — | `default`(默认)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`
|
|
513
|
+
| `scenario` | — | `default`(默认)/ `code-review`(见 §5)/ `scan`(同 §5 的 repo 只读工具但**中性无框架提示词**——objective+中心下发 skill 全权主导输出,OA 扫描类用;与 `code-review`/`discuss` 同属 clone-free 无执行环境场景,见 §5 末)/ **配置控制面可声明任意新场景**(`{name, toolset: none\|repo-readonly, prompt?}`,组合即配置、能力钉死在部署;restart-to-apply;center 可覆盖内建名,boot 日志 `config_center_scenarios.shadowsBuiltin` 可审计) |
|
|
514
514
|
| `repo` / `council` / `debate` | — | `repo` 为 `code-review`/`scan` 必填;`council`/`debate` 仅 `code-review`,见 §5 |
|
|
515
515
|
| ~~model / tools / prompt~~ | 🚫 | **不接受**——服务端注入 |
|
|
516
516
|
|
|
@@ -597,7 +597,7 @@ curl -s http://<host>:8090/v1/runs/<taskId> -H 'x-agent-principal: user:42' #
|
|
|
597
597
|
> (仅 contents/pull-requests 只读)。未知词拒启;`GIT_API_BASEURL` 指向 github.com 族而 kind 仍是
|
|
598
598
|
> gitea 也拒启并指路(Gitea 形状对 GitHub 恒 404,启动即报优于运行时逐调用 404)。
|
|
599
599
|
|
|
600
|
-
> **升级注记(7.7.0)**:本版起 `code-review` / `scan` / `team`
|
|
600
|
+
> **升级注记(7.7.0)**:本版起 `code-review` / `scan` / `discuss`(7.28.0 前叫 `team`)/ `toolset: repo-readonly|none` 的场景改跑
|
|
601
601
|
> 无执行环境的引擎。**升级前**若还有这些场景的 durable 挂起任务(需同时满足:配了 `REMOTE_EXEC`、开了
|
|
602
602
|
> `DURABLE_APPROVAL`、任务已 park),它们在新版上续跑会以 409 `checkpoint.unsupported_version` 响亮失败
|
|
603
603
|
> (run 行落 failed 并释放会话,不会占住会话)——重新提交该任务即可。升级前排空这类挂起任务可完全避免。
|
|
@@ -607,7 +607,7 @@ curl -s http://<host>:8090/v1/runs/<taskId> -H 'x-agent-principal: user:42' #
|
|
|
607
607
|
模型看到的工具面只有声明的只读仓库工具(`repo_tree` / `repo_read_file` / `repo_pull_diff`)加 `Now`,
|
|
608
608
|
**没有** `Bash` / `Edit` / `Write` / `Read` 等落盘工具,不 clone、不写盘、不起沙箱。
|
|
609
609
|
即使部署配了 `REMOTE_EXEC`(host/e2b/k8s/ssh/adb/local-docker)也如此:执行环境只属于 `default` / `code`
|
|
610
|
-
这类需要动手的场景。`
|
|
610
|
+
这类需要动手的场景。`discuss` 场景同理(协调者只调 `run_discussion`,成员是纯讨论人格)。
|
|
611
611
|
|
|
612
612
|
## 6. 逐 token 直播(同步流式,连接挂着)
|
|
613
613
|
```bash
|
|
@@ -118,7 +118,9 @@ export function createResolveSpec(ctx) {
|
|
|
118
118
|
// body.scenario is bounded to the allowlist — outside ⇒ typed 400 `scenario_not_allowed`). The ruling
|
|
119
119
|
// {scenario, allowlist} is center-RESOLVED and rides the caps view (TTL-cached, same fetch as runtimeCaps);
|
|
120
120
|
// no center / dry-run / anonymous / older center / caps blip ⇒ undefined ⇒ legacy body-or-default chain.
|
|
121
|
-
|
|
121
|
+
// #273 alias:`gateScenarioRequest` 是 HTTP lane 的唯一归一点(旧名 team → discuss,响亮 warn);
|
|
122
|
+
// 此后 `scenarioName` 只可能是现役主名,下游判据点一律按主名写(禁双名判)。
|
|
123
|
+
const requested = gateScenarioRequest(await principalCaps?.scenarioRuling(auth?.principal), body.scenario, config.defaultScenario, Object.keys(scenarios), logger);
|
|
122
124
|
// per-principal execution-lane policy — this worker's lane is a
|
|
123
125
|
// PROCESS identity (REMOTE_EXEC boot wiring, no per-task switch), so admission is the gate: lane ∉
|
|
124
126
|
// allowedLanes ⇒ typed 403 `execution_lane_not_allowed`. Same caps car as the scenario ruling (zero extra
|
|
@@ -714,7 +716,7 @@ export function createResolveSpec(ctx) {
|
|
|
714
716
|
// 退役键(timeoutSec/deadline 族)DROP 必须留痕:task_limits_legacy_key_dropped 带被丢键与最终生效墙钟
|
|
715
717
|
// (null=整任务无墙——单用户 turnkey 无 env 墙时的真后果;多租=被延到租户墙)。
|
|
716
718
|
...(() => {
|
|
717
|
-
const limits = resolveTaskLimits(body.limits, taskLimitCaps, taskTimeoutSec, config.requirePrincipal, body.council === true || body.debate === true || scenarioName === "
|
|
719
|
+
const limits = resolveTaskLimits(body.limits, taskLimitCaps, taskTimeoutSec, config.requirePrincipal, body.council === true || body.debate === true || scenarioName === "discuss", (event, fields) => logger.warn(event, { ...fields, sessionId: auth?.sessionId ?? null }));
|
|
718
720
|
// [1301]③ config catalog server 半场:env 封顶不再是「五层五值互不知情」的暗手——每个真在场
|
|
719
721
|
// 的运营方旋钮以 configOverrides 声明进 spec(advisory,core 折进 config.assembled 的
|
|
720
722
|
// overrideReasons;「谁设的顶」变成读帧不考古)。只声明 SET 了的键(缺省不设=不污染帧)。
|
|
@@ -1284,7 +1286,7 @@ export function createResolveSpec(ctx) {
|
|
|
1284
1286
|
isolatedExecEnv,
|
|
1285
1287
|
hasExecutionEnv: config.remoteExec != null,
|
|
1286
1288
|
...(config.autonomy ? { autonomy: config.autonomy } : {}),
|
|
1287
|
-
explicitTeam: body.council === true || body.debate === true || scenarioName === "
|
|
1289
|
+
explicitTeam: body.council === true || body.debate === true || scenarioName === "discuss",
|
|
1288
1290
|
});
|
|
1289
1291
|
logger.info("orchestration_routed", { mode: decision.mode, reason: decision.reason, sessionId: auth?.sessionId ?? null });
|
|
1290
1292
|
if (decision.mode === "supervisor") {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { verifyPromptArtifact } from "@sema-agent/core";
|
|
23
23
|
import { CORE_ENGINE_VERSION } from "../prompts-domain-validate.js";
|
|
24
|
+
import { scenarioConfigKeys } from "./scenario-alias.js";
|
|
24
25
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
25
26
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
26
27
|
/** 判形半场(validateCenterPrompts/validatePromptsDomain/CORE_ENGINE_VERSION 及两 interface)已下沉到
|
|
@@ -115,7 +116,7 @@ export async function applyCatalogToSource(source, next, prev, log, phase) {
|
|
|
115
116
|
* (F4:空替换集非 v1 语义),所以这里恒有非空声明——恒返回 provider(center-wins 无死分支)。
|
|
116
117
|
*/
|
|
117
118
|
export function centerPromptProvider(prompts, scenarioName) {
|
|
118
|
-
const decls = prompts
|
|
119
|
+
const decls = scenarioDecls(prompts, scenarioName);
|
|
119
120
|
// 声明数组在候选采用时已判形+冻结(main.ts ref 整体替换,数组不原位变异)——闭包直接引用,core 侧
|
|
120
121
|
// 1.315 还有声明边界快照(getter 无法让「验过的」≠「发出的」)。
|
|
121
122
|
return { stableBlocks: () => decls };
|
|
@@ -127,7 +128,25 @@ export function centerPromptProvider(prompts, scenarioName) {
|
|
|
127
128
|
* [1057]①f 的「server 不做语义复检」不动:这不是复检语义,是为 capabilities.appendSystemPrompt 的诚实面
|
|
128
129
|
* 探 core 会走哪条臂。 */
|
|
129
130
|
export function centerIdentityAssembled(prompts, scenarioName, isAssembled) {
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
return scenarioDecls(prompts, scenarioName).some((d) => d.slot === "identity" && isAssembled(d.text));
|
|
132
|
+
}
|
|
133
|
+
/** 选包的**唯一**实现(两个消费面共用,免得「谁赢」这条优先序有两份副本)。
|
|
134
|
+
* #273 alias 窗:`scenarioOverrides` 的键写**旧场景名**时同样命中(`scenarioConfigKeys`,主名优先)——
|
|
135
|
+
* 运维已经写好的 per-scenario 提示词包不因改名当版静默失效。
|
|
136
|
+
* #193 P2-13 同族守卫(顺手补,收拢时发现):`Record` 的裸索引对原型链键会取到 `Object.prototype` 上的
|
|
137
|
+
* 成员,而场景名词法 `SCENARIO_NAME_RE` 恰好**允许** `constructor` 这种名字 ⇒ 旧写法会把一个函数当
|
|
138
|
+
* 声明数组返回,下游 `.some(...)` 直接 TypeError。own-property 判定后这条路不存在。 */
|
|
139
|
+
function scenarioDecls(prompts, scenarioName) {
|
|
140
|
+
const overrides = prompts.scenarioOverrides;
|
|
141
|
+
if (overrides) {
|
|
142
|
+
for (const key of scenarioConfigKeys(scenarioName)) {
|
|
143
|
+
if (Object.hasOwn(overrides, key)) {
|
|
144
|
+
const hit = overrides[key];
|
|
145
|
+
if (hit)
|
|
146
|
+
return hit;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return prompts.sections;
|
|
132
151
|
}
|
|
133
152
|
//# sourceMappingURL=center-prompts.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Logger } from "../observability/logger.js";
|
|
2
|
+
export declare const SCENARIO_ALIASES: Readonly<Record<string, string>>;
|
|
3
|
+
/**
|
|
4
|
+
* 把请求/配置里的场景名归一到现役主名。
|
|
5
|
+
*
|
|
6
|
+
* - `known` 里**真有**这个名字 ⇒ 原样返回(live 定义优先于 alias)。⚠️ 窗内 center **无法**用旧名
|
|
7
|
+
* 另立场景:`centerScenarios` 把 alias 名当**保留名**响亮跳过(理由见那里——否则请求面归影子场景、
|
|
8
|
+
* 而按场景键的配置在 alias 窗里两边都算,MCP/A2A 会静默漏进主名的 roster)。所以这一支今天只是
|
|
9
|
+
* 纵深防御(万一将来有别的路径往表里塞同名场景,它至少不会被别名劫持)。
|
|
10
|
+
* - 否则命中 alias 表 ⇒ 返回主名 + 响亮 `scenario_alias_deprecated`(每 logger 每旧名一次)。
|
|
11
|
+
* - 都不命中 ⇒ 原样返回(未知名的处置归调用方:请求面 typed 400,指派面走 unknown→default 回退)。
|
|
12
|
+
*/
|
|
13
|
+
export declare function canonicalScenarioName(name: string, known: readonly string[] | undefined, logger?: Logger): string;
|
|
14
|
+
/**
|
|
15
|
+
* 「为场景 `name` 查按场景键的运维配置时,应命中哪些键」= 主名 + 其过渡期旧名。
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **无二义的前提**(codex 对抗复审 [high] 的修法):旧名在窗内是**保留名** —— `centerScenarios` /
|
|
18
|
+
* `centerScenarioDetails` 拒绝任何以 alias 名声明的 center 场景,所以「标了旧名的配置」在本部署里
|
|
19
|
+
* 只可能是冲着(改名前的)主名去的,不存在第二个属主可漏。二义在源头掐掉,而不是在这四个取用口
|
|
20
|
+
* 各塞一份 live 名册判据(那才是「同一语义多个属主」)。
|
|
21
|
+
*
|
|
22
|
+
* 请求面归一之后,运维**已写好的**旧名配置(skill frontmatter 的 `scenarios:`、center mcp/a2a 的
|
|
23
|
+
* `scenarios`、center prompts 的 `scenarioOverrides`)若不再被命中,就会在改名当版**静默失效**
|
|
24
|
+
* —— 静默是这里唯一不可接受的形态,所以窗内两名皆认。窗撤销时随 {@link SCENARIO_ALIASES} 一起塌回单键。
|
|
25
|
+
*/
|
|
26
|
+
export declare function scenarioConfigKeys(name: string): readonly string[];
|
|
27
|
+
//# sourceMappingURL=scenario-alias.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ── #273 / C-R14 场景名 alias 过渡窗(clay 直裁 [4123],server 表态 [4127])──────────────────────
|
|
2
|
+
//
|
|
3
|
+
// 「team」一词全生态只指 agent-teams(持久具名团队);一次性多 agent 讨论一律叫「discuss」。本仓的
|
|
4
|
+
// `team` 场景正中射程 ⇒ 主名改 `discuss`,旧名保**一版**入口别名。
|
|
5
|
+
//
|
|
6
|
+
// 🔴 **单点解析**:归一发生在 {@link canonicalScenarioName},由 {@link gateScenarioRequest}(HTTP lane)
|
|
7
|
+
// 与 run-local 各调**一次**;此后整条下游(`scenarioName` 的每个判据点、trace 字段、skills/mcp/a2a/
|
|
8
|
+
// prompts 的按场景过滤)只见现役主名。**禁**在判据点上一处一处加双名判 —— 改名批最经典的漏改形就是
|
|
9
|
+
// 「改了三处、漏了第四处」,而漏掉的那处会静默按 default 跑(讨论任务被 2400s 墙钟砍腰之类)。
|
|
10
|
+
//
|
|
11
|
+
// ⏳ 窗一版:下一 minor 删本表 + team.ts 的 `run_team` 别名工具(两处同批删,发车帖点名壳/center)。
|
|
12
|
+
export const SCENARIO_ALIASES = Object.freeze({ team: "discuss" });
|
|
13
|
+
/** 一次性 deprecation warn 的去重位:按 logger 实例记已喊过的旧名。部署的 logger 是进程单例 ⇒ 每个
|
|
14
|
+
* 旧名每进程喊一次(不是每请求刷屏);测试各建各的 logger ⇒ 每格都看得见自己的那一声。 */
|
|
15
|
+
const aliasWarned = new WeakMap();
|
|
16
|
+
/**
|
|
17
|
+
* 把请求/配置里的场景名归一到现役主名。
|
|
18
|
+
*
|
|
19
|
+
* - `known` 里**真有**这个名字 ⇒ 原样返回(live 定义优先于 alias)。⚠️ 窗内 center **无法**用旧名
|
|
20
|
+
* 另立场景:`centerScenarios` 把 alias 名当**保留名**响亮跳过(理由见那里——否则请求面归影子场景、
|
|
21
|
+
* 而按场景键的配置在 alias 窗里两边都算,MCP/A2A 会静默漏进主名的 roster)。所以这一支今天只是
|
|
22
|
+
* 纵深防御(万一将来有别的路径往表里塞同名场景,它至少不会被别名劫持)。
|
|
23
|
+
* - 否则命中 alias 表 ⇒ 返回主名 + 响亮 `scenario_alias_deprecated`(每 logger 每旧名一次)。
|
|
24
|
+
* - 都不命中 ⇒ 原样返回(未知名的处置归调用方:请求面 typed 400,指派面走 unknown→default 回退)。
|
|
25
|
+
*/
|
|
26
|
+
export function canonicalScenarioName(name, known, logger) {
|
|
27
|
+
if (known?.includes(name))
|
|
28
|
+
return name;
|
|
29
|
+
if (!Object.hasOwn(SCENARIO_ALIASES, name))
|
|
30
|
+
return name;
|
|
31
|
+
const resolved = SCENARIO_ALIASES[name];
|
|
32
|
+
if (resolved === undefined)
|
|
33
|
+
return name;
|
|
34
|
+
if (logger) {
|
|
35
|
+
let seen = aliasWarned.get(logger);
|
|
36
|
+
if (!seen)
|
|
37
|
+
aliasWarned.set(logger, (seen = new Set()));
|
|
38
|
+
if (!seen.has(name)) {
|
|
39
|
+
seen.add(name);
|
|
40
|
+
logger.warn("scenario_alias_deprecated", { requested: name, resolved });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 「为场景 `name` 查按场景键的运维配置时,应命中哪些键」= 主名 + 其过渡期旧名。
|
|
47
|
+
*
|
|
48
|
+
* 🔴 **无二义的前提**(codex 对抗复审 [high] 的修法):旧名在窗内是**保留名** —— `centerScenarios` /
|
|
49
|
+
* `centerScenarioDetails` 拒绝任何以 alias 名声明的 center 场景,所以「标了旧名的配置」在本部署里
|
|
50
|
+
* 只可能是冲着(改名前的)主名去的,不存在第二个属主可漏。二义在源头掐掉,而不是在这四个取用口
|
|
51
|
+
* 各塞一份 live 名册判据(那才是「同一语义多个属主」)。
|
|
52
|
+
*
|
|
53
|
+
* 请求面归一之后,运维**已写好的**旧名配置(skill frontmatter 的 `scenarios:`、center mcp/a2a 的
|
|
54
|
+
* `scenarios`、center prompts 的 `scenarioOverrides`)若不再被命中,就会在改名当版**静默失效**
|
|
55
|
+
* —— 静默是这里唯一不可接受的形态,所以窗内两名皆认。窗撤销时随 {@link SCENARIO_ALIASES} 一起塌回单键。
|
|
56
|
+
*/
|
|
57
|
+
export function scenarioConfigKeys(name) {
|
|
58
|
+
const olds = Object.entries(SCENARIO_ALIASES).filter(([, to]) => to === name).map(([from]) => from);
|
|
59
|
+
return olds.length > 0 ? [name, ...olds] : [name];
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=scenario-alias.js.map
|
|
@@ -4,6 +4,7 @@ import type { Logger } from "../observability/logger.js";
|
|
|
4
4
|
import { type RepoReadClient } from "./repo-tools.js";
|
|
5
5
|
import { type LoadedSkill } from "./skills.js";
|
|
6
6
|
import { type ScenarioHands } from "./hands-lane.js";
|
|
7
|
+
export { SCENARIO_ALIASES, canonicalScenarioName, scenarioConfigKeys } from "./scenario-alias.js";
|
|
7
8
|
/**
|
|
8
9
|
* Scenario routing — a deployment serves many scenarios from one image. Each request's `scenario`
|
|
9
10
|
* selects a pre-wired capability bundle: tools (incl. subagent roster) + a whole-harness prompt +
|
|
@@ -39,7 +40,7 @@ export interface ScenarioDeps {
|
|
|
39
40
|
subRunner: Runner;
|
|
40
41
|
/**
|
|
41
42
|
* #196:`subRunner` 的无手孪生 —— 同 deps,唯独不挂 `executionEnvFactory`。hands=none 的场景把它交给
|
|
42
|
-
* 自己造的子任务工具(council 的 lens/arbiter、
|
|
43
|
+
* 自己造的子任务工具(council 的 lens/arbiter、discuss 的成员/synthesizer):那些子任务只该读仓库/讨论,
|
|
43
44
|
* 拿全量可写手是 [3208] 病灶里最意外的一支(零工具人格照拿 Bash)。取用一律走 {@link pickHandsRunner},
|
|
44
45
|
* 不在场景体内手挑 —— 判别位是唯一开关。
|
|
45
46
|
*/
|
|
@@ -64,7 +65,7 @@ export interface ScenarioDeps {
|
|
|
64
65
|
/**
|
|
65
66
|
* When true, the `default` scenario carries the Sema product identity (see {@link semaIdentityProvider}).
|
|
66
67
|
* Set by the local/TOC deployment (CONFIG_PROVIDER=local / run-local) so the local engine answers "who
|
|
67
|
-
* are you" as Sema instead of the model's hallucinated identity. Business scenarios (
|
|
68
|
+
* are you" as Sema instead of the model's hallucinated identity. Business scenarios (discuss/code-review)
|
|
68
69
|
* keep their own prompts; cloud (CONFIG_PROVIDER≠local) leaves the default scenario neutral unless turned
|
|
69
70
|
* on, so integrators that inject their own persona are unaffected.
|
|
70
71
|
*/
|
|
@@ -170,7 +171,7 @@ export interface ScenarioDetail {
|
|
|
170
171
|
/** 机读原因键;仅 `available:false` 时在场(缺席=没有理由)。消费方按键分支,禁匹配英文文案。 */
|
|
171
172
|
unavailableReason?: ScenarioUnavailableReason;
|
|
172
173
|
}
|
|
173
|
-
/** 内建五场景详情。default/code/
|
|
174
|
+
/** 内建五场景详情。default/code/discuss 工厂对良性请求无副作用可真调(拿真实工具名单);code-review/scan 是
|
|
174
175
|
* fail-loud 语义(缺 GIT_API 配置/principal 即 throw)→ 探针失败落静态表兜底(表↔工厂一致性由测试锁:
|
|
175
176
|
* 测试喂 fake deps 真调工厂对账工具名)。enabled 对内建恒 true(约定②)。 */
|
|
176
177
|
export declare function builtinScenarioDetails(scenarios: Record<string, Scenario>, deps: Pick<ScenarioDeps, "brandIdentity" | "repoClient">): Record<string, ScenarioDetail>;
|
|
@@ -201,7 +202,7 @@ export type ScenarioAvailability = {
|
|
|
201
202
|
*
|
|
202
203
|
* 判据键 = **toolset**:内建详情与 center 条目都带这个字段,故两条产线天然共用同一份判据。
|
|
203
204
|
* `requiresRepo` 的 toolset 需要部署配好 git 后端(`GIT_API_BASEURL` ⇒ `deps.repoClient`)。
|
|
204
|
-
* 词表外的 toolset(内建的 `full-body`/`
|
|
205
|
+
* 词表外的 toolset(内建的 `full-body`/`discuss`)不依赖后端 ⇒ 恒可用。
|
|
205
206
|
*/
|
|
206
207
|
export declare function scenarioAvailability(deps: Pick<ScenarioDeps, "repoClient">, toolset: string): ScenarioAvailability;
|
|
207
208
|
/** 拒绝文案的唯一属主:闭集穷举 switch(新增原因词不在这里表态即编译红)。 */
|
|
@@ -241,5 +242,5 @@ export interface ScenarioRuling {
|
|
|
241
242
|
* 到现状,与 workflows 面的 fail-closed 是刻意的 posture 差异,见 runtime-caps-resolver)。
|
|
242
243
|
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。
|
|
243
244
|
*/
|
|
244
|
-
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[]): string;
|
|
245
|
+
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger): string;
|
|
245
246
|
//# sourceMappingURL=scenarios.d.ts.map
|
|
@@ -4,10 +4,14 @@ import { 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";
|
|
7
|
-
import {
|
|
7
|
+
import { createDiscussionTools, discussionCoordinatorPrompt, getTeam, teamNames } from "./team.js";
|
|
8
8
|
import { createWebSearchBackend, webSearchConfigFromSettings } from "../plugins/web-search.js";
|
|
9
9
|
import { skillsForScenario } from "./skills.js";
|
|
10
10
|
import { pickHandsRunner } from "./hands-lane.js";
|
|
11
|
+
import { SCENARIO_ALIASES, canonicalScenarioName } from "./scenario-alias.js";
|
|
12
|
+
// #273 / C-R14:alias 表与两只归一口的**属主**是 `scenario-alias.ts`(零依赖模块,避免
|
|
13
|
+
// scenarios ↔ skills/skills-mcp/center-prompts 的循环 import)。此处再导出一次,保持既有 import 面。
|
|
14
|
+
export { SCENARIO_ALIASES, canonicalScenarioName, scenarioConfigKeys } from "./scenario-alias.js";
|
|
11
15
|
/**
|
|
12
16
|
* Sema product identity, prepended to the core default base for the `default` scenario when
|
|
13
17
|
* {@link ScenarioDeps.brandIdentity} is on. Honest about the substrate (the engine runs whichever model
|
|
@@ -216,42 +220,49 @@ export function buildScenarios(deps) {
|
|
|
216
220
|
skills: skillsForScenario(deps.skills, "scan"),
|
|
217
221
|
};
|
|
218
222
|
},
|
|
219
|
-
// Multi-agent
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
|
|
223
|
+
// Multi-agent DISCUSSION (#273 / C-R14 改名,旧名 `team` = 入口 alias 一版,见 {@link SCENARIO_ALIASES}):
|
|
224
|
+
// pick a template, the consumer (OA) injects business context, we run it. Templates resolve from the
|
|
225
|
+
// live registry (`getTeam`) = BUILTIN_TEAMS overridden/extended by the sema registry at runtime
|
|
226
|
+
// (applyEffective → registerTeams), so the roster is config-driven; built-ins are the fallback when
|
|
227
|
+
// the center supplies none. ⚠️ 模板注册面(`req.team` / center `teams` 键 / registry TEAMS.md)刻意
|
|
228
|
+
// **不**改名:那是 registry-core 的配置词汇(跨仓契约),改它要另立件+center 侧同款 alias;C-R14 的
|
|
229
|
+
// server 射程 = 场景名/工具名/模型可见文案三件([4127] 认领形)。
|
|
230
|
+
discuss: (req) => {
|
|
224
231
|
const name = typeof req.team === "string" ? req.team : "default";
|
|
225
232
|
const template = getTeam(name); // built-ins, overridden/extended by the sema registry at runtime
|
|
226
233
|
if (!template) {
|
|
227
|
-
throw new HttpError(404, `unknown
|
|
234
|
+
throw new HttpError(404, `unknown discussion roster "${name}" (available: ${teamNames().join(", ")})`);
|
|
228
235
|
}
|
|
229
236
|
const objective = typeof req.objective === "string" ? req.objective : "Discuss the task.";
|
|
230
237
|
const businessContext = typeof req.businessContext === "string" ? req.businessContext : undefined;
|
|
231
238
|
// #196:成员/synthesizer 是纯讨论人格(模板声明零工具),跑在无手 subRunner 上。
|
|
232
239
|
const hands = "none";
|
|
233
240
|
return {
|
|
234
|
-
tools:
|
|
241
|
+
tools: createDiscussionTools(subRunnerFor(deps, hands), template, { objective, businessContext, ...(deps.logger ? { logger: deps.logger } : {}) }),
|
|
235
242
|
hands,
|
|
236
|
-
skills: skillsForScenario(deps.skills, "
|
|
237
|
-
promptProvider:
|
|
243
|
+
skills: skillsForScenario(deps.skills, "discuss"),
|
|
244
|
+
promptProvider: discussionCoordinatorPrompt,
|
|
238
245
|
};
|
|
239
246
|
},
|
|
240
247
|
};
|
|
241
248
|
}
|
|
242
249
|
/** Resolve the scenario for a request (falls back to `default`). */
|
|
243
250
|
export function selectScenario(scenarios, name) {
|
|
251
|
+
// #273 alias:两条产线(HTTP resolve-spec / run-local)在算 `scenarioName` 时已经归一过,所以这里
|
|
252
|
+
// 对它们是**幂等**的;真正需要这一手的是**库面**调用方(index.ts 导出 selectScenario,嵌入式装配
|
|
253
|
+
// 自己传名字)—— 不归一的话旧名会静默落 `default`,那是比「名字过时」更坏的结果(整套工具面换人)。
|
|
254
|
+
const resolved = name === undefined ? undefined : canonicalScenarioName(name, Object.keys(scenarios));
|
|
244
255
|
// #193 P2-13:own-property 守卫——`Record` 字面量的裸索引对原型链键("constructor" 形状)会把
|
|
245
256
|
// Object.prototype 上的函数当 Scenario 返回。上游 resolve-spec 按 Object.keys 校验 ⇒ 当前不可利用,
|
|
246
257
|
// 本守卫是纵深防御(本函数是导出面,不得依赖单一调用方的入口校验)。
|
|
247
|
-
return (
|
|
258
|
+
return (resolved && Object.hasOwn(scenarios, resolved) && scenarios[resolved]) || scenarios.default;
|
|
248
259
|
}
|
|
249
260
|
/** 把属主的判别式摊平成 wire 上的两个键(可用臂整键省略原因,契约=「缺席即没有理由」)。 */
|
|
250
261
|
function availabilityFields(deps, toolset) {
|
|
251
262
|
const verdict = scenarioAvailability(deps, toolset);
|
|
252
263
|
return verdict.available ? { available: true } : { available: false, unavailableReason: verdict.reason };
|
|
253
264
|
}
|
|
254
|
-
/** 内建五场景详情。default/code/
|
|
265
|
+
/** 内建五场景详情。default/code/discuss 工厂对良性请求无副作用可真调(拿真实工具名单);code-review/scan 是
|
|
255
266
|
* fail-loud 语义(缺 GIT_API 配置/principal 即 throw)→ 探针失败落静态表兜底(表↔工厂一致性由测试锁:
|
|
256
267
|
* 测试喂 fake deps 真调工厂对账工具名)。enabled 对内建恒 true(约定②)。 */
|
|
257
268
|
export function builtinScenarioDetails(scenarios, deps) {
|
|
@@ -277,7 +288,9 @@ export function builtinScenarioDetails(scenarios, deps) {
|
|
|
277
288
|
default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, ["Now"]), deps.brandIdentity ? "Sema 产品身份 + core 默认宪法(结构层由引擎合成)" : "core 默认宪法(中性,无场景附加 persona)"),
|
|
278
289
|
"code-review": mk("code-review", "代码评审(clone-free 只读仓库工具;council=true 升多镜头评审团)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员/评审团协调提示词(镜头分工+产出契约)"),
|
|
279
290
|
scan: mk("scan", "中性只读仓库扫描(输出契约由 objective+skills 决定)", "repo-readonly", probe("scan", { repo: "probe/probe" }, undefined, REPO_TOOLS), "无场景附加提示词(objective+center skills 拥有输出契约)"),
|
|
280
|
-
|
|
291
|
+
// #273 / C-R14:旧名 `team` 不再是一条**被声明**的场景(它只是 SCENARIO_ALIASES 里的入口别名)——
|
|
292
|
+
// 列举面只列现役主名,免得消费方把别名当成第二条能力画上去。
|
|
293
|
+
discuss: mk("discuss", "多智能体讨论圆桌(模板成员+synthesizer,模板可由 registry 配置)", "discuss", probe("discuss", {}, undefined, ["run_discussion", "run_team"]), "讨论协调者提示词(圆桌纪律+合成流程)"),
|
|
281
294
|
};
|
|
282
295
|
}
|
|
283
296
|
/** center 条目详情(有效性判定与 centerScenarios 完全同款:无效条目既不进 overlay 也不进详情——
|
|
@@ -289,6 +302,8 @@ export function centerScenarioDetails(specs, builtinNames, deps) {
|
|
|
289
302
|
continue;
|
|
290
303
|
if (typeof spec.name !== "string" || !SCENARIO_NAME_RE.test(spec.name))
|
|
291
304
|
continue;
|
|
305
|
+
if (Object.hasOwn(SCENARIO_ALIASES, spec.name))
|
|
306
|
+
continue; // #273:保留名与 centerScenarios 同判(约定①)
|
|
292
307
|
if (!Object.hasOwn(TOOLSETS, spec.toolset) || !TOOLSETS[spec.toolset])
|
|
293
308
|
continue; // P2-13 同族守卫
|
|
294
309
|
out[spec.name] = {
|
|
@@ -332,7 +347,7 @@ const SCENARIO_AVAILABLE = { available: true };
|
|
|
332
347
|
*
|
|
333
348
|
* 判据键 = **toolset**:内建详情与 center 条目都带这个字段,故两条产线天然共用同一份判据。
|
|
334
349
|
* `requiresRepo` 的 toolset 需要部署配好 git 后端(`GIT_API_BASEURL` ⇒ `deps.repoClient`)。
|
|
335
|
-
* 词表外的 toolset(内建的 `full-body`/`
|
|
350
|
+
* 词表外的 toolset(内建的 `full-body`/`discuss`)不依赖后端 ⇒ 恒可用。
|
|
336
351
|
*/
|
|
337
352
|
export function scenarioAvailability(deps, toolset) {
|
|
338
353
|
if (Object.hasOwn(TOOLSETS, toolset) && TOOLSETS[toolset]?.requiresRepo === true && deps.repoClient === undefined) { // P2-13 同族守卫
|
|
@@ -375,6 +390,18 @@ export function centerScenarios(deps, specs, builtinNames, logger) {
|
|
|
375
390
|
logger?.warn("sema_registry_scenario_invalid", { name: String(spec?.name), reason: "name must match " + String(SCENARIO_NAME_RE) });
|
|
376
391
|
continue;
|
|
377
392
|
}
|
|
393
|
+
// #273 alias 窗:**旧名是保留名**。窗内 `team` 只能是 `discuss` 的入口别名 —— 让 center 用它另立
|
|
394
|
+
// 一条场景会立刻造出两个属主:请求面归它、而按场景键的运维配置(skills/mcp/a2a/prompts 的旧名
|
|
395
|
+
// 标签)在 alias 窗里同时也算 `discuss` 的,于是那条影子场景的 MCP/A2A(工具面+egress 面)会静默
|
|
396
|
+
// 漏进 discuss 的 roster(codex 对抗复审 [high],验真后采纳)。二义在源头掐掉,并且**响亮**跳过
|
|
397
|
+
// ——静默跳过等于让运维以为声明生效了。窗撤销时随 SCENARIO_ALIASES 一起消失。
|
|
398
|
+
if (Object.hasOwn(SCENARIO_ALIASES, spec.name)) {
|
|
399
|
+
logger?.warn("sema_registry_scenario_invalid", {
|
|
400
|
+
name: spec.name,
|
|
401
|
+
reason: `"${spec.name}" is a RESERVED deprecated scenario alias during the rename window — it resolves to "${SCENARIO_ALIASES[spec.name]}". Declare this scenario under a different name.`,
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
378
405
|
const toolset = Object.hasOwn(TOOLSETS, spec.toolset) ? TOOLSETS[spec.toolset] : undefined; // P2-13 同族守卫
|
|
379
406
|
if (!toolset) {
|
|
380
407
|
logger?.warn("sema_registry_scenario_invalid", { name: spec.name, reason: `unknown toolset "${String(spec.toolset)}" (have: ${Object.keys(TOOLSETS).join(", ")})` });
|
|
@@ -493,27 +520,34 @@ export function mergeUserSkills(scenarioSkills, userSkills, logger) {
|
|
|
493
520
|
* 到现状,与 workflows 面的 fail-closed 是刻意的 posture 差异,见 runtime-caps-resolver)。
|
|
494
521
|
* - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。
|
|
495
522
|
*/
|
|
496
|
-
export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known) {
|
|
523
|
+
export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known, logger) {
|
|
497
524
|
if (typeof bodyScenario === "string") {
|
|
525
|
+
// #273 alias(HTTP lane 的**唯一**归一点,见 SCENARIO_ALIASES 头注):旧名在这里变主名,词表门/
|
|
526
|
+
// allowlist 门/返回值三者此后只谈主名。allowlist 一并归一——运维写的是旧名、调用方用新名(或反过来)
|
|
527
|
+
// 说的是**同一个能力**,窗内不许因为改名把人挡在门外。
|
|
528
|
+
const requested = canonicalScenarioName(bodyScenario, known, logger);
|
|
498
529
|
// [2395]D③(5.1.0):body 显式给出的**未知名**响亮 400——此前静默折 default(oa 退役后老调用方
|
|
499
530
|
// 无声换了场景=更宽工具面,与零兼容纲领相反)。词表面先于治理面:名字都不存在时谈不上 allowlist。
|
|
500
531
|
// 指派链(ruling/default)不在此验——悬空指派名仍走 resolveSpec 既有 unknown→default(runtime-caps
|
|
501
532
|
// 面的误配另案,不该让一次中心误配把每个请求 400 死)。
|
|
502
|
-
if (!known.includes(
|
|
533
|
+
if (!known.includes(requested)) {
|
|
534
|
+
// 文案点名调用方**发的**那个名字(归一后的名字对他没有意义:他没发过它)。
|
|
503
535
|
throw new HttpError(400, `unknown scenario "${bodyScenario}" — this deployment's scenarios are: ${known.join(", ")}`, {
|
|
504
536
|
code: "scenario_unknown",
|
|
505
537
|
extra: { known: [...known] },
|
|
506
538
|
});
|
|
507
539
|
}
|
|
508
540
|
const allowed = ruling?.allowlist ?? [];
|
|
509
|
-
if (allowed.length > 0 && !allowed.includes(
|
|
541
|
+
if (allowed.length > 0 && !allowed.map((n) => canonicalScenarioName(n, known)).includes(requested)) {
|
|
510
542
|
throw new HttpError(400, `scenario "${bodyScenario}" is not in this principal's assigned allowlist`, {
|
|
511
543
|
code: "scenario_not_allowed",
|
|
512
|
-
extra: { allowlist: allowed },
|
|
544
|
+
extra: { allowlist: allowed }, // 运维自己的字面(不替他改写配置),判定用的是归一后的集合
|
|
513
545
|
});
|
|
514
546
|
}
|
|
515
|
-
return
|
|
547
|
+
return requested;
|
|
516
548
|
}
|
|
517
|
-
|
|
549
|
+
// 指派面(ruling.scenario)/部署面(DEFAULT_SCENARIO)同样过归一:两者都是**配置里写的名字**,
|
|
550
|
+
// 旧名不归一就会落到 resolveSpec 的 unknown→default 回退上——静默换场景,正是本窗要避免的。
|
|
551
|
+
return canonicalScenarioName(ruling?.scenario ?? defaultScenario, known, logger);
|
|
518
552
|
}
|
|
519
553
|
//# sourceMappingURL=scenarios.js.map
|
|
@@ -47,7 +47,9 @@ export interface LoadSkillsOptions {
|
|
|
47
47
|
* `label` = 错误文本里的对象名(center-plugins 用它约束 manifest 本体,报错不该指成 skill entry)。 */
|
|
48
48
|
export declare function assertConfined(p: string, rootReal: string, label?: string): void;
|
|
49
49
|
export declare function loadSkills(dir: string, opts?: LoadSkillsOptions): LoadedSkill[];
|
|
50
|
-
/** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones.
|
|
50
|
+
/** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones.
|
|
51
|
+
* #273 alias 窗:标签写**旧场景名**的技能同样命中(`scenarioConfigKeys`)—— 请求面归一之后,运维已经
|
|
52
|
+
* 写好的 `scenarios: team` 若不再被命中,就是改名当版静默摘掉他的配置。 */
|
|
51
53
|
export declare function skillsForScenario(loaded: LoadedSkill[], scenario: string): SkillSpec[];
|
|
52
54
|
export declare function parseFrontmatter(raw: string): {
|
|
53
55
|
meta: Record<string, string>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, sep } from "node:path";
|
|
3
|
+
import { scenarioConfigKeys } from "./scenario-alias.js";
|
|
3
4
|
/** `p` 的 realpath 必须落在 `rootReal` 之内(含自身),否则抛。`sep` 边界判定防 `/a/root-evil` 撞
|
|
4
5
|
* `/a/root` 前缀。目标不存在 ⇒ realpath 抛 ENOENT,同样是拒绝(悬空链接不该被读)。
|
|
5
6
|
* `label` = 错误文本里的对象名(center-plugins 用它约束 manifest 本体,报错不该指成 skill entry)。 */
|
|
@@ -108,9 +109,12 @@ function parseSkill(raw, fallbackName) {
|
|
|
108
109
|
.filter(Boolean);
|
|
109
110
|
return { spec: { name, description, content: body.trim() }, scenarios };
|
|
110
111
|
}
|
|
111
|
-
/** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones.
|
|
112
|
+
/** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones.
|
|
113
|
+
* #273 alias 窗:标签写**旧场景名**的技能同样命中(`scenarioConfigKeys`)—— 请求面归一之后,运维已经
|
|
114
|
+
* 写好的 `scenarios: team` 若不再被命中,就是改名当版静默摘掉他的配置。 */
|
|
112
115
|
export function skillsForScenario(loaded, scenario) {
|
|
113
|
-
|
|
116
|
+
const keys = scenarioConfigKeys(scenario);
|
|
117
|
+
return loaded.filter((k) => k.scenarios.length === 0 || keys.some((n) => k.scenarios.includes(n))).map((k) => k.spec);
|
|
114
118
|
}
|
|
115
119
|
export function parseFrontmatter(raw) {
|
|
116
120
|
// Tolerate CRLF (`\r?\n`): a skill .md saved on Windows / via some editors would otherwise miss the regex and
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { runTeamDiscussion, type ModelRef, type ModelRole, type Runner, type ToolSpec } from "@sema-agent/core";
|
|
2
|
+
import type { Logger } from "../observability/logger.js";
|
|
2
3
|
/**
|
|
3
|
-
* `
|
|
4
|
-
* as ONE tool the lead calls once (same shape as the code-review council), so the run model is
|
|
4
|
+
* `discuss` scenario — run a configured multi-agent DISCUSSION (core `runTeamDiscussion`) on a task.
|
|
5
|
+
* Packaged as ONE tool the lead calls once (same shape as the code-review council), so the run model is
|
|
6
|
+
* unchanged.
|
|
7
|
+
*
|
|
8
|
+
* ── 词汇(#273 / C-R14,clay 直裁 [4123])──────────────────────────────────────────────────────
|
|
9
|
+
* 「team」= agent-teams(持久具名团队)专用词;一次性多 agent 讨论叫「discuss」。所以**面向调用方/
|
|
10
|
+
* 模型的名字**全部是 discussion:场景 `discuss`、工具 `run_discussion`、协调者提示词。
|
|
11
|
+
* 而**模板注册面**(`BUILTIN_TEAMS` / `registerTeams` / `getTeam` / center 的 `teams` 键)保留 team
|
|
12
|
+
* 词汇 —— 它是 registry-core 的配置契约(docs/TEAMS.md、`GET /api/config/effective → teams`),跨仓
|
|
13
|
+
* 同改要另立件+center 侧同款 alias;本批刻意不单方面改一半(半改的配置键比不改更坏)。
|
|
5
14
|
*
|
|
6
15
|
* HYBRID layering (mirrors sema-registry docs/TEAMS.md): the TEMPLATE here carries only the
|
|
7
16
|
* GENERIC orchestration skeleton (member roles + generic personas, rounds, synthesizer); the CONSUMER
|
|
@@ -9,7 +18,7 @@ import { runTeamDiscussion, type ModelRef, type ModelRole, type Runner, type Too
|
|
|
9
18
|
*
|
|
10
19
|
* Templates: BUILTIN_TEAMS are the defaults; the sema registry pushes more at runtime via
|
|
11
20
|
* `applyEffective → registerTeams` (GET /api/config/effective → `teams`), and `getTeam` resolves from
|
|
12
|
-
* that merged registry. So
|
|
21
|
+
* that merged registry. So rosters are config-driven; the built-ins are the fallback.
|
|
13
22
|
*/
|
|
14
23
|
type TeamFn = typeof runTeamDiscussion;
|
|
15
24
|
export interface TeamMemberTemplate {
|
|
@@ -41,13 +50,34 @@ export declare const BUILTIN_TEAMS: Record<string, TeamTemplate>;
|
|
|
41
50
|
export declare function registerTeams(extra: Record<string, TeamTemplate>): void;
|
|
42
51
|
export declare function getTeam(name: string): TeamTemplate | undefined;
|
|
43
52
|
export declare function teamNames(): string[];
|
|
44
|
-
/**
|
|
45
|
-
export declare
|
|
53
|
+
/** 现役工具名(模型可见 = 行为面)。 */
|
|
54
|
+
export declare const DISCUSSION_TOOL_NAME = "run_discussion";
|
|
55
|
+
/** #273 / C-R14 过渡窗的旧工具名。**下一 minor 与 `SCENARIO_ALIASES` 同批删**。 */
|
|
56
|
+
export declare const DEPRECATED_DISCUSSION_TOOL_ALIAS = "run_team";
|
|
57
|
+
/**
|
|
58
|
+
* Build the discussion tool(s) bound to a template + the consumer's per-call business injection:
|
|
59
|
+
* `[run_discussion, run_team]` —— 主名 + **同款可执行**的旧名别名(窗一版)。
|
|
60
|
+
*
|
|
61
|
+
* 为什么别名要真能跑(而不是只保留名字或直接删):这只工具的调用方是**模型**,而模型读的提示词
|
|
62
|
+
* 不一定是我们写的 —— center prompts(`scenarioOverrides`)与运维技能里完全可以写死 `run_team`。
|
|
63
|
+
* 名字直接消失 ⇒ 那些部署当场进「工具不存在」的空转循环;留个空壳 ⇒ 同样跑不出结论。
|
|
64
|
+
* 代价(诚实记账):模型有可能两只都调 = 两轮完整讨论。压住这条的是**提示词只教主名** + 别名
|
|
65
|
+
* description 第一句就说 deprecated/别调;命中即 warn(`discussion_tool_alias_deprecated`)——窗撤销
|
|
66
|
+
* 与否有真读数,不靠猜。
|
|
67
|
+
*/
|
|
68
|
+
export declare function createDiscussionTools(subRunner: Runner, template: TeamTemplate, opts: {
|
|
69
|
+
objective: string;
|
|
70
|
+
businessContext?: string;
|
|
71
|
+
teamFn?: TeamFn;
|
|
72
|
+
logger?: Logger;
|
|
73
|
+
}): ToolSpec[];
|
|
74
|
+
/** Build the `run_discussion` tool bound to a template + the consumer's per-call business injection. */
|
|
75
|
+
export declare function createDiscussionTool(subRunner: Runner, template: TeamTemplate, opts: {
|
|
46
76
|
objective: string;
|
|
47
77
|
businessContext?: string;
|
|
48
78
|
teamFn?: TeamFn;
|
|
49
79
|
}): ToolSpec;
|
|
50
|
-
/** The lead just runs the
|
|
51
|
-
export declare const
|
|
80
|
+
/** The lead just runs the discussion tool once and returns its conclusion verbatim. */
|
|
81
|
+
export declare const discussionCoordinatorPrompt: import("@sema-agent/core").PromptProvider;
|
|
52
82
|
export {};
|
|
53
83
|
//# sourceMappingURL=team.d.ts.map
|
|
@@ -37,11 +37,43 @@ export function getTeam(name) {
|
|
|
37
37
|
export function teamNames() {
|
|
38
38
|
return Object.keys(TEAMS);
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
41
|
-
export
|
|
40
|
+
/** 现役工具名(模型可见 = 行为面)。 */
|
|
41
|
+
export const DISCUSSION_TOOL_NAME = "run_discussion";
|
|
42
|
+
/** #273 / C-R14 过渡窗的旧工具名。**下一 minor 与 `SCENARIO_ALIASES` 同批删**。 */
|
|
43
|
+
export const DEPRECATED_DISCUSSION_TOOL_ALIAS = "run_team";
|
|
44
|
+
/**
|
|
45
|
+
* Build the discussion tool(s) bound to a template + the consumer's per-call business injection:
|
|
46
|
+
* `[run_discussion, run_team]` —— 主名 + **同款可执行**的旧名别名(窗一版)。
|
|
47
|
+
*
|
|
48
|
+
* 为什么别名要真能跑(而不是只保留名字或直接删):这只工具的调用方是**模型**,而模型读的提示词
|
|
49
|
+
* 不一定是我们写的 —— center prompts(`scenarioOverrides`)与运维技能里完全可以写死 `run_team`。
|
|
50
|
+
* 名字直接消失 ⇒ 那些部署当场进「工具不存在」的空转循环;留个空壳 ⇒ 同样跑不出结论。
|
|
51
|
+
* 代价(诚实记账):模型有可能两只都调 = 两轮完整讨论。压住这条的是**提示词只教主名** + 别名
|
|
52
|
+
* description 第一句就说 deprecated/别调;命中即 warn(`discussion_tool_alias_deprecated`)——窗撤销
|
|
53
|
+
* 与否有真读数,不靠猜。
|
|
54
|
+
*/
|
|
55
|
+
export function createDiscussionTools(subRunner, template, opts) {
|
|
56
|
+
const primary = createDiscussionTool(subRunner, template, opts);
|
|
57
|
+
return [
|
|
58
|
+
primary,
|
|
59
|
+
{
|
|
60
|
+
...primary,
|
|
61
|
+
name: DEPRECATED_DISCUSSION_TOOL_ALIAS,
|
|
62
|
+
description: `DEPRECATED alias of \`${DISCUSSION_TOOL_NAME}\` — do not call it; call \`${DISCUSSION_TOOL_NAME}\` instead. ` +
|
|
63
|
+
"Kept for one release so a deployment whose prompts still name the old tool keeps working. " +
|
|
64
|
+
primary.description,
|
|
65
|
+
execute: async (args, ctx) => {
|
|
66
|
+
opts.logger?.warn("discussion_tool_alias_deprecated", { requested: DEPRECATED_DISCUSSION_TOOL_ALIAS, resolved: DISCUSSION_TOOL_NAME });
|
|
67
|
+
return primary.execute(args, ctx);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
/** Build the `run_discussion` tool bound to a template + the consumer's per-call business injection. */
|
|
73
|
+
export function createDiscussionTool(subRunner, template, opts) {
|
|
42
74
|
return {
|
|
43
|
-
name:
|
|
44
|
-
description: "Run the configured multi-agent
|
|
75
|
+
name: DISCUSSION_TOOL_NAME,
|
|
76
|
+
description: "Run the configured multi-agent discussion on the task and return the synthesized " +
|
|
45
77
|
"conclusion. Call exactly once, with no arguments.",
|
|
46
78
|
effect: "read",
|
|
47
79
|
parameters: Type.Object({}),
|
|
@@ -75,12 +107,12 @@ export function createTeamTool(subRunner, template, opts) {
|
|
|
75
107
|
// 1.32: synthesizer 失败时 `conclusion` 是 `[unavailable…]` 垃圾串。标注失败,别把垃圾当综合结论返回。
|
|
76
108
|
const content = team.conclusionValid
|
|
77
109
|
? team.conclusion
|
|
78
|
-
: "[
|
|
110
|
+
: "[讨论综合失败:synthesizer 重试后仍未产出可靠结论,可重试或检查模型/配置]";
|
|
79
111
|
return { content, details: { team: template.name, members: template.members.length, rounds: template.rounds, conclusionValid: team.conclusionValid } };
|
|
80
112
|
},
|
|
81
113
|
};
|
|
82
114
|
}
|
|
83
|
-
/** The lead just runs the
|
|
84
|
-
export const
|
|
85
|
-
"synthesized conclusion, output it VERBATIM as your final answer — do not summarize or rewrite it.");
|
|
115
|
+
/** The lead just runs the discussion tool once and returns its conclusion verbatim. */
|
|
116
|
+
export const discussionCoordinatorPrompt = stablePrompt(`You run a multi-agent discussion. Call \`${DISCUSSION_TOOL_NAME}\` exactly once (no arguments). When it ` +
|
|
117
|
+
"returns the synthesized conclusion, output it VERBATIM as your final answer — do not summarize or rewrite it.");
|
|
86
118
|
//# sourceMappingURL=team.js.map
|
|
@@ -27,7 +27,8 @@ diskCacheDir?: string): Promise<LoadedSkill[]>;
|
|
|
27
27
|
export declare function resolveMcpServers(mcp: {
|
|
28
28
|
servers: CenterMcpServer[];
|
|
29
29
|
}, logger?: Logger): ScopedMcpServer[];
|
|
30
|
-
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
30
|
+
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
31
|
+
* #273 alias 窗:标签写**旧场景名**的条目同样命中(`scenarioConfigKeys`,同 `skillsForScenario`)。 */
|
|
31
32
|
export declare function mcpForScenario(servers: ScopedMcpServer[] | undefined, scenario: string): McpServerSpec[] | undefined;
|
|
32
33
|
/**
|
|
33
34
|
* DESIGN-269 §2.1 —— resolve center A2A peers (`/effective.a2a`) into core `A2aServerSpec`s + the scenarios
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* the facade re-exports every symbol below unchanged).
|
|
10
10
|
*/
|
|
11
11
|
import { skillContentHash } from "@sema-agent/registry-core";
|
|
12
|
+
import { scenarioConfigKeys } from "../capabilities/scenario-alias.js";
|
|
12
13
|
import { fetchSkillContent } from "./http-client.js";
|
|
13
14
|
import { SHA256_HEX_RE } from "../digest-form.js";
|
|
14
15
|
/**
|
|
@@ -146,11 +147,13 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
146
147
|
logger?.info("sema_registry_mcp", { servers: out.map((s) => s.spec.name) });
|
|
147
148
|
return out;
|
|
148
149
|
}
|
|
149
|
-
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
150
|
+
/** The MCP servers applicable to a scenario: tagged with it, or untagged (global). undefined when none.
|
|
151
|
+
* #273 alias 窗:标签写**旧场景名**的条目同样命中(`scenarioConfigKeys`,同 `skillsForScenario`)。 */
|
|
150
152
|
export function mcpForScenario(servers, scenario) {
|
|
151
153
|
if (!servers || servers.length === 0)
|
|
152
154
|
return undefined;
|
|
153
|
-
const
|
|
155
|
+
const keys = scenarioConfigKeys(scenario);
|
|
156
|
+
const hit = servers.filter((s) => s.scenarios.length === 0 || keys.some((n) => s.scenarios.includes(n))).map((s) => s.spec);
|
|
154
157
|
return hit.length > 0 ? hit : undefined;
|
|
155
158
|
}
|
|
156
159
|
/**
|
|
@@ -201,7 +204,8 @@ export function resolveA2aPeers(a2a, logger) {
|
|
|
201
204
|
export function a2aForScenario(peers, scenario) {
|
|
202
205
|
if (!peers || peers.length === 0)
|
|
203
206
|
return undefined;
|
|
204
|
-
const
|
|
207
|
+
const keys = scenarioConfigKeys(scenario); // #273 alias 窗:旧场景名标签同样命中(同 mcpForScenario)
|
|
208
|
+
const hit = peers.filter((p) => p.scenarios.length === 0 || keys.some((n) => p.scenarios.includes(n))).map((p) => p.spec);
|
|
205
209
|
return hit.length > 0 ? hit : undefined;
|
|
206
210
|
}
|
|
207
211
|
//# sourceMappingURL=skills-mcp.js.map
|
|
@@ -11,7 +11,7 @@ export interface TaskRequestBody {
|
|
|
11
11
|
objective: string;
|
|
12
12
|
sessionId?: string;
|
|
13
13
|
/** Which deployment SCENARIO assembles this task's tools/prompt/skills (the keys of the worker's scenario
|
|
14
|
-
* table — e.g. "default"/"code-review"/"scan"/"
|
|
14
|
+
* table — e.g. "default"/"code-review"/"scan"/"discuss"). An INTENT bounded by the deployment's own table AND,
|
|
15
15
|
* on a governed worker, by the principal's center-resolved allowlist: an unknown name is a typed 400
|
|
16
16
|
* `scenario_unknown`, a known-but-not-allowed one a typed 400 `scenario_not_allowed`; absent ⇒ the ruling's
|
|
17
17
|
* assigned scenario, else `DEFAULT_SCENARIO` (consumed at `boot/resolve-spec.ts` via `gateScenarioRequest`).
|
package/dist/main.js
CHANGED
|
@@ -311,7 +311,7 @@ async function main() {
|
|
|
311
311
|
// `Runner.sessions` 就是传进去的那只 store,两只 Runner 不会各持一份会话缓存),唯独不挂
|
|
312
312
|
// `executionEnvFactory`。core 的 `handsEnabled = ownedEnv || deps.executionEnv` 是 **per-Runner** 合同,
|
|
313
313
|
// 单 runner 服务全场景时结构上无法逐场景兑现 —— 这只孪生就是兑现它的那一半(设计小票 §2a)。
|
|
314
|
-
// 声明 hands:"none" 的场景(scan / code-review 两腿 /
|
|
314
|
+
// 声明 hands:"none" 的场景(scan / code-review 两腿 / discuss / center overlay)在 HTTP 执行点被路由到这里。
|
|
315
315
|
//
|
|
316
316
|
// 🔴 两条**已知边界**(codex 复审 2026-08-09 两轮提出,逐条亲验 core dist 后如实记账;两条的部署条件
|
|
317
317
|
// 互不相同 —— 不要把它们并成一句)。
|
|
@@ -428,7 +428,7 @@ async function main() {
|
|
|
428
428
|
// "memory backend diverges" premise was wrong; double-review caught it — see the cap site in http/server.ts).
|
|
429
429
|
// Static per deployment (deps are boot-fixed).
|
|
430
430
|
const workflowsCapable = workflowsCapability(runnerDeps);
|
|
431
|
-
// Sub-task runner (code-review council lenses/arbiter, subagent/
|
|
431
|
+
// Sub-task runner (code-review council lenses/arbiter, subagent/discussion children). [1364]①/[1367]①(core
|
|
432
432
|
// 1.350 fork 终修的部署要求):fork 子任务带 `requireExistingSession:true` 在这里执行而源会话在 HOST 店
|
|
433
433
|
// ——拆店=响亮 `resume.session_not_found`(cli fork 案的 server 半场)。但整店共享被 codex 判两 high
|
|
434
434
|
// (普通子任务铸 committed durable 会话:无 reaper 覆盖=无界增长+污染 /v1/sessions 枚举),故走
|
|
@@ -509,7 +509,7 @@ async function main() {
|
|
|
509
509
|
};
|
|
510
510
|
const subRunner = new Runner(subRunnerDeps);
|
|
511
511
|
// #196:`subRunner` 的无手孪生(同一份 deps 字面量摘掉 executionEnvFactory —— 差异面只有这一处,
|
|
512
|
-
// 结构上不可能漂移)。council 的 lens/arbiter、
|
|
512
|
+
// 结构上不可能漂移)。council 的 lens/arbiter、discuss 的成员/synthesizer 走它:那些子任务的人格声明
|
|
513
513
|
// 里一件工具都没有,却在修前照拿全量可写手(设计小票 §1 里最意外的一支)。
|
|
514
514
|
// ⚠️ 与主 subRunner 共享 `subRunnerSessions` 与 `checkpointStore` 同实例 —— park/fork 的行是同一批。
|
|
515
515
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
@@ -525,7 +525,8 @@ async function main() {
|
|
|
525
525
|
// map; undefined only on the env-only/no-backend deploy (which has no durable approvals either — consistent).
|
|
526
526
|
const approvalExemptionStore = backend ? backend.approvalExemption() : undefined;
|
|
527
527
|
const instanceId = uuidv7();
|
|
528
|
-
// Per-task wall-clock override (seconds). 0/absent = keep the
|
|
528
|
+
// Per-task wall-clock override (seconds). 0/absent = keep the tenancy default (see taskWallClockSec:
|
|
529
|
+
// 2400s, 3600s for the big lane = council/debate/discuss) —
|
|
529
530
|
// the override only ever RAISES (Math.max at the use site), so a misconfigured low value cannot shrink
|
|
530
531
|
// the council budget.
|
|
531
532
|
// 🔴 2026-08-01 env fail-loud 族:原为 `Math.max(0, Math.floor(Number(env ?? "0")) || 0)` ——
|
package/dist/run-local.js
CHANGED
|
@@ -65,7 +65,7 @@ import { makeLoadProjectMemory, makeProbeInstructionSources } from "./project-me
|
|
|
65
65
|
import { loadSkills } from "./capabilities/skills.js";
|
|
66
66
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
67
67
|
import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
|
|
68
|
-
import { buildScenarios, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
|
|
68
|
+
import { buildScenarios, selectScenario, centerScenarios, canonicalScenarioName } from "./capabilities/scenarios.js";
|
|
69
69
|
import { pickHandsRunner, withoutExecutionEnv } from "./capabilities/hands-lane.js";
|
|
70
70
|
import { HttpError } from "./security.js";
|
|
71
71
|
import { memoryEngineBackendFor, memorySpecForRequest } from "./memory-scope.js";
|
|
@@ -653,7 +653,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
653
653
|
: {}),
|
|
654
654
|
};
|
|
655
655
|
const runner = new Runner(runnerDeps);
|
|
656
|
-
// Sub-task runner (council lenses/
|
|
656
|
+
// Sub-task runner (council lenses/discussion members) — mirrors main.ts's subRunner, INCLUDING executionEnvFactory so a delegated
|
|
657
657
|
// child gets hands (Bash/Read/Edit) on the host lane. Without it the child's handsEnabled is false — the exact
|
|
658
658
|
// "subagent couldn't run pwd" defect fixed at main.ts:799; this is its run-local twin, caught by the systematic audit.
|
|
659
659
|
// [1367]① fork server half(main.ts subRunner 同款):ForkRoutingSessionStore——fork/resume 形
|
|
@@ -667,7 +667,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
667
667
|
const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
|
|
668
668
|
const subRunner = new Runner(subRunnerDeps);
|
|
669
669
|
// #196:无手孪生一对 —— **mirrors main.ts**(那边是 `handslessRunner` / `handslessSubRunner` 两只,同 deps
|
|
670
|
-
// 摘掉 executionEnvFactory)。run-local 是同形装配点,漏这一对 = 本地 lane 上 scan/council/
|
|
670
|
+
// 摘掉 executionEnvFactory)。run-local 是同形装配点,漏这一对 = 本地 lane 上 scan/council/discuss 的最终
|
|
671
671
|
// roster 仍被并集进全量可写手(server 修了、CLI 静默留病 —— 这条腿的 subRunner 当年就是这么漏配的)。
|
|
672
672
|
const handslessRunner = new Runner(withoutExecutionEnv(runnerDeps));
|
|
673
673
|
const handslessSubRunner = new Runner(withoutExecutionEnv(subRunnerDeps));
|
|
@@ -692,7 +692,10 @@ export async function runLocal(argv, deps = {}) {
|
|
|
692
692
|
}
|
|
693
693
|
// ── Build the TaskSpec the way the /v1/tasks sync handler's resolveSpec does (minus server-only fields:
|
|
694
694
|
// no auth/principal/durable/approval/budget-cap-from-body). Fresh sessionId; @-model mention honored. ──
|
|
695
|
-
|
|
695
|
+
// #273 alias:run-local lane 的唯一归一点(旧名 team → discuss + 响亮 warn),与 HTTP lane 的
|
|
696
|
+
// `gateScenarioRequest` 同一只属主。归一之后 `scenarioName` 只可能是现役主名——下面的墙钟/mcp/a2a
|
|
697
|
+
// 判据点按主名写,不做双名判。
|
|
698
|
+
const requested = canonicalScenarioName(args.scenario ?? config.defaultScenario, Object.keys(scenarios), logger);
|
|
696
699
|
const scenarioName = scenarios[requested] ? requested : "default";
|
|
697
700
|
// A scenario may THROW HttpError synchronously when a prerequisite is unmet (e.g. `scan` needs
|
|
698
701
|
// GIT_API_BASEURL + a repo — which a one-shot CLI may not
|
|
@@ -721,7 +724,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
721
724
|
const scope = args.user ?? "local";
|
|
722
725
|
// Timeout policy mirrors the server (taskWallClockSec): run-local IS the pure single-user
|
|
723
726
|
// TOC lane → NO wall clock by default (core's wedge safety-net still applies); explicit TASK_TIMEOUT_SEC
|
|
724
|
-
// wins with its raise-only floor (
|
|
727
|
+
// wins with its raise-only floor (discuss 3600s — a multi-agent coordinator legitimately exceeds the base).
|
|
725
728
|
// [854]④ 不落此腿:per-request body.limits 是 HTTP body 车道(resolveTaskLimits,main.ts resolveSpec);
|
|
726
729
|
// run-local 无 body,配速仍走 env(要 CLI flag 时另开件,别在这里半做)。
|
|
727
730
|
// 🔴 2026-08-01 env fail-loud 族:原为 `Math.max(0, Math.floor(Number(env ?? "0")) || 0)` ——
|
|
@@ -730,7 +733,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
730
733
|
// 且启动期零提示。改走 numEnv:非法值当场 fail-loud 并指名键与实际值。
|
|
731
734
|
// (numEnv 的注释早就预言了这个失败模式,只是它没 export、跨文件够不到,于是这两处手写了。)
|
|
732
735
|
const taskTimeoutSec = Math.max(0, Math.floor(numEnv("TASK_TIMEOUT_SEC", "0")));
|
|
733
|
-
const timeoutSec = taskWallClockSec(taskTimeoutSec, false, scenarioName === "
|
|
736
|
+
const timeoutSec = taskWallClockSec(taskTimeoutSec, false, scenarioName === "discuss");
|
|
734
737
|
const mcp = mcpForScenario(config.mcpServers, scenarioName);
|
|
735
738
|
// DESIGN-269 车1 件3(本腿半场):A2A peers 同样按场景过滤进 spec。**只有 center/config 基线这一半**
|
|
736
739
|
// —— run-local 无 HTTP body,所以没有「调用方自带 peer」那条腿(与 [854]④ 的 body.limits 同姿势:
|
package/dist/task-mcp.js
CHANGED
|
@@ -91,8 +91,15 @@ function normalizeServer(e) {
|
|
|
91
91
|
return null;
|
|
92
92
|
}
|
|
93
93
|
const spec = { name: o.name, transport };
|
|
94
|
-
|
|
94
|
+
// #272(#269 车1 codex F1 的孪生修,2026-08-16):`allowTools` 是**收窄**字段,core 把缺席解释成
|
|
95
|
+
// 「不限制」——旧形「坏形丢键、server 照挂」= 调用方要求只挂一条,实际挂上远端全部工具(每条还可能
|
|
96
|
+
// 带 write 语义),收窄意图坏形时退化成放行。判据与 task-a2a.ts 同一句:**一个键在场就必须合法,
|
|
97
|
+
// 否则这条 server 整条不进**(null ⇒ 上层记 dropped 带名)。缺席仍合法=真「不限制」。
|
|
98
|
+
if (o.allowTools !== undefined) {
|
|
99
|
+
if (!Array.isArray(o.allowTools) || !o.allowTools.every((a) => typeof a === "string"))
|
|
100
|
+
return null;
|
|
95
101
|
spec.allowTools = o.allowTools;
|
|
102
|
+
}
|
|
96
103
|
// caller-trust-root (host-lane single-user): the user's own effect/elicitation choices are kept (CC-parity).
|
|
97
104
|
if (typeof o.elicitation === "boolean")
|
|
98
105
|
spec.elicitation = o.elicitation;
|
package/dist/task-workflow.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare function normalizeRetainSubagentSessions(raw: unknown): boolean |
|
|
|
83
83
|
* - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
|
|
84
84
|
* (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
|
|
85
85
|
* never means "hangs forever"). CC has no whole-task decapitation either.
|
|
86
|
-
* - MULTI-TENANT: base 2400s (big tasks — council/debate/
|
|
86
|
+
* - MULTI-TENANT: base 2400s (big tasks — council/debate/discuss — 3600s). (PAIR-REVIEW F-4 附带修:此行
|
|
87
87
|
* 曾写 600s/1500s 与代码漂移——真值以下方 base 为准。)
|
|
88
88
|
* - Explicit `TASK_TIMEOUT_SEC` wins in ANY tenancy (the operator's deliberate wall) and keeps its documented
|
|
89
89
|
* raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink a council budget).
|
package/dist/task-workflow.js
CHANGED
|
@@ -96,7 +96,7 @@ export function normalizeRetainSubagentSessions(raw) {
|
|
|
96
96
|
* - SINGLE-USER turnkey(`requirePrincipal !== true`)= the user IS the superadmin → **NO wall clock**
|
|
97
97
|
* (undefined; core arms none — its separate wedge safety-net still catches a stuck tool call, so "no wall"
|
|
98
98
|
* never means "hangs forever"). CC has no whole-task decapitation either.
|
|
99
|
-
* - MULTI-TENANT: base 2400s (big tasks — council/debate/
|
|
99
|
+
* - MULTI-TENANT: base 2400s (big tasks — council/debate/discuss — 3600s). (PAIR-REVIEW F-4 附带修:此行
|
|
100
100
|
* 曾写 600s/1500s 与代码漂移——真值以下方 base 为准。)
|
|
101
101
|
* - Explicit `TASK_TIMEOUT_SEC` wins in ANY tenancy (the operator's deliberate wall) and keeps its documented
|
|
102
102
|
* raise-only floor (`Math.max` with the base, so a misconfigured low value cannot shrink a council budget).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.28.0-rc.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "^5.
|
|
57
|
+
"@sema-agent/core": "^5.37.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.16.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|