@sema-agent/server 7.130.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 +396 -0
- package/README.md +13 -6
- package/README.zh-CN.md +9 -5
- package/USAGE.md +187 -50
- package/dist/approval-card.d.ts +3 -1
- package/dist/approval-card.js +2 -3
- package/dist/audit.d.ts +7 -10
- package/dist/audit.js +4 -1
- package/dist/auth-bridge.d.ts +4 -0
- package/dist/auth-bridge.js +10 -11
- package/dist/bench/s1/live-deps.js +2 -0
- 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 +56 -61
- 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-02-web-admin-pull.js +9 -1
- 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 +12 -10
- 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/builtin-tools.js +2 -1
- 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/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/engine-capabilities.js +2 -1
- package/dist/capabilities/hands-lane.d.ts +9 -6
- package/dist/capabilities/hands-lane.js +3 -3
- package/dist/capabilities/host-tool-names.d.ts +17 -0
- package/dist/capabilities/host-tool-names.js +15 -0
- package/dist/capabilities/mcp-probe.d.ts +14 -1
- package/dist/capabilities/mcp-probe.js +22 -133
- package/dist/capabilities/repo-tools.js +4 -3
- package/dist/capabilities/scenarios.d.ts +42 -47
- package/dist/capabilities/scenarios.js +19 -35
- package/dist/capabilities/select-environment-tool.js +2 -1
- package/dist/capabilities/send-user-file-tool.d.ts +0 -1
- package/dist/capabilities/send-user-file-tool.js +2 -2
- package/dist/capabilities/skills.d.ts +19 -4
- package/dist/capabilities/skills.js +30 -10
- package/dist/capabilities/tool-defer.js +2 -1
- package/dist/config-catalog.js +22 -11
- package/dist/config-invariants.js +2 -2
- 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 +14 -6
- package/dist/config-types.d.ts +13 -9
- package/dist/config.d.ts +2 -2
- package/dist/config.js +34 -20
- package/dist/device-ws-protocol.d.ts +1 -1
- package/dist/device-ws-protocol.js +1 -0
- package/dist/elicitation.d.ts +7 -0
- package/dist/elicitation.js +20 -5
- 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 +80 -37
- package/dist/hooks/hook-runner.d.ts +273 -75
- package/dist/hooks/hook-runner.js +293 -317
- package/dist/http/admission.d.ts +8 -2
- package/dist/http/admission.js +103 -59
- package/dist/http/approval-sweeps.js +59 -18
- package/dist/http/checkpoint-ctx.d.ts +36 -18
- package/dist/http/checkpoint-ctx.js +14 -10
- 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 +6 -10
- 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 +48 -26
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +7 -17
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/rules.js +2 -2
- package/dist/http/routes/runs.js +13 -17
- package/dist/http/routes/session-sync.js +4 -2
- package/dist/http/routes/sessions.js +23 -11
- package/dist/http/routes/shared-memory.js +2 -13
- package/dist/http/routes/side-query.js +2 -3
- package/dist/http/routes/tasks.js +34 -19
- package/dist/http/scenario-read-face.d.ts +14 -0
- package/dist/http/scenario-read-face.js +5 -0
- package/dist/http/send.d.ts +13 -1
- package/dist/http/send.js +12 -1
- package/dist/http/server.d.ts +37 -23
- package/dist/http/server.js +30 -4
- package/dist/http/stored-body-replay.d.ts +10 -3
- package/dist/http/stored-body-replay.js +27 -6
- package/dist/http/submit-ctx.d.ts +25 -0
- package/dist/http/submit-ctx.js +39 -0
- package/dist/http/wire-types.d.ts +18 -5
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- 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 +3 -11
- package/dist/mcp-assertion-header.js +3 -9
- 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-entry-refusal.d.ts +1 -7
- package/dist/model-provider.d.ts +39 -12
- package/dist/model-provider.js +19 -2
- package/dist/model-route-endpoint.d.ts +52 -12
- package/dist/model-route-endpoint.js +48 -26
- package/dist/model-route-gap.d.ts +5 -4
- package/dist/model-route-gap.js +1 -1
- package/dist/model-select.d.ts +58 -0
- package/dist/model-select.js +31 -0
- package/dist/namespaced-rule-name.d.ts +7 -4
- package/dist/namespaced-rule-name.js +4 -3
- package/dist/observability/cost-taxonomy.d.ts +7 -7
- package/dist/observability/fail-open.d.ts +53 -12
- package/dist/observability/fail-open.js +47 -7
- 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 +3 -3
- package/dist/plugins/one-time-migrations.d.ts +147 -91
- package/dist/plugins/one-time-migrations.js +372 -208
- 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/question.d.ts +18 -9
- package/dist/question.js +21 -13
- package/dist/request-key-closure.d.ts +4 -4
- package/dist/request-key-closure.js +18 -13
- package/dist/rule-entry-list.d.ts +134 -24
- package/dist/rule-entry-list.js +162 -30
- package/dist/run-local.d.ts +3 -3
- package/dist/run-local.js +26 -25
- package/dist/runs.d.ts +48 -2
- package/dist/runs.js +61 -2
- 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 +34 -18
- 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-hooks-m20.d.ts +23 -0
- package/dist/stored-request-hooks-m20.js +124 -0
- package/dist/stored-request-tool-axes-m19.d.ts +20 -5
- 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 +54 -102
- package/dist/task-mcp.js +194 -92
- package/dist/task-settings.d.ts +47 -25
- package/dist/task-settings.js +46 -61
- 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.d.ts +29 -0
- package/dist/trace/ledger-sink.js +15 -1
- package/dist/trace/model-segment.js +1 -1
- package/dist/trace/project.d.ts +25 -6
- package/dist/trace/project.js +68 -22
- package/dist/trace/wire-projection-faces.d.ts +80 -2
- package/dist/trace/wire-projection-faces.js +23 -1
- package/dist/user-authored-text.d.ts +7 -0
- package/dist/user-authored-text.js +3 -0
- package/dist/web-admin-client/apply-effective.js +10 -5
- 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 +3 -4
- 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 +37 -3
- 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
package/dist/rule-entry-list.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { compileAllowList } from "@sema-agent/core";
|
|
1
|
+
import { PROTOCOL_TABLE, compileAllowList, parsePermissionRule } from "@sema-agent/core";
|
|
2
2
|
import { displayListEntry } from "./list-entry-display.js";
|
|
3
|
-
import {
|
|
3
|
+
import { limitRefusal } from "./limit-refusal.js";
|
|
4
|
+
import { TOOL_NAME_ROSTER } from "./namespaced-rule-name.js";
|
|
4
5
|
export const ruleEntryListShapeOk = (v) => Array.isArray(v) && v.every((x) => typeof x === "string");
|
|
5
6
|
export const MAX_ALLOW_LAYER_ENTRIES = 1_000;
|
|
7
|
+
const ALLOW_LAYER_OVER_CAP_TAIL = "Refused rather than truncated: a truncated list silently drops the entries past the cap. Merge entries into broader ones (mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**)) or trim the list";
|
|
8
|
+
function allowLayerOverCap(field, count) {
|
|
9
|
+
return limitRefusal(field, "entries", count, MAX_ALLOW_LAYER_ENTRIES, { tail: ALLOW_LAYER_OVER_CAP_TAIL });
|
|
10
|
+
}
|
|
6
11
|
export function describeAllowLayerOverCap(field, count) {
|
|
7
|
-
return (
|
|
8
|
-
`Merge entries into broader ones (mcp__<server>__*, Bash(<prefix>:*), Read(//<dir>/**)) or trim the list`);
|
|
12
|
+
return allowLayerOverCap(field, count).message;
|
|
9
13
|
}
|
|
10
14
|
export function allowLayerEntryCountIssue(field, list) {
|
|
11
|
-
|
|
12
|
-
return undefined;
|
|
13
|
-
return { message: describeAllowLayerOverCap(field, list.length), count: list.length, cap: MAX_ALLOW_LAYER_ENTRIES };
|
|
15
|
+
return list.length <= MAX_ALLOW_LAYER_ENTRIES ? undefined : allowLayerOverCap(field, list.length);
|
|
14
16
|
}
|
|
15
17
|
export function inertRuleEntriesOf(list, behavior) {
|
|
16
|
-
return compileAllowList(list, behavior,
|
|
18
|
+
return compileAllowList(list, behavior, TOOL_NAME_ROSTER).inert;
|
|
19
|
+
}
|
|
20
|
+
function peerSegmentIsWild(entry) {
|
|
21
|
+
const at = entry.indexOf("__");
|
|
22
|
+
return at >= 0 && (entry.slice(at + 2).split("__")[0] ?? "").includes("*");
|
|
17
23
|
}
|
|
18
24
|
export function inertRuleEntryGuidance(i) {
|
|
19
25
|
const reason = i.reason;
|
|
20
|
-
const write = i.currentName !== undefined ? ` — write ${displayListEntry(i.currentName)}` : "";
|
|
26
|
+
const write = i.currentName !== undefined && !peerSegmentIsWild(i.entry) ? ` — write ${displayListEntry(i.currentName)}` : "";
|
|
21
27
|
switch (reason) {
|
|
22
28
|
case "tool_name":
|
|
23
29
|
return `the tool name can never match a mounted tool (${i.verdict ?? "stale"})${write}`;
|
|
@@ -39,30 +45,87 @@ export function inertRuleEntryGuidance(i) {
|
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
}
|
|
48
|
+
export const MAX_NAMED_INERT_ENTRIES = 5;
|
|
42
49
|
export function describeInertRuleEntries(field, inert) {
|
|
43
|
-
|
|
50
|
+
const named = inert.slice(0, MAX_NAMED_INERT_ENTRIES).map((i) => `${field} entry ${displayListEntry(i.entry)} reads as nothing (${i.reason}): ${inertRuleEntryGuidance(i)}`).join("; ");
|
|
51
|
+
const more = inert.length - MAX_NAMED_INERT_ENTRIES;
|
|
52
|
+
return more > 0 ? `${named}; and ${more} more` : named;
|
|
53
|
+
}
|
|
54
|
+
const TOOL_LIST_FACES = {
|
|
55
|
+
"skills": { side: "deny", kind: "rule_table", code: "request.field_invalid" },
|
|
56
|
+
"agents.skills": { side: "deny", kind: "rule_table", code: "request.field_invalid" },
|
|
57
|
+
"skills.allowedTools": { side: "allow", kind: "rule_table", code: "request.field_invalid" },
|
|
58
|
+
"agents.skills.allowedTools": { side: "allow", kind: "rule_table", code: "request.field_invalid" },
|
|
59
|
+
"agents.denyTools": { side: "deny", kind: "selector", code: "config.agent.unknown_tool" },
|
|
60
|
+
"agents.allowTools": { side: "allow", kind: "selector", code: "config.agent.unknown_tool" },
|
|
61
|
+
"excludeTools": { side: "deny", kind: "selector", code: "config.tool_face_control" },
|
|
62
|
+
"allowTools": { side: "allow", kind: "selector", code: "config.tool_face_control" },
|
|
63
|
+
"settings.permissions.allow": { side: "allow", kind: "rule_table", code: "request.field_invalid" },
|
|
64
|
+
};
|
|
65
|
+
const LIST_OVER_CAP_CODE = "request.field_invalid";
|
|
66
|
+
export function replayInertEntry(layer, side, code, message, extra) {
|
|
67
|
+
return { layer, side, code, message, ...(extra !== undefined ? { extra } : {}) };
|
|
44
68
|
}
|
|
45
69
|
const isPlainRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const inert = inertRuleEntriesOf(deny, "deny");
|
|
53
|
-
if (inert.length === 0)
|
|
54
|
-
return undefined;
|
|
55
|
-
return `${describeInertRuleEntries("skill.disallowedTools", inert)} — a deny entry that reads as nothing refuses nothing, so it is refused here rather than taken as in effect`;
|
|
70
|
+
const REFUSAL_TAIL = {
|
|
71
|
+
deny: "a deny entry that reads as nothing refuses nothing, so it is refused here rather than taken as in effect",
|
|
72
|
+
allow: "an allow entry that reads as nothing admits nothing, so it is refused here rather than taken as in effect",
|
|
73
|
+
};
|
|
74
|
+
function selectorEntryName(entry) {
|
|
75
|
+
return entry !== entry.trim() ? entry : parsePermissionRule(entry).toolName;
|
|
56
76
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
const isProtocolWideName = (name) => PROTOCOL_TABLE.some((e) => name === `${e.prefix}*`);
|
|
78
|
+
function selectorInertEntriesOf(list, side) {
|
|
79
|
+
return list.flatMap((entry) => {
|
|
80
|
+
if (entry === "*" && side === "allow")
|
|
81
|
+
return [];
|
|
82
|
+
const name = selectorEntryName(entry);
|
|
83
|
+
if (side === "deny" && isProtocolWideName(name))
|
|
84
|
+
return [];
|
|
85
|
+
const inert = compileAllowList([name], "deny", TOOL_NAME_ROSTER).inert.map((i) => ({ ...i, entry }));
|
|
86
|
+
return side === "allow" ? inert.filter((i) => allowSideReadsAsNothing(i.reason)) : inert;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function allowSideReadsAsNothing(reason) {
|
|
90
|
+
switch (reason) {
|
|
91
|
+
case "near_miss":
|
|
92
|
+
return true;
|
|
93
|
+
case "grammar":
|
|
94
|
+
case "tool_name":
|
|
95
|
+
case "content_unsupported":
|
|
96
|
+
case "wildcard_all":
|
|
97
|
+
case "namespace_wildcard":
|
|
98
|
+
return false;
|
|
99
|
+
case "earlier_grammar":
|
|
100
|
+
throw new Error(`allow-entry inert reason ${JSON.stringify(reason)} belongs to the session-rule store's bucket reading, never to an allow list`);
|
|
101
|
+
default: {
|
|
102
|
+
const unhandled = reason;
|
|
103
|
+
throw new Error(`unhandled allow-entry inert reason: ${String(unhandled)}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function requestAllowRuleInertEntriesOf(list) {
|
|
108
|
+
return list.flatMap((entry) => compileAllowList([entry], "allow", TOOL_NAME_ROSTER).inert.filter((i) => allowSideReadsAsNothing(i.reason)));
|
|
109
|
+
}
|
|
110
|
+
function inertEntriesOfList(list, layer) {
|
|
111
|
+
const face = TOOL_LIST_FACES[layer];
|
|
112
|
+
if (face.kind === "selector")
|
|
113
|
+
return selectorInertEntriesOf(list, face.side);
|
|
114
|
+
return face.side === "deny" ? inertRuleEntriesOf(list, "deny") : requestAllowRuleInertEntriesOf(list);
|
|
115
|
+
}
|
|
116
|
+
function* toolListsOf(body) {
|
|
60
117
|
const skills = body["skills"];
|
|
61
118
|
if (Array.isArray(skills)) {
|
|
62
|
-
for (
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
119
|
+
for (let k = 0; k < skills.length; k++) {
|
|
120
|
+
const skill = skills[k];
|
|
121
|
+
if (!isPlainRecord(skill))
|
|
122
|
+
continue;
|
|
123
|
+
yield { layer: "skills", field: "skill.disallowedTools", prefix: "", at: `skills[${k}].disallowedTools`, list: skill["disallowedTools"] };
|
|
124
|
+
}
|
|
125
|
+
for (let k = 0; k < skills.length; k++) {
|
|
126
|
+
const skill = skills[k];
|
|
127
|
+
if (isPlainRecord(skill))
|
|
128
|
+
yield { layer: "skills.allowedTools", field: "skill.allowedTools", prefix: "", at: `skills[${k}].allowedTools`, list: skill["allowedTools"] };
|
|
66
129
|
}
|
|
67
130
|
}
|
|
68
131
|
const agents = body["agents"];
|
|
@@ -73,12 +136,81 @@ export function skillDenyInertEntryOf(body) {
|
|
|
73
136
|
if (!Array.isArray(agentSkills))
|
|
74
137
|
continue;
|
|
75
138
|
for (let j = 0; j < agentSkills.length; j++) {
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
|
|
139
|
+
const skill = agentSkills[j];
|
|
140
|
+
if (isPlainRecord(skill))
|
|
141
|
+
yield { layer: "agents.skills", field: "skill.disallowedTools", prefix: `agents[${i}]: skills[${j}]: `, at: `agents[${i}].skills[${j}].disallowedTools`, list: skill["disallowedTools"] };
|
|
79
142
|
}
|
|
80
143
|
}
|
|
144
|
+
for (let i = 0; i < agents.length; i++) {
|
|
145
|
+
const agent = agents[i];
|
|
146
|
+
const agentSkills = isPlainRecord(agent) ? agent["skills"] : undefined;
|
|
147
|
+
if (!Array.isArray(agentSkills))
|
|
148
|
+
continue;
|
|
149
|
+
for (let j = 0; j < agentSkills.length; j++) {
|
|
150
|
+
const skill = agentSkills[j];
|
|
151
|
+
if (isPlainRecord(skill))
|
|
152
|
+
yield { layer: "agents.skills.allowedTools", field: "skill.allowedTools", prefix: `agents[${i}]: skills[${j}]: `, at: `agents[${i}].skills[${j}].allowedTools`, list: skill["allowedTools"] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (let i = 0; i < agents.length; i++) {
|
|
156
|
+
const agent = agents[i];
|
|
157
|
+
if (!isPlainRecord(agent))
|
|
158
|
+
continue;
|
|
159
|
+
yield { layer: "agents.denyTools", field: `agents[${i}].denyTools`, prefix: "", at: `agents[${i}].denyTools`, list: agent["denyTools"] };
|
|
160
|
+
yield { layer: "agents.allowTools", field: `agents[${i}].allowTools`, prefix: "", at: `agents[${i}].allowTools`, list: agent["allowTools"] };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
yield { layer: "excludeTools", field: "excludeTools", prefix: "", at: "excludeTools", list: body["excludeTools"] };
|
|
164
|
+
yield { layer: "allowTools", field: "allowTools", prefix: "", at: "allowTools", list: body["allowTools"] };
|
|
165
|
+
const settings = body["settings"];
|
|
166
|
+
const permissions = isPlainRecord(settings) ? settings["permissions"] : undefined;
|
|
167
|
+
if (isPlainRecord(permissions))
|
|
168
|
+
yield { layer: "settings.permissions.allow", field: "settings.permissions.allow", prefix: "", at: "settings.permissions.allow", list: permissions["allow"] };
|
|
169
|
+
}
|
|
170
|
+
function entryOfList(l) {
|
|
171
|
+
const face = TOOL_LIST_FACES[l.layer];
|
|
172
|
+
const over = overCapOfList(l);
|
|
173
|
+
if (over !== undefined)
|
|
174
|
+
return over;
|
|
175
|
+
if (!ruleEntryListShapeOk(l.list))
|
|
176
|
+
return undefined;
|
|
177
|
+
const inert = inertEntriesOfList(l.list, l.layer);
|
|
178
|
+
if (inert.length === 0)
|
|
179
|
+
return undefined;
|
|
180
|
+
return { layer: l.layer, side: face.side, code: face.code, message: `${l.prefix}${describeInertRuleEntries(l.field, inert)} — ${REFUSAL_TAIL[face.side]}` };
|
|
181
|
+
}
|
|
182
|
+
function overCapOfList(l) {
|
|
183
|
+
if (!Array.isArray(l.list))
|
|
184
|
+
return undefined;
|
|
185
|
+
const over = allowLayerEntryCountIssue(l.at, l.list);
|
|
186
|
+
return over === undefined ? undefined : { layer: l.layer, side: TOOL_LIST_FACES[l.layer].side, code: LIST_OVER_CAP_CODE, message: over.message, extra: over.facts };
|
|
187
|
+
}
|
|
188
|
+
export function skillToolListsOverCap(skill, at, scope) {
|
|
189
|
+
if (!isPlainRecord(skill))
|
|
190
|
+
return undefined;
|
|
191
|
+
const allowLayer = scope === "top" ? "skills.allowedTools" : "agents.skills.allowedTools";
|
|
192
|
+
const denyLayer = scope === "top" ? "skills" : "agents.skills";
|
|
193
|
+
return (overCapOfList({ layer: allowLayer, field: "skill.allowedTools", prefix: "", at: `${at}.allowedTools`, list: skill["allowedTools"] }) ??
|
|
194
|
+
overCapOfList({ layer: denyLayer, field: "skill.disallowedTools", prefix: "", at: `${at}.disallowedTools`, list: skill["disallowedTools"] }));
|
|
195
|
+
}
|
|
196
|
+
function firstInertEntry(body, acceptLayer, acceptSide) {
|
|
197
|
+
if (!isPlainRecord(body))
|
|
198
|
+
return undefined;
|
|
199
|
+
for (const l of toolListsOf(body)) {
|
|
200
|
+
if (!acceptLayer(l.layer))
|
|
201
|
+
continue;
|
|
202
|
+
const entry = entryOfList(l);
|
|
203
|
+
if (entry === undefined)
|
|
204
|
+
continue;
|
|
205
|
+
if (entry.extra !== undefined || acceptSide(entry.side))
|
|
206
|
+
return entry;
|
|
81
207
|
}
|
|
82
208
|
return undefined;
|
|
83
209
|
}
|
|
210
|
+
export function skillDenyInertEntryOf(body) {
|
|
211
|
+
return firstInertEntry(body, (layer) => layer === "skills" || layer === "agents.skills", () => true);
|
|
212
|
+
}
|
|
213
|
+
export function inertToolListEntryOf(body, sides = ["allow", "deny"]) {
|
|
214
|
+
return firstInertEntry(body, () => true, (side) => sides.includes(side));
|
|
215
|
+
}
|
|
84
216
|
//# sourceMappingURL=rule-entry-list.js.map
|
package/dist/run-local.d.ts
CHANGED
|
@@ -54,15 +54,15 @@ export declare class UsageError extends Error {
|
|
|
54
54
|
* set: …" list rather than letting the first brain call crash with a cryptic auth error. Returns a list of
|
|
55
55
|
* missing items (empty = OK).
|
|
56
56
|
*
|
|
57
|
-
* 🔴 S-623:这里**不再**判网关 base URL
|
|
58
|
-
*
|
|
57
|
+
* 🔴 S-623:这里**不再**判网关 base URL。「将要服务的那张表里每一条 openai 路由都解析到显式端点」是配置源应用之后的拒启判据
|
|
58
|
+
* (`model-route-endpoint.ts` 的 `judgeServedModelRoute`,本入口在预检之前就判过 —— 没设网关的配置根本走不到
|
|
59
59
|
* 这里),原来那条臂(a)是它的一份更弱的手抄:注释要求「本机用户必须指向自己的网关」,判据却只查 `/v1` 后缀,
|
|
60
60
|
* 旧的出厂缺省值恰好以 `/v1` 结尾 ⇒ 缺省值照样过(本仓自己也被骗)。`/v1` 后缀本身也一并删,理由三条:
|
|
61
61
|
* ① 同一个问题两个判官 —— `serve` 从不要求 `/v1`,同一份 .env 在 `serve` 起得来、在 `run-local` 被拒;
|
|
62
62
|
* ② 它会误拒真实存在的 OpenAI 兼容端点(DeepSeek 根路径直接服务 `/chat/completions`,本仓
|
|
63
63
|
* `src/bench/s1/live-deps.ts` 用的正是 `https://api.deepseek.com`;Gemini 的兼容面 `…/v1beta/openai`、Azure 的
|
|
64
64
|
* `…/openai/deployments/<name>` 也都不以 `/v1` 结尾);
|
|
65
|
-
* ③ 规则集只许变小:端点判据只有
|
|
65
|
+
* ③ 规则集只许变小:端点判据只有 `model-route-endpoint.ts` 那一只。
|
|
66
66
|
*
|
|
67
67
|
* NOTE: with an injected mock `brain` the gateway is irrelevant (no real call is made), so the caller skips
|
|
68
68
|
* this check in that case — the check is about a REAL run, not the assembly.
|
package/dist/run-local.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { DEFAULT_AT_MENTION, DEFAULT_MODEL_REF, resolveTaskModel } from "./model-select.js";
|
|
3
3
|
import { defaultModelRefusal, modelUnavailableMessage } from "./model-entry-refusal.js";
|
|
4
|
+
import { servedModelTableLeg } from "./model-select.js";
|
|
4
5
|
import { createDurableAbsenceSeat } from "./observability/corrupt-read-seat.js";
|
|
5
6
|
import { outwardPlane } from "./terminal.js";
|
|
6
7
|
import { join } from "node:path";
|
|
7
8
|
import { realpathSync } from "node:fs";
|
|
8
9
|
import { createInterface } from "node:readline/promises";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
|
|
11
|
+
import { BIDI_CONTROL_CHARS, Runner, uuidv7, FileStorageBackend, NodeExecutionEnv, TtlSessionStore, CenterPromptSource, FilePromptArtifactStore, FilePromptSourceStateStore, MemoryPromptArtifactStore, MemoryPromptSourceStateStore, defaultTaskRegistry } from "@sema-agent/core";
|
|
11
12
|
import { readAskClassPair } from "./approval-card.js";
|
|
12
13
|
import { singleUserAutoAcceptBaselineApplies } from "./approval.js";
|
|
13
14
|
import { withUnattendedLeg } from "./unattended-permission-mode.js";
|
|
@@ -17,14 +18,13 @@ import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, buildAppr
|
|
|
17
18
|
import { applyRuntimeGovernance, autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
|
|
18
19
|
import { ForkRoutingSessionStore } from "./plugins/fork-routing-session-store.js";
|
|
19
20
|
import { taskWallClockSec } from "./task-workflow.js";
|
|
20
|
-
import {
|
|
21
|
-
import { CORE_ENGINE_VERSION } from "./prompts-domain-validate.js";
|
|
21
|
+
import { applyCenterArtifact, centerSourceOnError } from "./capabilities/center-prompts.js";
|
|
22
22
|
import { buildUnpricedCeilingNotice, loadConfig, logConfigDiagnostics, parseNumOrFailNonNegative } from "./config.js";
|
|
23
23
|
import { buildStoreOpenRefusalTail } from "./plugins/store-open-refusal.js";
|
|
24
24
|
import { acquireLocalBackendWithWait } from "./plugins/store-lock-wait.js";
|
|
25
25
|
import { configLocalRoot, configProviderKind, createConfigProvider } from "./config-provider.js";
|
|
26
26
|
import { CONFIG_DOMAIN_INVALID_NOTE, buildCandidateRejectedFields, judgeConfigCandidate } from "./web-admin-client/candidate-gate.js";
|
|
27
|
-
import { buildUnreadConfigFileFields } from "./web-admin-client/read-warnings.js";
|
|
27
|
+
import { buildUnreadConfigFileFields, buildUnreadConfigKeyFields } from "./web-admin-client/read-warnings.js";
|
|
28
28
|
import { composeExecutionLane, settleHostLaneShell } from "./boot/execution-lane.js";
|
|
29
29
|
import { judgeConsolidationValve } from "./boot/memory-consolidation.js";
|
|
30
30
|
import { logConfigDirMigration, migrateLocalConfigDirectory } from "./config-d-migration.js";
|
|
@@ -36,20 +36,20 @@ import { createInstructionSourceSeat } from "./instruction-source-notice.js";
|
|
|
36
36
|
import { loadDeploymentSkills } from "./capabilities/skills.js";
|
|
37
37
|
import { createRepoClient } from "./capabilities/repo-tools.js";
|
|
38
38
|
import { webSearchConfigFromEnv, createWebSearchBackend } from "./plugins/web-search.js";
|
|
39
|
-
import { buildScenarios,
|
|
39
|
+
import { buildPromptSeats, buildScenarios, createScenarioGate, selectScenario, centerScenarios } from "./capabilities/scenarios.js";
|
|
40
40
|
import { createTaskListLane } from "./boot/task-list-lane.js";
|
|
41
41
|
import { pickHandsRunner, withoutHands } from "./capabilities/hands-lane.js";
|
|
42
42
|
import { HttpError, sessionIdShapeIssue } from "./security.js";
|
|
43
43
|
import { createMemoryEngineIncidentSink } from "./memory-incident-sink.js";
|
|
44
44
|
import { fileMemoryEngineRoot, memoryEngineBackendFor, memoryProjectKeyFor, memorySpecForRequest } from "./memory-scope.js";
|
|
45
|
-
import {
|
|
45
|
+
import { judgeServedModelRoute } from "./model-route-endpoint.js";
|
|
46
46
|
import { createMemorySyncRunner, createMemorySyncTransport } from "./memory-sync-client.js";
|
|
47
47
|
import { buildPricing, cappedCeiling, createTracer } from "./budget.js";
|
|
48
48
|
import { createSharedRunnerDeps } from "./boot/runner-deps.js";
|
|
49
49
|
import { createGovernanceSeams } from "./boot/governance-seams.js";
|
|
50
50
|
import { assertRetentionLaneWirable } from "./boot/retention-lane.js";
|
|
51
51
|
import { createOrgMemoryAdmissionWiring } from "./boot/org-memory.js";
|
|
52
|
-
import {
|
|
52
|
+
import { assertDeploymentModsAdmitted, createDeploymentMods } from "./boot/deployment-mods.js";
|
|
53
53
|
import { createKeyResolver } from "./key-resolver.js";
|
|
54
54
|
import { createLogger } from "./observability/logger.js";
|
|
55
55
|
import { fatalExit } from "./boot/fatal-exit.js";
|
|
@@ -253,8 +253,9 @@ function argsDisplayOf(args) {
|
|
|
253
253
|
function buildLocalAskPrompt(req, argsShown) {
|
|
254
254
|
return `approve "${terminalSafeText(req.toolName)}"? ${terminalSafeText(req.message)}\n ${argsShown}\n[y/N] `;
|
|
255
255
|
}
|
|
256
|
+
const TERMINAL_UNSAFE_RE = new RegExp(`[\\u0000-\\u001f\\u007f-\\u009f${BIDI_CONTROL_CHARS}\\u200b-\\u200d\\u2028\\u2029\\u2060-\\u2065\\ufeff]`, "g");
|
|
256
257
|
function terminalSafeText(text) {
|
|
257
|
-
return text.replace(
|
|
258
|
+
return text.replace(TERMINAL_UNSAFE_RE, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
258
259
|
}
|
|
259
260
|
function inputAlreadyEnded(input) {
|
|
260
261
|
const state = input;
|
|
@@ -365,6 +366,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
365
366
|
else {
|
|
366
367
|
for (const f of r.unreadFiles ?? [])
|
|
367
368
|
logger.warn("config_unread_file", buildUnreadConfigFileFields(f, configProvider.kind === "local" ? root : undefined));
|
|
369
|
+
for (const k of r.unreadKeys ?? [])
|
|
370
|
+
logger.warn("config_unread_key", buildUnreadConfigKeyFields(k, configProvider.kind === "local" ? root : undefined));
|
|
368
371
|
for (const d of r.droppedEntries ?? [])
|
|
369
372
|
logger.warn("config_entry_dropped", { domain: d.domain, entry: d.entry, reasonCode: d.reasonCode, error: d.error, version: r.effective.version, phase: "boot", note: "this one entry failed its domain schema and was dropped on its own — the rest of the candidate applies (per-entry tolerance); fix the named entry to bring it back" });
|
|
370
373
|
applying = true;
|
|
@@ -386,12 +389,12 @@ export async function runLocal(argv, deps = {}) {
|
|
|
386
389
|
else
|
|
387
390
|
logger.warn("config_source_unreachable_using_env", { source: configProvider.kind, err: String(err) });
|
|
388
391
|
}
|
|
389
|
-
const noDefaultModel = defaultModelRefusal(config,
|
|
392
|
+
const noDefaultModel = defaultModelRefusal(config, servedModelTableLeg(config));
|
|
390
393
|
if (noDefaultModel !== undefined)
|
|
391
394
|
throw new Error(noDefaultModel);
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
394
|
-
throw new Error(
|
|
395
|
+
const routeVerdict = judgeServedModelRoute(config, { envTableIsPlaceholder: false });
|
|
396
|
+
if (routeVerdict !== undefined)
|
|
397
|
+
throw new Error(routeVerdict.reason);
|
|
395
398
|
const unpricedCeiling = buildUnpricedCeilingNotice(config.models, config);
|
|
396
399
|
if (unpricedCeiling)
|
|
397
400
|
logger.warn(unpricedCeiling.event, unpricedCeiling.fields);
|
|
@@ -487,25 +490,25 @@ export async function runLocal(argv, deps = {}) {
|
|
|
487
490
|
return new CenterPromptSource({
|
|
488
491
|
store: new FilePromptArtifactStore(promptEpochDir),
|
|
489
492
|
stateStore: new FilePromptSourceStateStore(promptEpochDir),
|
|
490
|
-
|
|
493
|
+
onError: centerSourceOnError(logger),
|
|
491
494
|
});
|
|
492
495
|
}
|
|
493
496
|
catch (e) {
|
|
494
497
|
logger.warn("center_prompts_store_unwritable", { dir: promptEpochDir, err: String(e), note: "file-backed prompt-epoch store unavailable — memory fallback; built-ins unaffected" });
|
|
495
|
-
return new CenterPromptSource({ store: new MemoryPromptArtifactStore(), stateStore: new MemoryPromptSourceStateStore(),
|
|
498
|
+
return new CenterPromptSource({ store: new MemoryPromptArtifactStore(), stateStore: new MemoryPromptSourceStateStore(), onError: centerSourceOnError(logger) });
|
|
496
499
|
}
|
|
497
500
|
})();
|
|
498
501
|
try {
|
|
499
502
|
await promptSource.restore();
|
|
500
503
|
if (candidatePrompts !== undefined) {
|
|
501
|
-
await
|
|
504
|
+
await applyCenterArtifact(promptSource, candidatePrompts, undefined, logger, "run-local");
|
|
502
505
|
}
|
|
503
506
|
else {
|
|
504
|
-
await
|
|
507
|
+
await applyCenterArtifact(promptSource, undefined, undefined, logger, "run-local");
|
|
505
508
|
}
|
|
506
509
|
}
|
|
507
510
|
catch (e) {
|
|
508
|
-
logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "
|
|
511
|
+
logger.warn("center_prompts_catalog_restore_failed", { err: String(e), note: "center candidate absent — built-ins serve" });
|
|
509
512
|
}
|
|
510
513
|
let refusedWithoutPerson = 0;
|
|
511
514
|
const onAsk = createLocalApprover({
|
|
@@ -558,6 +561,8 @@ export async function runLocal(argv, deps = {}) {
|
|
|
558
561
|
{ name: "toolResultStore", store: fileBackend.toolResultStore },
|
|
559
562
|
],
|
|
560
563
|
});
|
|
564
|
+
const deploymentMods = createDeploymentMods({ metrics, brandIdentity: true });
|
|
565
|
+
await assertDeploymentModsAdmitted(deploymentMods);
|
|
561
566
|
const sharedRunnerDeps = createSharedRunnerDeps({
|
|
562
567
|
metrics,
|
|
563
568
|
config,
|
|
@@ -574,10 +579,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
574
579
|
backgroundAgentStore: fileBackend.backgroundAgentStore,
|
|
575
580
|
mailboxStore: fileBackend.mailboxStore,
|
|
576
581
|
rosterStore: fileBackend.rosterStore,
|
|
577
|
-
|
|
578
|
-
...createPermissionDeniedMeter(metrics),
|
|
579
|
-
...(config.hooksTimeoutMs !== undefined ? { timeoutMs: config.hooksTimeoutMs } : {}),
|
|
580
|
-
},
|
|
582
|
+
deploymentMods,
|
|
581
583
|
toolResultStore: fileBackend.toolResultStore,
|
|
582
584
|
checkpointStore: undefined,
|
|
583
585
|
sessionPolicyStore: fileBackend.sessionPolicyStore,
|
|
@@ -603,7 +605,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
603
605
|
const subRunnerDeps = { ...sharedRunnerDeps, sessionStore: new ForkRoutingSessionStore(sessionStore, new TtlSessionStore({ defaultTtlDays: 1 / 24 })), onError: engineOnError, onAsk };
|
|
604
606
|
const subRunner = new Runner(subRunnerDeps);
|
|
605
607
|
const handslessRunner = new Runner(withoutHands(runnerDeps));
|
|
606
|
-
const deploymentSkills = loadDeploymentSkills(config);
|
|
608
|
+
const deploymentSkills = loadDeploymentSkills(config, logger);
|
|
607
609
|
const skills = effective?.skills ? await applySkillsManifest(deploymentSkills, effective.skills, configProvider, logger) : deploymentSkills;
|
|
608
610
|
const repoClient = config.gitApiBaseUrl ? createRepoClient(config.gitApiKind, config.gitApiBaseUrl, config.gitApiToken) : undefined;
|
|
609
611
|
const webSearchCfg = webSearchConfigFromEnv();
|
|
@@ -614,7 +616,6 @@ export async function runLocal(argv, deps = {}) {
|
|
|
614
616
|
...(webSearch ? { webSearch } : {}),
|
|
615
617
|
backgroundAgentStore: fileBackend.backgroundAgentStore,
|
|
616
618
|
taskListStoreFor: taskListLane.storeFor,
|
|
617
|
-
brandIdentity: true,
|
|
618
619
|
};
|
|
619
620
|
const scenarios = buildScenarios(scenarioDeps);
|
|
620
621
|
if (effective?.scenarios) {
|
|
@@ -625,7 +626,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
625
626
|
const sessionId = args.session ?? uuidv7();
|
|
626
627
|
let cap;
|
|
627
628
|
try {
|
|
628
|
-
scenarioName =
|
|
629
|
+
scenarioName = await createScenarioGate({ config, scenarios, principalCaps: undefined, logger }, "enforce")(undefined, args.scenario);
|
|
629
630
|
cap = selectScenario(scenarios, scenarioName)({ scenario: scenarioName, objective: args.objective }, undefined, { sessionId });
|
|
630
631
|
}
|
|
631
632
|
catch (err) {
|
|
@@ -672,7 +673,7 @@ export async function runLocal(argv, deps = {}) {
|
|
|
672
673
|
getApiKeyAndHeaders: keyResolver,
|
|
673
674
|
tools: cap.tools,
|
|
674
675
|
skills: cap.skills,
|
|
675
|
-
...(
|
|
676
|
+
...buildPromptSeats(scenarioName, cap.prompt, undefined),
|
|
676
677
|
...(cap.finalVerification === true ? { finalVerification: true } : {}),
|
|
677
678
|
...(mcp ? { mcp } : {}),
|
|
678
679
|
...(a2a ? { a2a } : {}),
|
package/dist/runs.d.ts
CHANGED
|
@@ -109,6 +109,12 @@ export declare function resumeAtHttpStatus(result: {
|
|
|
109
109
|
* `resumeAtHttpStatus` 自己(单元测试直接喂码)。
|
|
110
110
|
*/
|
|
111
111
|
export declare function syncRefusalHttpStatus(result: Pick<TaskResult, "terminal" | "stats">): number | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* 同步腿零工作拒体的机读事实(交 `refusalBody` 的 `facts`)—— 唯一一处。入参是**出口封口 + 脱敏后**的终局(调用方递 `redactTerminalResult(sealResultForExit(result)).terminal`,
|
|
114
|
+
* 与 run 记录读面 / 流式 `done.result` 上的 `terminal` 同一只来源),本函数只按码取它的 `detail.detail`,不另读、不另脱。码不在登记表里、终局不是 `failed`、
|
|
115
|
+
* 引擎没给 `coded` 因由 ⇒ `{}`(缺席不造键)。
|
|
116
|
+
*/
|
|
117
|
+
export declare function zeroWorkRefusalFacts(terminal: unknown): Readonly<Record<string, unknown>>;
|
|
112
118
|
/**
|
|
113
119
|
* 一条提交腿把引擎终局落行之后的**唯一**收尾:**零工作的拒不占提交幂等键**。判据就是上面这只 {@link syncRefusalHttpStatus}
|
|
114
120
|
* (零 turn、零 token 且是 prepare 期的拒码)—— 三条提交腿(同步 / 流式 / durable)同调这一只,不另开判据。
|
|
@@ -137,6 +143,38 @@ export declare function releaseKeyIfZeroWorkRefusal(runStore: {
|
|
|
137
143
|
* surfaces the degrade (a metric) so a SYSTEMATIC failure — e.g. an un-migrated DB missing `resume_anchor` — is
|
|
138
144
|
* observable instead of a silent feature outage.
|
|
139
145
|
*/
|
|
146
|
+
/**
|
|
147
|
+
* 「这一次用户消息提交是不是一句**提问**」的唯一读法 —— 三处用户回退锚(后台腿与续跑腿的 {@link TurnAnchorCapture}、`/v1/tasks/stream`
|
|
148
|
+
* 腿的 `userMsgEntryId`)同调这一只。
|
|
149
|
+
*
|
|
150
|
+
* 目标声明了附件时,引擎把每只附件铸成**一条自己的用户消息**,紧跟在目标后面提交(core `human_input.attachments` 顶注:
|
|
151
|
+
* 「each attachment is delivered as its own message after the objective; its `message_committed` is the delivery」)。
|
|
152
|
+
* 所以一次带 N 只附件的提交会出 1 + N 次 `message_committed{role:"user"}`,而「回到这条提问」的锚要的是**人的那条**
|
|
153
|
+
* (core 的 `resumeAt` 认它:「a `resumeAt` that keeps the message keeps its group」;以附件帧作 `resumeAt` 引擎拒 `resume_at.not_a_message`),
|
|
154
|
+
* 不是排在最后的附件帧。目标**之前**还可能有引擎排队的注入消息同批提交(亲读 core `engine/harness/agent-harness.js` 的 `takePrompts`:
|
|
155
|
+
* 排队消息 ⇒ 目标 ⇒ 附件帧;真案 = 工作区是 git 仓且带图时,git 状态帧单独排在目标前面),所以「窗口里第一次用户提交」不是判据。
|
|
156
|
+
*
|
|
157
|
+
* **为什么是「窗口 + 严格吻合」而不是读标记**:`message_committed` 上只有 `entryId / role / toolCallId?`,附件帧与用户真提交在事件上**不可辨认**;
|
|
158
|
+
* 落盘的行上有标记(`attachment` 键;引擎收尾的中断标记带 `provenance: "engine-note"`),但事件到手的那一刻本仓读不到那一行
|
|
159
|
+
* (会话店面没有按条目读的口,只有整日志导出),所以只能数。数的前提是「目标之后紧接恰好 N 只帧、其后才是别的」—— 前提成立才落锚,
|
|
160
|
+
* **不成立就不猜**:猜错的锚会落在附件帧上(引擎拒)或引擎的中断标记上(回到的不是用户那句)。
|
|
161
|
+
*
|
|
162
|
+
* 读法:
|
|
163
|
+
* · `human_input{delivery:"applied"}` 带 N 只附件 ⇒ 开窗;窗内的用户提交只攒、不落锚(此刻还不知道它们是不是『目标 + N 只帧』)。
|
|
164
|
+
* · 窗被**非用户提交**(第一次助手消息 / 工具结果)关上 ⇒ 窗内共 k 次用户提交,k ≥ N+1 时提问 = 倒数第 N+1 次(严格吻合);k < N+1(帧数不符)⇒ 不猜。
|
|
165
|
+
* · 窗被 `turn_end` 先切断 ⇒ 第一次模型调用没出任何提交就收了(halt / interrupt / 空失败):窗里可能夹着引擎收尾的中断标记,数 N 会落在帧或标记上 ⇒ 不猜。
|
|
166
|
+
* · 窗内又应用了一条别的输入(`human_input{applied}`,如第一次模型调用前排进来的 steer;事件先于它的正文提交到手)⇒ 窗里多了一条别人的消息,数 N 会落在帧上 ⇒ 窗作废、不猜,
|
|
167
|
+
* 之后的用户提交(含那条 steer 正文)是窗口之外的提交,最后一次赢。排队未应用(`delivery: "queued"`)的输入不动窗。
|
|
168
|
+
* · 「不猜」= 返回 `undefined`、这次提交不落锚;拿这次 run 的 taskId 去 `resumeAt` 得 404 `resume_at.unknown_event`(文案点名这一类成因)。
|
|
169
|
+
* 窗口之外的用户提交(如 interrupt 带话的 steer 正文,在 `turn_end` 之后提交)照修前读法(是提问,最后一次赢)。
|
|
170
|
+
* 返回这次提交之后的提问 `entryId`(调用方照旧「最后一次赢」地落锚),不是提问相关的提交返回 `undefined`。
|
|
171
|
+
*
|
|
172
|
+
* 退役条件:引擎在落盘的帧上(`message_committed`)带上附件标记之日,本读法整体换成「按标记判一次」(零计数、无窗口、无拒臂),
|
|
173
|
+
* 窗口 / 严格吻合 / 不猜三臂与 `resume_at.unknown_event` 文案里对应的那一句一并删除。
|
|
174
|
+
*/
|
|
175
|
+
export declare function createPromptCommitReader(): (ev: {
|
|
176
|
+
readonly type: string;
|
|
177
|
+
}) => string | undefined;
|
|
140
178
|
export declare class TurnAnchorCapture {
|
|
141
179
|
private readonly capture;
|
|
142
180
|
private readonly onCaptureFail?;
|
|
@@ -151,6 +189,8 @@ export declare class TurnAnchorCapture {
|
|
|
151
189
|
/** The turn's FIRST text_delta eventId = the message's stable resume handle (also stamped on the durable text event). */
|
|
152
190
|
firstTextEventId: string | undefined;
|
|
153
191
|
private turnHadTool;
|
|
192
|
+
/** 用户提交是不是提问的唯一读法({@link createPromptCommitReader};附件帧不是提问)。 */
|
|
193
|
+
private readonly promptCommit;
|
|
154
194
|
constructor(capture: ((eventId: string) => Promise<void>) | undefined, onCaptureFail?: (() => void) | undefined,
|
|
155
195
|
/** R8 (CC-parity rewind): capture a USER-message anchor straight from the
|
|
156
196
|
* `message_committed{role:"user"}` event — its `entryId` is the persisted user-message `SessionTreeEntry.id`,
|
|
@@ -160,9 +200,15 @@ export declare class TurnAnchorCapture {
|
|
|
160
200
|
* Keyed by the run's taskId (the handle the shell already holds from the POST response — no eventId→entryId
|
|
161
201
|
* side map / no `getLeafId` inference needed). The assistant-turn capture above stays as an additive bonus. */
|
|
162
202
|
captureUser?: ((entryId: string) => Promise<void>) | undefined);
|
|
203
|
+
/** A `human_input` arrived — the objective's declared attachments open the frame window of {@link createPromptCommitReader}. */
|
|
204
|
+
onHumanInput(ev: {
|
|
205
|
+
readonly type: string;
|
|
206
|
+
}): void;
|
|
163
207
|
/** A `message_committed` arrived — capture a USER-message rewind anchor (CC parity). Best-effort (a failure only
|
|
164
|
-
* makes that one prompt non-rewindable). Only
|
|
165
|
-
*
|
|
208
|
+
* makes that one prompt non-rewindable). Only a user commit that is a PROMPT (not an attachment frame trailing the
|
|
209
|
+
* objective — {@link createPromptCommitReader}; an attachment window is decided when a non-user commit closes it, so
|
|
210
|
+
* the anchor of an attachment-carrying prompt lands on THAT call); assistant/toolResult committed entries are handled by
|
|
211
|
+
* the turn-text path (assistant) or deliberately not anchored (toolResult — `resumeAt` rejects them). */
|
|
166
212
|
onMessageCommitted(role: string | undefined, entryId: string | undefined): Promise<void>;
|
|
167
213
|
/** A text_delta arrived — latch the first DEFINED eventId as the message handle (eventId is optional on the stream). */
|
|
168
214
|
onText(eventId: string | undefined): void;
|
package/dist/runs.js
CHANGED
|
@@ -76,8 +76,12 @@ const PREPARE_REFUSAL_HTTP_STATUS = {
|
|
|
76
76
|
"config.skill_listing_budget_invalid": 400,
|
|
77
77
|
"config.user_invoked_skill_unknown": 400,
|
|
78
78
|
"config.image_invalid": 400,
|
|
79
|
+
"config.user_attachment_invalid": 400,
|
|
79
80
|
"config.tool_face_control": 400,
|
|
81
|
+
"config.agent.unknown_tool": 400,
|
|
82
|
+
"config.legacy_tool_name": 400,
|
|
80
83
|
"config.memory_capture_unsupported": 409,
|
|
84
|
+
"prompt.assembled_role_refused": 400,
|
|
81
85
|
};
|
|
82
86
|
export function resumeAtHttpStatus(result) {
|
|
83
87
|
const code = failureCodeOf(result.terminal);
|
|
@@ -105,24 +109,75 @@ export function syncRefusalHttpStatus(result) {
|
|
|
105
109
|
return undefined;
|
|
106
110
|
return resumeAtHttpStatus(result);
|
|
107
111
|
}
|
|
112
|
+
const ZERO_WORK_REFUSAL_DETAIL_CODES = new Set(["config.user_attachment_invalid"]);
|
|
113
|
+
export function zeroWorkRefusalFacts(terminal) {
|
|
114
|
+
if (terminal === null || typeof terminal !== "object")
|
|
115
|
+
return {};
|
|
116
|
+
const t = terminal;
|
|
117
|
+
if (t.kind !== "failed" || typeof t.code !== "string" || !ZERO_WORK_REFUSAL_DETAIL_CODES.has(t.code))
|
|
118
|
+
return {};
|
|
119
|
+
const cause = t.detail;
|
|
120
|
+
if (cause === null || typeof cause !== "object")
|
|
121
|
+
return {};
|
|
122
|
+
const c = cause;
|
|
123
|
+
return c.kind === "coded" && c.detail !== undefined ? { detail: c.detail } : {};
|
|
124
|
+
}
|
|
108
125
|
export async function releaseKeyIfZeroWorkRefusal(runStore, taskId, result) {
|
|
109
126
|
if (syncRefusalHttpStatus(result) !== undefined)
|
|
110
127
|
await runStore.releaseIdemKey(taskId);
|
|
111
128
|
}
|
|
129
|
+
export function createPromptCommitReader() {
|
|
130
|
+
let attachmentWindow;
|
|
131
|
+
return (ev) => {
|
|
132
|
+
if (ev.type === "human_input") {
|
|
133
|
+
const h = ev;
|
|
134
|
+
if (h.delivery !== "applied")
|
|
135
|
+
return undefined;
|
|
136
|
+
attachmentWindow = Array.isArray(h.attachments) && h.attachments.length > 0 ? { frames: h.attachments.length, commits: [] } : undefined;
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (ev.type === "turn_end") {
|
|
140
|
+
attachmentWindow = undefined;
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (ev.type !== "message_committed")
|
|
144
|
+
return undefined;
|
|
145
|
+
const c = ev;
|
|
146
|
+
const entryId = typeof c.entryId === "string" && c.entryId.length > 0 ? c.entryId : undefined;
|
|
147
|
+
if (c.role === "user") {
|
|
148
|
+
if (attachmentWindow !== undefined) {
|
|
149
|
+
attachmentWindow.commits.push(entryId);
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return entryId;
|
|
153
|
+
}
|
|
154
|
+
const closed = attachmentWindow;
|
|
155
|
+
attachmentWindow = undefined;
|
|
156
|
+
if (closed === undefined || closed.commits.length < closed.frames + 1)
|
|
157
|
+
return undefined;
|
|
158
|
+
return closed.commits[closed.commits.length - 1 - closed.frames];
|
|
159
|
+
};
|
|
160
|
+
}
|
|
112
161
|
export class TurnAnchorCapture {
|
|
113
162
|
capture;
|
|
114
163
|
onCaptureFail;
|
|
115
164
|
captureUser;
|
|
116
165
|
firstTextEventId;
|
|
117
166
|
turnHadTool = false;
|
|
167
|
+
promptCommit = createPromptCommitReader();
|
|
118
168
|
constructor(capture, onCaptureFail, captureUser) {
|
|
119
169
|
this.capture = capture;
|
|
120
170
|
this.onCaptureFail = onCaptureFail;
|
|
121
171
|
this.captureUser = captureUser;
|
|
122
172
|
}
|
|
173
|
+
onHumanInput(ev) {
|
|
174
|
+
this.promptCommit(ev);
|
|
175
|
+
}
|
|
123
176
|
async onMessageCommitted(role, entryId) {
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
const committed = { type: "message_committed", role, entryId };
|
|
178
|
+
const prompt = this.promptCommit(committed);
|
|
179
|
+
if (prompt !== undefined && this.captureUser) {
|
|
180
|
+
await this.captureUser(prompt).catch(() => this.onCaptureFail?.());
|
|
126
181
|
}
|
|
127
182
|
}
|
|
128
183
|
onText(eventId) {
|
|
@@ -135,6 +190,7 @@ export class TurnAnchorCapture {
|
|
|
135
190
|
this.turnHadTool = true;
|
|
136
191
|
}
|
|
137
192
|
async onTurnEnd() {
|
|
193
|
+
this.promptCommit({ type: "turn_end" });
|
|
138
194
|
if (this.firstTextEventId && !this.turnHadTool && this.capture) {
|
|
139
195
|
await this.capture(this.firstTextEventId).catch(() => this.onCaptureFail?.());
|
|
140
196
|
}
|
|
@@ -373,6 +429,9 @@ export async function runInBackground(runner, spec, runStore, taskId, metrics, p
|
|
|
373
429
|
case "status":
|
|
374
430
|
metrics?.inc("brain_retry_total", { phase: String(ev.phase) });
|
|
375
431
|
break;
|
|
432
|
+
case "human_input":
|
|
433
|
+
anchor.onHumanInput(ev);
|
|
434
|
+
break;
|
|
376
435
|
case "message_committed":
|
|
377
436
|
await anchor.onMessageCommitted(ev.role, ev.entryId);
|
|
378
437
|
break;
|