@sema-agent/core 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/agents/roster-store.d.ts +9 -2
- package/dist/agents/roster-store.js +26 -5
- package/dist/agents/subagent.js +8 -3
- package/dist/bin/sema-tb.d.ts +1 -2
- package/dist/bin/sema-tb.js +13 -25
- package/dist/brain/anthropic.js +2 -3
- package/dist/brain/degrading.d.ts +2 -8
- package/dist/brain/degrading.js +3 -3
- package/dist/brain/terminal-cause.js +2 -8
- package/dist/core/hooks.js +4 -4
- package/dist/core/lsp.js +2 -3
- package/dist/core/mcp.d.ts +1 -1
- package/dist/core/mcp.js +2 -1
- package/dist/core/memory-engine/engine.js +2 -2
- package/dist/core/memory-engine/file-backend.js +3 -1
- package/dist/core/memory-engine/layout.d.ts +3 -0
- package/dist/core/memory-engine/layout.js +51 -0
- package/dist/core/memory.d.ts +0 -1
- package/dist/core/memory.js +1 -1
- package/dist/core/permission-rules.js +8 -7
- package/dist/core/protocol-table.d.ts +14 -0
- package/dist/core/protocol-table.js +23 -0
- package/dist/core/runner/active-skill-scope.js +7 -7
- package/dist/core/runner/prepare-memory.js +4 -1
- package/dist/core/runner/prepare-task.js +17 -16
- package/dist/core/runner/runtask.js +9 -3
- package/dist/core/runner/session-rule-policy.js +1 -1
- package/dist/core/runner/synthetic-tools.js +2 -2
- package/dist/core/runner/tool-disclosure.d.ts +0 -1
- package/dist/core/runner/tool-disclosure.js +0 -2
- package/dist/core/runner/turn-attachments.js +2 -1
- package/dist/core/sensitive-path-policy.js +2 -2
- package/dist/core/tool-policy.d.ts +0 -3
- package/dist/core/tool-policy.js +29 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/orchestration/run-spec.js +1 -1
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/orchestration/workflow-script-store.d.ts +1 -1
- package/dist/orchestration/workflow-script-store.js +1 -1
- package/dist/prompt-assembly/assemble.js +0 -1
- package/dist/prompt-assembly/tool-catalog.d.ts +2 -1
- package/dist/prompts/default.d.ts +0 -2
- package/dist/prompts/default.js +1 -5
- package/dist/stores/cc/mailbox-store.d.ts +4 -0
- package/dist/stores/cc/mailbox-store.js +16 -2
- package/dist/stores/file/session-policy-store.d.ts +10 -1
- package/dist/stores/file/session-policy-store.js +20 -4
- package/dist/tools/fs/fs-search-tools.js +1 -2
- package/dist/tools/fs/fs-shared.d.ts +0 -1
- package/dist/tools/fs/fs-shared.js +0 -1
- package/dist/tools/todo.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.0 (2026-08-03)
|
|
4
|
+
|
|
5
|
+
_The second (and final planned) baggage sweep: every remaining compatibility arm, plus a durable memory-key fix with an automatic adoption leg and the protocol table that clears the road for a second tool protocol. Version-line policy (project ruling): the major stays at 5 — breaking changes below are declared loudly here and coordinated with all in-org consumers, but do not mint a new major._
|
|
6
|
+
|
|
7
|
+
**BREAKING (declared, rides the 5.x line by project ruling)**
|
|
8
|
+
|
|
9
|
+
0. **The registry closes — every remaining compatibility arm is retired in this release:**
|
|
10
|
+
- `createDegradingBrain`: ONE form — the ordered `fallbacks` chain (`fallback`+`fallbackModel` retired; a chain-of-one is `fallbacks: [{brain, model}]`). The pair's two-trigger default retires with it: every construction gets the four-class gate; narrow explicitly via `downgradeOn`.
|
|
11
|
+
- `ToolSearch`: the pre-design/116 `select` ARRAY argument resolves nothing (the CC-canonical `select:A,B,C` string prefix in `query` is the live selection form).
|
|
12
|
+
- `Skill`: `skill` is REQUIRED (the `{name}` arm retired) — a stale replay bounces as loud, model-correctable invalid-arguments.
|
|
13
|
+
- `LSP`: the deprecated `symbol` alias for `query` leaves the tool schema (the `LspRequest.symbol` SEAM field is the wire contract, untouched).
|
|
14
|
+
- `TodoWrite`: `activeForm` is REQUIRED (exactly CC's TodoItem).
|
|
15
|
+
- `WorkflowScriptStore.resolveName`: ONE return shape — `{script, defaultArgs?}` (the bare-string arm retired).
|
|
16
|
+
- `sema-tb`: the `--message` argv leg is a usage error (process-list exposure once made a model's pgrep cleanup kill its own agent); use `--objective-file` or stdin.
|
|
17
|
+
- Anthropic brain: thinking enables ONLY on a valid non-`"off"` `ThinkingLevel` — the legacy-boolean silent 0.5-share enable is gone (out-of-contract value = absence).
|
|
18
|
+
- Terminal-cause predicates read `errorKind` ONLY — the `errorMessage`-sentinel fallback is deleted; stamping `errorKind` on every cut turn is now a documented Brain obligation.
|
|
19
|
+
- Read/Edit/Write schemas declare `file_path` only (the durable `path` alias retired; the security-gate readers keep their `path` over-read — that side is fail-closed coverage, not compat).
|
|
20
|
+
- The legacy prompt composer loses `memoryEnabled`/`consolidationEnabled` (dead in production since design/138 S4; `MEMORY_SAFETY`/`MEMORY_HYGIENE`/`MEMORY_GUIDANCE` stay exported as standalone assets).
|
|
21
|
+
1. **`PermissionResult` loses the legacy `reason` field.** All three arms carry `message` only, and `decisionText` reads `message` only. A TS policy returning `{reason}` compiles red; a JS policy keeps its verdict but its custom text degrades to the per-site generic default. Migrate: `reason:` → `message:`.
|
|
22
|
+
2. **`MemorySpecInput` is ONE form.** The singular `scope` field is removed; `scopes: string[]` is the only spelling. Migrate: `{scope: "x"}` → `{scopes: ["x"]}`. An untyped caller still passing `{scope}` gets "no usable scopes" = memory off for that task.
|
|
23
|
+
3. **The approval folds are STRICT.** `createApprovalPolicy.approve` and the `onAsk` boolean arm approve ONLY on the literal `true`; an out-of-contract truthy (`"yes"`, `1`, …) from an untyped bridge is a fail-closed deny naming the defect. The historical truthy leniency was a fail-open on the security face with no live producer (server bridge verified).
|
|
24
|
+
4. **Grep drops the retired keys (CC alignment).** `max_results` leaves the schema and the wiring — a stale caller's value applies NOTHING (`head_limit` is the one spelling). The `regex` key has no declared arm anywhere.
|
|
25
|
+
|
|
26
|
+
**Fixed**
|
|
27
|
+
|
|
28
|
+
- **One repo, one memory (RB-472).** `deriveRepoKey` canonicalizes (realpath) before minting, so a repo reached through a symlink alias and through its physical path no longer splits into two memory partitions. A pre-6.x deployment's legacy-keyed dirs are ADOPTED automatically at mount (one-shot whole-dir rename, concurrent-open race-tolerant). A store that already split is a loud `memory.partition_split` incident with both coordinates — never silently merged. Rename failure = `memory.partition_adopt_failed` disclosure; the mount never fails over adoption.
|
|
29
|
+
|
|
30
|
+
**Added**
|
|
31
|
+
|
|
32
|
+
- **Protocol namespace table**: `PROTOCOL_TABLE` / `MCP_NAMESPACE` / `protocolOf` / `ProtocolNamespace` / `ProtocolId` — "which protocol owns this tool name" as a lookup (`MCP_PREFIX` is now the table entry's prefix; a second protocol lands by appending one entry). `ToolOrigin`'s protocol slice derives from `ProtocolId`.
|
|
33
|
+
- **Corrupt-read disclosure seats** (the tolerances stay; the silence goes): `FileSessionPolicyStore`/`FileRosterStore` constructor `onCorruptRead`, `CcFileMailboxStoreOptions.onCorruptRead`, `recordRosterSpawn` optional `onError` (threaded to `onObserverError`, site `roster.recordSpawn`), and the durable-approval continuity read disclosing via `onObserverError` (site `resume.durableApprovalContinuity`). Plain ENOENT never fires any of them.
|
|
34
|
+
|
|
35
|
+
**Migration one-line table**: `reason:` → `message:` / `{scope}` → `{scopes: [..]}` / approver returns literal `true`·`false`·`"unavailable"`·`{allow}` only / `max_results` → `head_limit`.
|
|
36
|
+
|
|
3
37
|
## 5.0.1 (2026-08-02)
|
|
4
38
|
|
|
5
39
|
_Post-release review patch: the 5.0.0 review wave (five domain reviewers + two independent codex legs) surfaced silent-widening gaps in the new audit surfaces plus wiring/anchoring stragglers. All fixes are loud-direction; no new API._
|
|
@@ -42,7 +42,14 @@ export declare class FileRosterStore implements RosterStore {
|
|
|
42
42
|
private readonly path;
|
|
43
43
|
private readonly maxAgeMs;
|
|
44
44
|
private readonly gc;
|
|
45
|
-
|
|
45
|
+
private readonly onCorruptRead;
|
|
46
|
+
constructor(path: string, opts?: RosterGcOptions & {
|
|
47
|
+
onCorruptRead?: (info: {
|
|
48
|
+
path: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
}) => void;
|
|
51
|
+
});
|
|
52
|
+
private discloseCorrupt;
|
|
46
53
|
private read;
|
|
47
54
|
private write;
|
|
48
55
|
record(entry: RosterEntry): void;
|
|
@@ -50,4 +57,4 @@ export declare class FileRosterStore implements RosterStore {
|
|
|
50
57
|
list(): RosterEntry[];
|
|
51
58
|
releaseAgent(agentId: string): void;
|
|
52
59
|
}
|
|
53
|
-
export declare function recordRosterSpawn(roster: RosterStore | undefined, entry: RosterEntry): void;
|
|
60
|
+
export declare function recordRosterSpawn(roster: RosterStore | undefined, entry: RosterEntry, onError?: (err: unknown) => void): void;
|
|
@@ -76,10 +76,19 @@ export class FileRosterStore {
|
|
|
76
76
|
path;
|
|
77
77
|
maxAgeMs;
|
|
78
78
|
gc;
|
|
79
|
+
onCorruptRead;
|
|
79
80
|
constructor(path, opts) {
|
|
80
81
|
this.path = path;
|
|
81
82
|
this.maxAgeMs = opts?.maxAgeMs;
|
|
82
83
|
this.gc = opts;
|
|
84
|
+
this.onCorruptRead = opts?.onCorruptRead;
|
|
85
|
+
}
|
|
86
|
+
discloseCorrupt(reason) {
|
|
87
|
+
try {
|
|
88
|
+
this.onCorruptRead?.({ path: this.path, reason });
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
}
|
|
83
92
|
}
|
|
84
93
|
read(mode) {
|
|
85
94
|
let text;
|
|
@@ -89,8 +98,10 @@ export class FileRosterStore {
|
|
|
89
98
|
catch (e) {
|
|
90
99
|
if (e.code === "ENOENT")
|
|
91
100
|
return [];
|
|
92
|
-
if (mode === "lenient")
|
|
101
|
+
if (mode === "lenient") {
|
|
102
|
+
this.discloseCorrupt(`read failed (non-ENOENT): ${e.message}`);
|
|
93
103
|
return [];
|
|
104
|
+
}
|
|
94
105
|
throw e;
|
|
95
106
|
}
|
|
96
107
|
if (text.trim() === "")
|
|
@@ -102,8 +113,10 @@ export class FileRosterStore {
|
|
|
102
113
|
return raw.entries.filter((e) => typeof e === "object" && e !== null && typeof e.name === "string" && typeof e.agentId === "string" && typeof e.createdAt === "number");
|
|
103
114
|
}
|
|
104
115
|
catch (e) {
|
|
105
|
-
if (mode === "lenient")
|
|
116
|
+
if (mode === "lenient") {
|
|
117
|
+
this.discloseCorrupt(`corrupt roster document: ${e.message}`);
|
|
106
118
|
return [];
|
|
119
|
+
}
|
|
107
120
|
throw e;
|
|
108
121
|
}
|
|
109
122
|
}
|
|
@@ -127,12 +140,20 @@ export class FileRosterStore {
|
|
|
127
140
|
this.write(this.read("strict").filter((e) => e.agentId !== agentId));
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
|
-
export function recordRosterSpawn(roster, entry) {
|
|
143
|
+
export function recordRosterSpawn(roster, entry, onError) {
|
|
131
144
|
if (!roster)
|
|
132
145
|
return;
|
|
146
|
+
const disclose = (err) => {
|
|
147
|
+
try {
|
|
148
|
+
onError?.(err);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
}
|
|
152
|
+
};
|
|
133
153
|
try {
|
|
134
|
-
void Promise.resolve(roster.record(entry)).catch(
|
|
154
|
+
void Promise.resolve(roster.record(entry)).catch(disclose);
|
|
135
155
|
}
|
|
136
|
-
catch {
|
|
156
|
+
catch (err) {
|
|
157
|
+
disclose(err);
|
|
137
158
|
}
|
|
138
159
|
}
|
package/dist/agents/subagent.js
CHANGED
|
@@ -1693,7 +1693,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1693
1693
|
}, { priority: "later" }), "subagent.reapTerminalNotify");
|
|
1694
1694
|
});
|
|
1695
1695
|
if (agentName !== undefined) {
|
|
1696
|
-
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() });
|
|
1696
|
+
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
1697
1697
|
}
|
|
1698
1698
|
const bgSink = ctx.onBackgroundChildEvent;
|
|
1699
1699
|
const sinkEmit = (event) => {
|
|
@@ -2096,7 +2096,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2096
2096
|
return { isError: true, content: `Sub-agent not started in background: ${e instanceof Error ? e.message : String(e)}${wt ? `\n${wt}` : ""}`, details: { error: "register_failed" } };
|
|
2097
2097
|
}
|
|
2098
2098
|
if (agentName !== undefined) {
|
|
2099
|
-
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() });
|
|
2099
|
+
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...((typeof childModel === "string" ? resolveModelDisplayLabel(childModel) : childModel?.id) !== undefined ? { model: typeof childModel === "string" ? childModel : childModel?.id } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
2100
2100
|
}
|
|
2101
2101
|
const bgSink = ctx.onBackgroundChildEvent;
|
|
2102
2102
|
const sinkEmit = (event) => {
|
|
@@ -2333,7 +2333,12 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2333
2333
|
resumeConfig.durableApproval = { scope: cp0.scope };
|
|
2334
2334
|
}
|
|
2335
2335
|
}
|
|
2336
|
-
catch {
|
|
2336
|
+
catch (err) {
|
|
2337
|
+
try {
|
|
2338
|
+
opts.onObserverError?.(err, { site: "resume.durableApprovalContinuity" });
|
|
2339
|
+
}
|
|
2340
|
+
catch {
|
|
2341
|
+
}
|
|
2337
2342
|
}
|
|
2338
2343
|
}
|
|
2339
2344
|
const bgStream = await opts.runner.resumeStream(pr.ticket.token, pr.outcome, resumeConfig, {
|
package/dist/bin/sema-tb.d.ts
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { type Model } from "../index.js";
|
|
3
3
|
interface Args {
|
|
4
4
|
model: string;
|
|
5
|
-
message: string | undefined;
|
|
6
5
|
objectiveFile: string | undefined;
|
|
7
6
|
jsonl: string | undefined;
|
|
8
7
|
}
|
|
@@ -19,7 +18,7 @@ export type InstructionResolution = {
|
|
|
19
18
|
} | {
|
|
20
19
|
error: string;
|
|
21
20
|
};
|
|
22
|
-
export declare function resolveInstruction(args: Pick<Args, "
|
|
21
|
+
export declare function resolveInstruction(args: Pick<Args, "objectiveFile">, readInput: (source: string | 0) => string): InstructionResolution;
|
|
23
22
|
type TbCost = Model["cost"];
|
|
24
23
|
export type TbCostResolution = {
|
|
25
24
|
cost: TbCost;
|
package/dist/bin/sema-tb.js
CHANGED
|
@@ -6,11 +6,11 @@ import { positiveIntFromEnv, tbModelInput } from "./tb-env.js";
|
|
|
6
6
|
import { inferMaxTokensField } from "../brain/openai.js";
|
|
7
7
|
function usage(code = 2) {
|
|
8
8
|
const out = code === 0 ? process.stdout : process.stderr;
|
|
9
|
-
out.write("Usage: sema-tb --model <provider/model> [--
|
|
10
|
-
" Instruction legs: --objective-file <path> (preferred for real task text) |
|
|
9
|
+
out.write("Usage: sema-tb --model <provider/model> [--objective-file <path>] [--jsonl <path>]\n" +
|
|
10
|
+
" Instruction legs: --objective-file <path> (preferred for real task text) | stdin\n" +
|
|
11
11
|
" | stdin (default when neither flag is given). Both flags together = usage error.\n" +
|
|
12
12
|
" WARNING: argv is visible to every process in the sandbox (ps / pgrep -f / /proc/*/cmdline) — long\n" +
|
|
13
|
-
"
|
|
13
|
+
" Instructions ride --objective-file or stdin; the retired --message argv leg is refused (process-list exposure).\n" +
|
|
14
14
|
"Env: <PROVIDER>_API_KEY / <PROVIDER>_BASE_URL, TB_MAX_TURNS, TB_TIMEOUT_SEC, TB_FULLBODY, TB_TODOREMIND, TB_BGTASKS, TB_SUMMARIZE,\n" +
|
|
15
15
|
" TB_FINALVERIFY, TB_CALLCAP, TB_FINALIZE, TB_REASONING, TB_JSONL, TB_CONTEXT_WINDOW, TB_MAX_OUTPUT_TOKENS, TB_TASKLIST, TB_VISION\n" +
|
|
16
16
|
' TB_COST="input,output[,cacheRead[,cacheWrite]]" — USD per MTok; explicit pricing override (omitted\n' +
|
|
@@ -20,19 +20,19 @@ function usage(code = 2) {
|
|
|
20
20
|
}
|
|
21
21
|
export function parseArgs(argv, env = process.env) {
|
|
22
22
|
let model;
|
|
23
|
-
let message;
|
|
24
23
|
let objectiveFile;
|
|
25
24
|
let jsonl;
|
|
26
25
|
for (let i = 0; i < argv.length; i++) {
|
|
27
26
|
const a = argv[i];
|
|
28
|
-
if (a === "--
|
|
27
|
+
if (a === "--message") {
|
|
28
|
+
return { usageError: "--message is retired (argv is visible to every sandbox process — the exposure once made a model's pgrep cleanup kill its own agent). Use --objective-file <path> or pipe the instruction on stdin." };
|
|
29
|
+
}
|
|
30
|
+
if (a === "--model" || a === "--objective-file" || a === "--jsonl") {
|
|
29
31
|
const v = argv[++i];
|
|
30
32
|
if (v === undefined)
|
|
31
33
|
return { usageError: `missing value for ${a}` };
|
|
32
34
|
if (a === "--model")
|
|
33
35
|
model = v;
|
|
34
|
-
else if (a === "--message")
|
|
35
|
-
message = v;
|
|
36
36
|
else if (a === "--objective-file")
|
|
37
37
|
objectiveFile = v;
|
|
38
38
|
else
|
|
@@ -46,28 +46,16 @@ export function parseArgs(argv, env = process.env) {
|
|
|
46
46
|
if (!model || !model.includes("/")) {
|
|
47
47
|
return { usageError: "--model must be <provider/model> (e.g. deepseek/deepseek-v4-flash)" };
|
|
48
48
|
}
|
|
49
|
-
return { args: { model,
|
|
49
|
+
return { args: { model, objectiveFile, jsonl: jsonl ?? env.TB_JSONL } };
|
|
50
50
|
}
|
|
51
51
|
export function resolveInstruction(args, readInput) {
|
|
52
|
-
|
|
53
|
-
return { error: "--message and --objective-file are mutually exclusive (pick one instruction leg)" };
|
|
54
|
-
}
|
|
55
|
-
const leg = args.objectiveFile !== undefined
|
|
56
|
-
? `--objective-file ${args.objectiveFile}`
|
|
57
|
-
: args.message !== undefined
|
|
58
|
-
? "--message"
|
|
59
|
-
: "stdin (no --message/--objective-file)";
|
|
52
|
+
const leg = args.objectiveFile !== undefined ? `--objective-file ${args.objectiveFile}` : "stdin (no --objective-file)";
|
|
60
53
|
let raw;
|
|
61
|
-
|
|
62
|
-
raw = args.
|
|
54
|
+
try {
|
|
55
|
+
raw = readInput(args.objectiveFile ?? 0);
|
|
63
56
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raw = readInput(args.objectiveFile ?? 0);
|
|
67
|
-
}
|
|
68
|
-
catch (e) {
|
|
69
|
-
return { error: `cannot read instruction from ${leg}: ${e instanceof Error ? e.message : String(e)}` };
|
|
70
|
-
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
return { error: `cannot read instruction from ${leg}: ${e instanceof Error ? e.message : String(e)}` };
|
|
71
59
|
}
|
|
72
60
|
const instruction = raw.trim();
|
|
73
61
|
if (!instruction)
|
package/dist/brain/anthropic.js
CHANGED
|
@@ -253,7 +253,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
253
253
|
if (options?.temperature !== undefined && anthCompat.supportsTemperature !== false) {
|
|
254
254
|
body.temperature = options.temperature;
|
|
255
255
|
}
|
|
256
|
-
if (model.reasoning && options?.reasoning && options.reasoning !== "off") {
|
|
256
|
+
if (model.reasoning && isThinkingLevel(options?.reasoning) && options.reasoning !== "off") {
|
|
257
257
|
if (anthCompat.thinkingMode === "adaptive") {
|
|
258
258
|
body.thinking = { type: "adaptive" };
|
|
259
259
|
}
|
|
@@ -264,8 +264,7 @@ export function createAnthropicBrain(config = {}) {
|
|
|
264
264
|
delete body.thinking;
|
|
265
265
|
}
|
|
266
266
|
else {
|
|
267
|
-
const
|
|
268
|
-
const share = config.thinkingBudgetShare ?? tierShare ?? 0.5;
|
|
267
|
+
const share = config.thinkingBudgetShare ?? reasoningBudgetShare(options.reasoning);
|
|
269
268
|
const { max, budget } = thinkingBudget(body.max_tokens, share, config.thinkingBudgetTokens, hardCap);
|
|
270
269
|
body.max_tokens = max;
|
|
271
270
|
body.thinking = { type: "enabled", budget_tokens: budget };
|
|
@@ -12,18 +12,12 @@ interface DegradingBrainBaseOptions {
|
|
|
12
12
|
primary: Brain;
|
|
13
13
|
downgradeOn?: DegradeReason[];
|
|
14
14
|
}
|
|
15
|
-
export type DegradingBrainOptions =
|
|
15
|
+
export type DegradingBrainOptions = DegradingBrainBaseOptions & {
|
|
16
16
|
fallbacks: Array<{
|
|
17
17
|
brain: Brain;
|
|
18
18
|
model: Model;
|
|
19
19
|
}>;
|
|
20
|
-
|
|
21
|
-
fallbackModel?: never;
|
|
22
|
-
}) | (DegradingBrainBaseOptions & {
|
|
23
|
-
fallbacks?: never;
|
|
24
|
-
fallback: Brain;
|
|
25
|
-
fallbackModel: Model;
|
|
26
|
-
});
|
|
20
|
+
};
|
|
27
21
|
export declare function readDegradation(msg: {
|
|
28
22
|
diagnostics?: AssistantMessageDiagnostic[];
|
|
29
23
|
} | undefined): DegradationInfo | undefined;
|
package/dist/brain/degrading.js
CHANGED
|
@@ -115,12 +115,12 @@ export function passthroughStream(brain, model, context, options) {
|
|
|
115
115
|
return out;
|
|
116
116
|
}
|
|
117
117
|
export function createDegradingBrain(opts) {
|
|
118
|
-
const triggers = new Set(opts.downgradeOn ??
|
|
119
|
-
const rawHops = opts.fallbacks ??
|
|
118
|
+
const triggers = new Set(opts.downgradeOn ?? ["rate_limit", "breaker_open", "server_error", "last_resort"]);
|
|
119
|
+
const rawHops = opts.fallbacks ?? [];
|
|
120
120
|
const seenIds = new Set();
|
|
121
121
|
const hops = rawHops.filter((h) => (seenIds.has(h.model.id) ? false : (seenIds.add(h.model.id), true))).slice(0, MAX_CHAIN);
|
|
122
122
|
if (hops.length === 0) {
|
|
123
|
-
throw new Error("createDegradingBrain:
|
|
123
|
+
throw new Error("createDegradingBrain: `fallbacks` needs at least one {brain, model} hop (RB-482 #3: the legacy fallback+fallbackModel pair is retired — a chain-of-one is `fallbacks: [{brain, model}]`)");
|
|
124
124
|
}
|
|
125
125
|
const stream = (model, context, options) => {
|
|
126
126
|
if (options?.resilience?.allowDegrade === false) {
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import { DEGENERATE_MESSAGE } from "./repetition.js";
|
|
2
|
-
import { WALLTIME_CUTOFF_MESSAGE } from "./walltime.js";
|
|
3
1
|
export function isDegenerateCutMessage(message) {
|
|
4
|
-
|
|
5
|
-
return true;
|
|
6
|
-
return message.errorMessage === DEGENERATE_MESSAGE;
|
|
2
|
+
return message.errorKind === "degenerate";
|
|
7
3
|
}
|
|
8
4
|
export function isWalltimeCutMessage(message) {
|
|
9
|
-
|
|
10
|
-
return true;
|
|
11
|
-
return message.errorMessage === WALLTIME_CUTOFF_MESSAGE;
|
|
5
|
+
return message.errorKind === "walltime_cutoff";
|
|
12
6
|
}
|
package/dist/core/hooks.js
CHANGED
|
@@ -177,7 +177,7 @@ export async function runToolGate(input) {
|
|
|
177
177
|
if (decision.action === "ask" && req.toolName === ASK_USER_QUESTION_TOOL_NAME) {
|
|
178
178
|
decision = {
|
|
179
179
|
action: "deny",
|
|
180
|
-
|
|
180
|
+
message: "AskUserQuestion could not be parked for a durable human answer on this path (no checkpoint " +
|
|
181
181
|
"facility, or the suspend failed) and a synchronous permission approver cannot carry an answer — " +
|
|
182
182
|
"the question was NOT shown to anyone. Proceed with your best judgment and state the assumption " +
|
|
183
183
|
"you made.",
|
|
@@ -211,7 +211,7 @@ export async function runToolGate(input) {
|
|
|
211
211
|
}
|
|
212
212
|
catch (err) {
|
|
213
213
|
traceHookCrash(input, err, notifier);
|
|
214
|
-
editDenied = { action: "deny",
|
|
214
|
+
editDenied = { action: "deny", message: preToolUseCrashReason(`the approved edit for "${toolName}"`, err) };
|
|
215
215
|
denySource = "hook";
|
|
216
216
|
break;
|
|
217
217
|
}
|
|
@@ -219,7 +219,7 @@ export async function runToolGate(input) {
|
|
|
219
219
|
if (hr.additionalContext)
|
|
220
220
|
preToolContext.push(hr.additionalContext);
|
|
221
221
|
if (hr.action === "deny") {
|
|
222
|
-
editDenied = { action: "deny",
|
|
222
|
+
editDenied = { action: "deny", message: decisionText(hr) ?? `the approved edit for "${toolName}" was blocked by a PreToolUse hook` };
|
|
223
223
|
denySource = "hook";
|
|
224
224
|
break;
|
|
225
225
|
}
|
|
@@ -244,7 +244,7 @@ export async function runToolGate(input) {
|
|
|
244
244
|
catch (err) {
|
|
245
245
|
recheck = {
|
|
246
246
|
action: "deny",
|
|
247
|
-
|
|
247
|
+
message: `policy re-check of the approved edit for "${toolName}" errored: ${err instanceof Error ? err.message : String(err)}`,
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
if (recheck.action === "deny") {
|
package/dist/core/lsp.js
CHANGED
|
@@ -171,18 +171,17 @@ export function createLspTool(manager, opts = {}) {
|
|
|
171
171
|
line: Type.Optional(Type.Number({ description: "The line number (1-based, as shown in editors)." })),
|
|
172
172
|
character: Type.Optional(Type.Number({ description: "The character offset (1-based, as shown in editors)." })),
|
|
173
173
|
query: Type.Optional(Type.String({ description: "Symbol query for workspaceSymbol (may be empty to browse broadly)." })),
|
|
174
|
-
symbol: Type.Optional(Type.String({ description: "Deprecated alias for query." })),
|
|
175
174
|
}),
|
|
176
175
|
effect: "read",
|
|
177
176
|
execute: async (args, ctx) => {
|
|
178
|
-
const { operation, filePath, line, character, query
|
|
177
|
+
const { operation, filePath, line, character, query } = args;
|
|
179
178
|
if (POSITION_OPS.has(operation) && (line === undefined || character === undefined)) {
|
|
180
179
|
return errorResult(`Error (LSP): the "${operation}" operation needs both line and character.`);
|
|
181
180
|
}
|
|
182
181
|
if (POSITION_OPS.has(operation) && (!Number.isInteger(line) || line < 1 || !Number.isInteger(character) || character < 1)) {
|
|
183
182
|
return errorResult(`Error (LSP): line and character are 1-based integers (as shown in editors); got line=${line}, character=${character}.`);
|
|
184
183
|
}
|
|
185
|
-
const wsQuery = query
|
|
184
|
+
const wsQuery = query;
|
|
186
185
|
if (operation === "workspaceSymbol" && wsQuery === undefined) {
|
|
187
186
|
return errorResult(`Error (LSP): the "workspaceSymbol" operation needs a query.`);
|
|
188
187
|
}
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export interface McpServerStatus {
|
|
|
56
56
|
error?: string;
|
|
57
57
|
transportClosed?: boolean;
|
|
58
58
|
}
|
|
59
|
-
export declare const MCP_PREFIX
|
|
59
|
+
export declare const MCP_PREFIX: string;
|
|
60
60
|
export declare function resolveMcpDeclaredResultSize(meta: Record<string, unknown> | undefined): number | undefined;
|
|
61
61
|
export declare function gateMcpOutput(content: Array<TextContent | ImageContent>, limitTokens?: number): Array<TextContent | ImageContent>;
|
|
62
62
|
export declare function truncateMcpErrorText(s: string): string;
|
package/dist/core/mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
2
3
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
4
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
5
|
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -12,7 +13,7 @@ import { truncateError } from "./tool-errors.js";
|
|
|
12
13
|
import { delimitUntrusted, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
13
14
|
import { validateJsonSchemaShape } from "./runner/strict-output-schema.js";
|
|
14
15
|
const NAME_SEP = "__";
|
|
15
|
-
export const MCP_PREFIX =
|
|
16
|
+
export const MCP_PREFIX = MCP_NAMESPACE.prefix;
|
|
16
17
|
const MCP_OUTPUT_TOKENS_DEFAULT = 25_000;
|
|
17
18
|
const MCP_CHARS_PER_TOKEN = 4;
|
|
18
19
|
const MCP_META_RESULT_SIZE_CAP = 500_000;
|
|
@@ -6,7 +6,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
|
|
|
6
6
|
import { formatMemoryAge } from "../memory-recall.js";
|
|
7
7
|
import { computeEntryRev, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
8
|
import { DEFAULT_MAX_ENTRY_DEPTH, MEMORY_INDEX_FILENAME, scanEntryFiles } from "./file-backend.js";
|
|
9
|
-
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
9
|
+
import { QUARANTINE_DIR, SCAN_FUSE_THRESHOLD, quarantineAndTombstone, readIndexRevs, writeIndexRevs, bumpScanFuse, canonicalize, claimRootScope, clearScanFuse, adoptLegacyRepoDirs, deriveControlPlaneDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, ensureDirExists, isContainedIn, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirFor, } from "./layout.js";
|
|
10
10
|
import { scanMemoryFileName, scanMemoryWrite, scanRemediation } from "./scan.js";
|
|
11
11
|
export const MEMORY_INSTRUCTION_TEMPLATE = `# Memory
|
|
12
12
|
|
|
@@ -61,7 +61,7 @@ export class MemoryEngine {
|
|
|
61
61
|
this.controlDir =
|
|
62
62
|
typeof backendControl === "string" && backendControl
|
|
63
63
|
? backendControl
|
|
64
|
-
: (opts.controlDir ?? deriveControlPlaneDir(resolveMemoryEngineRoot(), opts.memoryDir));
|
|
64
|
+
: (opts.controlDir ?? (adoptLegacyRepoDirs(resolveMemoryEngineRoot(), opts.memoryDir), deriveControlPlaneDir(resolveMemoryEngineRoot(), opts.memoryDir)));
|
|
65
65
|
this.now = opts.now ?? Date.now;
|
|
66
66
|
this.perScopeBudgetBytes = opts.perScopeBudgetBytes ?? Number.MAX_SAFE_INTEGER;
|
|
67
67
|
this.perFileBytes = opts.perFileBytes ?? MAX_MEMORY_BYTES;
|
|
@@ -5,7 +5,7 @@ import { jaccardDistance, termSet } from "../memory-vector.js";
|
|
|
5
5
|
import { MAX_MEMORY_BYTES } from "../memory.js";
|
|
6
6
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
7
7
|
import { computeEntryRev, entryFromFile, isValidEntryId, parseEntryFile, serializeEntryFile } from "./frontmatter.js";
|
|
8
|
-
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, atomicWriteFileSync, quarantineAndTombstone, claimRootScope, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirName, writeAllSync, } from "./layout.js";
|
|
8
|
+
import { ControlPlaneCorruptError, CURSORS_FILE, QUARANTINE_DIR, atomicWriteFileSync, quarantineAndTombstone, claimRootScope, adoptLegacyRepoDirs, deriveControlPlaneDir, ensureDirExists, enqueueMemoryAnnouncement, registerScope, registeredScopes, resolveMemoryEngineRoot, scopeDirName, writeAllSync, } from "./layout.js";
|
|
9
9
|
import { scanMemoryFileName, scanMemoryWrite } from "./scan.js";
|
|
10
10
|
export const MEMORY_INDEX_FILENAME = "MEMORY.md";
|
|
11
11
|
export const DEFAULT_MAX_ENTRY_DEPTH = 3;
|
|
@@ -78,6 +78,8 @@ export class FileMemoryEngineBackend {
|
|
|
78
78
|
constructor(dir, opts = {}) {
|
|
79
79
|
this.now = opts.now ?? Date.now;
|
|
80
80
|
this.directoryRoot = dir;
|
|
81
|
+
if (opts.controlDir === undefined)
|
|
82
|
+
adoptLegacyRepoDirs(resolveMemoryEngineRoot(), dir);
|
|
81
83
|
this.controlPlaneRoot = opts.controlDir ?? deriveControlPlaneDir(resolveMemoryEngineRoot(), dir);
|
|
82
84
|
ensureDirExists(dir);
|
|
83
85
|
ensureDirExists(this.controlPlaneRoot);
|
|
@@ -6,6 +6,9 @@ export declare class ControlPlaneCorruptError extends Error {
|
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
8
|
export declare function deriveRepoKey(repoRoot: string): string;
|
|
9
|
+
export declare function adoptLegacyRepoDirs(configRoot: string, repoRoot: string, onIncident?: (err: Error & {
|
|
10
|
+
code?: string;
|
|
11
|
+
}) => void): void;
|
|
9
12
|
export declare function deriveRepoMemoryDir(configRoot: string, repoRoot: string): string;
|
|
10
13
|
export declare function deriveProjectMemoryDir(configRoot: string, projectId: string): string;
|
|
11
14
|
export declare function deriveProjectControlDir(configRoot: string, projectId: string): string;
|
|
@@ -11,8 +11,59 @@ export class ControlPlaneCorruptError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
export function deriveRepoKey(repoRoot) {
|
|
14
|
+
return canonicalize(resolve(repoRoot)).normalize("NFC").replace(/[^A-Za-z0-9]/g, "-");
|
|
15
|
+
}
|
|
16
|
+
function deriveRepoKeyLegacy(repoRoot) {
|
|
14
17
|
return resolve(repoRoot).normalize("NFC").replace(/[^A-Za-z0-9]/g, "-");
|
|
15
18
|
}
|
|
19
|
+
export function adoptLegacyRepoDirs(configRoot, repoRoot, onIncident) {
|
|
20
|
+
const disclose = (code, message) => {
|
|
21
|
+
try {
|
|
22
|
+
const e = new Error(message);
|
|
23
|
+
e.code = code;
|
|
24
|
+
onIncident?.(e);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
try {
|
|
30
|
+
const newKey = deriveRepoKey(repoRoot);
|
|
31
|
+
const oldKey = deriveRepoKeyLegacy(repoRoot);
|
|
32
|
+
if (newKey === oldKey)
|
|
33
|
+
return;
|
|
34
|
+
const oldDir = join(configRoot, oldKey);
|
|
35
|
+
const newDir = join(configRoot, newKey);
|
|
36
|
+
const exists = (p) => {
|
|
37
|
+
try {
|
|
38
|
+
statSync(p);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
if (!exists(oldDir))
|
|
46
|
+
return;
|
|
47
|
+
if (exists(newDir)) {
|
|
48
|
+
disclose("memory.partition_split", `memory partition split detected for ${repoRoot}: BOTH the legacy alias-keyed dir (${oldDir}) and the canonical dir (${newDir}) exist. ` +
|
|
49
|
+
`This session uses the canonical dir; the legacy dir is left untouched (never merged — CAS baselines would collide). ` +
|
|
50
|
+
`If the legacy side holds newer memory, move its rows by hand and remove it.`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
renameSync(oldDir, newDir);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const code = err.code;
|
|
58
|
+
if ((code === "ENOENT" || code === "EEXIST" || code === "ENOTEMPTY") && exists(newDir))
|
|
59
|
+
return;
|
|
60
|
+
disclose("memory.partition_adopt_failed", `could not adopt the legacy memory partition ${oldDir} → ${newDir} (${code ?? String(err)}): this session proceeds on the canonical dir; ` +
|
|
61
|
+
`the legacy dir still holds the prior memory — move it by hand.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
}
|
|
66
|
+
}
|
|
16
67
|
export function deriveRepoMemoryDir(configRoot, repoRoot) {
|
|
17
68
|
return join(configRoot, deriveRepoKey(repoRoot), "memory");
|
|
18
69
|
}
|
package/dist/core/memory.d.ts
CHANGED
package/dist/core/memory.js
CHANGED
|
@@ -354,7 +354,7 @@ function mtimeMsOf(ts) {
|
|
|
354
354
|
export function normalizeMemorySpec(input) {
|
|
355
355
|
if (!input)
|
|
356
356
|
return undefined;
|
|
357
|
-
const raw = input.scopes
|
|
357
|
+
const raw = input.scopes ?? [];
|
|
358
358
|
const scopes = [];
|
|
359
359
|
const seen = new Set();
|
|
360
360
|
for (const s of raw) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
1
2
|
export const BASH_GENERIC_PARAMS = new Set(["command", "timeout", "description", "run_in_background"]);
|
|
2
3
|
const DEFAULT_CAPS = {
|
|
3
4
|
maxRules: 256,
|
|
@@ -95,7 +96,7 @@ const FILE_TOOL_PARAMS = {
|
|
|
95
96
|
Write: new Set(["file_path", "path", "content"]),
|
|
96
97
|
NotebookEdit: new Set(["notebook_path", "path", "cell_id", "cell_type", "edit_mode", "new_source"]),
|
|
97
98
|
Grep: new Set([
|
|
98
|
-
"pattern", "path", "glob", "type", "output_mode", "head_limit", "
|
|
99
|
+
"pattern", "path", "glob", "type", "output_mode", "head_limit", "offset",
|
|
99
100
|
"multiline", "context", "ignore_case", "-i", "-n", "-o", "-A", "-B", "-C",
|
|
100
101
|
]),
|
|
101
102
|
Glob: new Set(["pattern", "path", "max_results"]),
|
|
@@ -142,7 +143,7 @@ function compile(rules, caps, primaryFieldGeneric) {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
const toolName = parsed.toolName;
|
|
145
|
-
if (toolName.startsWith(
|
|
146
|
+
if (toolName.startsWith(MCP_NAMESPACE.prefix) && (parsed.ruleContent !== undefined || indexOfUnescaped(text, "(") !== -1)) {
|
|
146
147
|
bad(text, "unsupported.mcp_paren", "MCP rules do not support patterns in parentheses (CC parity); use the toolAxes system");
|
|
147
148
|
continue;
|
|
148
149
|
}
|
|
@@ -274,18 +275,18 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
274
275
|
const entry = byTool.get(req.toolName);
|
|
275
276
|
if (entry) {
|
|
276
277
|
if (entry.bare.deny) {
|
|
277
|
-
return { action: "deny",
|
|
278
|
+
return { action: "deny", message: ruleMessage("denied", entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
278
279
|
}
|
|
279
280
|
const paramDeny = matchParamRules(entry.param.deny, req.args, caps.maxScalarValueChars);
|
|
280
281
|
if (paramDeny) {
|
|
281
|
-
return { action: "deny",
|
|
282
|
+
return { action: "deny", message: ruleMessage("denied", paramDeny.ruleText, paramDeny.source) };
|
|
282
283
|
}
|
|
283
284
|
if (entry.bare.ask) {
|
|
284
|
-
return { action: "ask",
|
|
285
|
+
return { action: "ask", message: ruleMessage("flagged", entry.bare.ask.ruleText, entry.bare.ask.source) };
|
|
285
286
|
}
|
|
286
287
|
const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
|
|
287
288
|
if (paramAsk) {
|
|
288
|
-
return { action: "ask",
|
|
289
|
+
return { action: "ask", message: ruleMessage("flagged", paramAsk.ruleText, paramAsk.source) };
|
|
289
290
|
}
|
|
290
291
|
if (entry.bare.allow) {
|
|
291
292
|
return { action: "allow" };
|
|
@@ -293,7 +294,7 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
293
294
|
}
|
|
294
295
|
if (defaultAction === "allow")
|
|
295
296
|
return { action: "allow" };
|
|
296
|
-
return { action: defaultAction,
|
|
297
|
+
return { action: defaultAction, message: `no permission rule matched "${req.toolName}" (default-${defaultAction} rule set)` };
|
|
297
298
|
},
|
|
298
299
|
};
|
|
299
300
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type ProtocolId = "mcp";
|
|
2
|
+
export interface ProtocolNamespace {
|
|
3
|
+
readonly id: ProtocolId;
|
|
4
|
+
readonly prefix: string;
|
|
5
|
+
makeName(peer: string, tool: string): string;
|
|
6
|
+
parse(name: string): {
|
|
7
|
+
peer: string;
|
|
8
|
+
tool: string;
|
|
9
|
+
} | undefined;
|
|
10
|
+
displayGroupKey(name: string): string;
|
|
11
|
+
}
|
|
12
|
+
export declare const MCP_NAMESPACE: ProtocolNamespace;
|
|
13
|
+
export declare const PROTOCOL_TABLE: readonly ProtocolNamespace[];
|
|
14
|
+
export declare function protocolOf(name: string): ProtocolNamespace | undefined;
|