@fungi.computer/truffle 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/private-local-exec-review.d.ts +3 -1
- package/dist/private-local-exec-review.js +29 -17
- package/dist/private-local-exec-rules.d.ts +5 -3
- package/dist/private-local-exec-rules.js +30 -9
- package/dist/private-local-runtime-host.d.ts +9 -0
- package/dist/private-local-runtime-host.js +44 -19
- package/dist/private-local-service-listener.js +1 -1
- package/dist/private-local-service-log.d.ts +10 -0
- package/dist/private-local-service-log.js +3 -0
- package/dist/private-local-service-owner.d.ts +1 -1
- package/dist/private-local-service-owner.js +3 -1
- package/dist/private-mcp-form-contract.d.ts +2 -0
- package/dist/private-mcp-form-contract.js +2 -0
- package/dist/private-mcp-form-session.d.ts +6 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rulePrefix } from "./private-local-exec-rules.js";
|
|
1
|
+
import { gitRuleCoverage, rulePrefix } from "./private-local-exec-rules.js";
|
|
2
2
|
const gitHookGrantSubcommands = new Set([
|
|
3
3
|
"add",
|
|
4
4
|
"branch",
|
|
@@ -17,14 +17,15 @@ const gitHookGrantSubcommands = new Set([
|
|
|
17
17
|
"tag",
|
|
18
18
|
]);
|
|
19
19
|
/** Build the host's review before the approval record is staged or displayed. */
|
|
20
|
-
export function localExecEscapeReview(request, cwd) {
|
|
20
|
+
export function localExecEscapeReview(request, cwd, gitGrant) {
|
|
21
21
|
if (request.escape === undefined)
|
|
22
22
|
throw new Error("An escape requires a justification");
|
|
23
23
|
const command = request.command ??
|
|
24
24
|
JSON.stringify([request.executable, ...(request.args ?? [])]);
|
|
25
|
-
const prefix = request.executable === undefined
|
|
25
|
+
const prefix = request.executable === undefined ||
|
|
26
|
+
(request.executable === "git" && gitGrant === undefined)
|
|
26
27
|
? undefined
|
|
27
|
-
: rulePrefix([request.executable, ...(request.args ?? [])]);
|
|
28
|
+
: rulePrefix([request.executable, ...(request.args ?? [])], gitGrant?.configuredRemotes);
|
|
28
29
|
const review = {
|
|
29
30
|
command,
|
|
30
31
|
cwd,
|
|
@@ -32,19 +33,30 @@ export function localExecEscapeReview(request, cwd) {
|
|
|
32
33
|
};
|
|
33
34
|
if (prefix !== undefined) {
|
|
34
35
|
review.prefix = prefix;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
(
|
|
39
|
-
? "Project hooks can run code as you under this grant. Only the hooksPath setting must pre-exist; this Agent can create missing hook files there. A configured fsmonitor path in the worktree may also be Agent-writable."
|
|
40
|
-
: "This command runs as you outside the sandbox.") +
|
|
41
|
-
(prefix[1] === "push"
|
|
42
|
-
? " This grant can push all branches, force-update them, or delete remote branches."
|
|
43
|
-
: "") +
|
|
44
|
-
(["push", "fetch", "pull"].includes(prefix[1] ?? "")
|
|
45
|
-
? " Remote URLs can invoke ssh with your HOME and keys."
|
|
46
|
-
: "");
|
|
47
|
-
}
|
|
36
|
+
review.alwaysAllowCoverage =
|
|
37
|
+
prefix[0] === "git"
|
|
38
|
+
? gitRuleCoverage(prefix[1] ?? "")
|
|
39
|
+
: `Direct argv commands beginning ${JSON.stringify(prefix)} in this project; shell commands still ask.`;
|
|
48
40
|
}
|
|
41
|
+
if (request.executable === "git") {
|
|
42
|
+
const hookCapable = gitHookGrantSubcommands.has(request.args?.[0] ?? "");
|
|
43
|
+
review.warning =
|
|
44
|
+
(hookCapable
|
|
45
|
+
? "Project hooks can run code as you under this grant. Only the hooksPath setting must pre-exist; this Agent can create missing hook files there. A configured fsmonitor path in the worktree may also be Agent-writable."
|
|
46
|
+
: "This command runs as you outside the sandbox.") +
|
|
47
|
+
(request.args?.[0] === "push"
|
|
48
|
+
? " This grant can push all branches, force-update them, or delete remote branches."
|
|
49
|
+
: "") +
|
|
50
|
+
(["push", "fetch", "pull"].includes(request.args?.[0] ?? "")
|
|
51
|
+
? " Remote URLs can invoke ssh with your HOME and keys."
|
|
52
|
+
: "");
|
|
53
|
+
}
|
|
54
|
+
if (prefix === undefined)
|
|
55
|
+
review.alwaysAllowUnavailableReason =
|
|
56
|
+
request.executable === undefined
|
|
57
|
+
? "Shell command text cannot match a saved argv rule; Approve once or Deny."
|
|
58
|
+
: request.executable === "git" && gitGrant === undefined
|
|
59
|
+
? "A protected project Git repository is unavailable; no reusable Git rule can match."
|
|
60
|
+
: "This command or option cannot match a safe reusable rule; Approve once or Deny.";
|
|
49
61
|
return review;
|
|
50
62
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/** Offer only direct
|
|
2
|
-
export declare function rulePrefix(argv: readonly string[]): string[] | undefined;
|
|
1
|
+
/** Offer only direct commands that this same matcher can authorize. */
|
|
2
|
+
export declare function rulePrefix(argv: readonly string[], configuredRemotes?: ReadonlySet<string>): string[] | undefined;
|
|
3
|
+
/** Text for the same Git option profile that gates matching, not a second list. */
|
|
4
|
+
export declare function gitRuleCoverage(subcommand: string): string;
|
|
3
5
|
/** One host-owned project file; every Agent in the project sees the same rules. */
|
|
4
6
|
export declare function localExecRules(project: string): {
|
|
5
7
|
list: () => string[][];
|
|
6
|
-
add: (prefix: readonly string[]) =>
|
|
8
|
+
add: (prefix: readonly string[]) => import("@fungi.computer/cairn").CairnId<"local-exec-rules", "rule", 1>;
|
|
7
9
|
remove: (prefix: readonly string[]) => void;
|
|
8
10
|
match: (argv: readonly string[], configuredRemotes?: ReadonlySet<string>) => {
|
|
9
11
|
ruleId: import("@fungi.computer/cairn").CairnId<"local-exec-rules", "rule", 1>;
|
|
@@ -62,7 +62,7 @@ const gitOptions = {
|
|
|
62
62
|
"-s",
|
|
63
63
|
"--signoff",
|
|
64
64
|
],
|
|
65
|
-
values: ["-m", "--message"],
|
|
65
|
+
values: ["-m", "--message", "-am"],
|
|
66
66
|
},
|
|
67
67
|
diff: {
|
|
68
68
|
flags: [
|
|
@@ -250,8 +250,8 @@ function validPrefix(prefix) {
|
|
|
250
250
|
prefix.length === 2 &&
|
|
251
251
|
gitBuiltins.has(prefix[1] ?? ""))));
|
|
252
252
|
}
|
|
253
|
-
/** Offer only direct
|
|
254
|
-
export function rulePrefix(argv) {
|
|
253
|
+
/** Offer only direct commands that this same matcher can authorize. */
|
|
254
|
+
export function rulePrefix(argv, configuredRemotes) {
|
|
255
255
|
if (argv.length === 0 ||
|
|
256
256
|
argv[0]?.startsWith(".") ||
|
|
257
257
|
argv[0]?.includes("/") ||
|
|
@@ -260,7 +260,9 @@ export function rulePrefix(argv) {
|
|
|
260
260
|
return undefined;
|
|
261
261
|
if (basename(argv[0] ?? "") === "git") {
|
|
262
262
|
const prefix = argv.slice(0, 2);
|
|
263
|
-
return validPrefix(prefix)
|
|
263
|
+
return validPrefix(prefix) && safeGitCommand(argv, configuredRemotes)
|
|
264
|
+
? prefix
|
|
265
|
+
: undefined;
|
|
264
266
|
}
|
|
265
267
|
if (basename(argv[0] ?? "") === "fungi" &&
|
|
266
268
|
argv[1] === "extension" &&
|
|
@@ -269,6 +271,21 @@ export function rulePrefix(argv) {
|
|
|
269
271
|
const parsed = prefixSchema.safeParse(argv.slice(0, 2));
|
|
270
272
|
return parsed.success && validPrefix(parsed.data) ? parsed.data : undefined;
|
|
271
273
|
}
|
|
274
|
+
/** Text for the same Git option profile that gates matching, not a second list. */
|
|
275
|
+
export function gitRuleCoverage(subcommand) {
|
|
276
|
+
const profile = gitProfiles.get(subcommand);
|
|
277
|
+
if (profile === undefined)
|
|
278
|
+
throw new Error("No reusable Git option profile");
|
|
279
|
+
const flags = profile.flags.filter((flag) => flag !== "--").join(", ");
|
|
280
|
+
const values = profile.values.map((option) => `${option} VALUE`).join(", ");
|
|
281
|
+
const remote = subcommand === "push" ||
|
|
282
|
+
subcommand === "fetch" ||
|
|
283
|
+
subcommand === "pull" ||
|
|
284
|
+
subcommand === "remote"
|
|
285
|
+
? " Remote operands must be configured names or permitted explicit URLs."
|
|
286
|
+
: "";
|
|
287
|
+
return `Direct argv git ${subcommand} for Workspace Agents in this protected project repository. Only these options are reusable: ${flags || "none"}${values ? `; ${values}` : ""}; -- and permitted operands.${remote} Shell commands and unlisted Git options still ask.`;
|
|
288
|
+
}
|
|
272
289
|
function safeRemote(remote, configuredRemotes) {
|
|
273
290
|
if (remote === undefined)
|
|
274
291
|
return true;
|
|
@@ -378,14 +395,18 @@ export function localExecRules(project) {
|
|
|
378
395
|
const parsed = prefixSchema.parse(prefix);
|
|
379
396
|
if (!validPrefix(parsed))
|
|
380
397
|
throw new Error("Unsafe Always-allow prefix");
|
|
398
|
+
let ruleId;
|
|
381
399
|
lockedUpdate(project, () => {
|
|
382
400
|
const existing = readProject(project).rules;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
401
|
+
ruleId = existing.find((rule) => JSON.stringify(rule.prefix) === JSON.stringify(parsed))?.id;
|
|
402
|
+
if (ruleId === undefined) {
|
|
403
|
+
ruleId = projectRuleIds.rule.generate();
|
|
404
|
+
writeProject(project, [...existing, { id: ruleId, prefix: parsed }]);
|
|
405
|
+
}
|
|
388
406
|
});
|
|
407
|
+
if (ruleId === undefined)
|
|
408
|
+
throw new Error("Exec rule was not persisted");
|
|
409
|
+
return ruleId;
|
|
389
410
|
};
|
|
390
411
|
const remove = (prefix) => {
|
|
391
412
|
const parsed = prefixSchema.parse(prefix);
|
|
@@ -48,6 +48,13 @@ export type PrivateLocalTools = Readonly<{
|
|
|
48
48
|
/** Background Computer processes, scoped per Session; the host closes them. */
|
|
49
49
|
processes: PrivateLocalComputerProcesses;
|
|
50
50
|
}>;
|
|
51
|
+
export type ExecApprovalDecision = {
|
|
52
|
+
agentId: string;
|
|
53
|
+
recordId: string;
|
|
54
|
+
decision: "approve" | "deny";
|
|
55
|
+
scope?: "once" | "always";
|
|
56
|
+
ruleId?: string;
|
|
57
|
+
};
|
|
51
58
|
/** Inputs for the one private local Agent opener. */
|
|
52
59
|
export type PrivateLocalRuntimeHostOptions = Readonly<{
|
|
53
60
|
/** Service-selected host configuration, independent of this Agent's Files root. */
|
|
@@ -63,6 +70,8 @@ export type PrivateLocalRuntimeHostOptions = Readonly<{
|
|
|
63
70
|
executable: string;
|
|
64
71
|
ruleId: string;
|
|
65
72
|
}>) => void;
|
|
73
|
+
/** The human's committed approval choice, without command text or secrets. */
|
|
74
|
+
onExecApprovalDecision?: (event: Readonly<ExecApprovalDecision>) => void;
|
|
66
75
|
/** Existing Agent database to reopen instead of creating a fresh local store. */
|
|
67
76
|
databasePath?: string | undefined;
|
|
68
77
|
/** Stable Local store identity for replaying one Agent create request. */
|
|
@@ -466,7 +466,7 @@ export function createPrivateLocalModelSource(models, providerId, modelId, provi
|
|
|
466
466
|
signal,
|
|
467
467
|
});
|
|
468
468
|
if (startup === null)
|
|
469
|
-
throw new ShiitakeError("model", "rejected", "No signed-in model provider is available.
|
|
469
|
+
throw new ShiitakeError("model", "rejected", "No signed-in model provider is available. Use your own Claude or ChatGPT subscription: /login in a Session, or fungi login --provider anthropic --auth-type oauth (Claude) or fungi login --provider openai --auth-type oauth (ChatGPT) in the CLI. For hosted Fungi: fungi login --provider fungi.");
|
|
470
470
|
const choice = startup.preference;
|
|
471
471
|
const selectedProvider = choice.providerId;
|
|
472
472
|
if (selectedProvider === "fungi") {
|
|
@@ -1125,14 +1125,29 @@ export async function openPrivateLocalRuntimeHost(input, suppliedModels) {
|
|
|
1125
1125
|
const grantRepository = lstatSync(join(root, ".git"), { throwIfNoEntry: false }) === undefined
|
|
1126
1126
|
? undefined
|
|
1127
1127
|
: gitDirectories(root, root);
|
|
1128
|
+
const gitGrantFor = (cwd, executionPath) => {
|
|
1129
|
+
// A nested/Agent-created repository has writable config and hooks.
|
|
1130
|
+
const protectedGit = grantRepository === undefined
|
|
1131
|
+
? undefined
|
|
1132
|
+
: gitDirectories(root, root, executionPath);
|
|
1133
|
+
const selectedGit = gitDirectories(root, cwd, executionPath);
|
|
1134
|
+
if (protectedGit?.common === undefined ||
|
|
1135
|
+
protectedGit.common !== grantRepository?.common ||
|
|
1136
|
+
protectedGit.worktree !== grantRepository.worktree ||
|
|
1137
|
+
selectedGit?.common !== protectedGit.common ||
|
|
1138
|
+
selectedGit.worktree !== protectedGit.worktree)
|
|
1139
|
+
return undefined;
|
|
1140
|
+
const configuredRemotes = protectedGitRemoteNames(root, protectedGit.common, executionPath);
|
|
1141
|
+
return configuredRemotes === undefined
|
|
1142
|
+
? undefined
|
|
1143
|
+
: { configuredRemotes };
|
|
1144
|
+
};
|
|
1128
1145
|
// The attached human's answer, never form JSON or the model, selects
|
|
1129
1146
|
// this short-lived intent. Commit the rule before dispatching the command.
|
|
1130
1147
|
const ruleIntents = new Map();
|
|
1131
1148
|
const selectRule = (recordId, prefix) => (selected) => {
|
|
1132
1149
|
if (selected === "prefix")
|
|
1133
|
-
ruleIntents.set(recordId, () =>
|
|
1134
|
-
execRules.add(prefix);
|
|
1135
|
-
});
|
|
1150
|
+
ruleIntents.set(recordId, () => execRules.add(prefix));
|
|
1136
1151
|
else
|
|
1137
1152
|
ruleIntents.delete(recordId);
|
|
1138
1153
|
};
|
|
@@ -1192,12 +1207,32 @@ export async function openPrivateLocalRuntimeHost(input, suppliedModels) {
|
|
|
1192
1207
|
decision: async (recordId) => {
|
|
1193
1208
|
const answered = escapeLedger.get(recordId);
|
|
1194
1209
|
if (answered?.state === "approved") {
|
|
1210
|
+
let ruleId;
|
|
1195
1211
|
if (answered.module === "computer" &&
|
|
1196
1212
|
answered.operation === "exec" &&
|
|
1197
1213
|
answered.authorityFence === escapeFence())
|
|
1198
|
-
ruleIntents.get(recordId)?.();
|
|
1214
|
+
ruleId = ruleIntents.get(recordId)?.();
|
|
1215
|
+
if (answered.module === "computer" && answered.operation === "exec") {
|
|
1216
|
+
const event = {
|
|
1217
|
+
agentId,
|
|
1218
|
+
recordId,
|
|
1219
|
+
decision: "approve",
|
|
1220
|
+
scope: ruleIntents.has(recordId) ? "always" : "once",
|
|
1221
|
+
};
|
|
1222
|
+
if (ruleId !== undefined)
|
|
1223
|
+
event.ruleId = ruleId;
|
|
1224
|
+
input.onExecApprovalDecision?.(event);
|
|
1225
|
+
}
|
|
1199
1226
|
await escapeLedger.apply(recordId, escapeApply);
|
|
1200
1227
|
}
|
|
1228
|
+
if (answered?.state === "denied" &&
|
|
1229
|
+
answered.module === "computer" &&
|
|
1230
|
+
answered.operation === "exec")
|
|
1231
|
+
input.onExecApprovalDecision?.({
|
|
1232
|
+
agentId,
|
|
1233
|
+
recordId,
|
|
1234
|
+
decision: "deny",
|
|
1235
|
+
});
|
|
1201
1236
|
for (const pending of escapeLedger.pendingHandoffs())
|
|
1202
1237
|
await deliverEscapeResult(pending);
|
|
1203
1238
|
},
|
|
@@ -1246,7 +1281,9 @@ export async function openPrivateLocalRuntimeHost(input, suppliedModels) {
|
|
|
1246
1281
|
recordId: staged.record.recordId,
|
|
1247
1282
|
description: staged.record.description,
|
|
1248
1283
|
};
|
|
1249
|
-
const review = localExecEscapeReview(request, cwd
|
|
1284
|
+
const review = localExecEscapeReview(request, cwd, "executable" in request && request.executable === "git"
|
|
1285
|
+
? gitGrantFor(cwd, executionPath)
|
|
1286
|
+
: undefined);
|
|
1250
1287
|
const presented = await localTools.presentApproval(sessionId, pending, escapeApprovals, review, review.prefix === undefined
|
|
1251
1288
|
? undefined
|
|
1252
1289
|
: selectRule(pending.recordId, review.prefix));
|
|
@@ -1259,19 +1296,7 @@ export async function openPrivateLocalRuntimeHost(input, suppliedModels) {
|
|
|
1259
1296
|
return undefined;
|
|
1260
1297
|
let configuredRemotes;
|
|
1261
1298
|
if (request.executable === "git") {
|
|
1262
|
-
|
|
1263
|
-
// Git grant. A nested/Agent-created repository has writable config.
|
|
1264
|
-
const protectedGit = grantRepository === undefined
|
|
1265
|
-
? undefined
|
|
1266
|
-
: gitDirectories(root, root, executionPath);
|
|
1267
|
-
const selectedGit = gitDirectories(root, cwd, executionPath);
|
|
1268
|
-
if (protectedGit?.common === undefined ||
|
|
1269
|
-
protectedGit.common !== grantRepository?.common ||
|
|
1270
|
-
protectedGit.worktree !== grantRepository.worktree ||
|
|
1271
|
-
selectedGit?.common !== protectedGit.common ||
|
|
1272
|
-
selectedGit.worktree !== protectedGit.worktree)
|
|
1273
|
-
return undefined;
|
|
1274
|
-
configuredRemotes = protectedGitRemoteNames(root, protectedGit.common, executionPath);
|
|
1299
|
+
configuredRemotes = gitGrantFor(cwd, executionPath)?.configuredRemotes;
|
|
1275
1300
|
if (configuredRemotes === undefined)
|
|
1276
1301
|
return undefined;
|
|
1277
1302
|
}
|
|
@@ -97,7 +97,7 @@ readRecoveryManifest, root = capturePrivateLocalConfigRoot()) {
|
|
|
97
97
|
if (native === undefined)
|
|
98
98
|
throw new Error("Local service native staging is not ready");
|
|
99
99
|
return native;
|
|
100
|
-
});
|
|
100
|
+
}, diagnostics.approvalDecision);
|
|
101
101
|
const prefix = `/service/${serviceId}`;
|
|
102
102
|
const releaseAccountOwner = registerFungiAccountOwner(root.credentialPath, () => owners.refreshMesh());
|
|
103
103
|
const app = new Hono();
|
|
@@ -55,6 +55,13 @@ export type ServiceLogEvent = Readonly<{
|
|
|
55
55
|
agentId: string;
|
|
56
56
|
executable: string;
|
|
57
57
|
ruleId: string;
|
|
58
|
+
}> | Readonly<{
|
|
59
|
+
event: "exec-approval-decision";
|
|
60
|
+
agentId: string;
|
|
61
|
+
recordId: string;
|
|
62
|
+
decision: "approve" | "deny";
|
|
63
|
+
scope?: "once" | "always";
|
|
64
|
+
ruleId?: string;
|
|
58
65
|
}> | Readonly<{
|
|
59
66
|
event: "memory";
|
|
60
67
|
rss: number;
|
|
@@ -88,6 +95,9 @@ export type ServiceLogLifecycle = Readonly<{
|
|
|
88
95
|
ruleMatch: (match: Omit<Extract<ServiceLogEvent, {
|
|
89
96
|
event: "exec-rule-match";
|
|
90
97
|
}>, "event">) => void;
|
|
98
|
+
approvalDecision: (decision: Omit<Extract<ServiceLogEvent, {
|
|
99
|
+
event: "exec-approval-decision";
|
|
100
|
+
}>, "event">) => void;
|
|
91
101
|
recoveryDisabled: (code: RecoveryPersistenceFailureCode) => void;
|
|
92
102
|
/** Synchronous, so it lands before the bounded drain exits the process. */
|
|
93
103
|
drainTimeout: (drain: Readonly<{
|
|
@@ -290,6 +290,9 @@ export function createServiceLogLifecycle(input) {
|
|
|
290
290
|
ruleMatch(match) {
|
|
291
291
|
emit({ event: "exec-rule-match", ...match });
|
|
292
292
|
},
|
|
293
|
+
approvalDecision(decision) {
|
|
294
|
+
emit({ event: "exec-approval-decision", ...decision });
|
|
295
|
+
},
|
|
293
296
|
recoveryDisabled(code) {
|
|
294
297
|
if (closed || recovery !== undefined)
|
|
295
298
|
return;
|
|
@@ -38,7 +38,7 @@ readRecoveryManifest?: (path: string) => Promise<string>,
|
|
|
38
38
|
/** Report only the classification, never the manifest or exception text. */
|
|
39
39
|
onRecoveryDisabled?: ServiceLogLifecycle["recoveryDisabled"], configRoot?: PrivateLocalConfigRoot, meshLinksFactory?: typeof createLocalMeshLinks, onExecRuleMatch?: NonNullable<Parameters<typeof openPrivateLocalRuntimeHost>[0]["onExecRuleMatch"]>,
|
|
40
40
|
/** Supplied by the listener after startup; individual Agents never close it. */
|
|
41
|
-
serviceNative?: () => NonNullable<Parameters<typeof openPrivateLocalRuntimeHost>[0]["native"]>): {
|
|
41
|
+
serviceNative?: () => NonNullable<Parameters<typeof openPrivateLocalRuntimeHost>[0]["native"]>, onExecApprovalDecision?: NonNullable<Parameters<typeof openPrivateLocalRuntimeHost>[0]["onExecApprovalDecision"]>): {
|
|
42
42
|
refreshMesh: () => Promise<void>;
|
|
43
43
|
createSession: (creation: PrivateLocalSessionCreation) => Promise<{
|
|
44
44
|
agentId: string;
|
|
@@ -71,7 +71,7 @@ readRecoveryManifest = (path) => readFile(path, "utf8"),
|
|
|
71
71
|
/** Report only the classification, never the manifest or exception text. */
|
|
72
72
|
onRecoveryDisabled = () => undefined, configRoot = capturePrivateLocalConfigRoot(), meshLinksFactory = createLocalMeshLinks, onExecRuleMatch = () => undefined,
|
|
73
73
|
/** Supplied by the listener after startup; individual Agents never close it. */
|
|
74
|
-
serviceNative) {
|
|
74
|
+
serviceNative, onExecApprovalDecision = () => undefined) {
|
|
75
75
|
const extensions = suppliedExtensions ?? localExtensions({ home: configRoot.home });
|
|
76
76
|
const recoveryPath = recoveryDirectory === undefined
|
|
77
77
|
? undefined
|
|
@@ -734,6 +734,7 @@ serviceNative) {
|
|
|
734
734
|
mail: projectControl ? mailRouting(invocationRoot) : undefined,
|
|
735
735
|
meshAdmission,
|
|
736
736
|
onExecRuleMatch,
|
|
737
|
+
onExecApprovalDecision,
|
|
737
738
|
};
|
|
738
739
|
if (serviceNative !== undefined)
|
|
739
740
|
options = { ...options, native: serviceNative() };
|
|
@@ -1049,6 +1050,7 @@ serviceNative) {
|
|
|
1049
1050
|
agentControl: directory(invocationRoot),
|
|
1050
1051
|
mail: mailRouting(invocationRoot),
|
|
1051
1052
|
onExecRuleMatch,
|
|
1053
|
+
onExecApprovalDecision,
|
|
1052
1054
|
};
|
|
1053
1055
|
if (serviceNative !== undefined)
|
|
1054
1056
|
options = { ...options, native: serviceNative() };
|
|
@@ -185,6 +185,8 @@ export declare const mcpFormApprovalContract: {
|
|
|
185
185
|
readonly justification: Schema.String;
|
|
186
186
|
readonly prefix: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
187
187
|
readonly warning: Schema.optionalKey<Schema.String>;
|
|
188
|
+
readonly alwaysAllowCoverage: Schema.optionalKey<Schema.String>;
|
|
189
|
+
readonly alwaysAllowUnavailableReason: Schema.optionalKey<Schema.String>;
|
|
188
190
|
}>>;
|
|
189
191
|
}>>;
|
|
190
192
|
}>;
|
|
@@ -38,6 +38,8 @@ const McpFormPendingSchema = Schema.Struct({
|
|
|
38
38
|
justification: Schema.String,
|
|
39
39
|
prefix: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
40
40
|
warning: Schema.optionalKey(Schema.String),
|
|
41
|
+
alwaysAllowCoverage: Schema.optionalKey(Schema.String),
|
|
42
|
+
alwaysAllowUnavailableReason: Schema.optionalKey(Schema.String),
|
|
41
43
|
})),
|
|
42
44
|
});
|
|
43
45
|
/** The existing Session extension slot carries only display-safe form data. */
|
|
@@ -7,6 +7,8 @@ export type LocalApprovalReview = {
|
|
|
7
7
|
justification: string;
|
|
8
8
|
prefix?: readonly string[];
|
|
9
9
|
warning?: string;
|
|
10
|
+
alwaysAllowCoverage?: string;
|
|
11
|
+
alwaysAllowUnavailableReason?: string;
|
|
10
12
|
};
|
|
11
13
|
type Pending = {
|
|
12
14
|
sessionId: string;
|
|
@@ -129,6 +131,8 @@ export declare function createMcpFormSession(): {
|
|
|
129
131
|
readonly justification: string;
|
|
130
132
|
readonly prefix?: readonly string[];
|
|
131
133
|
readonly warning?: string;
|
|
134
|
+
readonly alwaysAllowCoverage?: string;
|
|
135
|
+
readonly alwaysAllowUnavailableReason?: string;
|
|
132
136
|
};
|
|
133
137
|
}[];
|
|
134
138
|
}, {
|
|
@@ -181,6 +185,8 @@ export declare function createMcpFormSession(): {
|
|
|
181
185
|
readonly justification: string;
|
|
182
186
|
readonly prefix?: readonly string[];
|
|
183
187
|
readonly warning?: string;
|
|
188
|
+
readonly alwaysAllowCoverage?: string;
|
|
189
|
+
readonly alwaysAllowUnavailableReason?: string;
|
|
184
190
|
};
|
|
185
191
|
}[];
|
|
186
192
|
}>;
|