@sema-agent/server 7.131.0 → 7.132.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 +309 -0
- package/README.md +5 -1
- package/README.zh-CN.md +3 -1
- package/USAGE.md +163 -54
- package/dist/approval-card.d.ts +3 -1
- package/dist/approval-card.js +2 -3
- package/dist/audit.d.ts +7 -10
- package/dist/auth-bridge.d.ts +4 -0
- package/dist/auth-bridge.js +10 -11
- package/dist/boot/deployment-mods.d.ts +31 -0
- package/dist/boot/deployment-mods.js +26 -0
- package/dist/boot/execution-env.d.ts +1 -1
- package/dist/boot/execution-env.js +19 -6
- package/dist/boot/memory-boundary.d.ts +2 -2
- package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
- package/dist/boot/read-deny-anchor-audit.js +36 -0
- package/dist/boot/resolve-spec.d.ts +1 -5
- package/dist/boot/resolve-spec.js +19 -47
- package/dist/boot/runner-deps.d.ts +19 -8
- package/dist/boot/runner-deps.js +14 -2
- package/dist/boot/session-faces.d.ts +1 -7
- package/dist/boot/stage-03-store-backend.d.ts +1 -1
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +4 -4
- package/dist/boot/stage-05-execution-env.js +7 -10
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +5 -4
- package/dist/boot/stage-07-capability-layer.d.ts +2 -2
- package/dist/boot/stage-07-capability-layer.js +3 -3
- package/dist/boot/stage-08-reapers.d.ts +2 -2
- package/dist/boot/stage-09-leader.d.ts +3 -9
- package/dist/boot/stage-10-http-server.d.ts +3 -9
- package/dist/boot/stage-10-http-server.js +6 -7
- package/dist/boot/stores.d.ts +1 -1
- package/dist/boot/stores.js +5 -6
- package/dist/boot/web-admin.d.ts +0 -3
- package/dist/boot/web-admin.js +35 -35
- package/dist/brain.js +17 -22
- package/dist/budget.js +5 -4
- package/dist/caller-text-echo.d.ts +20 -0
- package/dist/caller-text-echo.js +24 -0
- package/dist/capabilities/brand-identity.d.ts +26 -0
- package/dist/capabilities/brand-identity.js +27 -0
- package/dist/capabilities/center-plugins.js +1 -1
- package/dist/capabilities/center-prompts.d.ts +32 -59
- package/dist/capabilities/center-prompts.js +18 -32
- package/dist/capabilities/engine-capabilities.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +1 -1
- package/dist/capabilities/mcp-probe.d.ts +14 -1
- package/dist/capabilities/mcp-probe.js +22 -133
- package/dist/capabilities/scenarios.d.ts +34 -40
- package/dist/capabilities/scenarios.js +16 -33
- package/dist/capabilities/skills.d.ts +19 -4
- package/dist/capabilities/skills.js +30 -10
- package/dist/config-catalog.js +6 -6
- package/dist/config-lkg.d.ts +9 -9
- package/dist/config-lkg.js +20 -4
- package/dist/config-provider.d.ts +3 -1
- package/dist/config-provider.js +12 -4
- package/dist/config-types.d.ts +7 -9
- package/dist/config.js +17 -4
- package/dist/device-ws-protocol.d.ts +1 -1
- package/dist/device-ws-protocol.js +1 -0
- package/dist/env-name-allowlist-knobs.d.ts +12 -2
- package/dist/env-name-allowlist-knobs.js +5 -4
- package/dist/exec-bounds.d.ts +15 -3
- package/dist/exec-bounds.js +4 -0
- package/dist/execution-lane-caps.d.ts +9 -2
- package/dist/execution-lane-caps.js +11 -0
- package/dist/hooks/classic-stations.d.ts +86 -0
- package/dist/hooks/classic-stations.js +27 -0
- package/dist/hooks/hook-llm.d.ts +8 -5
- package/dist/hooks/hook-llm.js +78 -35
- package/dist/hooks/hook-runner.d.ts +232 -80
- package/dist/hooks/hook-runner.js +218 -312
- package/dist/http/admission.d.ts +8 -2
- package/dist/http/admission.js +101 -57
- package/dist/http/approval-sweeps.js +57 -16
- package/dist/http/checkpoint-ctx.d.ts +25 -8
- package/dist/http/checkpoint-ctx.js +11 -7
- package/dist/http/claimed-run.d.ts +13 -0
- package/dist/http/claimed-run.js +15 -0
- package/dist/http/device-placement.js +5 -10
- package/dist/http/dispatch.d.ts +0 -4
- package/dist/http/dispatch.js +5 -9
- package/dist/http/durable-run-leg.d.ts +2 -1
- package/dist/http/durable-run-leg.js +5 -3
- package/dist/http/health-self-report.d.ts +7 -5
- package/dist/http/health-self-report.js +4 -18
- package/dist/http/principal-gate.d.ts +23 -6
- package/dist/http/principal-gate.js +24 -5
- package/dist/http/refusal-body.d.ts +21 -0
- package/dist/http/refusal-body.js +4 -0
- package/dist/http/request-guards.d.ts +27 -0
- package/dist/http/request-guards.js +24 -3
- package/dist/http/resume-legs.d.ts +25 -0
- package/dist/http/resume-legs.js +43 -22
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +7 -7
- package/dist/http/routes/session-sync.js +4 -2
- package/dist/http/routes/sessions.js +1 -1
- package/dist/http/routes/shared-memory.js +2 -13
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +19 -9
- package/dist/http/send.d.ts +9 -7
- package/dist/http/send.js +7 -2
- package/dist/http/server.d.ts +21 -11
- package/dist/http/server.js +28 -2
- package/dist/http/submit-ctx.d.ts +25 -0
- package/dist/http/submit-ctx.js +39 -0
- package/dist/http/wire-types.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lane-home-probe.d.ts +43 -0
- package/dist/lane-home-probe.js +35 -0
- package/dist/limit-refusal.d.ts +47 -0
- package/dist/limit-refusal.js +12 -0
- package/dist/main.js +1 -1
- package/dist/mcp-assertion-header.d.ts +1 -9
- package/dist/mcp-assertion-header.js +0 -6
- package/dist/mcp-tool-faces.d.ts +5 -0
- package/dist/mcp-tool-faces.js +5 -1
- package/dist/memory-scope.d.ts +1 -1
- package/dist/model-provider.d.ts +39 -12
- package/dist/model-provider.js +19 -2
- package/dist/model-route-endpoint.d.ts +1 -1
- package/dist/model-route-endpoint.js +5 -5
- package/dist/observability/cost-taxonomy.d.ts +7 -7
- package/dist/observability/fail-open.d.ts +42 -6
- package/dist/observability/fail-open.js +42 -6
- package/dist/observability/metrics.js +3 -1
- package/dist/observability/prompt-manifest.d.ts +47 -82
- package/dist/observability/prompt-manifest.js +119 -17
- package/dist/observability/run-terminal.js +14 -6
- package/dist/observability/secret-env-scrub.d.ts +13 -1
- package/dist/observability/secret-env-scrub.js +13 -1
- package/dist/observability/tool-trace.d.ts +16 -21
- package/dist/observability/tool-trace.js +51 -42
- package/dist/per-task-image.d.ts +7 -1
- package/dist/per-task-image.js +3 -3
- package/dist/plain-string-record.d.ts +25 -0
- package/dist/plain-string-record.js +32 -0
- package/dist/plugins/blob-backend.d.ts +68 -13
- package/dist/plugins/blob-backend.js +53 -10
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -4
- package/dist/plugins/checkpoint-store-sql.js +39 -26
- package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
- package/dist/plugins/k8s-bg-scripts.js +22 -0
- package/dist/plugins/local-checkpoint-store.js +5 -2
- package/dist/plugins/local-session-store.js +1 -1
- package/dist/plugins/one-time-migrations.d.ts +137 -90
- package/dist/plugins/one-time-migrations.js +356 -207
- package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
- package/dist/plugins/permission-rule-store-sql.js +4 -18
- package/dist/plugins/pg-session-storage.d.ts +4 -5
- package/dist/plugins/pg-session-storage.js +7 -17
- package/dist/plugins/remote-env-adb.d.ts +6 -0
- package/dist/plugins/remote-env-adb.js +9 -4
- package/dist/plugins/remote-env-device.js +3 -0
- package/dist/plugins/remote-env-e2b.d.ts +1 -1
- package/dist/plugins/remote-env-e2b.js +21 -9
- package/dist/plugins/remote-env-host.d.ts +9 -9
- package/dist/plugins/remote-env-host.js +10 -32
- package/dist/plugins/remote-env-k8s.d.ts +95 -26
- package/dist/plugins/remote-env-k8s.js +265 -90
- package/dist/plugins/remote-env-local-docker.d.ts +20 -1
- package/dist/plugins/remote-env-local-docker.js +77 -9
- package/dist/plugins/remote-env-ssh.d.ts +15 -0
- package/dist/plugins/remote-env-ssh.js +109 -3
- package/dist/plugins/remote-shell.d.ts +0 -1
- package/dist/plugins/remote-shell.js +1 -2
- package/dist/plugins/retention-store-sql.js +6 -2
- package/dist/plugins/schema-consistency.d.ts +54 -0
- package/dist/plugins/schema-consistency.js +52 -0
- package/dist/plugins/session-epoch-anchor.d.ts +13 -0
- package/dist/plugins/session-epoch-anchor.js +23 -0
- package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
- package/dist/plugins/shared-memory-store-sql.js +5 -16
- package/dist/plugins/sql-driver.d.ts +141 -0
- package/dist/plugins/sql-driver.js +115 -1
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +36 -0
- package/dist/plugins/store-backend.js +2 -1
- package/dist/plugins/tidb-session-storage.d.ts +7 -11
- package/dist/plugins/tidb-session-storage.js +6 -16
- package/dist/plugins/tidb-session-store.js +1 -1
- package/dist/prompts-domain-validate.d.ts +17 -61
- package/dist/prompts-domain-validate.js +9 -119
- package/dist/request-key-closure.d.ts +2 -2
- package/dist/request-key-closure.js +2 -4
- package/dist/rule-entry-list.d.ts +12 -9
- package/dist/rule-entry-list.js +37 -21
- package/dist/run-local.js +19 -19
- package/dist/runs.d.ts +6 -0
- package/dist/runs.js +16 -0
- package/dist/server-secret-env.d.ts +44 -18
- package/dist/server-secret-env.js +49 -8
- package/dist/session-incarnation.d.ts +15 -5
- package/dist/session-incarnation.js +16 -9
- package/dist/session-titler.js +12 -2
- package/dist/spec-fields.d.ts +28 -16
- package/dist/spec-fields.js +46 -53
- package/dist/sql-engine-posture.d.ts +29 -4
- package/dist/sql-engine-posture.js +36 -1
- package/dist/stored-request-tool-axes-m19.d.ts +19 -4
- package/dist/stored-request-tool-axes-m19.js +30 -2
- package/dist/task-a2a.d.ts +9 -1
- package/dist/task-a2a.js +73 -47
- package/dist/task-mcp.d.ts +46 -58
- package/dist/task-mcp.js +99 -65
- package/dist/task-settings.d.ts +7 -21
- package/dist/task-settings.js +7 -36
- package/dist/text-bidi.d.ts +3 -46
- package/dist/text-bidi.js +3 -3
- package/dist/tool-approval.d.ts +1 -1
- package/dist/trace/core-keyset-guard.d.ts +24 -4
- package/dist/trace/engine-notice-wire.d.ts +11 -3
- package/dist/trace/engine-notice-wire.js +8 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/ledger-sink.js +4 -1
- package/dist/trace/model-segment.js +1 -1
- package/dist/trace/project.d.ts +19 -6
- package/dist/trace/project.js +57 -22
- package/dist/trace/wire-projection-faces.d.ts +79 -1
- package/dist/trace/wire-projection-faces.js +22 -0
- package/dist/web-admin-client/candidate-gate.d.ts +6 -6
- package/dist/web-admin-client/candidate-gate.js +3 -3
- package/dist/web-admin-client/http-client.js +0 -6
- package/dist/web-admin-client/read-warnings.d.ts +13 -0
- package/dist/web-admin-client/read-warnings.js +10 -0
- package/dist/web-admin-client/skills-mcp.d.ts +3 -0
- package/dist/web-admin-client/skills-mcp.js +9 -6
- package/dist/web-admin-client/types.d.ts +2 -3
- package/dist/workspace-cache.d.ts +35 -0
- package/dist/workspace-cache.js +36 -0
- package/dist/workspace-space.d.ts +3 -2
- package/dist/workspace-space.js +2 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +8 -1
- package/package.json +4 -4
- package/scripts/drain-check-local.mjs +34 -1
- package/scripts/drain-check.mysql.sql +30 -2
- package/scripts/drain-check.pg.sql +29 -2
- package/dist/capabilities/memory-notice.d.ts +0 -138
- package/dist/capabilities/memory-notice.js +0 -64
- package/dist/capabilities/prompt.d.ts +0 -17
- package/dist/capabilities/prompt.js +0 -6
- package/dist/mcp-content-origin.d.ts +0 -34
- package/dist/mcp-content-origin.js +0 -10
- package/dist/tool-axis-words.d.ts +0 -27
- package/dist/tool-axis-words.js +0 -9
- package/dist/wire-name-bound.d.ts +0 -14
- package/dist/wire-name-bound.js +0 -13
|
@@ -19,17 +19,20 @@ import { type Runner, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
|
19
19
|
/** 场景对手带工具面的表态(闭集)。`full` = core 手带 band 照常 mount;`none` = 不挂 executionEnvFactory 的 Runner。 */
|
|
20
20
|
export type ScenarioHands = "full" | "none";
|
|
21
21
|
/**
|
|
22
|
-
* 无手孪生 Runner 的 deps 构造:把 `executionEnvFactory`
|
|
22
|
+
* 无手孪生 Runner 的 deps 构造:把 `executionEnvFactory` **摘键**(不是置 undefined)。
|
|
23
23
|
* 摘键让「这只 Runner 没有手」在 deps 字面量上自证;置 undefined 留下一个值为 undefined 的键,
|
|
24
24
|
* 与 core 现在的 `deps.executionEnvFactory ||` 判读结果相同,但把正确性寄托在真值判断上。
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
*
|
|
26
|
+
* **LSP 的 manager 座照传,不摘**:挂不挂 LSP 由引擎一处决定 —— core 7.49.2 起 LSP 只在「有手腿 ∧ 接了 manager 座」的腿上挂,无手腿
|
|
27
|
+
* (`hands=none` 的场景、委派子代、后台代理)天然没有。本仓此前在这里再摘一次 `lspManager`(宿主车道的 `NodeLspManager` 经
|
|
28
|
+
* `defaultLspReadText` 直读本地盘、没有读面围栏,当年引擎还会在 StubExecutionEnv 上挂它),是同一件事的第二只写者:引擎一旦决定
|
|
29
|
+
* 它自己的读法,两处只会各改各的。引擎的读数钉在 `test/scenario-hands-lane.test.ts` 的 S-1502 段(同一份含 manager 座的 deps,有手腿 roster 有
|
|
30
|
+
* `LSP`、无手孪生没有)。
|
|
27
31
|
*/
|
|
28
32
|
export declare function withoutHands<T extends {
|
|
29
33
|
executionEnvFactory?: unknown;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/** 一对 Runner:同 deps,唯一差别是 `handsless` 不挂 `executionEnvFactory` 与 `lspManager`。 */
|
|
34
|
+
}>(deps: T): Omit<T, "executionEnvFactory">;
|
|
35
|
+
/** 一对 Runner:同 deps,唯一差别是 `handsless` 不挂 `executionEnvFactory`。 */
|
|
33
36
|
export interface HandsRunnerPair {
|
|
34
37
|
full: Runner;
|
|
35
38
|
handsless: Runner;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { handBandEffects, judgeRuleToolName, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
|
|
2
2
|
import { TOOL_NAME_ROSTER } from "../namespaced-rule-name.js";
|
|
3
3
|
export function withoutHands(deps) {
|
|
4
|
-
const { executionEnvFactory: _handsDropped,
|
|
4
|
+
const { executionEnvFactory: _handsDropped, ...rest } = deps;
|
|
5
5
|
return rest;
|
|
6
6
|
}
|
|
7
7
|
export function pickHandsRunner(hands, pair) {
|
|
@@ -108,7 +108,7 @@ export declare function buildMcpProbeWalkTimeoutMs(declarations: number): number
|
|
|
108
108
|
* 一次走查里**同时追裁决**的台数,喂 core 的 `opts.concurrency`。
|
|
109
109
|
*
|
|
110
110
|
* 为什么不用缺省的 1:core 的 d.ts 明写整次调用的时长 = 每台时长在 `ceil(n / concurrency)` 轮上的和。
|
|
111
|
-
* 缺省 1 下一个 32 台(=
|
|
111
|
+
* 缺省 1 下一个 32 台(= `task-mcp.ts` 的 `MAX_REQUEST_MCP_SERVERS`)的申报最坏是 32 × {@link MCP_PROBE_VERDICT_TIMEOUT_MS}
|
|
112
112
|
* —— 一条 HTTP 请求挂五分钟。8 把最坏收进 4 轮。代价 core 也写明了:并发只影响 `connectMs` 这个读数的
|
|
113
113
|
* 争用度,**行与下标对齐不受影响**(而本面根本不发 `connectMs`)。
|
|
114
114
|
* ⚠️ 它**不是**「同时开几条传输」的帽:被时钟放弃的拨号仍在飞,最坏可达一条/台申报(core 原话)。
|
|
@@ -159,6 +159,19 @@ export interface McpProbeRefusal {
|
|
|
159
159
|
/**
|
|
160
160
|
* `POST …/probe` 的体读器:闭形、逐条点名、零静默丢。成功 ⇒ 交给 core 的 `specs`(**原样**转发被读出来的
|
|
161
161
|
* 条目,`McpServerSpec` 的可选键一个都不重写 —— 重写就是在 core 的型上做第二次投影)。
|
|
162
|
+
*
|
|
163
|
+
* ## 申报「能不能原样用」:问提交腿 fresh 门**同一只**回答者,不另判
|
|
164
|
+
* 本面只判自己那一层(体是对象、顶层只受理 `mcpServers`、这个键必填);`mcpServers` 本身 —— 列表的形、上限、每条的键名与键值 —— 全部交给
|
|
165
|
+
* `task-mcp.ts` 的 `requestMcpDeclarationRefusal`,拒(码 + 句 + `unknownKeys`)原样转述:同一份申报在两扇门上只有一个答案,零拨号之前就给。
|
|
166
|
+
* 回答者受理的申报由**同一只**读器(`validateRequestMcp`)交回,读器的产物就是申报本身(只差判官自己的合法归一),经 `toCoreMcpServerSpec`
|
|
167
|
+
* 得到的就是一条任务提交会交给引擎的那一份 —— 本面拨的正是它。读器只在回答者受理之后才跑,所以它对重放存量 body 才有的降级留痕
|
|
168
|
+
* (`source` 写坏的 F 类丢弃)不会在一次被拒的探测上虚记。
|
|
169
|
+
*
|
|
170
|
+
* ## 唯一一处按门分的后果:声明了 `assertionHeader`(引擎尚无断言头座)
|
|
171
|
+
* 事实只有一个、读它的谓词只有一只(`declaresMcpAssertionHeader`):这台 server 声明的断言头本版**承载、不发送**,交给引擎的规格里没有它。
|
|
172
|
+
* 新提交照挂并给运维一行 info(`mcp_assertion_header_not_sent`;settings 面同读法);本面**整条拒** —— 拨出去的不是申报的那一份,一台握手放行、
|
|
173
|
+
* 调用才要断言的对端会被答成 connected,而它的调用会被拒。判据同一只、后果按门分(与重放腿对同一组身份头「降级到升级前读法」同一个姿势);
|
|
174
|
+
* 引擎长出同名座的那天,`toCoreMcpServerSpec` 的剥键与这一拒一起退役(`mcp-assertion-header.ts` 的退役门编译红)。
|
|
162
175
|
*/
|
|
163
176
|
export declare function readMcpProbeBody(raw: unknown): {
|
|
164
177
|
ok: true;
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { probeMcpServers
|
|
1
|
+
import { probeMcpServers } from "@sema-agent/core";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { createFixedWindowRateLimiter } from "../device-enrollment.js";
|
|
4
4
|
import { canonicalJson } from "../canonical-json.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { toCoreMcpServerSpec } from "../mcp-assertion-header.js";
|
|
9
|
-
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
5
|
+
import { keyClosureClause, scanKeyClosure } from "../request-key-closure.js";
|
|
6
|
+
import { requestMcpDeclarationRefusal, validateRequestMcp } from "../task-mcp.js";
|
|
7
|
+
import { declaresMcpAssertionHeader, toCoreMcpServerSpec } from "../mcp-assertion-header.js";
|
|
10
8
|
import { projectWiringManifestMcpRows } from "../trace/project.js";
|
|
11
9
|
export const MCP_PROBE_REFUSAL_CODES = [
|
|
12
10
|
"capability.mcp_injection_required",
|
|
@@ -42,113 +40,6 @@ const shapeRefusal = (message, unknownKeys) => ({
|
|
|
42
40
|
...(unknownKeys !== undefined ? { unknownKeys } : {}),
|
|
43
41
|
});
|
|
44
42
|
const fieldRefusal = (message) => ({ errorCode: "request.field_invalid", message });
|
|
45
|
-
function entryKeyIssue(entry, at) {
|
|
46
|
-
const buckets = { unknown: [], unsupported: [] };
|
|
47
|
-
scanKeyClosure(entry, `${at}.`, new Set(MCP_SERVER_ENTRY_KEYS), NO_TOLERATED_KEYS, buckets);
|
|
48
|
-
const t = entry.transport;
|
|
49
|
-
if (t !== null && typeof t === "object" && !Array.isArray(t)) {
|
|
50
|
-
const kind = t.kind;
|
|
51
|
-
const keys = kind === "stdio" ? STDIO_MCP_TRANSPORT_KEYS : kind === "http" ? HTTP_MCP_TRANSPORT_KEYS : undefined;
|
|
52
|
-
if (keys !== undefined)
|
|
53
|
-
scanKeyClosure(t, `${at}.transport.`, new Set(keys), NO_TOLERATED_KEYS, buckets);
|
|
54
|
-
}
|
|
55
|
-
if (buckets.unknown.length === 0) {
|
|
56
|
-
const refusal = requestEntryIdentityRefusal(entry, at);
|
|
57
|
-
if (refusal === undefined)
|
|
58
|
-
return null;
|
|
59
|
-
return refusal.errorCode === "request.body_shape" ? shapeRefusal(refusal.message) : fieldRefusal(refusal.message);
|
|
60
|
-
}
|
|
61
|
-
buckets.unknown.sort();
|
|
62
|
-
return shapeRefusal(`body: unrecognized key(s) on an MCP declaration — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
63
|
-
`${keyClosureClause(buckets.unknown, "unknown (not an mcpServers[] key — check the spelling)")}. ` +
|
|
64
|
-
`Accepted per entry: ${[...MCP_SERVER_ENTRY_KEYS].sort().join(",")} (transport.kind "stdio": ${[...STDIO_MCP_TRANSPORT_KEYS].sort().join(",")}; "http": ${[...HTTP_MCP_TRANSPORT_KEYS].sort().join(",")})`, buckets.unknown);
|
|
65
|
-
}
|
|
66
|
-
function entryValueIssue(entry, at) {
|
|
67
|
-
const axesRefusal = requestEntryToolAxesRefusal(entry, at);
|
|
68
|
-
if (axesRefusal !== undefined)
|
|
69
|
-
return fieldRefusal(axesRefusal.message);
|
|
70
|
-
const read = validateRequestMcp([entry]);
|
|
71
|
-
if (read === null)
|
|
72
|
-
return fieldRefusal(`${at} is not a readable MCP declaration`);
|
|
73
|
-
const dropped = read.dropped[0];
|
|
74
|
-
if (dropped !== undefined || read.ok.length !== 1) {
|
|
75
|
-
const field = dropped?.field ?? "entry";
|
|
76
|
-
return fieldRefusal(`${at}.${field} is not a usable value${urlEchoFor(entry, field)} — this endpoint refuses the whole list rather than dialing a silently shortened one` +
|
|
77
|
-
` (a probe answering "can I use these" must not quietly answer about fewer)`);
|
|
78
|
-
}
|
|
79
|
-
if (read.badContentOrigin.length > 0) {
|
|
80
|
-
return fieldRefusal(`${at}.contentOrigin must be one of ${CONTENT_ORIGIN_WORDS.map((w) => `"${w}"`).join(" | ")} — got ${describeBadContentOrigin(entry.contentOrigin)} (omit the key for the default: this server's tools classify as external content)`);
|
|
81
|
-
}
|
|
82
|
-
const read0 = read.ok[0];
|
|
83
|
-
return declarationDowngradeIssue(entry, read0 === undefined ? undefined : toCoreMcpServerSpec(read0), at);
|
|
84
|
-
}
|
|
85
|
-
function declarationDowngradeIssue(entry, spec, at) {
|
|
86
|
-
if (spec === undefined)
|
|
87
|
-
return fieldRefusal(`${at} is not a readable MCP declaration`);
|
|
88
|
-
const diff = jsonPathDiff(stripEquivalentEmpties(declaredAsJudged(entry)), stripEquivalentEmpties(spec), at);
|
|
89
|
-
if (diff.length === 0)
|
|
90
|
-
return null;
|
|
91
|
-
diff.sort();
|
|
92
|
-
return fieldRefusal(`${keyClosureClause(diff, "unusable value(s)")} — this deployment's declaration reader could not use these exactly as written, and a probe must not dial a configuration that differs from the one you declared` +
|
|
93
|
-
" (this endpoint refuses, because the reading would then describe a server you did not ask about)");
|
|
94
|
-
}
|
|
95
|
-
function declaredAsJudged(entry) {
|
|
96
|
-
const declared = entry;
|
|
97
|
-
if (!("toolAxes" in declared))
|
|
98
|
-
return declared;
|
|
99
|
-
const read = readMcpToolAxes(declared.toolAxes);
|
|
100
|
-
if (!read.ok)
|
|
101
|
-
return declared;
|
|
102
|
-
const { toolAxes: _declaredAxes, ...rest } = declared;
|
|
103
|
-
return read.toolAxes === undefined ? rest : { ...rest, toolAxes: read.toolAxes };
|
|
104
|
-
}
|
|
105
|
-
function stripEquivalentEmpties(entry) {
|
|
106
|
-
const emptyRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0;
|
|
107
|
-
const emptyArray = (v) => Array.isArray(v) && v.length === 0;
|
|
108
|
-
const keep = (bag, recordKeys, arrayKeys) => {
|
|
109
|
-
const out = {};
|
|
110
|
-
for (const [k, v] of Object.entries(bag)) {
|
|
111
|
-
if (recordKeys.includes(k) && emptyRecord(v))
|
|
112
|
-
continue;
|
|
113
|
-
if (arrayKeys.includes(k) && emptyArray(v))
|
|
114
|
-
continue;
|
|
115
|
-
out[k] = v;
|
|
116
|
-
}
|
|
117
|
-
return out;
|
|
118
|
-
};
|
|
119
|
-
const top = keep(entry, MCP_ENTRY_EMPTY_RECORD_KEYS, MCP_ENTRY_EMPTY_ARRAY_KEYS);
|
|
120
|
-
const t = top.transport;
|
|
121
|
-
if (t !== null && typeof t === "object" && !Array.isArray(t)) {
|
|
122
|
-
top.transport = keep(t, MCP_TRANSPORT_EMPTY_RECORD_KEYS, MCP_TRANSPORT_EMPTY_ARRAY_KEYS);
|
|
123
|
-
}
|
|
124
|
-
return top;
|
|
125
|
-
}
|
|
126
|
-
function jsonPathDiff(a, b, path) {
|
|
127
|
-
if (canonicalJson(a) === canonicalJson(b))
|
|
128
|
-
return [];
|
|
129
|
-
const aRec = a !== null && typeof a === "object" && !Array.isArray(a) ? a : undefined;
|
|
130
|
-
const bRec = b !== null && typeof b === "object" && !Array.isArray(b) ? b : undefined;
|
|
131
|
-
if (aRec !== undefined && bRec !== undefined) {
|
|
132
|
-
const out = [];
|
|
133
|
-
for (const k of new Set([...Object.keys(aRec), ...Object.keys(bRec)]))
|
|
134
|
-
out.push(...jsonPathDiff(aRec[k], bRec[k], `${path}.${k}`));
|
|
135
|
-
return out.length > 0 ? out : [path];
|
|
136
|
-
}
|
|
137
|
-
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
138
|
-
const out = [];
|
|
139
|
-
for (let i = 0; i < a.length; i++)
|
|
140
|
-
out.push(...jsonPathDiff(a[i], b[i], `${path}[${i}]`));
|
|
141
|
-
return out.length > 0 ? out : [path];
|
|
142
|
-
}
|
|
143
|
-
return [path];
|
|
144
|
-
}
|
|
145
|
-
function urlEchoFor(entry, field) {
|
|
146
|
-
if (field !== "transport.url")
|
|
147
|
-
return "";
|
|
148
|
-
const t = entry.transport;
|
|
149
|
-
const raw = t !== null && typeof t === "object" ? t.url : undefined;
|
|
150
|
-
return typeof raw === "string" ? ` (got "${buildDisplaySafeUrl(raw)}"; an MCP declaration's address must be an http(s) URL — put credentials in transport.headers, never in the URL)` : "";
|
|
151
|
-
}
|
|
152
43
|
export function readMcpProbeBody(raw) {
|
|
153
44
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
154
45
|
return { ok: false, refusal: shapeRefusal("body must be a JSON object of the shape { mcpServers: [ … ] } (the same declaration list a task submits)") };
|
|
@@ -164,32 +55,30 @@ export function readMcpProbeBody(raw) {
|
|
|
164
55
|
};
|
|
165
56
|
}
|
|
166
57
|
const list = raw.mcpServers;
|
|
167
|
-
if (
|
|
168
|
-
return { ok: false, refusal: shapeRefusal("mcpServers
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
for (let i = 0; i < list.length; i++) {
|
|
173
|
-
const at = `mcpServers[${i}]`;
|
|
174
|
-
const entry = list[i];
|
|
175
|
-
if (entry === null || typeof entry !== "object" || Array.isArray(entry))
|
|
176
|
-
return { ok: false, refusal: shapeRefusal(`${at} must be an object`) };
|
|
177
|
-
const keyIssue = entryKeyIssue(entry, at);
|
|
178
|
-
if (keyIssue)
|
|
179
|
-
return { ok: false, refusal: keyIssue };
|
|
180
|
-
const valueIssue = entryValueIssue(entry, at);
|
|
181
|
-
if (valueIssue)
|
|
182
|
-
return { ok: false, refusal: valueIssue };
|
|
183
|
-
}
|
|
58
|
+
if (list === undefined || list === null)
|
|
59
|
+
return { ok: false, refusal: shapeRefusal("mcpServers is required: the declaration list to probe (send [] to ask about nothing)") };
|
|
60
|
+
const refusal = requestMcpDeclarationRefusal(list);
|
|
61
|
+
if (refusal !== undefined)
|
|
62
|
+
return { ok: false, refusal };
|
|
184
63
|
const validated = validateRequestMcp(list);
|
|
185
|
-
|
|
64
|
+
const declared = Array.isArray(list) ? list.length : 0;
|
|
65
|
+
if (validated === null || validated.ok.length !== declared) {
|
|
186
66
|
return {
|
|
187
67
|
ok: false,
|
|
188
|
-
refusal: fieldRefusal(`mcpServers: this deployment's declaration reader
|
|
189
|
-
" so the probe refuses rather than dialing a silently shortened list. This is a belt on a path that should not be reachable (the reader
|
|
68
|
+
refusal: fieldRefusal(`mcpServers: this deployment's declaration reader kept ${validated === null ? 0 : validated.ok.length} of the ${declared} declarations its judge accepted,` +
|
|
69
|
+
" so the probe refuses rather than dialing a silently shortened list. This is a belt on a path that should not be reachable (the judge and the reader read every key with the same predicate)," +
|
|
190
70
|
" so nothing in your body needs changing — if you see it, this deployment has a bug worth reporting"),
|
|
191
71
|
};
|
|
192
72
|
}
|
|
73
|
+
const carried = validated.ok.findIndex(declaresMcpAssertionHeader);
|
|
74
|
+
if (carried >= 0) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
refusal: fieldRefusal(`mcpServers[${carried}].transport.assertionHeader is declared, but this engine does not send an assertion header yet — a task would mount the server WITHOUT it, ` +
|
|
78
|
+
"so a probe dialing it would describe a configuration you did not declare (a server that answers the handshake and asks for the assertion only on calls would read as connected). " +
|
|
79
|
+
"Probe the entry without the key to see how the server answers without it"),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
193
82
|
return { ok: true, specs: validated.ok.map(toCoreMcpServerSpec) };
|
|
194
83
|
}
|
|
195
84
|
export function buildMcpProbeIdentity(principal) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type
|
|
1
|
+
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type Runner, type SkillSpec, type TaskListStore, type TaskSpec, type ToolSpec, type WebSearchConfig } from "@sema-agent/core";
|
|
2
2
|
import type { Logger } from "../observability/logger.js";
|
|
3
3
|
import { type SubagentExtraToolSet } from "./builtin-tools.js";
|
|
4
4
|
import { type RepoReadClient } from "./repo-tools.js";
|
|
@@ -6,8 +6,8 @@ import { type LoadedSkill } from "./skills.js";
|
|
|
6
6
|
import type { ScenarioHands } from "./hands-lane.js";
|
|
7
7
|
/**
|
|
8
8
|
* Scenario routing — a deployment serves many scenarios from one image. Each request's `scenario`
|
|
9
|
-
* selects a pre-wired capability bundle: tools (incl. subagent roster) +
|
|
10
|
-
*
|
|
9
|
+
* selects a pre-wired capability bundle: tools (incl. subagent roster) + its prompt seats + skills.
|
|
10
|
+
* Heavy bits (HTTP clients, skill files) are startup singletons; per task we only bind
|
|
11
11
|
* coordinates and build cheap closures, so there is no per-request "assembly" cost.
|
|
12
12
|
*/
|
|
13
13
|
export interface ScenarioRequest {
|
|
@@ -26,12 +26,33 @@ export interface ScenarioBundle {
|
|
|
26
26
|
* `scan` 声明 clone-free 四件、实际拿到全量可写手)。
|
|
27
27
|
*/
|
|
28
28
|
hands: ScenarioHands;
|
|
29
|
-
|
|
29
|
+
/** 本场景对提示的贡献(缺席 = 不贡献:引擎目录的缺省角色)。经 {@link buildPromptSeats} 写进 `TaskSpec`,这是场景到提示的唯一通道。 */
|
|
30
|
+
prompt?: ScenarioPrompt;
|
|
30
31
|
/** [ref] 场景解释层定死的终验开关(core TaskSpec.finalVerification):bench 实测产品默认请求与
|
|
31
32
|
* bench 态差距三件之一。[ref] 起无场景定死(autonomous 退役)——纯 caller 显式旋钮;spec 组装处 OR 折入(场景只会
|
|
32
33
|
* 多加一轮收官验证,绝不收窄 caller 意图),壳/caller 不感知。 */
|
|
33
34
|
finalVerification?: true;
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 一个场景对提示的贡献 —— 只有两个座,都是 `TaskSpec` 上的任务类座位:
|
|
38
|
+
* · `role`:选目录里的哪一种角色基底(`TaskSpec.promptRole`;缺席 = 引擎缺省 `general`);
|
|
39
|
+
* · `roleText`:场景自己的角色文本,写在调用方 `systemPrompt` 之前一起进 `TaskSpec.systemPrompt`(它整替选中的角色基底)。
|
|
40
|
+
* 两者都**不许**是引擎目录的文本(抄目录文本 = 第二份文本;逐字含锁定段的角色文本引擎在 prepare 拒 `prompt.assembled_role_refused`)。
|
|
41
|
+
* 宪法、行为段、记忆段、追加段的落位全归引擎。
|
|
42
|
+
*/
|
|
43
|
+
export interface ScenarioPrompt {
|
|
44
|
+
readonly role?: NonNullable<TaskSpec["promptRole"]>;
|
|
45
|
+
readonly roleText?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 一次请求写进 `TaskSpec` 的提示座 —— **`promptBinding` 的唯一写点**,HTTP 腿(`resolveSpec`)与 `run-local` 同用这一只:
|
|
49
|
+
* · `promptBinding` = 场景名,恒写:它是引擎提示选择器的一维,每一类 overlay(中心 artifact / 部署注册 / 任务)的 `when.binding` 都读它 ——
|
|
50
|
+
* 不写 ⇒ 每条 `anyOf` 恒假、每条 `noneOf` 恒真,按场景定向的中心段与部署的品牌行全部失效。场景名过了场景门的文法
|
|
51
|
+
* (`SCENARIO_NAME_RE`,≤ 32 个 ASCII 字)⇒ 恒在引擎 binding 文法之内(非空白、≤ `PROMPT_BINDING_MAX_CHARS` 码点);委派子代经父 spec 座继承它。
|
|
52
|
+
* · `promptRole` = 场景的角色选择器(缺席 = 引擎缺省 `general`);
|
|
53
|
+
* · `systemPrompt` = 角色文本(场景自己的在前、调用方 `systemPrompt` 在后,`\n\n` 相连;空串不占位;两者都缺席 ⇒ 键不写,调用方给的空串原样交引擎)。
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildPromptSeats(scenarioName: string, prompt: ScenarioPrompt | undefined, callerSystemPrompt: string | undefined): Pick<TaskSpec, "promptBinding" | "promptRole" | "systemPrompt">;
|
|
35
56
|
/**
|
|
36
57
|
* [ref]:场景工厂的**服务端事实**上下文 —— 与 `req`(= 调用方请求体,不可信)分开的第三个入参。
|
|
37
58
|
* 这里的每一位都由鉴权信道解析,调用方无法自报;新增位一律走本接口,绝不往 `req` 上加。
|
|
@@ -70,14 +91,6 @@ export interface ScenarioDeps {
|
|
|
70
91
|
* 字节形,core 落「summarization unavailable」诚实兜底)。 */
|
|
71
92
|
webFetchSummarize?: NonNullable<NonNullable<WebFetchConfig["summarize"]>>;
|
|
72
93
|
logger?: Logger;
|
|
73
|
-
/**
|
|
74
|
-
* When true, the `default` scenario carries the Sema product identity (see {@link semaIdentityProvider}).
|
|
75
|
-
* Set by the local/TOC deployment (CONFIG_PROVIDER=local / run-local) so the local engine answers "who
|
|
76
|
-
* are you" as Sema instead of the model's hallucinated identity. Business scenarios (code-review)
|
|
77
|
-
* keep their own prompts; cloud (CONFIG_PROVIDER≠local) leaves the default scenario neutral unless turned
|
|
78
|
-
* on, so integrators that inject their own persona are unaffected.
|
|
79
|
-
*/
|
|
80
|
-
brandIdentity?: boolean;
|
|
81
94
|
/**
|
|
82
95
|
* core 1.287 `SubagentToolOptions.extraTools` seam wire-through — per-SPAWN dynamic tool injection into the
|
|
83
96
|
* delegated (Agent-tool) child roster. Deployment-injected per-task tools (today: SendUserFile — its instance is
|
|
@@ -126,8 +139,9 @@ export interface ScenarioDeps {
|
|
|
126
139
|
taskListStoreFor?: (sessionId: string) => TaskListStore;
|
|
127
140
|
}
|
|
128
141
|
/**
|
|
129
|
-
* Sema product identity
|
|
130
|
-
*
|
|
142
|
+
* Sema product identity — the brand line of the `default` / `code` scenarios on a branded deployment (local / TOC). It is not a
|
|
143
|
+
* scenario seat: it is one deployment-class overlay (`capabilities/brand-identity.ts`, minted by `createDeploymentMods`) whose
|
|
144
|
+
* `when.binding` names the two scenarios. Honest about the substrate (the engine runs whichever model
|
|
131
145
|
* the operator configured — not Claude/Anthropic), so the agent never inherits the model's training-time
|
|
132
146
|
* "I am Claude" hallucination.
|
|
133
147
|
*
|
|
@@ -138,34 +152,10 @@ export interface ScenarioDeps {
|
|
|
138
152
|
* that maker's misstate its own substrate. Same for
|
|
139
153
|
* the gateway protocol (the brain stack serves Anthropic-compatible routes too — routing brain +
|
|
140
154
|
* createAnthropicBrain — so "OpenAI-compatible gateway" would be the same class of lie). Neutral wording
|
|
141
|
-
* (
|
|
142
|
-
* seam and reads poorly for ugly preset ids). The anti-"I am Claude" design is kept — only the baked
|
|
155
|
+
* (interpolating the model id reads poorly for ugly preset ids). The anti-"I am Claude" design is kept — only the baked
|
|
143
156
|
* substrate facts are removed.
|
|
144
157
|
*/
|
|
145
158
|
export { SEMA_IDENTITY } from "./prompts/identity.js";
|
|
146
|
-
/**
|
|
147
|
-
* Prompt provider for the branded `default` scenario: SEMA_IDENTITY on top of the ROLE base — the
|
|
148
|
-
* task's own `body.systemPrompt`, or core's {@link DEFAULT_SYSTEM_PROMPT} when none. Core 1.243
|
|
149
|
-
* (提示词主权批) appends the constitution structurally AFTER this role layer (harness
|
|
150
|
-
* safety blocks + modes + memory guidance + the prefix-cache-safe `<user_memory>` tail), so identity
|
|
151
|
-
* cannot displace the safety blocks. The pre-1.243 form (identity + `defaultPromptProvider.stableSystem`,
|
|
152
|
-
* i.e. an ALREADY-assembled prompt) would now trip the migration guard (anchor-detect → pass-through +
|
|
153
|
-
* `onError(phase:"prompt-constitution")`) — this role-only form is the guard-free contract.
|
|
154
|
-
*/
|
|
155
|
-
export declare function semaIdentityProvider(): PromptProvider;
|
|
156
|
-
/**
|
|
157
|
-
* `code` 场景的提示词提供者([ref] 造,[ref] clay 裁定改名 autonomous→code):role 基底 = core
|
|
158
|
-
* {@link CODE_SYSTEM_PROMPT}(core 1.298 [ref] 改名批正名,旧名 FULL_BODY_SYSTEM_PROMPT=恒等
|
|
159
|
-
* 别名过渡窗;CODE_AGENT persona + AUTONOMY_SELF_AUDIT + ANTI_VERBOSITY +
|
|
160
|
-
* TOOL_PARAM_JSON;[ref] 谱系定论 = CC 生产编码提示词的蒸馏去牌版,底本随 CC 版本对表 cc-anchors,
|
|
161
|
-
* 非自研)——与 bench 的请求组装完全同源,引用 core 常量随版本走,绝不复制字符串(tb bench 实测:
|
|
162
|
-
* 产品默认请求与同引擎 bench 态的差距根因就在这三件 persona/自检/终验)。品牌层与
|
|
163
|
-
* {@link semaIdentityProvider} 同构:brandIdentity 开时 SEMA_IDENTITY 叠在 role 基底之上;任务自带
|
|
164
|
-
* systemPrompt 按同一优先序赢过基底(role 合成规则与 default 场景一致)。宪法由 core 1.243 在 role
|
|
165
|
-
* 层之后结构化追加——此处只产 role-only 形态(guard-free 契约,同 semaIdentityProvider 注释)。
|
|
166
|
-
* 无 systemPrompt 且无品牌时原样返回 core 常量(测试以引用比对锁 bench 同源,不做字符串快照)。
|
|
167
|
-
*/
|
|
168
|
-
export declare function codePromptProvider(brand: boolean): PromptProvider;
|
|
169
159
|
export declare function buildScenarios(deps: ScenarioDeps): Record<string, Scenario>;
|
|
170
160
|
/**
|
|
171
161
|
* Resolve the scenario bundle for a name. `undefined` = no preference ⇒ the built-in `default`; any OTHER name must be
|
|
@@ -203,7 +193,11 @@ export interface ScenarioDetail {
|
|
|
203
193
|
* 这里没跟 = 编译红;wire 变更清单的自铸登记表(`trace/self-minted-wire.ts` 的 `builtinScenario`)同按它 `satisfies`。 */
|
|
204
194
|
export declare const BUILTIN_SCENARIO_NAMES: readonly ["code", "default", "code-review", "scan"];
|
|
205
195
|
export type BuiltinScenarioName = (typeof BUILTIN_SCENARIO_NAMES)[number];
|
|
206
|
-
|
|
196
|
+
/**
|
|
197
|
+
* `branded` = 这台部署是不是品牌部署 —— 部署座挂品牌行读的同一只判官(`brand-identity.ts` `isBrandedDeployment`)交进来;没有默认值:每个调用点说出这件事实。
|
|
198
|
+
* 品牌部署上,品牌行 `when.binding` 那张表里的场景(`BRAND_IDENTITY_SCENARIOS`)的 `promptSummary` 点名 Sema 产品身份 —— 读面说的就是模型收到的。
|
|
199
|
+
*/
|
|
200
|
+
export declare function builtinScenarioDetails(scenarios: Record<string, Scenario>, deps: Pick<ScenarioDeps, "repoClient">, branded: boolean): Record<string, ScenarioDetail>;
|
|
207
201
|
/** center 条目详情(有效性判定与 centerScenarios 完全同款:无效条目既不进 overlay 也不进详情——
|
|
208
202
|
* 保证约定①「详情显示的来源=运行实际用的定义」永不错位)。 */
|
|
209
203
|
export declare function centerScenarioDetails(specs: CenterScenarioSpec[] | undefined, builtinNames: string[], deps: Pick<ScenarioDeps, "repoClient">): Record<string, ScenarioDetail>;
|
|
@@ -1,31 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
2
|
import { HttpError, createCodedHttpError } from "../security.js";
|
|
3
3
|
import { createSubagentExtraTools, extraToolsErrorFields, nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
5
5
|
import { NOW_TOOL_NAME, REPO_PULL_DIFF_TOOL_NAME, REPO_READ_FILE_TOOL_NAME, REPO_TREE_TOOL_NAME } from "./host-tool-names.js";
|
|
6
|
-
import { stablePrompt } from "./prompt.js";
|
|
7
6
|
import { createWebSearchBackend, judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
8
7
|
import { skillsForScenario } from "./skills.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{ id: "sema/identity", slot: "identity", text: `${SEMA_IDENTITY}\n\n${ctx.userSystemPrompt ?? DEFAULT_SYSTEM_PROMPT}` },
|
|
15
|
-
],
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export function codePromptProvider(brand) {
|
|
19
|
-
const roleBase = (ctx) => ctx.userSystemPrompt ?? (ctx.promptProfile === "classic" ? CODE_SYSTEM_PROMPT : CODE_AGENT_PROMPT);
|
|
20
|
-
if (!brand) {
|
|
21
|
-
return { stableSystem: (ctx) => roleBase(ctx) };
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
stableBlocks: (ctx) => [
|
|
25
|
-
{ id: "sema/identity", slot: "identity", text: `${SEMA_IDENTITY}\n\n${roleBase(ctx)}` },
|
|
26
|
-
],
|
|
27
|
-
};
|
|
8
|
+
import { BRAND_IDENTITY_SCENARIOS } from "./brand-identity.js";
|
|
9
|
+
export function buildPromptSeats(scenarioName, prompt, callerSystemPrompt) {
|
|
10
|
+
const parts = [prompt?.roleText, callerSystemPrompt].filter((t) => typeof t === "string" && t.length > 0);
|
|
11
|
+
const systemPrompt = parts.length > 0 ? parts.join("\n\n") : callerSystemPrompt;
|
|
12
|
+
return { promptBinding: scenarioName, ...(prompt?.role !== undefined ? { promptRole: prompt.role } : {}), ...(systemPrompt !== undefined ? { systemPrompt } : {}) };
|
|
28
13
|
}
|
|
14
|
+
export { SEMA_IDENTITY } from "./prompts/identity.js";
|
|
29
15
|
function requestWebSearchBackend(verdict, deployment, logger) {
|
|
30
16
|
switch (verdict.kind) {
|
|
31
17
|
case "absent":
|
|
@@ -75,14 +61,13 @@ export function buildScenarios(deps) {
|
|
|
75
61
|
],
|
|
76
62
|
hands: "full",
|
|
77
63
|
skills: skillsForScenario(deps.skills, "default"),
|
|
78
|
-
...(deps.brandIdentity ? { promptProvider: semaIdentityProvider() } : {}),
|
|
79
64
|
};
|
|
80
65
|
};
|
|
81
66
|
return {
|
|
82
67
|
default: defaultScenario,
|
|
83
68
|
code: (req, principal, ctx) => ({
|
|
84
69
|
...defaultScenario(req, principal, ctx),
|
|
85
|
-
|
|
70
|
+
prompt: { role: "code" },
|
|
86
71
|
}),
|
|
87
72
|
"code-review": (req) => codeReview(deps, req),
|
|
88
73
|
scan: (req) => {
|
|
@@ -111,7 +96,7 @@ function availabilityFields(deps, toolset) {
|
|
|
111
96
|
return verdict.available ? { available: true } : { available: false, unavailableReason: verdict.reason };
|
|
112
97
|
}
|
|
113
98
|
export const BUILTIN_SCENARIO_NAMES = ["code", "default", "code-review", "scan"];
|
|
114
|
-
export function builtinScenarioDetails(scenarios, deps) {
|
|
99
|
+
export function builtinScenarioDetails(scenarios, deps, branded) {
|
|
115
100
|
const probe = (name, req, principal, fallback) => {
|
|
116
101
|
try {
|
|
117
102
|
return ((Object.hasOwn(scenarios, name) ? scenarios[name] : undefined)?.(req, principal)?.tools ?? []).map((t) => t.name);
|
|
@@ -125,11 +110,10 @@ export function builtinScenarioDetails(scenarios, deps) {
|
|
|
125
110
|
name, source: "builtin", builtin: true, summary, toolset, tools, promptSummary, enabled: true,
|
|
126
111
|
...availabilityFields(deps, toolset),
|
|
127
112
|
});
|
|
113
|
+
const brandedScenario = (name) => branded && BRAND_IDENTITY_SCENARIOS.includes(name);
|
|
128
114
|
const details = {
|
|
129
|
-
code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, [NOW_TOOL_NAME]),
|
|
130
|
-
|
|
131
|
-
: "core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"),
|
|
132
|
-
default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, [NOW_TOOL_NAME]), deps.brandIdentity ? "Sema 产品身份 + core 默认宪法(结构层由引擎合成)" : "core 默认宪法(中性,无场景附加 persona)"),
|
|
115
|
+
code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, [NOW_TOOL_NAME]), `${brandedScenario("code") ? "Sema 产品身份 + " : ""}core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)`),
|
|
116
|
+
default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, [NOW_TOOL_NAME]), brandedScenario("default") ? "Sema 产品身份 + core 默认宪法(结构层由引擎合成)" : "core 默认宪法(中性,无场景附加 persona)"),
|
|
133
117
|
"code-review": mk("code-review", "代码评审(clone-free 只读仓库工具)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员提示词(产出契约)"),
|
|
134
118
|
scan: mk("scan", "中性只读仓库扫描(输出契约由 objective+skills 决定)", "repo-readonly", probe("scan", { repo: "probe/probe" }, undefined, REPO_TOOLS), "无场景附加提示词(objective+center skills 拥有输出契约)"),
|
|
135
119
|
};
|
|
@@ -230,13 +214,12 @@ function codeReview(deps, req) {
|
|
|
230
214
|
const repoTools = repoToolsFor(repoClient, coords);
|
|
231
215
|
const skills = skillsForScenario(deps.skills, "code-review");
|
|
232
216
|
const hands = "none";
|
|
233
|
-
return { tools: [...repoTools, nowTool()], hands, skills,
|
|
217
|
+
return { tools: [...repoTools, nowTool()], hands, skills, prompt: { roleText: REVIEWER_ROLE_TEXT } };
|
|
234
218
|
}
|
|
235
|
-
const
|
|
236
|
-
"(blocker / major / minor), each with `path:line`, the problem, and a concrete fix. Cite real code — never invent files or symbols.";
|
|
237
|
-
const directReviewerPrompt = stablePrompt("You are a senior code reviewer. Call repo_tree to map the repository, then repo_read_file to read " +
|
|
219
|
+
const REVIEWER_ROLE_TEXT = "You are a senior code reviewer. Call repo_tree to map the repository, then repo_read_file to read " +
|
|
238
220
|
"the files in scope, then review them yourself. " +
|
|
239
|
-
|
|
221
|
+
"Produce ONE prioritized review: a short summary, then findings grouped by severity " +
|
|
222
|
+
"(blocker / major / minor), each with `path:line`, the problem, and a concrete fix. Cite real code — never invent files or symbols.";
|
|
240
223
|
export function mergeUserSkills(scenarioSkills, userSkills, logger) {
|
|
241
224
|
if (!userSkills || userSkills.length === 0)
|
|
242
225
|
return scenarioSkills;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SkillSpec } from "@sema-agent/core";
|
|
2
2
|
import type { RemoteExecProvider } from "../execution-lane-caps.js";
|
|
3
|
+
import { type Logger } from "../observability/logger.js";
|
|
3
4
|
/**
|
|
4
5
|
* Built-in skills baked into the image, loaded ONCE at startup (no per-task disk IO). A skill is a
|
|
5
6
|
* Markdown capability (frontmatter + body). core uses **progressive disclosure** (core ≥1.98.0):
|
|
@@ -26,6 +27,10 @@ import type { RemoteExecProvider } from "../execution-lane-caps.js";
|
|
|
26
27
|
* description: when to use it
|
|
27
28
|
* scenarios: code-review, oa # which scenarios get it; omit = all scenarios
|
|
28
29
|
* ---
|
|
30
|
+
*
|
|
31
|
+
* 🔴 **frontmatter 读不了的技能不装,不降成全局技能**(安全轴:`scenarios` 是场景围栏,丢了它技能就对每个场景可见):文件第一行是围栏(`---`,行尾只许空白 / 制表 / CR,
|
|
32
|
+
* 与 CC 2.1.296 的开头正则同判)而块没有收尾 ⇒ {@link parseFrontmatter} 答 `unclosed`,这一个技能被**拒载**并落一行 `skill_refused` warn(点名技能与文件、不带正文);
|
|
33
|
+
* 兄弟技能照装。第一行不是围栏的文件没有 frontmatter,是正当的全局技能。
|
|
29
34
|
*/
|
|
30
35
|
export interface LoadedSkill {
|
|
31
36
|
spec: SkillSpec;
|
|
@@ -52,6 +57,8 @@ export interface LoadSkillsOptions {
|
|
|
52
57
|
* core 的律也明写缺席 ⇒ 结果逐字节与 baseDir 到货之前相同)。两个方向不对称 ⇒ 默认取轻的那边。
|
|
53
58
|
*/
|
|
54
59
|
toolsShareThisFilesystem?: boolean;
|
|
60
|
+
/** 拒载一行 warn 的出口(见文件头注「frontmatter 读不了的技能不装」)。缺席 ⇒ 进程的 JSON 行 logger(`createLogger("warn")`,stderr)—— 拒载没有「安静」的形;装配点传自己的 logger 只为字段与级别跟部署同一份。 */
|
|
61
|
+
logger?: Logger;
|
|
55
62
|
}
|
|
56
63
|
/** `p` 的 realpath 必须落在 `rootReal` 之内(含自身),否则抛。`sep` 边界判定防 `/a/root-evil` 撞
|
|
57
64
|
* `/a/root` 前缀。目标不存在 ⇒ realpath 抛 ENOENT,同样是拒绝(悬空链接不该被读)。
|
|
@@ -70,12 +77,20 @@ export declare function loadDeploymentSkills(config: {
|
|
|
70
77
|
readonly remoteExec?: {
|
|
71
78
|
readonly provider: RemoteExecProvider;
|
|
72
79
|
} | undefined;
|
|
73
|
-
}): LoadedSkill[];
|
|
80
|
+
}, logger?: Logger): LoadedSkill[];
|
|
74
81
|
/** Skills applicable to a scenario: those tagged with it, plus untagged (global) ones.
|
|
75
82
|
* [ref]:alias 窗撤销,单键查询——退役旧名标签不再命中(迁移面在 7.44 发车帖/MIGRATION 记载)。 */
|
|
76
83
|
export declare function skillsForScenario(loaded: LoadedSkill[], scenario: string): SkillSpec[];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
/** {@link parseFrontmatter} 的三种读数(闭集):`absent` = 第一行不是围栏,没有 frontmatter(正当的全局技能)· `block` = 围栏收好了的块 · `unclosed` = 第一行是围栏而块没收好(读不了 —— 调用方拒载)。 */
|
|
85
|
+
export type FrontmatterRead = {
|
|
86
|
+
readonly kind: "absent";
|
|
87
|
+
readonly body: string;
|
|
88
|
+
} | {
|
|
89
|
+
readonly kind: "block";
|
|
90
|
+
readonly meta: Record<string, string>;
|
|
91
|
+
readonly body: string;
|
|
92
|
+
} | {
|
|
93
|
+
readonly kind: "unclosed";
|
|
80
94
|
};
|
|
95
|
+
export declare function parseFrontmatter(raw: string): FrontmatterRead;
|
|
81
96
|
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import { toolsRunOnThisHost } from "../task-cwd.js";
|
|
4
|
+
import { createLogger } from "../observability/logger.js";
|
|
4
5
|
export function assertConfined(p, rootReal, label = "skill entry") {
|
|
5
6
|
let real;
|
|
6
7
|
try {
|
|
@@ -14,6 +15,7 @@ export function assertConfined(p, rootReal, label = "skill entry") {
|
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
export function loadSkills(dir, opts) {
|
|
18
|
+
const logger = opts?.logger ?? createLogger("warn");
|
|
17
19
|
let entries;
|
|
18
20
|
try {
|
|
19
21
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
@@ -41,7 +43,10 @@ export function loadSkills(dir, opts) {
|
|
|
41
43
|
if (confineRoot !== undefined)
|
|
42
44
|
assertConfined(join(dir, entry.name), confineRoot);
|
|
43
45
|
const raw = readFileSync(join(dir, entry.name), "utf8");
|
|
44
|
-
|
|
46
|
+
const source = join(dir, entry.name);
|
|
47
|
+
const parsed = parseSkill(raw, entry.name.replace(/\.md$/, ""), source, logger);
|
|
48
|
+
if (parsed !== undefined)
|
|
49
|
+
push(parsed, source);
|
|
45
50
|
}
|
|
46
51
|
else if (isDirLike(entry, join(dir, entry.name))) {
|
|
47
52
|
const skillDir = join(dir, entry.name);
|
|
@@ -56,7 +61,9 @@ export function loadSkills(dir, opts) {
|
|
|
56
61
|
}
|
|
57
62
|
if (confineRoot !== undefined)
|
|
58
63
|
assertConfined(join(skillDir, "SKILL.md"), confineRoot);
|
|
59
|
-
const skill = parseSkill(raw, entry.name);
|
|
64
|
+
const skill = parseSkill(raw, entry.name, join(skillDir, "SKILL.md"), logger);
|
|
65
|
+
if (skill === undefined)
|
|
66
|
+
continue;
|
|
60
67
|
const files = collectAttachments(skillDir, "", confineRoot);
|
|
61
68
|
if (files.length > 0)
|
|
62
69
|
skill.spec.files = files;
|
|
@@ -67,8 +74,8 @@ export function loadSkills(dir, opts) {
|
|
|
67
74
|
}
|
|
68
75
|
return out;
|
|
69
76
|
}
|
|
70
|
-
export function loadDeploymentSkills(config) {
|
|
71
|
-
return loadSkills(config.skillsDir, { toolsShareThisFilesystem: toolsRunOnThisHost(config) });
|
|
77
|
+
export function loadDeploymentSkills(config, logger) {
|
|
78
|
+
return loadSkills(config.skillsDir, { toolsShareThisFilesystem: toolsRunOnThisHost(config), ...(logger !== undefined ? { logger } : {}) });
|
|
72
79
|
}
|
|
73
80
|
function collectAttachments(skillDir, rel, confineRoot) {
|
|
74
81
|
const files = [];
|
|
@@ -93,8 +100,19 @@ function collectAttachments(skillDir, rel, confineRoot) {
|
|
|
93
100
|
}
|
|
94
101
|
return files;
|
|
95
102
|
}
|
|
96
|
-
function parseSkill(raw, fallbackName) {
|
|
97
|
-
const
|
|
103
|
+
function parseSkill(raw, fallbackName, source, logger) {
|
|
104
|
+
const fm = parseFrontmatter(raw);
|
|
105
|
+
if (fm.kind === "unclosed") {
|
|
106
|
+
logger.warn("skill_refused", {
|
|
107
|
+
skill: fallbackName,
|
|
108
|
+
source,
|
|
109
|
+
reason: "frontmatter_unclosed",
|
|
110
|
+
note: "the file opens a frontmatter fence (`---`) that is never closed, so its `scenarios` restriction cannot be read — this skill is NOT loaded (it is not demoted to a global skill); close the fence or remove the opening line",
|
|
111
|
+
});
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const meta = fm.kind === "block" ? fm.meta : {};
|
|
115
|
+
const body = fm.body;
|
|
98
116
|
const name = meta.name ?? fallbackName;
|
|
99
117
|
const description = meta.description ?? firstHeading(body) ?? name;
|
|
100
118
|
const scenarios = (meta.scenarios ?? "")
|
|
@@ -106,17 +124,19 @@ function parseSkill(raw, fallbackName) {
|
|
|
106
124
|
export function skillsForScenario(loaded, scenario) {
|
|
107
125
|
return loaded.filter((k) => k.scenarios.length === 0 || k.scenarios.includes(scenario)).map((k) => k.spec);
|
|
108
126
|
}
|
|
127
|
+
const FRONTMATTER_OPENS_RE = /^---[ \t]*\r?(?:\n|$)/;
|
|
128
|
+
const FRONTMATTER_BLOCK_RE = /^---[ \t]*\r?\n(?:---[ \t]*\r?(?:\n|$)|([\s\S]*?)\r?\n---[ \t]*\r?(?:\n|$))([\s\S]*)$/;
|
|
109
129
|
export function parseFrontmatter(raw) {
|
|
110
|
-
const m =
|
|
130
|
+
const m = FRONTMATTER_BLOCK_RE.exec(raw);
|
|
111
131
|
if (!m)
|
|
112
|
-
return {
|
|
132
|
+
return FRONTMATTER_OPENS_RE.test(raw) ? { kind: "unclosed" } : { kind: "absent", body: raw };
|
|
113
133
|
const meta = {};
|
|
114
|
-
for (const line of m[1].split("\n")) {
|
|
134
|
+
for (const line of (m[1] ?? "").split("\n")) {
|
|
115
135
|
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line.trim());
|
|
116
136
|
if (kv)
|
|
117
137
|
meta[kv[1]] = kv[2].trim();
|
|
118
138
|
}
|
|
119
|
-
return { meta, body: m[2] };
|
|
139
|
+
return { kind: "block", meta, body: m[2] };
|
|
120
140
|
}
|
|
121
141
|
function firstHeading(body) {
|
|
122
142
|
for (const line of body.split("\n")) {
|