@ordewell/core 0.8.1 → 0.9.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/dist/{IFileSystem-D96mT-bm.d.ts → IFileSystem-DpaaFdit.d.ts} +1 -1
- package/dist/{IFileSystem-DXJCRw5x.d.mts → IFileSystem-_m7HdoxJ.d.mts} +1 -1
- package/dist/{IRunner-CGCihlGQ.d.ts → IRunner-BiQio8gJ.d.ts} +161 -27
- package/dist/{IRunner-XVUddYVX.d.mts → IRunner-CxiymkP_.d.mts} +161 -27
- package/dist/{chunk-BLLIBCCS.mjs → chunk-QUARDKVT.mjs} +6 -3
- package/dist/chunk-QUARDKVT.mjs.map +1 -0
- package/dist/index.d.mts +32 -8
- package/dist/index.d.ts +32 -8
- package/dist/index.js +724 -111
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +711 -108
- package/dist/index.mjs.map +1 -1
- package/dist/{plan-utils-Cv48fe7r.d.ts → plan-utils-6rUr7ZOx.d.ts} +1 -1
- package/dist/{plan-utils-BMayhGBj.d.mts → plan-utils-P3PIEwNa.d.mts} +1 -1
- package/dist/plan-utils.d.mts +2 -2
- package/dist/plan-utils.d.ts +2 -2
- package/dist/plan-utils.js +5 -2
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +1 -1
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +2 -0
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BLLIBCCS.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ __export(index_exports, {
|
|
|
57
57
|
DAEMON_SUBPROTOCOL: () => DAEMON_SUBPROTOCOL,
|
|
58
58
|
DAEMON_TOKEN_SUBPROTOCOL_PREFIX: () => DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
|
|
59
59
|
DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
|
|
60
|
+
DEFAULT_PLANNER_ALLOWLIST: () => DEFAULT_PLANNER_ALLOWLIST,
|
|
60
61
|
DEFAULT_RUNNERS: () => DEFAULT_RUNNERS,
|
|
61
62
|
DENY_ALL: () => DENY_ALL,
|
|
62
63
|
EMPTY_CONVERSATION: () => EMPTY_CONVERSATION,
|
|
@@ -193,6 +194,7 @@ __export(index_exports, {
|
|
|
193
194
|
coerceAssignments: () => coerceAssignments,
|
|
194
195
|
collectDirectDependencyOutputs: () => collectDirectDependencyOutputs,
|
|
195
196
|
collectProviderCredentials: () => collectProviderCredentials,
|
|
197
|
+
commandAllowedBy: () => commandAllowedBy,
|
|
196
198
|
composeAugmentedPrompt: () => composeAugmentedPrompt,
|
|
197
199
|
composeContinuationPrompt: () => composeContinuationPrompt,
|
|
198
200
|
configuredProviders: () => configuredProviders,
|
|
@@ -219,6 +221,7 @@ __export(index_exports, {
|
|
|
219
221
|
discoverGeminiModels: () => discoverGeminiModels,
|
|
220
222
|
drainNext: () => drainNext,
|
|
221
223
|
effectiveAllowlist: () => effectiveAllowlist,
|
|
224
|
+
effectiveApprovalMode: () => effectiveApprovalMode,
|
|
222
225
|
emptyWarnings: () => emptyWarnings,
|
|
223
226
|
enabledRunners: () => enabledRunners,
|
|
224
227
|
ensureDir: () => ensureDir,
|
|
@@ -301,6 +304,8 @@ __export(index_exports, {
|
|
|
301
304
|
outputLines: () => outputLines,
|
|
302
305
|
outputPreview: () => outputPreview,
|
|
303
306
|
ownerOnlyConfigFile: () => ownerOnlyConfigFile,
|
|
307
|
+
parseAllowlist: () => parseAllowlist,
|
|
308
|
+
parseApprovalModeSetting: () => parseApprovalModeSetting,
|
|
304
309
|
parseAutonomyLevel: () => parseAutonomyLevel,
|
|
305
310
|
parseMaxParallel: () => parseMaxParallel,
|
|
306
311
|
parsePartialPlan: () => parsePartialPlan,
|
|
@@ -358,6 +363,7 @@ __export(index_exports, {
|
|
|
358
363
|
savePrdMarkdown: () => savePrdMarkdown,
|
|
359
364
|
saveSession: () => saveSession,
|
|
360
365
|
saveState: () => saveState,
|
|
366
|
+
scopeAllowed: () => scopeAllowed,
|
|
361
367
|
serializePlan: () => serializePlan,
|
|
362
368
|
serializeTask: () => serializeTask,
|
|
363
369
|
serializeTaskStatus: () => serializeTaskStatus,
|
|
@@ -395,6 +401,7 @@ __export(index_exports, {
|
|
|
395
401
|
toTaskLogEvent: () => toTaskLogEvent,
|
|
396
402
|
tokenSubprotocols: () => tokenSubprotocols,
|
|
397
403
|
tokensMatch: () => tokensMatch,
|
|
404
|
+
toolAllowed: () => toolAllowed,
|
|
398
405
|
toolHeadline: () => toolHeadline,
|
|
399
406
|
trimToolOutput: () => trimToolOutput,
|
|
400
407
|
truncateCheckpointSummary: () => truncateCheckpointSummary,
|
|
@@ -1149,7 +1156,7 @@ function ansiCQuoteEnd(s, start) {
|
|
|
1149
1156
|
function backtickBody(body, dialect) {
|
|
1150
1157
|
return dialect.escape === "\\" ? body.replace(/\\([$`\\])/g, "$1") : body;
|
|
1151
1158
|
}
|
|
1152
|
-
function skipHereDocuments(s, start, docs,
|
|
1159
|
+
function skipHereDocuments(s, start, docs, nested2, dialect) {
|
|
1153
1160
|
let pos = start;
|
|
1154
1161
|
for (const doc of docs) {
|
|
1155
1162
|
const bodyStart = pos;
|
|
@@ -1176,19 +1183,19 @@ function skipHereDocuments(s, start, docs, nested, dialect) {
|
|
|
1176
1183
|
if (body[i] === "$" && body[i + 1] === "(") {
|
|
1177
1184
|
const close = matchParen(body, i + 1, dialect);
|
|
1178
1185
|
if (close < 0) return close;
|
|
1179
|
-
|
|
1186
|
+
nested2.push(body.slice(i + 2, close));
|
|
1180
1187
|
i = close;
|
|
1181
1188
|
} else if (body[i] === "`") {
|
|
1182
1189
|
const close = matchBacktick(body, i, dialect);
|
|
1183
1190
|
if (close < 0) return -1;
|
|
1184
|
-
|
|
1191
|
+
nested2.push(backtickBody(body.slice(i + 1, close), dialect));
|
|
1185
1192
|
i = close;
|
|
1186
1193
|
}
|
|
1187
1194
|
}
|
|
1188
1195
|
}
|
|
1189
1196
|
return pos;
|
|
1190
1197
|
}
|
|
1191
|
-
function lex(command,
|
|
1198
|
+
function lex(command, nested2, dialect) {
|
|
1192
1199
|
const segments = [];
|
|
1193
1200
|
let unsafeRedirect;
|
|
1194
1201
|
let processSubstitution = false;
|
|
@@ -1202,6 +1209,7 @@ function lex(command, nested, dialect) {
|
|
|
1202
1209
|
let bareSegment = false;
|
|
1203
1210
|
let tokens = [];
|
|
1204
1211
|
let expandable = false;
|
|
1212
|
+
let globbed = false;
|
|
1205
1213
|
let current = "";
|
|
1206
1214
|
let started = false;
|
|
1207
1215
|
let piped = false;
|
|
@@ -1265,7 +1273,7 @@ function lex(command, nested, dialect) {
|
|
|
1265
1273
|
followable = false;
|
|
1266
1274
|
bareSegment = true;
|
|
1267
1275
|
}
|
|
1268
|
-
segments.push({ ...seg, expandable, stdinRedirected, inputs, joinedBy });
|
|
1276
|
+
segments.push({ ...seg, expandable, globbed, stdinRedirected, inputs, joinedBy });
|
|
1269
1277
|
} else if (!last || segments.length > 0 || joinedBy !== void 0) {
|
|
1270
1278
|
followable = false;
|
|
1271
1279
|
bareSegment = true;
|
|
@@ -1273,6 +1281,7 @@ function lex(command, nested, dialect) {
|
|
|
1273
1281
|
tokens = [];
|
|
1274
1282
|
inputs = [];
|
|
1275
1283
|
expandable = false;
|
|
1284
|
+
globbed = false;
|
|
1276
1285
|
stdinRedirected = false;
|
|
1277
1286
|
piped = nextPiped;
|
|
1278
1287
|
joinedBy = next;
|
|
@@ -1311,7 +1320,7 @@ function lex(command, nested, dialect) {
|
|
|
1311
1320
|
unbalanced = true;
|
|
1312
1321
|
break;
|
|
1313
1322
|
}
|
|
1314
|
-
|
|
1323
|
+
nested2.push(command.slice(i + 2, close));
|
|
1315
1324
|
current += command.slice(i, close + 1);
|
|
1316
1325
|
started = true;
|
|
1317
1326
|
expandable = true;
|
|
@@ -1324,7 +1333,7 @@ function lex(command, nested, dialect) {
|
|
|
1324
1333
|
unbalanced = true;
|
|
1325
1334
|
break;
|
|
1326
1335
|
}
|
|
1327
|
-
|
|
1336
|
+
nested2.push(backtickBody(command.slice(i + 1, close), dialect));
|
|
1328
1337
|
current += command.slice(i, close + 1);
|
|
1329
1338
|
started = true;
|
|
1330
1339
|
expandable = true;
|
|
@@ -1455,7 +1464,7 @@ function lex(command, nested, dialect) {
|
|
|
1455
1464
|
endSegment(false, c === "&" && command[i + 1] === "&" ? "and" : "other");
|
|
1456
1465
|
if (c === "\n") inComment = false;
|
|
1457
1466
|
if (c === "\n" && hereDocs.length > 0) {
|
|
1458
|
-
const end = skipHereDocuments(command, i + 1, hereDocs.splice(0),
|
|
1467
|
+
const end = skipHereDocuments(command, i + 1, hereDocs.splice(0), nested2, dialect);
|
|
1459
1468
|
if (end === UNREADABLE) {
|
|
1460
1469
|
unreadable = true;
|
|
1461
1470
|
break;
|
|
@@ -1483,6 +1492,7 @@ function lex(command, nested, dialect) {
|
|
|
1483
1492
|
else if (c === "{") braceOpen = true;
|
|
1484
1493
|
else if (braceOpen && (c === "," || c === "." && command[i + 1] === ".")) braceList = true;
|
|
1485
1494
|
else if (braceOpen && braceList && c === "}") expandable = true;
|
|
1495
|
+
else if (c === "*" || c === "?" || c === "[") globbed = true;
|
|
1486
1496
|
current += c;
|
|
1487
1497
|
started = true;
|
|
1488
1498
|
i++;
|
|
@@ -1518,8 +1528,10 @@ function toSegment(tokens, piped, dialect) {
|
|
|
1518
1528
|
piped,
|
|
1519
1529
|
stdinRedirected: false,
|
|
1520
1530
|
expandable: false,
|
|
1531
|
+
globbed: false,
|
|
1521
1532
|
inputs: [],
|
|
1522
1533
|
...first !== void 0 && isComputedWord(first, dialect) ? { computedBinary: first } : {},
|
|
1534
|
+
...first !== void 0 && /[/\\]/.test(first) ? { binaryPath: first } : {},
|
|
1523
1535
|
...ambiguousCmdName !== void 0 ? { ambiguousCmdName } : {}
|
|
1524
1536
|
};
|
|
1525
1537
|
}
|
|
@@ -1808,7 +1820,7 @@ function unwrapXargs(seg, dialect) {
|
|
|
1808
1820
|
if (replace && tokens[0].includes(replace)) {
|
|
1809
1821
|
return { reason: `"xargs" substitutes its input into "${tokens[0]}", so the input decides which program runs. Name the program directly.` };
|
|
1810
1822
|
}
|
|
1811
|
-
return { seg: { ...toSegment(tokens, false, dialect), expandable: seg.expandable } };
|
|
1823
|
+
return { seg: { ...toSegment(tokens, false, dialect), expandable: seg.expandable, globbed: seg.globbed } };
|
|
1812
1824
|
}
|
|
1813
1825
|
function unwrap(seg, dialect) {
|
|
1814
1826
|
const wrappers = [];
|
|
@@ -1839,7 +1851,7 @@ function unwrap(seg, dialect) {
|
|
|
1839
1851
|
wrappers.push(current.binary);
|
|
1840
1852
|
const inner = toSegment(scan.tokens, current.piped, dialect);
|
|
1841
1853
|
assignments.push(...inner.assignments);
|
|
1842
|
-
current = { ...inner, assignments: [], expandable: current.expandable, stdinRedirected: current.stdinRedirected };
|
|
1854
|
+
current = { ...inner, assignments: [], expandable: current.expandable, globbed: current.globbed, stdinRedirected: current.stdinRedirected };
|
|
1843
1855
|
}
|
|
1844
1856
|
return { seg: { ...current, assignments }, wrappers, runner };
|
|
1845
1857
|
}
|
|
@@ -3690,6 +3702,340 @@ function awkProgramHazard(program) {
|
|
|
3690
3702
|
return void 0;
|
|
3691
3703
|
}
|
|
3692
3704
|
|
|
3705
|
+
// src/services/plannerAllowlist.ts
|
|
3706
|
+
var MUTATING_VERB = /^(abort|ack|activate|add|apply|approve|archive|assign|attach|bind|call|cancel|clear|clone|close|commit|copy|cp|create|deactivate|delete|deploy|destroy|detach|disable|download|drain|drop|dump|edit|enable|evict|exec|execute|expire|export|failover|flush|grant|import|insert|install|invoke|kill|label|lock|login|logout|merge|migrate|modify|move|mv|patch|pause|promote|publish|pull|purge|push|put|reboot|redeploy|register|reject|release|remove|rename|reopen|replace|request|reset|resize|restart|restore|resume|revoke|rm|rollback|rotate|run|scale|scp|send|set|ssh|start|stop|submit|suspend|sync|rsync|tag|taint|terminate|transfer|truncate|undelete|uninstall|unlock|unregister|unset|untag|update|upgrade|upload|write)([-_:.].*)?$/i;
|
|
3707
|
+
function globRegExp(glob, flags = "") {
|
|
3708
|
+
const escaped = glob.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join(".*");
|
|
3709
|
+
return new RegExp(`^${escaped}$`, flags);
|
|
3710
|
+
}
|
|
3711
|
+
function ruleWord(pattern) {
|
|
3712
|
+
if (!pattern.includes("*")) return { matches: (word) => word === pattern, wildcard: false };
|
|
3713
|
+
const re = globRegExp(pattern);
|
|
3714
|
+
return { matches: (word) => re.test(word), wildcard: true };
|
|
3715
|
+
}
|
|
3716
|
+
function exclusionWord(pattern) {
|
|
3717
|
+
const re = /^-[A-Za-z]$/.test(pattern) ? new RegExp(`^-[A-Za-z]*${pattern[1]}`) : globRegExp(pattern, "i");
|
|
3718
|
+
return { matches: (word) => re.test(word), wildcard: pattern.includes("*") };
|
|
3719
|
+
}
|
|
3720
|
+
function isScopePattern(entry) {
|
|
3721
|
+
return /^(\/|~|https?:\/\/|mcp__|[A-Za-z]:[\\/])/.test(entry);
|
|
3722
|
+
}
|
|
3723
|
+
function parseAllowlist(entries) {
|
|
3724
|
+
const list = { commands: [], commandExclusions: [], tools: [], toolExclusions: [], scopes: [], scopeExclusions: [] };
|
|
3725
|
+
for (const raw of entries) {
|
|
3726
|
+
const exclude = raw.trim().startsWith("!");
|
|
3727
|
+
const entry = raw.trim().replace(/^!\s*/, "");
|
|
3728
|
+
if (!entry) continue;
|
|
3729
|
+
if (entry.startsWith("mcp:")) {
|
|
3730
|
+
(exclude ? list.toolExclusions : list.tools).push(globRegExp(entry.slice(4).trim(), "i"));
|
|
3731
|
+
} else if (isScopePattern(entry)) {
|
|
3732
|
+
(exclude ? list.scopeExclusions : list.scopes).push(globRegExp(entry));
|
|
3733
|
+
} else {
|
|
3734
|
+
const [binary, ...words] = entry.split(/\s+/);
|
|
3735
|
+
(exclude ? list.commandExclusions : list.commands).push({ text: raw.trim(), binary, words: words.map(exclude ? exclusionWord : ruleWord) });
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
return list;
|
|
3739
|
+
}
|
|
3740
|
+
function leadingWords(args) {
|
|
3741
|
+
const lead = [];
|
|
3742
|
+
for (const arg of args) {
|
|
3743
|
+
if (arg.startsWith("-")) break;
|
|
3744
|
+
lead.push(arg);
|
|
3745
|
+
}
|
|
3746
|
+
return lead;
|
|
3747
|
+
}
|
|
3748
|
+
function ruleCovers(rule, command) {
|
|
3749
|
+
if (rule.binary !== command.binary) return false;
|
|
3750
|
+
const lead = leadingWords(command.args);
|
|
3751
|
+
if (lead.length < rule.words.length || rule.words.length < (command.scopeWords ?? 0)) return false;
|
|
3752
|
+
return rule.words.every((word, i) => word.matches(lead[i]) && !(word.wildcard && MUTATING_VERB.test(lead[i])));
|
|
3753
|
+
}
|
|
3754
|
+
function exclusionCovers(rule, command) {
|
|
3755
|
+
if (rule.binary !== "*" && rule.binary !== command.binary) return false;
|
|
3756
|
+
let next = 0;
|
|
3757
|
+
for (const arg of command.args) {
|
|
3758
|
+
if (next < rule.words.length && rule.words[next].matches(arg)) next += 1;
|
|
3759
|
+
}
|
|
3760
|
+
return next === rule.words.length;
|
|
3761
|
+
}
|
|
3762
|
+
function commandAllowedBy(list, command) {
|
|
3763
|
+
if (list.commandExclusions.some((rule) => exclusionCovers(rule, command))) return void 0;
|
|
3764
|
+
return list.commands.find((rule) => ruleCovers(rule, command))?.text;
|
|
3765
|
+
}
|
|
3766
|
+
function toolAllowed(list, tool2) {
|
|
3767
|
+
if (list.toolExclusions.some((re) => re.test(tool2))) return false;
|
|
3768
|
+
return list.tools.some((re) => re.test(tool2));
|
|
3769
|
+
}
|
|
3770
|
+
function scopeAllowed(list, scope) {
|
|
3771
|
+
if (list.scopeExclusions.some((re) => re.test(scope))) return false;
|
|
3772
|
+
return list.scopes.some((re) => re.test(scope)) || list.commands.some((rule) => rule.text === scope);
|
|
3773
|
+
}
|
|
3774
|
+
function withArgument(binary, verbs) {
|
|
3775
|
+
return verbs.flatMap((verb) => [`${binary} ${verb}`, `${binary} ${verb} *`]);
|
|
3776
|
+
}
|
|
3777
|
+
function nested(binary, verbs, depth) {
|
|
3778
|
+
return verbs.flatMap((verb) => Array.from({ length: depth }, (_, i) => [binary, ...Array(i).fill("*"), verb].join(" ")));
|
|
3779
|
+
}
|
|
3780
|
+
var KUBE_VERB_READS = ["get", "describe", "logs", "top", "explain", "events"];
|
|
3781
|
+
var KUBE_READS = [
|
|
3782
|
+
"api-resources",
|
|
3783
|
+
"api-versions",
|
|
3784
|
+
"version",
|
|
3785
|
+
"cluster-info",
|
|
3786
|
+
"auth can-i",
|
|
3787
|
+
"auth whoami",
|
|
3788
|
+
"config get-contexts",
|
|
3789
|
+
"config current-context",
|
|
3790
|
+
"config get-clusters",
|
|
3791
|
+
"rollout status",
|
|
3792
|
+
"rollout history"
|
|
3793
|
+
];
|
|
3794
|
+
var CONTAINER_VERB_READS = ["ps", "images", "inspect", "logs", "history", "top", "port"];
|
|
3795
|
+
var CONTAINER_READS = [
|
|
3796
|
+
"version",
|
|
3797
|
+
"info",
|
|
3798
|
+
"image ls",
|
|
3799
|
+
"image list",
|
|
3800
|
+
"image inspect",
|
|
3801
|
+
"image history",
|
|
3802
|
+
"container ls",
|
|
3803
|
+
"container list",
|
|
3804
|
+
"container inspect",
|
|
3805
|
+
"container logs",
|
|
3806
|
+
"network ls",
|
|
3807
|
+
"network inspect",
|
|
3808
|
+
"volume ls",
|
|
3809
|
+
"volume inspect",
|
|
3810
|
+
"context ls",
|
|
3811
|
+
"context list"
|
|
3812
|
+
];
|
|
3813
|
+
var DEFAULT_PLANNER_ALLOWLIST = [
|
|
3814
|
+
// GitHub, GitLab, Jira
|
|
3815
|
+
...[
|
|
3816
|
+
"issue list",
|
|
3817
|
+
"issue view",
|
|
3818
|
+
"issue status",
|
|
3819
|
+
"pr list",
|
|
3820
|
+
"pr view",
|
|
3821
|
+
"pr diff",
|
|
3822
|
+
"pr checks",
|
|
3823
|
+
"pr status",
|
|
3824
|
+
"run list",
|
|
3825
|
+
"run view",
|
|
3826
|
+
"workflow list",
|
|
3827
|
+
"workflow view",
|
|
3828
|
+
"repo view",
|
|
3829
|
+
"repo list",
|
|
3830
|
+
"search",
|
|
3831
|
+
"label list",
|
|
3832
|
+
"search *",
|
|
3833
|
+
"status",
|
|
3834
|
+
"cache list",
|
|
3835
|
+
"project list",
|
|
3836
|
+
"project view",
|
|
3837
|
+
"project item-list",
|
|
3838
|
+
"gist list",
|
|
3839
|
+
"gist view",
|
|
3840
|
+
"variable list",
|
|
3841
|
+
"ruleset list",
|
|
3842
|
+
"ruleset view",
|
|
3843
|
+
"org list"
|
|
3844
|
+
].map((c) => `gh ${c}`),
|
|
3845
|
+
...[
|
|
3846
|
+
"issue list",
|
|
3847
|
+
"issue view",
|
|
3848
|
+
"mr list",
|
|
3849
|
+
"mr view",
|
|
3850
|
+
"mr diff",
|
|
3851
|
+
"ci list",
|
|
3852
|
+
"ci status",
|
|
3853
|
+
"repo view",
|
|
3854
|
+
"release list",
|
|
3855
|
+
"release view"
|
|
3856
|
+
].map((c) => `glab ${c}`),
|
|
3857
|
+
...["issue list", "issue view", "sprint list", "epic list", "project list"].map((c) => `jira ${c}`),
|
|
3858
|
+
"!gh -w",
|
|
3859
|
+
"!glab -w",
|
|
3860
|
+
// Azure: `az` takes no positional arguments, so a group path is always groups then verb.
|
|
3861
|
+
...nested("az", ["list", "show"], 5),
|
|
3862
|
+
"az version",
|
|
3863
|
+
"az lock list",
|
|
3864
|
+
"az tag list",
|
|
3865
|
+
"az monitor log-analytics query",
|
|
3866
|
+
"az monitor app-insights query",
|
|
3867
|
+
"!az *keys*",
|
|
3868
|
+
"!az *secret*",
|
|
3869
|
+
"!az *credential*",
|
|
3870
|
+
"!az *password*",
|
|
3871
|
+
"!az *connection-string*",
|
|
3872
|
+
"!az *appsettings*",
|
|
3873
|
+
"!az *token*",
|
|
3874
|
+
// Google Cloud. `run` is spelled out: a wildcard never stands for it.
|
|
3875
|
+
...nested("gcloud", ["list", "describe", "get-iam-policy"], 5),
|
|
3876
|
+
"gcloud run * list",
|
|
3877
|
+
"gcloud run * * list",
|
|
3878
|
+
"gcloud run * describe",
|
|
3879
|
+
"gcloud run * * describe",
|
|
3880
|
+
"gcloud config list",
|
|
3881
|
+
"gcloud config get",
|
|
3882
|
+
"gcloud config get-value",
|
|
3883
|
+
"gcloud auth list",
|
|
3884
|
+
"gcloud logging read",
|
|
3885
|
+
"gcloud info",
|
|
3886
|
+
"gcloud version",
|
|
3887
|
+
...["ls", "du"].map((c) => `gsutil ${c}`),
|
|
3888
|
+
...["ls", "show", "head"].map((c) => `bq ${c}`),
|
|
3889
|
+
"!gcloud *secret*",
|
|
3890
|
+
"!gcloud *token*",
|
|
3891
|
+
// AWS: always `aws <service> <operation>`.
|
|
3892
|
+
"aws * describe-*",
|
|
3893
|
+
"aws * list-*",
|
|
3894
|
+
"aws s3 ls",
|
|
3895
|
+
"aws sts get-caller-identity",
|
|
3896
|
+
"aws configure list",
|
|
3897
|
+
"aws logs filter-log-events",
|
|
3898
|
+
"aws logs get-log-events",
|
|
3899
|
+
"aws cloudwatch get-metric-data",
|
|
3900
|
+
"aws cloudwatch get-metric-statistics",
|
|
3901
|
+
"aws ce get-cost-and-usage",
|
|
3902
|
+
"aws iam get-user",
|
|
3903
|
+
"aws iam get-role",
|
|
3904
|
+
"!aws secretsmanager",
|
|
3905
|
+
"!aws *secret*",
|
|
3906
|
+
// Other clouds and platforms
|
|
3907
|
+
...nested("doctl", ["list", "get"], 3),
|
|
3908
|
+
"doctl account get",
|
|
3909
|
+
"!doctl -u",
|
|
3910
|
+
...nested("hcloud", ["list", "describe"], 2),
|
|
3911
|
+
...nested("linode-cli", ["list", "view"], 2),
|
|
3912
|
+
...nested("oci", ["list"], 4),
|
|
3913
|
+
...[
|
|
3914
|
+
"status",
|
|
3915
|
+
"releases",
|
|
3916
|
+
"apps list",
|
|
3917
|
+
"machine list",
|
|
3918
|
+
"machines list",
|
|
3919
|
+
"secrets list",
|
|
3920
|
+
"regions list",
|
|
3921
|
+
"ips list",
|
|
3922
|
+
"volumes list"
|
|
3923
|
+
].flatMap((c) => [`fly ${c}`, `flyctl ${c}`]),
|
|
3924
|
+
...["apps", "apps:info", "ps", "logs", "releases", "addons", "domains", "pipelines"].map((c) => `heroku ${c}`),
|
|
3925
|
+
...["ls", "list", "inspect", "logs", "env ls", "domains ls", "project ls", "whoami"].map((c) => `vercel ${c}`),
|
|
3926
|
+
"netlify status",
|
|
3927
|
+
"netlify sites:list",
|
|
3928
|
+
"firebase projects:list",
|
|
3929
|
+
"firebase apps:list",
|
|
3930
|
+
"firebase hosting:channel:list",
|
|
3931
|
+
"wrangler whoami",
|
|
3932
|
+
"wrangler deployments list",
|
|
3933
|
+
"wrangler d1 list",
|
|
3934
|
+
"wrangler kv namespace list",
|
|
3935
|
+
"supabase projects list",
|
|
3936
|
+
"supabase functions list",
|
|
3937
|
+
"railway status",
|
|
3938
|
+
// Kubernetes and friends. A kubeconfig the command names can run a credential plugin; `--raw` reads any API path.
|
|
3939
|
+
...withArgument("kubectl", KUBE_VERB_READS),
|
|
3940
|
+
...KUBE_READS.map((c) => `kubectl ${c}`),
|
|
3941
|
+
...[...KUBE_VERB_READS, ...KUBE_READS, "status", "whoami", "projects"].map((c) => `oc ${c}`),
|
|
3942
|
+
...["kubectl", "oc"].flatMap((bin) => ["*secret*", "-s", "dump", "--raw*", "-k", "--kustomize*"].map((w) => `!${bin} ${w}`)),
|
|
3943
|
+
"!oc whoami -t",
|
|
3944
|
+
"!oc *show-token*",
|
|
3945
|
+
...withArgument("helm", ["list", "ls", "status", "history", "search"]),
|
|
3946
|
+
"helm version",
|
|
3947
|
+
"!helm --kube*",
|
|
3948
|
+
"eksctl get",
|
|
3949
|
+
"kind get",
|
|
3950
|
+
"k3d cluster list",
|
|
3951
|
+
"minikube status",
|
|
3952
|
+
"minikube profile list",
|
|
3953
|
+
"!kind *kubeconfig*",
|
|
3954
|
+
"argocd app list",
|
|
3955
|
+
"argocd app get",
|
|
3956
|
+
"argocd app history",
|
|
3957
|
+
"istioctl analyze",
|
|
3958
|
+
"istioctl proxy-status",
|
|
3959
|
+
"istioctl version",
|
|
3960
|
+
"!istioctl -c",
|
|
3961
|
+
"nomad status",
|
|
3962
|
+
"nomad job status",
|
|
3963
|
+
"consul members",
|
|
3964
|
+
"consul catalog services",
|
|
3965
|
+
"vault status",
|
|
3966
|
+
// Containers. Not `compose`, which reads the repository's compose file, remote includes and all.
|
|
3967
|
+
...["docker", "podman"].flatMap((bin) => [...withArgument(bin, CONTAINER_VERB_READS), ...CONTAINER_READS.map((c) => `${bin} ${c}`), `!${bin} -H`]),
|
|
3968
|
+
// Infrastructure as code: only what reads no backend, whose address the repository's config sets.
|
|
3969
|
+
...["version", "workspace show"].flatMap((c) => [`terraform ${c}`, `tofu ${c}`]),
|
|
3970
|
+
// Package metadata from what is installed: npm reads `node_modules`, pip its own environment.
|
|
3971
|
+
...withArgument("npm", ["ls", "list", "why", "explain"]),
|
|
3972
|
+
...["pip", "pip3"].flatMap((bin) => [...withArgument(bin, ["show"]), `${bin} list`, `${bin} freeze`, `!${bin} -i`, `!${bin} -f`]),
|
|
3973
|
+
// A module query (`@latest`) fetches from whatever host the module path names.
|
|
3974
|
+
...withArgument("go", ["list", "env"]),
|
|
3975
|
+
"go version",
|
|
3976
|
+
"go mod graph",
|
|
3977
|
+
"go mod why",
|
|
3978
|
+
"!go -w",
|
|
3979
|
+
"!go -u",
|
|
3980
|
+
"!go -mod*",
|
|
3981
|
+
"!go *-toolexec*",
|
|
3982
|
+
"!go *-exec*",
|
|
3983
|
+
"!go *-compiled*",
|
|
3984
|
+
"!go *-export*",
|
|
3985
|
+
"!go *@*",
|
|
3986
|
+
// The local machine
|
|
3987
|
+
"ps",
|
|
3988
|
+
"lsof",
|
|
3989
|
+
"uptime",
|
|
3990
|
+
"free",
|
|
3991
|
+
"nproc",
|
|
3992
|
+
"hostname",
|
|
3993
|
+
"id",
|
|
3994
|
+
"journalctl",
|
|
3995
|
+
"!journalctl --vacuum*",
|
|
3996
|
+
"!journalctl --rotate",
|
|
3997
|
+
"!journalctl --flush",
|
|
3998
|
+
"!journalctl --sync",
|
|
3999
|
+
"!journalctl --relinquish*",
|
|
4000
|
+
"!journalctl --smart-relinquish*",
|
|
4001
|
+
"!journalctl --setup-keys",
|
|
4002
|
+
"!journalctl --update-catalog",
|
|
4003
|
+
"!hostname *",
|
|
4004
|
+
// Anything that would write a file, open a browser, or send the user's credentials to an address the command names.
|
|
4005
|
+
...[
|
|
4006
|
+
"--output-file*",
|
|
4007
|
+
"--out-file*",
|
|
4008
|
+
"--outfile*",
|
|
4009
|
+
"--output-document*",
|
|
4010
|
+
"--output-directory*",
|
|
4011
|
+
"--log-file*",
|
|
4012
|
+
"--logfile*",
|
|
4013
|
+
"--download*",
|
|
4014
|
+
"--show-secrets*",
|
|
4015
|
+
"--with-decryption*",
|
|
4016
|
+
"--reveal*",
|
|
4017
|
+
"--web",
|
|
4018
|
+
"--server*",
|
|
4019
|
+
"*-addr*",
|
|
4020
|
+
"--endpoint*",
|
|
4021
|
+
"--api-url*",
|
|
4022
|
+
"--url*",
|
|
4023
|
+
"--host*",
|
|
4024
|
+
"--registry*",
|
|
4025
|
+
"--index-url*",
|
|
4026
|
+
"--extra-index-url*",
|
|
4027
|
+
"--find-links*",
|
|
4028
|
+
"--proxy*",
|
|
4029
|
+
"--kubeconfig*",
|
|
4030
|
+
"*apiserver*",
|
|
4031
|
+
"*http://*",
|
|
4032
|
+
"*https://*"
|
|
4033
|
+
].map((w) => `!* ${w}`),
|
|
4034
|
+
// MCP tools that read: the verb leads the tool's own name.
|
|
4035
|
+
...["get*", "list*", "find*", "search*", "read*", "view*", "describe*", "show*"].map((v) => `mcp:${v}`),
|
|
4036
|
+
...["*secret*", "*token*", "*password*", "*credential*", "*key*", "*url*", "*fetch*", "*http*"].map((w) => `!mcp:${w}`)
|
|
4037
|
+
];
|
|
4038
|
+
|
|
3693
4039
|
// src/services/commandPolicy.ts
|
|
3694
4040
|
var AUTO_COMMANDS = [
|
|
3695
4041
|
"ls",
|
|
@@ -4123,18 +4469,20 @@ function patternArgs(seg) {
|
|
|
4123
4469
|
}
|
|
4124
4470
|
var SCOPE_LEAD_ARGS = 2;
|
|
4125
4471
|
function scopeFor(seg) {
|
|
4126
|
-
|
|
4472
|
+
const name = seg.binaryPath ?? seg.binary;
|
|
4473
|
+
if (!MULTIPLEXERS.includes(seg.binary.toLowerCase())) return name;
|
|
4127
4474
|
const lead = [];
|
|
4128
4475
|
for (const arg of seg.args) {
|
|
4129
4476
|
if (arg.startsWith("-")) break;
|
|
4130
4477
|
lead.push(arg);
|
|
4131
4478
|
if (lead.length === SCOPE_LEAD_ARGS) break;
|
|
4132
4479
|
}
|
|
4133
|
-
return [
|
|
4480
|
+
return [name, ...lead].join(" ");
|
|
4134
4481
|
}
|
|
4135
4482
|
function isAuto(seg, opts) {
|
|
4136
4483
|
if (seg.binary === "cd") return !opts.cdpathSet && !seg.cdSteered && literalCdTarget(seg) !== void 0;
|
|
4137
4484
|
if (!AUTO_COMMANDS.includes(seg.binary)) return false;
|
|
4485
|
+
if (seg.binaryPath !== void 0) return false;
|
|
4138
4486
|
if (seg.expandable) return false;
|
|
4139
4487
|
if (seg.binary !== "git") return true;
|
|
4140
4488
|
const sub = subcommandOf(seg);
|
|
@@ -4321,7 +4669,18 @@ function classifyCommand(command, opts = {}) {
|
|
|
4321
4669
|
return { tier: "ask", scope: trimmed };
|
|
4322
4670
|
}
|
|
4323
4671
|
const scope = [...new Set(nonAuto.map((u) => u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)))].sort().join(" + ");
|
|
4324
|
-
|
|
4672
|
+
const allowedBy = opts.allow ? allowlistRule(nonAuto, opts.allow) : void 0;
|
|
4673
|
+
return { tier: "ask", scope, ...allowedBy ? { allowedBy } : {} };
|
|
4674
|
+
}
|
|
4675
|
+
function allowlistRule(parts, allow) {
|
|
4676
|
+
const rules = [];
|
|
4677
|
+
for (const { seg, runner } of parts) {
|
|
4678
|
+
if (runner || seg.expandable || seg.globbed || seg.binaryPath !== void 0 || seg.assignments.length > 0) return void 0;
|
|
4679
|
+
const rule = commandAllowedBy(allow, { binary: seg.binary, args: seg.args, scopeWords: scopeFor(seg).split(" ").length - 1 });
|
|
4680
|
+
if (!rule) return void 0;
|
|
4681
|
+
rules.push(rule);
|
|
4682
|
+
}
|
|
4683
|
+
return [...new Set(rules)].join(" + ");
|
|
4325
4684
|
}
|
|
4326
4685
|
|
|
4327
4686
|
// src/services/researchShell.ts
|
|
@@ -4637,7 +4996,8 @@ ${refs.output.trim()}`);
|
|
|
4637
4996
|
* directory the next command names.
|
|
4638
4997
|
*/
|
|
4639
4998
|
async bash(command, signal) {
|
|
4640
|
-
const
|
|
4999
|
+
const allow = this.approval.allowlist?.();
|
|
5000
|
+
const { tier, scope, reason, allowedBy } = classifyCommand(command, { ...this.policyOptions(), ...allow ? { allow } : {} });
|
|
4641
5001
|
if (tier === "refuse") {
|
|
4642
5002
|
return { success: false, output: `Command refused: ${reason}`, truncated: false };
|
|
4643
5003
|
}
|
|
@@ -4652,13 +5012,14 @@ ${refs.output.trim()}`);
|
|
|
4652
5012
|
subject: asks ? command : outside[0].abs,
|
|
4653
5013
|
scope: scopes[0],
|
|
4654
5014
|
...scopes.length > 1 ? { scopes } : {},
|
|
5015
|
+
...asks && allowedBy ? { allowedBy } : {},
|
|
4655
5016
|
detail: outside.length > 0 ? `Planner research wants to run "${command}", which touches ${touched}, outside the workspace (${root}).` : `Planner research wants to run: ${command}`
|
|
4656
5017
|
});
|
|
4657
5018
|
if (!granted) {
|
|
4658
5019
|
return {
|
|
4659
5020
|
success: false,
|
|
4660
5021
|
output: outside.length > 0 ? `Access denied: "${command}" touches ${touched}, outside the workspace root (${root}), and was not approved. Keep research inside the workspace, or ask the user to approve this location.` : `Command not approved: ${command}
|
|
4661
|
-
It is outside the auto-allowed read-only set (${scope}). Continue
|
|
5022
|
+
It is outside the auto-allowed read-only set and the planner allowlist (${scope}). Continue without it. If it would change something outside the repository, add it to the plan as an ops task; if it only reads, ask the user to approve it or add it to the planner allowlist.`,
|
|
4662
5023
|
truncated: false
|
|
4663
5024
|
};
|
|
4664
5025
|
}
|
|
@@ -4886,14 +5247,19 @@ async function runTool(name, args, fs17, fetcher, signal) {
|
|
|
4886
5247
|
}
|
|
4887
5248
|
|
|
4888
5249
|
// src/services/ApprovalPolicy.ts
|
|
4889
|
-
function
|
|
4890
|
-
if (
|
|
4891
|
-
|
|
4892
|
-
|
|
5250
|
+
function effectiveApprovalMode(setting, autonomous) {
|
|
5251
|
+
if (setting !== "auto") return setting;
|
|
5252
|
+
return autonomous ? "deny" : "ask";
|
|
5253
|
+
}
|
|
5254
|
+
function parseApprovalModeSetting(raw) {
|
|
5255
|
+
const value = (raw ?? "").trim().toLowerCase();
|
|
5256
|
+
if (value === "allowlist") return "deny";
|
|
5257
|
+
return value === "ask" || value === "allow" || value === "deny" ? value : "auto";
|
|
4893
5258
|
}
|
|
4894
5259
|
var ApprovalPolicy = class {
|
|
4895
|
-
|
|
4896
|
-
|
|
5260
|
+
modeOf;
|
|
5261
|
+
entriesOf;
|
|
5262
|
+
parsed = null;
|
|
4897
5263
|
asker;
|
|
4898
5264
|
onDecision;
|
|
4899
5265
|
granted = /* @__PURE__ */ new Set();
|
|
@@ -4905,8 +5271,9 @@ var ApprovalPolicy = class {
|
|
|
4905
5271
|
// generation it was started under and bails if reset has intervened.
|
|
4906
5272
|
generation = 0;
|
|
4907
5273
|
constructor(opts = {}) {
|
|
4908
|
-
|
|
4909
|
-
this.
|
|
5274
|
+
const { mode = "ask", preApproved = [] } = opts;
|
|
5275
|
+
this.modeOf = typeof mode === "function" ? mode : () => mode;
|
|
5276
|
+
this.entriesOf = typeof preApproved === "function" ? preApproved : () => preApproved;
|
|
4910
5277
|
this.asker = opts.ask;
|
|
4911
5278
|
this.onDecision = opts.onDecision;
|
|
4912
5279
|
}
|
|
@@ -4917,19 +5284,19 @@ var ApprovalPolicy = class {
|
|
|
4917
5284
|
};
|
|
4918
5285
|
const scopes = approvalScopes(req);
|
|
4919
5286
|
const key = scopes.join("\n");
|
|
4920
|
-
|
|
4921
|
-
const
|
|
5287
|
+
const list = this.allowlist();
|
|
5288
|
+
const preApproved = (scope, i) => scopeAllowed(list, scope) || i === 0 && (req.kind === "shell_command" && req.allowedBy !== void 0 || req.kind === "mcp_tool" && req.tool !== void 0 && toolAllowed(list, req.tool));
|
|
5289
|
+
const open = scopes.filter((s, i) => !this.granted.has(s) && !preApproved(s, i));
|
|
4922
5290
|
if (open.length === 0) {
|
|
4923
5291
|
return decide(true, scopes.every((s) => this.granted.has(s)) ? "remembered" : "pre-approved");
|
|
4924
5292
|
}
|
|
4925
|
-
if (this.
|
|
5293
|
+
if (scopes.some((s) => this.refused.has(s)) || this.refused.has(key)) return decide(false, "remembered");
|
|
5294
|
+
const mode = this.modeOf();
|
|
5295
|
+
if (mode === "allow") {
|
|
4926
5296
|
open.forEach((s) => this.granted.add(s));
|
|
4927
5297
|
return decide(true, "mode");
|
|
4928
5298
|
}
|
|
4929
|
-
if (
|
|
4930
|
-
this.refused.add(key);
|
|
4931
|
-
return decide(false, "mode");
|
|
4932
|
-
}
|
|
5299
|
+
if (mode === "deny") return decide(false, "mode");
|
|
4933
5300
|
if (!this.asker) return decide(false, "no-channel");
|
|
4934
5301
|
const openKey = open.join("\n");
|
|
4935
5302
|
const existing = this.inFlight.get(openKey);
|
|
@@ -4947,6 +5314,13 @@ var ApprovalPolicy = class {
|
|
|
4947
5314
|
}
|
|
4948
5315
|
return decide(answer2, "asked");
|
|
4949
5316
|
}
|
|
5317
|
+
/** The standing approvals, parsed from the entries in force now. */
|
|
5318
|
+
allowlist() {
|
|
5319
|
+
const entries = this.entriesOf();
|
|
5320
|
+
const key = entries.join("\n");
|
|
5321
|
+
if (this.parsed?.key !== key) this.parsed = { key, list: parseAllowlist(entries) };
|
|
5322
|
+
return this.parsed.list;
|
|
5323
|
+
}
|
|
4950
5324
|
/** Scopes the user has granted this session — for display and for persistence. */
|
|
4951
5325
|
grantedScopes() {
|
|
4952
5326
|
return [...this.granted];
|
|
@@ -5044,14 +5418,17 @@ var KNOWN_TOOLS = {
|
|
|
5044
5418
|
local_shell: "bash",
|
|
5045
5419
|
// OpenCode
|
|
5046
5420
|
list: "list_dir",
|
|
5047
|
-
fetch: "fetch"
|
|
5421
|
+
fetch: "fetch",
|
|
5422
|
+
// Ordewell's own research shell (ADR-0026), under every runner's name for it
|
|
5423
|
+
runcommand: "bash"
|
|
5048
5424
|
};
|
|
5425
|
+
var ORDEWELL_TOOL_PREFIX = /^(mcp__ordewell__|ordewell_)/i;
|
|
5049
5426
|
var FILE_EDIT_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "multiedit", "notebookedit", "applypatch", "patch", "add", "update", "delete", "filechange"]);
|
|
5050
5427
|
function isFileEditTool(name) {
|
|
5051
5428
|
return FILE_EDIT_TOOLS.has(name.trim().toLowerCase().replace(/[_-]/g, ""));
|
|
5052
5429
|
}
|
|
5053
5430
|
function mapAgentTool(name) {
|
|
5054
|
-
const normalized = name.trim().toLowerCase().replace(/[_-]/g, "");
|
|
5431
|
+
const normalized = name.trim().replace(ORDEWELL_TOOL_PREFIX, "").toLowerCase().replace(/[_-]/g, "");
|
|
5055
5432
|
const direct = KNOWN_TOOLS[normalized] ?? KNOWN_TOOLS[name.trim().toLowerCase()];
|
|
5056
5433
|
if (direct) {
|
|
5057
5434
|
return { tool: direct, toolLabel: name };
|
|
@@ -6700,8 +7077,10 @@ var BaseConfig = class {
|
|
|
6700
7077
|
return nonNegativeInt(process.env.ORDEWELL_CONFLICT_REPAIR_ATTEMPTS, 2);
|
|
6701
7078
|
}
|
|
6702
7079
|
get approvalMode() {
|
|
6703
|
-
|
|
6704
|
-
|
|
7080
|
+
return parseApprovalModeSetting(process.env.ORDEWELL_APPROVAL_MODE);
|
|
7081
|
+
}
|
|
7082
|
+
get approvalDefaults() {
|
|
7083
|
+
return process.env.ORDEWELL_APPROVAL_DEFAULTS !== "false" && process.env.ORDEWELL_APPROVAL_DEFAULTS !== "0";
|
|
6705
7084
|
}
|
|
6706
7085
|
get approvalPreApproved() {
|
|
6707
7086
|
return (process.env.ORDEWELL_APPROVAL_ALLOW || "").split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
|
|
@@ -7544,7 +7923,7 @@ var GitWorktreeIsolation = class {
|
|
|
7544
7923
|
return this.admin(workspaceRoot, async () => {
|
|
7545
7924
|
const scan = await this.scanGroup(workspaceRoot);
|
|
7546
7925
|
const paths = "paths" in scan ? scan.paths : [SELF_REPO];
|
|
7547
|
-
const
|
|
7926
|
+
const nested2 = "paths" in scan ? scan.nested : [];
|
|
7548
7927
|
const run = { id: this.mintRunId(), workspaceRoot, repos: [], shared: [], sharedRepos: [], tasks: {} };
|
|
7549
7928
|
for (const repoPath of paths) {
|
|
7550
7929
|
const repo = await this.startRepo(run, repoPath);
|
|
@@ -7552,7 +7931,7 @@ var GitWorktreeIsolation = class {
|
|
|
7552
7931
|
else run.sharedRepos.push(repoPath);
|
|
7553
7932
|
}
|
|
7554
7933
|
if (run.repos.length === 0) throw new Error(`No repository could be isolated: ${run.sharedRepos.join(", ")}`);
|
|
7555
|
-
run.shared = paths.includes(SELF_REPO) ?
|
|
7934
|
+
run.shared = paths.includes(SELF_REPO) ? nested2 : this.sharedPaths(workspaceRoot, run.repos.map((r) => r.path));
|
|
7556
7935
|
return run;
|
|
7557
7936
|
});
|
|
7558
7937
|
}
|
|
@@ -9093,6 +9472,7 @@ function researchPhaseBlock(harnessMode) {
|
|
|
9093
9472
|
const toolSpecific = harnessMode ? [
|
|
9094
9473
|
"- Explore the repository with your own tools until you understand its architecture",
|
|
9095
9474
|
"- You are planning, not implementing: do NOT edit, create, or delete any file, and do not run commands that change the workspace",
|
|
9475
|
+
"- Run commands \u2014 git, gh, cloud CLIs, the project's own tooling \u2014 with the run_command tool, not a shell of your own. MCP tools the user configured (issue trackers, todo lists) are yours to read from too. Reads run at once; anything else is approved or refused by the user's settings. When something the plan needs to change is refused, add it to the plan as an ops task instead",
|
|
9096
9476
|
"- If you delegate exploration to your own agents, WAIT for their results inside this reply. Do NOT launch them in the background or async and end your turn saying you will report back later: your turn ending is what hands the conversation back to the user, and anything you say after it never reaches them."
|
|
9097
9477
|
] : [
|
|
9098
9478
|
"- Use list_dir with depth=2-3 for a tree overview",
|
|
@@ -12559,6 +12939,9 @@ var listRunnersInput = import_v4.z.object({});
|
|
|
12559
12939
|
var listModelsInput = import_v4.z.object({
|
|
12560
12940
|
runner: import_v4.z.string().min(1).describe("A runner id from list_runners.")
|
|
12561
12941
|
});
|
|
12942
|
+
var runCommandInput = import_v4.z.object({
|
|
12943
|
+
command: import_v4.z.string().min(1).describe("One shell command line, run from the workspace root.")
|
|
12944
|
+
});
|
|
12562
12945
|
var loadSkillInput = import_v4.z.object({
|
|
12563
12946
|
name: import_v4.z.string().min(1).describe("The name of an Ordewell planner skill to load.")
|
|
12564
12947
|
});
|
|
@@ -12698,6 +13081,13 @@ var PLANNER_TOOLS = [
|
|
|
12698
13081
|
(h) => h.taskOutput?.bind(h),
|
|
12699
13082
|
PLANNER_READ_ONLY
|
|
12700
13083
|
),
|
|
13084
|
+
tool(
|
|
13085
|
+
"run_command",
|
|
13086
|
+
"Run a shell command for research: git, gh, az, kubectl, the project's own tooling, anything you need to look at. Read-only commands run at once; others ask the user, once per command family; commands that write files are refused. Use it instead of a shell of your own.",
|
|
13087
|
+
runCommandInput,
|
|
13088
|
+
(h) => h.runCommand?.bind(h),
|
|
13089
|
+
PLANNER_READ_ONLY
|
|
13090
|
+
),
|
|
12701
13091
|
tool(
|
|
12702
13092
|
"load_skill",
|
|
12703
13093
|
"Load an Ordewell planner skill by name. Returns its instructions; only model-invocable planner skills can be loaded.",
|
|
@@ -15185,6 +15575,7 @@ var SETTINGS_ENV_REFUSED = [
|
|
|
15185
15575
|
"ORDEWELL_AUTONOMOUS_MODE",
|
|
15186
15576
|
"ORDEWELL_APPROVAL_MODE",
|
|
15187
15577
|
"ORDEWELL_APPROVAL_ALLOW",
|
|
15578
|
+
"ORDEWELL_APPROVAL_DEFAULTS",
|
|
15188
15579
|
// Where settings are persisted.
|
|
15189
15580
|
"ORDEWELL_SETTINGS_PATH",
|
|
15190
15581
|
// Runtime options and dynamic loaders.
|
|
@@ -15783,6 +16174,32 @@ function killTree(proc, deps = {}) {
|
|
|
15783
16174
|
// src/services/harness/openCodeOrdewell.ts
|
|
15784
16175
|
var NAMES = prefixedToolNames(`${ORDEWELL_MCP_SERVER_NAME}_`);
|
|
15785
16176
|
var RULE = NAMES.toolName("*");
|
|
16177
|
+
var OPENCODE_PLANNER_PERMISSION = {
|
|
16178
|
+
"*": "ask",
|
|
16179
|
+
read: "allow",
|
|
16180
|
+
glob: "allow",
|
|
16181
|
+
grep: "allow",
|
|
16182
|
+
list: "allow",
|
|
16183
|
+
lsp: "allow",
|
|
16184
|
+
skill: "allow",
|
|
16185
|
+
todoread: "allow",
|
|
16186
|
+
websearch: "allow",
|
|
16187
|
+
codesearch: "allow",
|
|
16188
|
+
webfetch: "ask",
|
|
16189
|
+
external_directory: "ask",
|
|
16190
|
+
edit: "deny",
|
|
16191
|
+
write: "deny",
|
|
16192
|
+
patch: "deny",
|
|
16193
|
+
apply_patch: "deny",
|
|
16194
|
+
multiedit: "deny",
|
|
16195
|
+
bash: "deny",
|
|
16196
|
+
task: "deny",
|
|
16197
|
+
question: "deny",
|
|
16198
|
+
todowrite: "deny",
|
|
16199
|
+
shell: "deny",
|
|
16200
|
+
subagent: "deny",
|
|
16201
|
+
browser: "deny"
|
|
16202
|
+
};
|
|
15786
16203
|
var OPENCODE_CONFIG_VARIABLE = "OPENCODE_CONFIG_CONTENT";
|
|
15787
16204
|
function isRecord2(value) {
|
|
15788
16205
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -15820,7 +16237,7 @@ function withoutParentOrdewell(content) {
|
|
|
15820
16237
|
const kept = withoutOrdewell(parsed);
|
|
15821
16238
|
return JSON.stringify(kept) === JSON.stringify(parsed) ? content : JSON.stringify(kept);
|
|
15822
16239
|
}
|
|
15823
|
-
function mergeOrdewellConfig(existing, mcp) {
|
|
16240
|
+
function mergeOrdewellConfig(existing, mcp, role) {
|
|
15824
16241
|
let base = {};
|
|
15825
16242
|
if (existing?.trim()) {
|
|
15826
16243
|
let parsed;
|
|
@@ -15832,10 +16249,14 @@ function mergeOrdewellConfig(existing, mcp) {
|
|
|
15832
16249
|
if (!isRecord2(parsed)) return null;
|
|
15833
16250
|
base = withoutOrdewell(parsed);
|
|
15834
16251
|
}
|
|
16252
|
+
if (role === "planner") {
|
|
16253
|
+
delete base.permission;
|
|
16254
|
+
delete base.permissions;
|
|
16255
|
+
}
|
|
15835
16256
|
const policy = typeof base.permission === "string" ? { "*": base.permission } : base.permission;
|
|
15836
16257
|
const ours = {
|
|
15837
|
-
mcp: { [mcp.name]: { type: "remote", url: mcp.url, headers: mcp.headers, enabled: true } },
|
|
15838
|
-
permission: { [RULE]: "allow" }
|
|
16258
|
+
...mcp ? { mcp: { [mcp.name]: { type: "remote", url: mcp.url, headers: mcp.headers, enabled: true } } } : {},
|
|
16259
|
+
permission: { ...role === "planner" ? OPENCODE_PLANNER_PERMISSION : {}, ...mcp ? { [RULE]: "allow" } : {} }
|
|
15839
16260
|
};
|
|
15840
16261
|
return JSON.stringify(deepMerge({ ...base, ...policy === void 0 ? {} : { permission: policy } }, ours));
|
|
15841
16262
|
}
|
|
@@ -16280,7 +16701,24 @@ var DISALLOWED_TOOLS = [
|
|
|
16280
16701
|
"EnterPlanMode",
|
|
16281
16702
|
"ExitPlanMode"
|
|
16282
16703
|
];
|
|
16283
|
-
var PLANNER_PERMISSION_MODE = "
|
|
16704
|
+
var PLANNER_PERMISSION_MODE = "default";
|
|
16705
|
+
var PATH_TOOLS = {
|
|
16706
|
+
Read: { field: "file_path", directory: false },
|
|
16707
|
+
Grep: { field: "path", directory: true },
|
|
16708
|
+
Glob: { field: "path", directory: true },
|
|
16709
|
+
LS: { field: "path", directory: true }
|
|
16710
|
+
};
|
|
16711
|
+
function plannerAsk(name, input, server) {
|
|
16712
|
+
if (name.startsWith("mcp__")) {
|
|
16713
|
+
const tool2 = name.split("__").slice(2).join("__");
|
|
16714
|
+
return { kind: "mcp", scope: name, tool: tool2 || name, ...server ? { server } : {} };
|
|
16715
|
+
}
|
|
16716
|
+
if (name === "WebFetch" && typeof input.url === "string") return { kind: "fetch", url: input.url };
|
|
16717
|
+
const pathTool = PATH_TOOLS[name];
|
|
16718
|
+
const target = pathTool ? input[pathTool.field] : void 0;
|
|
16719
|
+
if (pathTool && typeof target === "string") return { kind: "path", path: target, directory: pathTool.directory };
|
|
16720
|
+
return { kind: "other" };
|
|
16721
|
+
}
|
|
16284
16722
|
var TASK_DISALLOWED_TOOLS = ["AskUserQuestion"];
|
|
16285
16723
|
var PROTOCOL_ARGS = [
|
|
16286
16724
|
"-p",
|
|
@@ -16390,6 +16828,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
16390
16828
|
const args = [
|
|
16391
16829
|
...PROTOCOL_ARGS,
|
|
16392
16830
|
// The read-only guarantee, enforced at spawn rather than by prompt.
|
|
16831
|
+
"--permission-prompt-tool",
|
|
16832
|
+
"stdio",
|
|
16393
16833
|
"--permission-mode",
|
|
16394
16834
|
PLANNER_PERMISSION_MODE,
|
|
16395
16835
|
"--disallowedTools",
|
|
@@ -16576,32 +17016,20 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
16576
17016
|
const subagentId = msg.parent_tool_use_id ?? void 0;
|
|
16577
17017
|
switch (msg.type) {
|
|
16578
17018
|
// The control channel: Claude asks whether a tool may run when its mode
|
|
16579
|
-
// cannot decide alone.
|
|
16580
|
-
//
|
|
16581
|
-
//
|
|
17019
|
+
// cannot decide alone. The request is left open — a task's for someone
|
|
17020
|
+
// to answer (ADR-0018, A1), a planner's for its envelope (ADR-0026) —
|
|
17021
|
+
// and must be answered, because an unacknowledged request stalls the turn.
|
|
16582
17022
|
case "control_request": {
|
|
16583
17023
|
if (msg.request?.subtype !== "can_use_tool") return;
|
|
16584
17024
|
const input = msg.request.input ?? {};
|
|
16585
17025
|
const id = msg.request_id ?? "";
|
|
16586
|
-
if (this.role === "planner") {
|
|
16587
|
-
this.writeLine({
|
|
16588
|
-
type: "control_response",
|
|
16589
|
-
response: {
|
|
16590
|
-
subtype: "success",
|
|
16591
|
-
request_id: msg.request_id,
|
|
16592
|
-
response: { behavior: "deny", message: "The Ordewell planner is read-only. Mutation belongs to the runners that execute the plan." }
|
|
16593
|
-
}
|
|
16594
|
-
});
|
|
16595
|
-
emit({ type: "permission_request", id, name: msg.request.tool_name ?? "unknown", detail: JSON.stringify(input) });
|
|
16596
|
-
return;
|
|
16597
|
-
}
|
|
16598
17026
|
const name = msg.request.tool_name ?? "unknown";
|
|
16599
17027
|
if (this.givenOrdewell && CLAUDE_ORDEWELL.isOrdewellAsk(name)) {
|
|
16600
17028
|
this.writeLine({ type: "control_response", response: { subtype: "success", request_id: id, response: { behavior: "allow", updatedInput: input } } });
|
|
16601
17029
|
emit({ type: "permission_request", id, name, detail: JSON.stringify(input), input, decided: { decision: "allow" } });
|
|
16602
17030
|
return;
|
|
16603
17031
|
}
|
|
16604
|
-
const suggestions = msg.request.permission_suggestions ?? [];
|
|
17032
|
+
const suggestions = this.role === "task" ? msg.request.permission_suggestions ?? [] : [];
|
|
16605
17033
|
this.openPermissions.set(id, { input, suggestions });
|
|
16606
17034
|
emit({
|
|
16607
17035
|
type: "permission_request",
|
|
@@ -16610,7 +17038,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
16610
17038
|
detail: JSON.stringify(input),
|
|
16611
17039
|
input,
|
|
16612
17040
|
suggestions,
|
|
16613
|
-
...msg.request.tool_use_id ? { toolUseId: msg.request.tool_use_id } : {}
|
|
17041
|
+
...msg.request.tool_use_id ? { toolUseId: msg.request.tool_use_id } : {},
|
|
17042
|
+
...this.role === "planner" ? { ask: plannerAsk(name, input, msg.request.mcp_server?.name) } : {}
|
|
16614
17043
|
});
|
|
16615
17044
|
return;
|
|
16616
17045
|
}
|
|
@@ -16902,6 +17331,20 @@ var CODEX_ORDEWELL = {
|
|
|
16902
17331
|
`They are not in your tool list up front. Find them with the tool discovery you have (the \`exec\` tool's \`ALL_TOOLS\` list) and call them by their full names, ${quoted2(this.toolNames("task"))}.`,
|
|
16903
17332
|
`Look for them before you finish; the task is reported complete through \`${TASK_COMPLETE_TOOL}\`.`
|
|
16904
17333
|
].join(" ");
|
|
17334
|
+
},
|
|
17335
|
+
/**
|
|
17336
|
+
* The same discovery note for a planner, which also needs to know that its
|
|
17337
|
+
* commands go through `run_command`: Codex's own shell is sandboxed without
|
|
17338
|
+
* network, so `gh` or a cloud CLI fails there, and a model that tried its
|
|
17339
|
+
* shell first concludes the command cannot run.
|
|
17340
|
+
*/
|
|
17341
|
+
plannerInstructions() {
|
|
17342
|
+
const tools = ordewellToolNames("planner");
|
|
17343
|
+
return [
|
|
17344
|
+
`You have ${COUNT_WORDS[tools.length] ?? tools.length} tools from the \`${ORDEWELL_MCP_SERVER_NAME}\` MCP server: ${quoted2(tools)}.`,
|
|
17345
|
+
`They are not in your tool list up front. Find them with the tool discovery you have (the \`exec\` tool's \`ALL_TOOLS\` list) and call them by their full names, ${quoted2(this.toolNames("planner"))}.`,
|
|
17346
|
+
`Run shell commands with \`${this.toolName("run_command")}\`, not your own shell: yours is sandboxed without network, so git remotes, gh and cloud CLIs fail there.`
|
|
17347
|
+
].join(" ");
|
|
16905
17348
|
}
|
|
16906
17349
|
};
|
|
16907
17350
|
|
|
@@ -17089,6 +17532,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
|
|
|
17089
17532
|
interruptedTurnIds = /* @__PURE__ */ new Set();
|
|
17090
17533
|
/** A task's approval requests still waiting for an answer, by the request id as a string. */
|
|
17091
17534
|
openPermissions = /* @__PURE__ */ new Map();
|
|
17535
|
+
/** The tool each MCP server's latest call is for, which names the tool an approval asks about when its question does not. */
|
|
17536
|
+
mcpCallsInProgress = /* @__PURE__ */ new Map();
|
|
17092
17537
|
/**
|
|
17093
17538
|
* The files each in-flight `fileChange` item touches. Its approval request
|
|
17094
17539
|
* names only the item, and a person deciding needs to see the files.
|
|
@@ -17196,14 +17641,18 @@ ${this.exitMessage()}`);
|
|
|
17196
17641
|
return {
|
|
17197
17642
|
...common,
|
|
17198
17643
|
sandbox: "read-only",
|
|
17199
|
-
|
|
17644
|
+
// `on-request`, so a user's MCP tool reaches the planner's envelope as an approval
|
|
17645
|
+
// (ADR-0026); under `never` Codex fails the call without asking. Commands and file
|
|
17646
|
+
// changes it asks about are still declined: the sandbox holds, and commands go
|
|
17647
|
+
// through Ordewell's `run_command`.
|
|
17648
|
+
approvalPolicy: "on-request",
|
|
17200
17649
|
...threadConfig,
|
|
17201
17650
|
// `developerInstructions` layers on top of Codex's own base prompt, the
|
|
17202
17651
|
// way Claude Code's `--append-system-prompt` does. `baseInstructions`
|
|
17203
17652
|
// replaces it — which takes Codex's description of its own tools with
|
|
17204
17653
|
// it, and a planner that has forgotten it can read the workspace
|
|
17205
17654
|
// answers from a web search instead.
|
|
17206
|
-
developerInstructions: opts?.systemPrompt
|
|
17655
|
+
developerInstructions: [opts?.systemPrompt, opts?.mcp ? CODEX_ORDEWELL.plannerInstructions() : ""].filter(Boolean).join("\n\n") || void 0
|
|
17207
17656
|
};
|
|
17208
17657
|
}
|
|
17209
17658
|
/**
|
|
@@ -17307,7 +17756,7 @@ ${this.exitMessage()}`);
|
|
|
17307
17756
|
if (!open || !this.process) return false;
|
|
17308
17757
|
this.openPermissions.delete(id);
|
|
17309
17758
|
this.rpc.respond(open.requestId, TASK_APPROVALS[open.method].answer(decision.decision, open.params));
|
|
17310
|
-
const note = decision.decision === "deny" ? decision.note?.trim() : void 0;
|
|
17759
|
+
const note = decision.decision === "deny" && this.role === "task" ? decision.note?.trim() : void 0;
|
|
17311
17760
|
if (note && this.turn?.id) this.requestSteer(this.turn.id, note);
|
|
17312
17761
|
return true;
|
|
17313
17762
|
}
|
|
@@ -17593,10 +18042,12 @@ ${this.exitMessage()}`);
|
|
|
17593
18042
|
*/
|
|
17594
18043
|
answerServerRequest(msg, emit) {
|
|
17595
18044
|
const method = msg.method;
|
|
17596
|
-
|
|
18045
|
+
const params = msg.params ?? {};
|
|
18046
|
+
if (CODEX_ORDEWELL.isOrdewellAsk({ method, params })) {
|
|
17597
18047
|
this.rpc.respond(msg.id, { action: "accept", content: {} });
|
|
17598
18048
|
return;
|
|
17599
18049
|
}
|
|
18050
|
+
if (this.holdMcpToolApproval(msg, method, params, emit)) return;
|
|
17600
18051
|
const result = DECLINE_RESULTS[method];
|
|
17601
18052
|
if (result) {
|
|
17602
18053
|
this.rpc.respond(msg.id, result);
|
|
@@ -17608,9 +18059,30 @@ ${this.exitMessage()}`);
|
|
|
17608
18059
|
type: "permission_request",
|
|
17609
18060
|
id: String(msg.id ?? ""),
|
|
17610
18061
|
name: method.split("/").pop() ?? method,
|
|
17611
|
-
detail: JSON.stringify(msg.params ?? {})
|
|
18062
|
+
detail: JSON.stringify(msg.params ?? {}),
|
|
18063
|
+
decided: { decision: "deny" }
|
|
17612
18064
|
});
|
|
17613
18065
|
}
|
|
18066
|
+
/**
|
|
18067
|
+
* A planner's approval for a tool of the user's own MCP server, held for the
|
|
18068
|
+
* planner's envelope to answer (ADR-0026). Codex names the server; the tool
|
|
18069
|
+
* is in its question, or else the call it last started on that server.
|
|
18070
|
+
*/
|
|
18071
|
+
holdMcpToolApproval(msg, method, params, emit) {
|
|
18072
|
+
if (method !== "mcpServer/elicitation/request") return false;
|
|
18073
|
+
const meta = params._meta;
|
|
18074
|
+
const server = params.serverName;
|
|
18075
|
+
if (meta?.codex_approval_kind !== "mcp_tool_call" || typeof server !== "string") return false;
|
|
18076
|
+
const asked = typeof params.message === "string" ? /run tool "([^"]+)"/.exec(params.message)?.[1] : void 0;
|
|
18077
|
+
const tool2 = asked ?? this.mcpCallsInProgress.get(server);
|
|
18078
|
+
if (!tool2) return false;
|
|
18079
|
+
const id = String(msg.id);
|
|
18080
|
+
const input = typeof meta.tool_params === "object" && meta.tool_params !== null ? meta.tool_params : {};
|
|
18081
|
+
const scope = `mcp__${server}__${tool2}`;
|
|
18082
|
+
this.openPermissions.set(id, { requestId: msg.id, method, params });
|
|
18083
|
+
emit({ type: "permission_request", id, name: scope, detail: JSON.stringify(input), input, ask: { kind: "mcp", scope, tool: tool2, server } });
|
|
18084
|
+
return true;
|
|
18085
|
+
}
|
|
17614
18086
|
/** A tool item entering `inProgress` — announce the call so the timeline moves. */
|
|
17615
18087
|
emitItemStart(item, emit, subagentId) {
|
|
17616
18088
|
if (!item?.id) return;
|
|
@@ -17622,6 +18094,7 @@ ${this.exitMessage()}`);
|
|
|
17622
18094
|
emit({ type: "tool_call", id: item.id, name: "shell", args: { command: item.command, cwd: item.cwd }, ...subagentId ? { subagentId } : {} });
|
|
17623
18095
|
return;
|
|
17624
18096
|
case "mcpToolCall":
|
|
18097
|
+
if (item.server && item.tool) this.mcpCallsInProgress.set(item.server, item.tool);
|
|
17625
18098
|
emit({ type: "tool_call", id: item.id, name: shownToolName(item), args: item.arguments ?? {}, ...subagentId ? { subagentId } : {} });
|
|
17626
18099
|
return;
|
|
17627
18100
|
case "dynamicToolCall":
|
|
@@ -17794,6 +18267,55 @@ var import_crypto9 = require("crypto");
|
|
|
17794
18267
|
// src/services/harness/openCodeTransport.ts
|
|
17795
18268
|
var import_crypto8 = require("crypto");
|
|
17796
18269
|
var STREAM_CONNECT_TIMEOUT_MS = 5e3;
|
|
18270
|
+
var OPENCODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
18271
|
+
"bash",
|
|
18272
|
+
"edit",
|
|
18273
|
+
"write",
|
|
18274
|
+
"patch",
|
|
18275
|
+
"apply_patch",
|
|
18276
|
+
"multiedit",
|
|
18277
|
+
"read",
|
|
18278
|
+
"glob",
|
|
18279
|
+
"grep",
|
|
18280
|
+
"list",
|
|
18281
|
+
"lsp",
|
|
18282
|
+
"task",
|
|
18283
|
+
"question",
|
|
18284
|
+
"todowrite",
|
|
18285
|
+
"todoread",
|
|
18286
|
+
"skill",
|
|
18287
|
+
"websearch",
|
|
18288
|
+
"codesearch",
|
|
18289
|
+
"webfetch",
|
|
18290
|
+
"external_directory",
|
|
18291
|
+
"doom_loop",
|
|
18292
|
+
"invalid",
|
|
18293
|
+
"batch",
|
|
18294
|
+
"shell",
|
|
18295
|
+
"subagent",
|
|
18296
|
+
"browser",
|
|
18297
|
+
"execute",
|
|
18298
|
+
"search",
|
|
18299
|
+
"opencode_read_mcp_resource",
|
|
18300
|
+
"opencode_list_mcp_resources"
|
|
18301
|
+
]);
|
|
18302
|
+
function openCodePlannerAsk(permission, details, servers) {
|
|
18303
|
+
const meta = details.metadata ?? {};
|
|
18304
|
+
const pattern = details.patterns?.[0];
|
|
18305
|
+
if (permission === "webfetch") {
|
|
18306
|
+
const url = typeof meta.url === "string" ? meta.url : pattern;
|
|
18307
|
+
return url ? { kind: "fetch", url } : { kind: "other" };
|
|
18308
|
+
}
|
|
18309
|
+
if (permission === "external_directory") {
|
|
18310
|
+
if (typeof meta.filepath === "string") return { kind: "path", path: meta.filepath, directory: false };
|
|
18311
|
+
const dir = typeof meta.parentDir === "string" ? meta.parentDir : pattern?.replace(/[\\/]\*$/, "");
|
|
18312
|
+
return dir ? { kind: "path", path: dir, directory: true } : { kind: "other" };
|
|
18313
|
+
}
|
|
18314
|
+
if (OPENCODE_BUILTINS.has(permission)) return { kind: "other" };
|
|
18315
|
+
const server = servers.filter((s) => permission.startsWith(`${s}_`)).sort((a, b) => b.length - a.length)[0];
|
|
18316
|
+
if (!server) return { kind: "other" };
|
|
18317
|
+
return { kind: "mcp", scope: permission, tool: permission.slice(server.length + 1), server };
|
|
18318
|
+
}
|
|
17797
18319
|
function splitModelId(id) {
|
|
17798
18320
|
const slash = id.indexOf("/");
|
|
17799
18321
|
if (slash <= 0 || slash === id.length - 1) return null;
|
|
@@ -17887,7 +18409,6 @@ var PendingSteers = class {
|
|
|
17887
18409
|
}
|
|
17888
18410
|
};
|
|
17889
18411
|
var ALLOW = { decision: "allow" };
|
|
17890
|
-
var DENY = { decision: "deny" };
|
|
17891
18412
|
var OpenCodePermissions = class {
|
|
17892
18413
|
constructor(reply2, ordewell) {
|
|
17893
18414
|
this.reply = reply2;
|
|
@@ -17898,23 +18419,8 @@ var OpenCodePermissions = class {
|
|
|
17898
18419
|
/** Requests waiting for an answer, by request id, with the session that asked. */
|
|
17899
18420
|
open = /* @__PURE__ */ new Map();
|
|
17900
18421
|
/**
|
|
17901
|
-
* A
|
|
17902
|
-
*
|
|
17903
|
-
* announced so the timeline shows the planner reaching for something it may
|
|
17904
|
-
* not have. The planner's own submission path, which the allow rule should
|
|
17905
|
-
* already have settled, is the one request it may not be refused.
|
|
17906
|
-
*/
|
|
17907
|
-
refuse(request, sessionId, seen, onEvent) {
|
|
17908
|
-
if (!firstSight(request.id, seen)) return;
|
|
17909
|
-
if (this.isOrdewellTool(request.name)) {
|
|
17910
|
-
this.send(request.id, sessionId, ALLOW);
|
|
17911
|
-
return;
|
|
17912
|
-
}
|
|
17913
|
-
onEvent(request);
|
|
17914
|
-
this.send(request.id, sessionId, DENY);
|
|
17915
|
-
}
|
|
17916
|
-
/**
|
|
17917
|
-
* A task's request. Under a mode whose manifest sets `approvals: auto` it is
|
|
18422
|
+
* A task's request, or a planner's (with `autoApprove` false) for the
|
|
18423
|
+
* planner's envelope to answer (ADR-0026). Under a mode whose manifest sets `approvals: auto` it is
|
|
17918
18424
|
* answered at once with what `opencode run --auto` answers, so the same plan
|
|
17919
18425
|
* follows the manifest (ADR-0001), and announced already
|
|
17920
18426
|
* decided so the log still shows it. Any other mode leaves it open for an
|
|
@@ -18093,10 +18599,7 @@ async function streamEvents(connect, signal, onFrame, onConnected, onActivity) {
|
|
|
18093
18599
|
// src/services/harness/OpenCodeV2.ts
|
|
18094
18600
|
var ACTIVE_POLL_INTERVAL_MS = 1e3;
|
|
18095
18601
|
var IDLE_POLLS_BEFORE_START = 3;
|
|
18096
|
-
var PLANNER_RULES = [
|
|
18097
|
-
{ action: "question", resource: "*", effect: "deny" },
|
|
18098
|
-
{ action: "edit", resource: "*", effect: "deny" }
|
|
18099
|
-
];
|
|
18602
|
+
var PLANNER_RULES = Object.entries(OPENCODE_PLANNER_PERMISSION).map(([action, effect]) => ({ action, resource: "*", effect }));
|
|
18100
18603
|
var TASK_RULES = [{ action: "question", resource: "*", effect: "deny" }];
|
|
18101
18604
|
function newTurnState() {
|
|
18102
18605
|
return { ...streamTurn(), toolNames: /* @__PURE__ */ new Map(), stepModels: /* @__PURE__ */ new Map(), briefs: /* @__PURE__ */ new Map(), streamedMessages: /* @__PURE__ */ new Set() };
|
|
@@ -18143,6 +18646,7 @@ var OpenCodeV2 = class {
|
|
|
18143
18646
|
return this.sessionId;
|
|
18144
18647
|
}
|
|
18145
18648
|
async start() {
|
|
18649
|
+
await this.exposeOrdewellTools();
|
|
18146
18650
|
const resume = this.opts.resumeSessionId;
|
|
18147
18651
|
if (resume) {
|
|
18148
18652
|
const existing = await this.host.json("GET", `/api/session/${resume}`).catch(() => null);
|
|
@@ -18164,6 +18668,19 @@ var OpenCodeV2 = class {
|
|
|
18164
18668
|
this.sessionId = created.data.id;
|
|
18165
18669
|
await this.applyInstructions();
|
|
18166
18670
|
}
|
|
18671
|
+
/**
|
|
18672
|
+
* 2.x lists an MCP server's tools inside its code-mode `execute` tool, where
|
|
18673
|
+
* a model told to call `task_complete` does not look for it. The server is
|
|
18674
|
+
* replaced at runtime with `codemode: false`, which lists its tools as tools
|
|
18675
|
+
* of their own: the config it started with is in the 1.x shape both versions
|
|
18676
|
+
* read, and that shape drops the setting. If the route is gone, the tools
|
|
18677
|
+
* are still reachable through `execute`.
|
|
18678
|
+
*/
|
|
18679
|
+
async exposeOrdewellTools() {
|
|
18680
|
+
const mcp = this.opts.mcp;
|
|
18681
|
+
if (!mcp) return;
|
|
18682
|
+
await this.host.request("PUT", `/api/experimental/mcp/${mcp.name}`, { config: { type: "remote", url: mcp.url, headers: mcp.headers, codemode: false } }).catch(() => void 0);
|
|
18683
|
+
}
|
|
18167
18684
|
/** Allow the Ordewell server's tools, so a call never waits on a person (ADR-0022, S3). */
|
|
18168
18685
|
ordewellRules() {
|
|
18169
18686
|
return this.opts.mcp ? OPENCODE_ORDEWELL.sessionRules() : [];
|
|
@@ -18424,12 +18941,14 @@ var OpenCodeV2 = class {
|
|
|
18424
18941
|
...ask.source?.id ? { toolUseId: ask.source.id } : {}
|
|
18425
18942
|
};
|
|
18426
18943
|
}
|
|
18427
|
-
/** A planner's request is
|
|
18944
|
+
/** A planner's request is held for its envelope (ADR-0026); a task's is answered at once or left open for a card. */
|
|
18428
18945
|
onPermissionAsked(ask, session, state, onEvent) {
|
|
18429
18946
|
const request = this.permissionRequest(ask);
|
|
18430
18947
|
if (!request) return;
|
|
18431
|
-
if (this.role !== "task")
|
|
18432
|
-
|
|
18948
|
+
if (this.role !== "task") {
|
|
18949
|
+
const target = openCodePlannerAsk(request.name, { patterns: ask.resources }, this.host.mcpServers?.() ?? []);
|
|
18950
|
+
this.permissions.ask({ ...request, ask: target }, session, false, state.seen, onEvent);
|
|
18951
|
+
} else this.permissions.ask(request, session, this.opts.kind === "task" && autoApproves(this.opts), state.seen, onEvent);
|
|
18433
18952
|
}
|
|
18434
18953
|
};
|
|
18435
18954
|
|
|
@@ -18447,7 +18966,9 @@ var DISABLED_TOOLS = {
|
|
|
18447
18966
|
edit: false,
|
|
18448
18967
|
write: false,
|
|
18449
18968
|
apply_patch: false,
|
|
18450
|
-
todowrite: false
|
|
18969
|
+
todowrite: false,
|
|
18970
|
+
bash: false,
|
|
18971
|
+
task: false
|
|
18451
18972
|
};
|
|
18452
18973
|
var TASK_DISABLED_TOOLS = { question: false };
|
|
18453
18974
|
function describeError(err) {
|
|
@@ -18518,6 +19039,8 @@ var OpenCodeAdapter = class {
|
|
|
18518
19039
|
taskTurn = null;
|
|
18519
19040
|
/** An abort was posted during the current task turn, so the idle that follows ends it as interrupted. */
|
|
18520
19041
|
interruptRequested = false;
|
|
19042
|
+
/** The user's MCP servers, as `/mcp` last listed them: what tells a planner's MCP tool request from a built-in's (ADR-0026). */
|
|
19043
|
+
mcpServers = [];
|
|
18521
19044
|
permissions = new OpenCodePermissions(
|
|
18522
19045
|
(id, sessionId, decision) => this.replyPermission(id, sessionId, permissionReply(decision, "reply")),
|
|
18523
19046
|
() => this.ordewell
|
|
@@ -18550,7 +19073,8 @@ var OpenCodeAdapter = class {
|
|
|
18550
19073
|
command: "opencode",
|
|
18551
19074
|
args: ["serve", "--hostname", "127.0.0.1", "--port", "0"],
|
|
18552
19075
|
env: (workspaceEnv) => {
|
|
18553
|
-
const
|
|
19076
|
+
const existing = workspaceEnv[OPENCODE_CONFIG_VARIABLE] ?? process.env[OPENCODE_CONFIG_VARIABLE];
|
|
19077
|
+
const ordewellConfig = opts.kind === "planner" ? OPENCODE_ORDEWELL.configContent(existing, opts.mcp ?? null, "planner") ?? OPENCODE_ORDEWELL.configContent(void 0, opts.mcp ?? null, "planner") : opts.mcp ? OPENCODE_ORDEWELL.configContent(existing, opts.mcp, "task") : null;
|
|
18554
19078
|
if (opts.mcp && ordewellConfig === null) {
|
|
18555
19079
|
console.error("[opencode] OPENCODE_CONFIG_CONTENT is not a JSON object, so the Ordewell tools were not injected.");
|
|
18556
19080
|
}
|
|
@@ -18608,7 +19132,8 @@ var OpenCodeAdapter = class {
|
|
|
18608
19132
|
openEvents: (signal) => this.deps.fetch(`${this.baseUrl}/api/event`, { signal, headers: this.headers() }),
|
|
18609
19133
|
processEnded: this.processEnded,
|
|
18610
19134
|
isExited: () => this.exited,
|
|
18611
|
-
exitMessage: () => this.exitMessage()
|
|
19135
|
+
exitMessage: () => this.exitMessage(),
|
|
19136
|
+
mcpServers: () => this.mcpServers
|
|
18612
19137
|
}, { ...opts, mcp: this.ordewell ?? void 0 }, this.role());
|
|
18613
19138
|
await this.v2.start();
|
|
18614
19139
|
this.sessionId = this.v2.nativeSessionId();
|
|
@@ -19074,7 +19599,15 @@ var OpenCodeAdapter = class {
|
|
|
19074
19599
|
const sessionId = ask.sessionID ?? this.sessionId ?? "";
|
|
19075
19600
|
const name = permissionName(ask);
|
|
19076
19601
|
if (!this.task) {
|
|
19077
|
-
this.permissions.
|
|
19602
|
+
this.permissions.ask({
|
|
19603
|
+
type: "permission_request",
|
|
19604
|
+
id,
|
|
19605
|
+
name,
|
|
19606
|
+
detail: permissionDetail(ask),
|
|
19607
|
+
input: ask.metadata ?? {},
|
|
19608
|
+
...ask.tool?.callID ? { toolUseId: ask.tool.callID } : {},
|
|
19609
|
+
ask: openCodePlannerAsk(name, ask, this.mcpServers)
|
|
19610
|
+
}, sessionId, false, seen, onEvent);
|
|
19078
19611
|
return;
|
|
19079
19612
|
}
|
|
19080
19613
|
const request = {
|
|
@@ -19131,10 +19664,21 @@ var OpenCodeAdapter = class {
|
|
|
19131
19664
|
if (!ordewell || !this.baseUrl) return false;
|
|
19132
19665
|
return awaitAttach(async () => {
|
|
19133
19666
|
if (this.exited) return "failed";
|
|
19134
|
-
const servers = await this.json("GET", "/mcp").catch(() => null);
|
|
19667
|
+
const servers = this.v2 ? await this.v2McpStatus() : await this.json("GET", "/mcp").catch(() => null);
|
|
19668
|
+
if (servers) this.mcpServers = Object.keys(servers).filter((name) => name !== ordewell.name);
|
|
19135
19669
|
return OPENCODE_ORDEWELL.attachState(servers?.[ordewell.name]?.status);
|
|
19136
19670
|
}, MCP_ATTACH_TIMEOUT_MS3, MCP_STATUS_POLL_MS2);
|
|
19137
19671
|
}
|
|
19672
|
+
/** 2.x lists its servers as `{ name, status: { status } }`, and only once it has started connecting them. */
|
|
19673
|
+
async v2McpStatus() {
|
|
19674
|
+
const listed2 = await this.json("GET", "/api/mcp").catch(() => null);
|
|
19675
|
+
if (!Array.isArray(listed2?.data)) return null;
|
|
19676
|
+
const servers = {};
|
|
19677
|
+
for (const { name, status } of listed2.data) {
|
|
19678
|
+
if (typeof name === "string") servers[name] = typeof status?.status === "string" ? { status: status.status } : {};
|
|
19679
|
+
}
|
|
19680
|
+
return servers;
|
|
19681
|
+
}
|
|
19138
19682
|
dispose() {
|
|
19139
19683
|
if (this.disposed) return;
|
|
19140
19684
|
this.disposed = true;
|
|
@@ -21721,6 +22265,10 @@ function plannerToolHandler(host) {
|
|
|
21721
22265
|
}
|
|
21722
22266
|
return { text: skill.content };
|
|
21723
22267
|
},
|
|
22268
|
+
async runCommand({ command }, { signal }) {
|
|
22269
|
+
const outcome = await host.runCommand(command, signal);
|
|
22270
|
+
return { text: outcome.output || (outcome.success ? "(no output)" : "The command failed with no output."), ...outcome.success ? {} : { isError: true } };
|
|
22271
|
+
},
|
|
21724
22272
|
async listRunners() {
|
|
21725
22273
|
const catalog = await host.liveCatalog();
|
|
21726
22274
|
return answer({
|
|
@@ -21970,6 +22518,47 @@ var TurnStream = class {
|
|
|
21970
22518
|
}
|
|
21971
22519
|
};
|
|
21972
22520
|
|
|
22521
|
+
// src/services/harness/plannerPermissions.ts
|
|
22522
|
+
var READ_ONLY_DENIAL = "The Ordewell planner is read-only: it changes no files. Use run_command for commands; changes belong to the plan's tasks.";
|
|
22523
|
+
var OPS_HINT = "Continue without it. If it would change something outside the repository, add it to the plan as an ops task; if it only reads, ask the user to approve it or add it to the planner allowlist.";
|
|
22524
|
+
var ALLOW2 = { decision: "allow" };
|
|
22525
|
+
var SUBJECT_MAX_CHARS = 400;
|
|
22526
|
+
function deny(note) {
|
|
22527
|
+
return { decision: "deny", note };
|
|
22528
|
+
}
|
|
22529
|
+
async function decidePlannerPermission(ask, input, gate) {
|
|
22530
|
+
if (!ask || ask.kind === "other") return deny(READ_ONLY_DENIAL);
|
|
22531
|
+
if (!gate) return deny("Nobody can approve this here. Continue without it.");
|
|
22532
|
+
switch (ask.kind) {
|
|
22533
|
+
case "mcp": {
|
|
22534
|
+
const args = JSON.stringify(input);
|
|
22535
|
+
const subject = `${ask.server ?? "MCP"} \xB7 ${ask.tool} ${args.length > SUBJECT_MAX_CHARS ? `${args.slice(0, SUBJECT_MAX_CHARS)}\u2026` : args}`;
|
|
22536
|
+
const granted = await gate.approval.request({
|
|
22537
|
+
kind: "mcp_tool",
|
|
22538
|
+
subject,
|
|
22539
|
+
scope: ask.scope,
|
|
22540
|
+
tool: ask.tool,
|
|
22541
|
+
detail: `The planner wants to use "${ask.tool}"${ask.server ? ` from ${ask.server}` : ""}. It may change things outside the repository.`
|
|
22542
|
+
});
|
|
22543
|
+
return granted ? ALLOW2 : deny(`Not approved: ${ask.scope}. ${OPS_HINT}`);
|
|
22544
|
+
}
|
|
22545
|
+
case "fetch":
|
|
22546
|
+
if (!gate.fetcher) return deny("This fetch cannot be approved here.");
|
|
22547
|
+
return await gate.fetcher.confirm(ask.url) ? ALLOW2 : deny(`Fetch denied (blocked host or not approved): ${ask.url}. ${OPS_HINT}`);
|
|
22548
|
+
case "path": {
|
|
22549
|
+
const { abs, inside } = resolveWithin(gate.workspaceRoot, ask.path);
|
|
22550
|
+
if (inside) return ALLOW2;
|
|
22551
|
+
const granted = await gate.approval.request({
|
|
22552
|
+
kind: "external_path",
|
|
22553
|
+
subject: abs,
|
|
22554
|
+
scope: grantScopeFor(abs, ask.directory ? "directory" : "file"),
|
|
22555
|
+
detail: `Planner research wants to read ${abs}, outside the workspace (${gate.workspaceRoot}).`
|
|
22556
|
+
});
|
|
22557
|
+
return granted ? ALLOW2 : deny(`Access denied: "${abs}" is outside the workspace and was not approved.`);
|
|
22558
|
+
}
|
|
22559
|
+
}
|
|
22560
|
+
}
|
|
22561
|
+
|
|
21973
22562
|
// src/services/harness/CliAgentAiService.ts
|
|
21974
22563
|
var MAX_AGENT_WAITS = 2;
|
|
21975
22564
|
function waitForAgentsPrompt(running) {
|
|
@@ -22013,6 +22602,7 @@ var CliAgentAiService = class {
|
|
|
22013
22602
|
* every session boundary — nothing from one goal may reach the next.
|
|
22014
22603
|
*/
|
|
22015
22604
|
lastNativeSessionId = null;
|
|
22605
|
+
/** `gate` is what the agent's own tool requests are decided against; absent, only Ordewell's tools are allowed (ADR-0026). */
|
|
22016
22606
|
conversation = null;
|
|
22017
22607
|
mcpServer;
|
|
22018
22608
|
/** The planner token the running process was spawned with; revoked with the process (ADR-0022, A3). */
|
|
@@ -22081,7 +22671,8 @@ var CliAgentAiService = class {
|
|
|
22081
22671
|
};
|
|
22082
22672
|
const tools = req.plannerTools;
|
|
22083
22673
|
await this.startAdapter(startOptions, tools);
|
|
22084
|
-
|
|
22674
|
+
const gate = req.approval ? { approval: req.approval, fetcher: req.fetcher, workspaceRoot: startOptions.cwd } : void 0;
|
|
22675
|
+
this.conversation = { startOptions, tools, gate };
|
|
22085
22676
|
const opening = this.openingMessage(req);
|
|
22086
22677
|
return this.runConversation(opening, req.onProgress, req.signal);
|
|
22087
22678
|
}
|
|
@@ -22171,6 +22762,8 @@ var CliAgentAiService = class {
|
|
|
22171
22762
|
segmentId = (0, import_uuid6.v4)();
|
|
22172
22763
|
};
|
|
22173
22764
|
const streamedThinking = /* @__PURE__ */ new Set();
|
|
22765
|
+
const deniedCalls = /* @__PURE__ */ new Set();
|
|
22766
|
+
const gate = this.conversation?.gate;
|
|
22174
22767
|
const subagentUsage = /* @__PURE__ */ new Map();
|
|
22175
22768
|
let error;
|
|
22176
22769
|
let stepIndex = 0;
|
|
@@ -22225,7 +22818,7 @@ var CliAgentAiService = class {
|
|
|
22225
22818
|
return;
|
|
22226
22819
|
}
|
|
22227
22820
|
case "tool_result":
|
|
22228
|
-
settle(event.id, event.output, event.success, event.success ? "success" : "failure", event.subagentId);
|
|
22821
|
+
settle(event.id, event.output, event.success, event.success ? "success" : deniedCalls.has(event.id) ? "denied" : "failure", event.subagentId);
|
|
22229
22822
|
return;
|
|
22230
22823
|
case "usage": {
|
|
22231
22824
|
const { subagentId } = event.record;
|
|
@@ -22253,18 +22846,28 @@ var CliAgentAiService = class {
|
|
|
22253
22846
|
backgroundAgents++;
|
|
22254
22847
|
return;
|
|
22255
22848
|
case "permission_request": {
|
|
22256
|
-
const
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
pendingCalls.
|
|
22260
|
-
|
|
22849
|
+
const announceDenial = (output) => {
|
|
22850
|
+
const mapped = mapAgentTool(event.name);
|
|
22851
|
+
const args = JSON.stringify({ detail: event.detail });
|
|
22852
|
+
if (!pendingCalls.has(event.id)) {
|
|
22853
|
+
pendingCalls.set(event.id, { tool: mapped.tool, toolLabel: mapped.toolLabel, args });
|
|
22854
|
+
onProgress({ type: "tool_call", tool: mapped.tool, toolLabel: mapped.toolLabel, toolArgs: args, toolCallId: event.id });
|
|
22855
|
+
}
|
|
22856
|
+
settle(event.id, output, false, "denied");
|
|
22857
|
+
};
|
|
22858
|
+
if (!event.decided && adapter.answerPermission) {
|
|
22859
|
+
const { id, toolUseId } = event;
|
|
22860
|
+
void decidePlannerPermission(event.ask, event.input ?? {}, gate).catch(() => ({ decision: "deny" })).then((decision) => {
|
|
22861
|
+
if (decision.decision === "deny") {
|
|
22862
|
+
if (toolUseId) deniedCalls.add(toolUseId);
|
|
22863
|
+
else announceDenial(`Access denied: "${event.name}" was refused. ${decision.note ?? ""}`.trim());
|
|
22864
|
+
}
|
|
22865
|
+
adapter.answerPermission?.(id, decision);
|
|
22866
|
+
});
|
|
22867
|
+
return;
|
|
22261
22868
|
}
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
`Access denied: the planner runs read-only, so "${event.name}" was refused. Mutation belongs to the runners that execute the plan.`,
|
|
22265
|
-
false,
|
|
22266
|
-
"denied"
|
|
22267
|
-
);
|
|
22869
|
+
if (event.decided?.decision !== "deny") return;
|
|
22870
|
+
announceDenial(`Access denied: the planner runs read-only, so "${event.name}" was refused. Mutation belongs to the runners that execute the plan.`);
|
|
22268
22871
|
return;
|
|
22269
22872
|
}
|
|
22270
22873
|
case "error":
|
|
@@ -23634,7 +24237,7 @@ var HttpWebFetcher = class {
|
|
|
23634
24237
|
}
|
|
23635
24238
|
async fetch(url) {
|
|
23636
24239
|
if (!await this.confirm(url)) {
|
|
23637
|
-
return { success: false, output: `Fetch denied (blocked host or not approved): ${url}
|
|
24240
|
+
return { success: false, output: `Fetch denied (blocked host or not approved): ${url}. Continue without it, or ask the user to approve this origin or add it to the planner allowlist.`, truncated: false };
|
|
23638
24241
|
}
|
|
23639
24242
|
return this.get(url, "Fetch");
|
|
23640
24243
|
}
|
|
@@ -24736,8 +25339,9 @@ function createSession(deps) {
|
|
|
24736
25339
|
checkpointQuestion: (taskId) => orchestrator.getCheckpointQuestion(taskId)
|
|
24737
25340
|
});
|
|
24738
25341
|
const approvalPolicy = new ApprovalPolicy({
|
|
24739
|
-
|
|
24740
|
-
|
|
25342
|
+
// Read per request, so a change of autonomy level or allowlist applies to the next call.
|
|
25343
|
+
mode: () => effectiveApprovalMode(deps.config.approvalMode, deps.config.autonomousMode),
|
|
25344
|
+
preApproved: () => [...deps.config.approvalDefaults ? DEFAULT_PLANNER_ALLOWLIST : [], ...deps.config.approvalPreApproved],
|
|
24741
25345
|
ask: (req) => approvals.ask(req),
|
|
24742
25346
|
// The interactive path (`asked`) already broadcasts approval_request +
|
|
24743
25347
|
// approval_settled; only the silent sources need a signal, or a
|
|
@@ -24840,7 +25444,8 @@ var Session = class {
|
|
|
24840
25444
|
read: (signature, answer2) => this.conversation.read(signature, answer2),
|
|
24841
25445
|
liveOutput: (taskId, opts) => this.orchestrator.getLiveOutput(taskId, opts),
|
|
24842
25446
|
lastAttempt: (taskId) => lastAttemptDigest(this.taskLogLocation, taskId),
|
|
24843
|
-
taskSkills: () => this.workspaceSkills()
|
|
25447
|
+
taskSkills: () => this.workspaceSkills(),
|
|
25448
|
+
runCommand: (command, signal) => this.fsAdapter.bash(command, signal)
|
|
24844
25449
|
});
|
|
24845
25450
|
constructor(parts) {
|
|
24846
25451
|
this.config = parts.config;
|
|
@@ -25297,6 +25902,7 @@ var Session = class {
|
|
|
25297
25902
|
contextWindow: this.modelResolver.contextWindowFor?.(this.config.orchestratorModel),
|
|
25298
25903
|
fs: this.fsAdapter,
|
|
25299
25904
|
fetcher: this.fetcher,
|
|
25905
|
+
approval: this.approvalPolicy,
|
|
25300
25906
|
plannerTools: { sessionId: this.sessionId, handler: this.plannerTools },
|
|
25301
25907
|
skills: this.workspaceSkills().listSkills()
|
|
25302
25908
|
};
|
|
@@ -26201,8 +26807,8 @@ function taskRowView(task, context = {}) {
|
|
|
26201
26807
|
function findNamed(tasks, id) {
|
|
26202
26808
|
for (const task of tasks) {
|
|
26203
26809
|
if (task.id === id) return task;
|
|
26204
|
-
const
|
|
26205
|
-
if (
|
|
26810
|
+
const nested2 = findNamed(task.subtasks ?? [], id);
|
|
26811
|
+
if (nested2) return nested2;
|
|
26206
26812
|
}
|
|
26207
26813
|
return void 0;
|
|
26208
26814
|
}
|
|
@@ -27471,6 +28077,7 @@ var PlannerModelMemory = class {
|
|
|
27471
28077
|
DAEMON_SUBPROTOCOL,
|
|
27472
28078
|
DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
|
|
27473
28079
|
DEFAULT_MAX_PARALLEL,
|
|
28080
|
+
DEFAULT_PLANNER_ALLOWLIST,
|
|
27474
28081
|
DEFAULT_RUNNERS,
|
|
27475
28082
|
DENY_ALL,
|
|
27476
28083
|
EMPTY_CONVERSATION,
|
|
@@ -27607,6 +28214,7 @@ var PlannerModelMemory = class {
|
|
|
27607
28214
|
coerceAssignments,
|
|
27608
28215
|
collectDirectDependencyOutputs,
|
|
27609
28216
|
collectProviderCredentials,
|
|
28217
|
+
commandAllowedBy,
|
|
27610
28218
|
composeAugmentedPrompt,
|
|
27611
28219
|
composeContinuationPrompt,
|
|
27612
28220
|
configuredProviders,
|
|
@@ -27633,6 +28241,7 @@ var PlannerModelMemory = class {
|
|
|
27633
28241
|
discoverGeminiModels,
|
|
27634
28242
|
drainNext,
|
|
27635
28243
|
effectiveAllowlist,
|
|
28244
|
+
effectiveApprovalMode,
|
|
27636
28245
|
emptyWarnings,
|
|
27637
28246
|
enabledRunners,
|
|
27638
28247
|
ensureDir,
|
|
@@ -27715,6 +28324,8 @@ var PlannerModelMemory = class {
|
|
|
27715
28324
|
outputLines,
|
|
27716
28325
|
outputPreview,
|
|
27717
28326
|
ownerOnlyConfigFile,
|
|
28327
|
+
parseAllowlist,
|
|
28328
|
+
parseApprovalModeSetting,
|
|
27718
28329
|
parseAutonomyLevel,
|
|
27719
28330
|
parseMaxParallel,
|
|
27720
28331
|
parsePartialPlan,
|
|
@@ -27772,6 +28383,7 @@ var PlannerModelMemory = class {
|
|
|
27772
28383
|
savePrdMarkdown,
|
|
27773
28384
|
saveSession,
|
|
27774
28385
|
saveState,
|
|
28386
|
+
scopeAllowed,
|
|
27775
28387
|
serializePlan,
|
|
27776
28388
|
serializeTask,
|
|
27777
28389
|
serializeTaskStatus,
|
|
@@ -27809,6 +28421,7 @@ var PlannerModelMemory = class {
|
|
|
27809
28421
|
toTaskLogEvent,
|
|
27810
28422
|
tokenSubprotocols,
|
|
27811
28423
|
tokensMatch,
|
|
28424
|
+
toolAllowed,
|
|
27812
28425
|
toolHeadline,
|
|
27813
28426
|
trimToolOutput,
|
|
27814
28427
|
truncateCheckpointSummary,
|