@mrciphersmith/keryx 0.3.67 → 0.3.68
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/README.md +12 -0
- package/dist/cli.js +1280 -90
- package/dist/core.js +18 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20107,7 +20107,7 @@ var package_default;
|
|
|
20107
20107
|
var init_package = __esm(() => {
|
|
20108
20108
|
package_default = {
|
|
20109
20109
|
name: "@mrciphersmith/keryx",
|
|
20110
|
-
version: "0.3.
|
|
20110
|
+
version: "0.3.68",
|
|
20111
20111
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
20112
20112
|
private: false,
|
|
20113
20113
|
publishConfig: {
|
|
@@ -81424,7 +81424,7 @@ ${shown.join(`
|
|
|
81424
81424
|
result = precomputedResult;
|
|
81425
81425
|
} else {
|
|
81426
81426
|
try {
|
|
81427
|
-
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks, untrustedOrigin, io.trustedMcpTools, io.mcpToolFingerprint, io.mcpToolDestructive, deps.unattended === true ? undefined : io.beforeMutation);
|
|
81427
|
+
result = await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.readOnly, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal, deps.busLeases, deps.hardDeny === undefined ? undefined : { check: deps.hardDeny, onDenied: io.onUnattendedDenial }, deps.hooks, untrustedOrigin, io.mcpGrantsApply?.() === false ? undefined : io.trustedMcpTools, io.mcpToolFingerprint, io.mcpToolDestructive, deps.unattended === true ? undefined : io.beforeMutation);
|
|
81428
81428
|
} catch (err) {
|
|
81429
81429
|
const message = err instanceof Error ? err.message : String(err);
|
|
81430
81430
|
result = { output: `${call.name} failed: ${message}`, isError: true };
|
|
@@ -91621,6 +91621,34 @@ function allowShellPattern(pattern, dir) {
|
|
|
91621
91621
|
}
|
|
91622
91622
|
return trimmed;
|
|
91623
91623
|
}
|
|
91624
|
+
function removeShellPattern(pattern, dir) {
|
|
91625
|
+
const wanted = pattern.trim();
|
|
91626
|
+
if (wanted.length === 0)
|
|
91627
|
+
return false;
|
|
91628
|
+
try {
|
|
91629
|
+
const file = shellPermissionsPath(dir);
|
|
91630
|
+
if (!existsSync36(file))
|
|
91631
|
+
return false;
|
|
91632
|
+
const read = readConfigFile(file);
|
|
91633
|
+
if (!read.ok)
|
|
91634
|
+
return false;
|
|
91635
|
+
const raw = JSON.parse(read.text);
|
|
91636
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
91637
|
+
return false;
|
|
91638
|
+
const allowRaw = raw.allow;
|
|
91639
|
+
if (!Array.isArray(allowRaw))
|
|
91640
|
+
return false;
|
|
91641
|
+
const kept = allowRaw.filter((entry) => !(typeof entry === "string" && entry.trim() === wanted));
|
|
91642
|
+
if (kept.length === allowRaw.length)
|
|
91643
|
+
return false;
|
|
91644
|
+
ensureKeryxConfigDir(path212.dirname(file));
|
|
91645
|
+
writeOwnerOnlyFile(file, `${JSON.stringify({ ...raw, allow: kept }, null, 2)}
|
|
91646
|
+
`);
|
|
91647
|
+
return true;
|
|
91648
|
+
} catch {
|
|
91649
|
+
return false;
|
|
91650
|
+
}
|
|
91651
|
+
}
|
|
91624
91652
|
function matchShellPattern(pattern, command) {
|
|
91625
91653
|
const p = pattern.trim();
|
|
91626
91654
|
const c = command.trim();
|
|
@@ -91844,7 +91872,16 @@ function evaluateShellApproval(input) {
|
|
|
91844
91872
|
const hookAsk = input.meta?.hookAsk === true;
|
|
91845
91873
|
const untrustedOrigin = input.meta?.untrustedOrigin === true;
|
|
91846
91874
|
const audit = io.loadAudit();
|
|
91847
|
-
|
|
91875
|
+
const current = new Set(audit.permissions.allow);
|
|
91876
|
+
const loadedBefore = loadedFromDisk.get(input.sessionAllow);
|
|
91877
|
+
if (loadedBefore !== undefined) {
|
|
91878
|
+
for (const pattern of loadedBefore) {
|
|
91879
|
+
if (!current.has(pattern))
|
|
91880
|
+
input.sessionAllow.delete(pattern);
|
|
91881
|
+
}
|
|
91882
|
+
}
|
|
91883
|
+
loadedFromDisk.set(input.sessionAllow, current);
|
|
91884
|
+
for (const pattern of current) {
|
|
91848
91885
|
input.sessionAllow.add(pattern);
|
|
91849
91886
|
}
|
|
91850
91887
|
const tampered = io.fingerprint() !== input.fingerprintAtStart;
|
|
@@ -91899,7 +91936,7 @@ function formatShellApprovalHints(evaled) {
|
|
|
91899
91936
|
}
|
|
91900
91937
|
return lines;
|
|
91901
91938
|
}
|
|
91902
|
-
var defaultApprovalIO;
|
|
91939
|
+
var defaultApprovalIO, loadedFromDisk;
|
|
91903
91940
|
var init_shell_approval = __esm(() => {
|
|
91904
91941
|
init_command_risk();
|
|
91905
91942
|
init_shell_permissions();
|
|
@@ -91907,6 +91944,7 @@ var init_shell_approval = __esm(() => {
|
|
|
91907
91944
|
loadAudit: () => loadShellPermissionsWithAudit(),
|
|
91908
91945
|
fingerprint: () => shellPermissionsFingerprint()
|
|
91909
91946
|
};
|
|
91947
|
+
loadedFromDisk = new WeakMap;
|
|
91910
91948
|
});
|
|
91911
91949
|
|
|
91912
91950
|
// src/mcp-servers/json-utils.ts
|
|
@@ -101030,9 +101068,34 @@ function parseRemoteConfig(value) {
|
|
|
101030
101068
|
if (!orphan.ok) {
|
|
101031
101069
|
return orphan;
|
|
101032
101070
|
}
|
|
101033
|
-
|
|
101034
|
-
if (
|
|
101035
|
-
|
|
101071
|
+
let runTimeoutMs = DEFAULT_RUN_TIMEOUT_MS;
|
|
101072
|
+
if (doc.runTimeoutMs !== undefined) {
|
|
101073
|
+
if (doc.runTimeoutMs === 0) {
|
|
101074
|
+
runTimeoutMs = 0;
|
|
101075
|
+
} else {
|
|
101076
|
+
const run = timeout("runTimeoutMs", DEFAULT_RUN_TIMEOUT_MS);
|
|
101077
|
+
if (!run.ok) {
|
|
101078
|
+
return { ok: false, reason: `remote config runTimeoutMs must be 0 (no limit) or an integer from 1 to ${MAX_TIMEOUT_MS3} (milliseconds)` };
|
|
101079
|
+
}
|
|
101080
|
+
runTimeoutMs = run.value;
|
|
101081
|
+
}
|
|
101082
|
+
}
|
|
101083
|
+
let permissionMode = DEFAULT_REMOTE_PERMISSION_MODE;
|
|
101084
|
+
if (doc.permissionMode !== undefined) {
|
|
101085
|
+
if (doc.permissionMode !== "ask" && doc.permissionMode !== "trust") {
|
|
101086
|
+
return { ok: false, reason: 'remote config permissionMode must be "ask" or "trust" (auto is not available here)' };
|
|
101087
|
+
}
|
|
101088
|
+
permissionMode = doc.permissionMode;
|
|
101089
|
+
}
|
|
101090
|
+
let approvalTimeoutMs = DEFAULT_APPROVAL_WAIT_MS;
|
|
101091
|
+
if (doc.approvalTimeoutMs !== undefined) {
|
|
101092
|
+
if (!isSafeInt(doc.approvalTimeoutMs) || doc.approvalTimeoutMs < MIN_APPROVAL_WAIT_MS || doc.approvalTimeoutMs > MAX_APPROVAL_WAIT_MS) {
|
|
101093
|
+
return {
|
|
101094
|
+
ok: false,
|
|
101095
|
+
reason: `remote config approvalTimeoutMs must be an integer from ${MIN_APPROVAL_WAIT_MS} to ${MAX_APPROVAL_WAIT_MS} (milliseconds)`
|
|
101096
|
+
};
|
|
101097
|
+
}
|
|
101098
|
+
approvalTimeoutMs = doc.approvalTimeoutMs;
|
|
101036
101099
|
}
|
|
101037
101100
|
if (doc.rendering !== undefined && !isRenderMode(doc.rendering)) {
|
|
101038
101101
|
return { ok: false, reason: `remote config rendering must be ${RENDER_MODE_CHOICES}` };
|
|
@@ -101044,7 +101107,9 @@ function parseRemoteConfig(value) {
|
|
|
101044
101107
|
chatId: doc.chatId,
|
|
101045
101108
|
allowedUserIds: [...new Set(ids)],
|
|
101046
101109
|
orphanMs: orphan.value,
|
|
101047
|
-
runTimeoutMs
|
|
101110
|
+
runTimeoutMs,
|
|
101111
|
+
permissionMode,
|
|
101112
|
+
approvalTimeoutMs,
|
|
101048
101113
|
...doc.rendering === undefined ? {} : { rendering: doc.rendering }
|
|
101049
101114
|
}
|
|
101050
101115
|
};
|
|
@@ -101072,7 +101137,46 @@ function saveRemoteConfig(config, dir) {
|
|
|
101072
101137
|
return checked;
|
|
101073
101138
|
}
|
|
101074
101139
|
ensureRemoteDir(dir);
|
|
101075
|
-
|
|
101140
|
+
const { permissionMode, approvalTimeoutMs, ...base } = checked.value;
|
|
101141
|
+
const onDisk = {
|
|
101142
|
+
...base,
|
|
101143
|
+
...config.permissionMode === undefined ? {} : { permissionMode },
|
|
101144
|
+
...config.approvalTimeoutMs === undefined ? {} : { approvalTimeoutMs }
|
|
101145
|
+
};
|
|
101146
|
+
writeOwnerOnlyFileAtomic(remoteConfigPath(dir), `${JSON.stringify(onDisk, null, 2)}
|
|
101147
|
+
`);
|
|
101148
|
+
return checked;
|
|
101149
|
+
}
|
|
101150
|
+
function updateRemotePolicy(patch, dir) {
|
|
101151
|
+
const file = remoteConfigPath(dir);
|
|
101152
|
+
const read = readConfigFile(file);
|
|
101153
|
+
if (!read.ok) {
|
|
101154
|
+
return {
|
|
101155
|
+
ok: false,
|
|
101156
|
+
reason: read.reason === "absent" ? `no remote-control config at ${file}; connect Telegram first` : `remote-control config at ${file} cannot be read (${read.reason})`
|
|
101157
|
+
};
|
|
101158
|
+
}
|
|
101159
|
+
let doc;
|
|
101160
|
+
try {
|
|
101161
|
+
doc = JSON.parse(read.text);
|
|
101162
|
+
} catch {
|
|
101163
|
+
return { ok: false, reason: `remote-control config at ${file} is not valid JSON` };
|
|
101164
|
+
}
|
|
101165
|
+
if (typeof doc !== "object" || doc === null || Array.isArray(doc)) {
|
|
101166
|
+
return { ok: false, reason: "remote config must be a JSON object" };
|
|
101167
|
+
}
|
|
101168
|
+
const next = { ...doc };
|
|
101169
|
+
if (patch.permissionMode !== undefined)
|
|
101170
|
+
next.permissionMode = patch.permissionMode;
|
|
101171
|
+
if (patch.runTimeoutMs !== undefined)
|
|
101172
|
+
next.runTimeoutMs = patch.runTimeoutMs;
|
|
101173
|
+
if (patch.approvalTimeoutMs !== undefined)
|
|
101174
|
+
next.approvalTimeoutMs = patch.approvalTimeoutMs;
|
|
101175
|
+
const checked = parseRemoteConfig(next);
|
|
101176
|
+
if (!checked.ok) {
|
|
101177
|
+
return checked;
|
|
101178
|
+
}
|
|
101179
|
+
writeOwnerOnlyFileAtomic(file, `${JSON.stringify(next, null, 2)}
|
|
101076
101180
|
`);
|
|
101077
101181
|
return checked;
|
|
101078
101182
|
}
|
|
@@ -101139,15 +101243,27 @@ function removeBotToken(dir) {
|
|
|
101139
101243
|
function removeRemoteConfig(dir) {
|
|
101140
101244
|
return removeFile2(remoteConfigPath(dir));
|
|
101141
101245
|
}
|
|
101142
|
-
var REMOTE_CONFIG_SCHEMA_VERSION = 1, DEFAULT_ORPHAN_MS, DEFAULT_RUN_TIMEOUT_MS, MAX_TIMEOUT_MS3, CONFIG_KEYS, TOKEN_SHAPE;
|
|
101246
|
+
var REMOTE_CONFIG_SCHEMA_VERSION = 1, DEFAULT_ORPHAN_MS, DEFAULT_RUN_TIMEOUT_MS = 0, LEGACY_RUN_TIMEOUT_MS, DEFAULT_APPROVAL_WAIT_MS, LEGACY_APPROVAL_WAIT_MS, MIN_APPROVAL_WAIT_MS = 30000, MAX_APPROVAL_WAIT_MS, DEFAULT_REMOTE_PERMISSION_MODE = "trust", MAX_TIMEOUT_MS3, CONFIG_KEYS, TOKEN_SHAPE;
|
|
101143
101247
|
var init_config11 = __esm(() => {
|
|
101144
101248
|
init_config_dir();
|
|
101145
101249
|
init_paths4();
|
|
101146
101250
|
init_rendering_mode();
|
|
101147
101251
|
DEFAULT_ORPHAN_MS = 10 * 60000;
|
|
101148
|
-
|
|
101252
|
+
LEGACY_RUN_TIMEOUT_MS = 30 * 60000;
|
|
101253
|
+
DEFAULT_APPROVAL_WAIT_MS = 15 * 60000;
|
|
101254
|
+
LEGACY_APPROVAL_WAIT_MS = 5 * 60000;
|
|
101255
|
+
MAX_APPROVAL_WAIT_MS = 60 * 60000;
|
|
101149
101256
|
MAX_TIMEOUT_MS3 = 7 * 24 * 60 * 60000;
|
|
101150
|
-
CONFIG_KEYS = new Set([
|
|
101257
|
+
CONFIG_KEYS = new Set([
|
|
101258
|
+
"schemaVersion",
|
|
101259
|
+
"chatId",
|
|
101260
|
+
"allowedUserIds",
|
|
101261
|
+
"orphanMs",
|
|
101262
|
+
"runTimeoutMs",
|
|
101263
|
+
"permissionMode",
|
|
101264
|
+
"approvalTimeoutMs",
|
|
101265
|
+
"rendering"
|
|
101266
|
+
]);
|
|
101151
101267
|
TOKEN_SHAPE = /^\d{5,}:[A-Za-z0-9_-]{20,}$/;
|
|
101152
101268
|
});
|
|
101153
101269
|
|
|
@@ -101219,6 +101335,308 @@ var init_rendering_command = __esm(() => {
|
|
|
101219
101335
|
};
|
|
101220
101336
|
});
|
|
101221
101337
|
|
|
101338
|
+
// src/commands/permissions-command.ts
|
|
101339
|
+
function loadPermissionsView(options = {}) {
|
|
101340
|
+
const audit = loadShellPermissionsWithAudit(options.dir);
|
|
101341
|
+
const stored = new Set([...audit.permissions.allow, ...audit.rejected.map((entry) => entry.pattern)]);
|
|
101342
|
+
const rows = [];
|
|
101343
|
+
const push = (pattern, kind, reason) => {
|
|
101344
|
+
rows.push({ n: rows.length + 1, pattern, kind, ...reason !== undefined ? { reason } : {} });
|
|
101345
|
+
};
|
|
101346
|
+
for (const pattern of audit.permissions.allow)
|
|
101347
|
+
push(pattern, "active");
|
|
101348
|
+
audit.rejected.forEach((entry) => push(entry.pattern, "inactive", entry.reason));
|
|
101349
|
+
for (const pattern of options.sessionAllow ?? []) {
|
|
101350
|
+
if (!stored.has(pattern))
|
|
101351
|
+
push(pattern, "session");
|
|
101352
|
+
}
|
|
101353
|
+
return { path: shellPermissionsPath(options.dir), rows };
|
|
101354
|
+
}
|
|
101355
|
+
function patternOnOneLine(pattern) {
|
|
101356
|
+
return pattern.replace(/\r?\n/g, "\\n").replace(CONTROL, " ");
|
|
101357
|
+
}
|
|
101358
|
+
function permissionsLines(view) {
|
|
101359
|
+
const of = (kind) => view.rows.filter((row) => row.kind === kind);
|
|
101360
|
+
const active = of("active");
|
|
101361
|
+
const inactive = of("inactive");
|
|
101362
|
+
const session = of("session");
|
|
101363
|
+
const lines = [];
|
|
101364
|
+
lines.push(active.length === 0 ? "No saved shell rules: every command that is not read-only asks first." : `Saved shell rules (${active.length}): commands that run without asking.`);
|
|
101365
|
+
for (const row of active)
|
|
101366
|
+
lines.push(` ${row.n}. ${patternOnOneLine(row.pattern)}`);
|
|
101367
|
+
if (inactive.length > 0) {
|
|
101368
|
+
lines.push("", `Not honoured (${inactive.length}): kept in the file, they approve nothing.`);
|
|
101369
|
+
for (const row of inactive) {
|
|
101370
|
+
lines.push(` ${row.n}. ${patternOnOneLine(row.pattern)}`);
|
|
101371
|
+
lines.push(` ${row.reason ?? "refused by the validators"}`);
|
|
101372
|
+
}
|
|
101373
|
+
}
|
|
101374
|
+
if (session.length > 0) {
|
|
101375
|
+
lines.push("", `Granted for this session only (${session.length}):`);
|
|
101376
|
+
for (const row of session)
|
|
101377
|
+
lines.push(` ${row.n}. ${patternOnOneLine(row.pattern)}`);
|
|
101378
|
+
}
|
|
101379
|
+
lines.push("", `File: ${view.path}`);
|
|
101380
|
+
if (view.rows.length > 0)
|
|
101381
|
+
lines.push("Remove one with /permissions remove <number>, or keryx permissions remove <number>.");
|
|
101382
|
+
return lines;
|
|
101383
|
+
}
|
|
101384
|
+
function resolvePermissionTarget(view, target) {
|
|
101385
|
+
const wanted = target.trim();
|
|
101386
|
+
if (wanted.length === 0)
|
|
101387
|
+
return;
|
|
101388
|
+
const byText = view.rows.find((row) => row.pattern === wanted);
|
|
101389
|
+
if (byText !== undefined)
|
|
101390
|
+
return byText;
|
|
101391
|
+
if (/^\d+$/.test(wanted))
|
|
101392
|
+
return view.rows.find((row) => row.n === Number(wanted));
|
|
101393
|
+
return;
|
|
101394
|
+
}
|
|
101395
|
+
function removePermission(target, options = {}) {
|
|
101396
|
+
const view = loadPermissionsView({
|
|
101397
|
+
...options.dir !== undefined ? { dir: options.dir } : {},
|
|
101398
|
+
...options.sessionAllow !== undefined ? { sessionAllow: options.sessionAllow } : {}
|
|
101399
|
+
});
|
|
101400
|
+
const row = resolvePermissionTarget(view, target);
|
|
101401
|
+
if (row === undefined) {
|
|
101402
|
+
return { ok: false, text: `No saved rule matches ${JSON.stringify(target.trim())}. Run list to see the numbers.` };
|
|
101403
|
+
}
|
|
101404
|
+
if (row.kind === "session") {
|
|
101405
|
+
options.sessionAllow?.delete(row.pattern);
|
|
101406
|
+
return { ok: true, pattern: row.pattern, text: `Removed the session grant: ${patternOnOneLine(row.pattern)}` };
|
|
101407
|
+
}
|
|
101408
|
+
if (!removeShellPattern(row.pattern, options.dir)) {
|
|
101409
|
+
return { ok: false, pattern: row.pattern, text: `Could not remove ${patternOnOneLine(row.pattern)} from ${view.path}.` };
|
|
101410
|
+
}
|
|
101411
|
+
options.sessionAllow?.delete(row.pattern);
|
|
101412
|
+
options.onChanged?.();
|
|
101413
|
+
return { ok: true, pattern: row.pattern, text: `Removed: ${patternOnOneLine(row.pattern)}. It asks again from now on.` };
|
|
101414
|
+
}
|
|
101415
|
+
function permissionsSlashText(rest, options = {}) {
|
|
101416
|
+
const trimmed = rest.trim();
|
|
101417
|
+
const [sub] = trimmed.split(/\s+/);
|
|
101418
|
+
if (sub === "remove" || sub === "rm") {
|
|
101419
|
+
const target = trimmed.slice(sub.length).trim();
|
|
101420
|
+
if (target.length === 0)
|
|
101421
|
+
return `Usage: /permissions remove <number | pattern>
|
|
101422
|
+
`;
|
|
101423
|
+
return `${removePermission(target, options).text}
|
|
101424
|
+
`;
|
|
101425
|
+
}
|
|
101426
|
+
if (trimmed.length > 0 && sub !== "list")
|
|
101427
|
+
return `Usage: /permissions [list | remove <number | pattern>]
|
|
101428
|
+
`;
|
|
101429
|
+
return `${permissionsLines(loadPermissionsView({
|
|
101430
|
+
...options.dir !== undefined ? { dir: options.dir } : {},
|
|
101431
|
+
...options.sessionAllow !== undefined ? { sessionAllow: options.sessionAllow } : {}
|
|
101432
|
+
})).join(`
|
|
101433
|
+
`)}
|
|
101434
|
+
`;
|
|
101435
|
+
}
|
|
101436
|
+
function printPermissionsHelp() {
|
|
101437
|
+
console.log([
|
|
101438
|
+
"Usage: keryx permissions <subcommand>",
|
|
101439
|
+
"",
|
|
101440
|
+
" keryx permissions list [--json] The saved shell rules: honoured, and not honoured with the reason.",
|
|
101441
|
+
" keryx permissions remove <number|pattern>",
|
|
101442
|
+
" Take one rule back. The number is the one `list` prints.",
|
|
101443
|
+
"",
|
|
101444
|
+
"A rule is saved when an approval is answered with Always, in the shell or from Telegram. It",
|
|
101445
|
+
"is removed here, never from a chat. A running shell stops using a removed rule on its next",
|
|
101446
|
+
"approval and warns that the file changed."
|
|
101447
|
+
].join(`
|
|
101448
|
+
`));
|
|
101449
|
+
}
|
|
101450
|
+
async function permissionsCommand(args) {
|
|
101451
|
+
const [sub, ...rest] = args;
|
|
101452
|
+
if (sub === "--help" || sub === "-h" || sub === "help") {
|
|
101453
|
+
printPermissionsHelp();
|
|
101454
|
+
return;
|
|
101455
|
+
}
|
|
101456
|
+
if (sub === undefined || sub === "list" || sub === "--json") {
|
|
101457
|
+
const flags = sub === "--json" ? [sub, ...rest] : rest;
|
|
101458
|
+
const unknown = flags.filter((arg) => arg !== "--json");
|
|
101459
|
+
if (unknown.length > 0) {
|
|
101460
|
+
throw new Error(`Unknown option${unknown.length > 1 ? "s" : ""} for \`keryx permissions list\`: ${unknown.join(", ")}. Accepted: --json.`);
|
|
101461
|
+
}
|
|
101462
|
+
const view = loadPermissionsView();
|
|
101463
|
+
if (flags.includes("--json")) {
|
|
101464
|
+
console.log(JSON.stringify({
|
|
101465
|
+
path: view.path,
|
|
101466
|
+
rules: view.rows.map((row) => ({
|
|
101467
|
+
n: row.n,
|
|
101468
|
+
pattern: row.pattern,
|
|
101469
|
+
honoured: row.kind === "active",
|
|
101470
|
+
...row.reason !== undefined ? { reason: row.reason } : {}
|
|
101471
|
+
}))
|
|
101472
|
+
}, null, 2));
|
|
101473
|
+
return;
|
|
101474
|
+
}
|
|
101475
|
+
console.log(permissionsLines(view).join(`
|
|
101476
|
+
`));
|
|
101477
|
+
return;
|
|
101478
|
+
}
|
|
101479
|
+
if (sub === "remove" || sub === "rm") {
|
|
101480
|
+
const target = rest.join(" ").trim();
|
|
101481
|
+
if (target.length === 0 || target.startsWith("--")) {
|
|
101482
|
+
throw new Error("Usage: keryx permissions remove <number | pattern>");
|
|
101483
|
+
}
|
|
101484
|
+
const result = removePermission(target);
|
|
101485
|
+
if (!result.ok)
|
|
101486
|
+
throw new Error(result.text);
|
|
101487
|
+
console.log(result.text);
|
|
101488
|
+
return;
|
|
101489
|
+
}
|
|
101490
|
+
throw new Error(`Unknown permissions subcommand: ${sub}. Run \`keryx permissions --help\`.`);
|
|
101491
|
+
}
|
|
101492
|
+
var CONTROL;
|
|
101493
|
+
var init_permissions_command = __esm(() => {
|
|
101494
|
+
init_shell_permissions();
|
|
101495
|
+
CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
|
|
101496
|
+
});
|
|
101497
|
+
|
|
101498
|
+
// src/commands/remote-policy-command.ts
|
|
101499
|
+
function formatPolicyDuration(ms) {
|
|
101500
|
+
if (ms <= 0)
|
|
101501
|
+
return "none";
|
|
101502
|
+
const seconds = Math.round(ms / 1000);
|
|
101503
|
+
if (seconds < 60)
|
|
101504
|
+
return `${seconds}s`;
|
|
101505
|
+
const minutes = Math.floor(seconds / 60);
|
|
101506
|
+
const rest = seconds % 60;
|
|
101507
|
+
if (rest !== 0)
|
|
101508
|
+
return `${minutes}m ${rest}s`;
|
|
101509
|
+
if (minutes >= 60 && minutes % 60 === 0)
|
|
101510
|
+
return `${minutes / 60}h`;
|
|
101511
|
+
return `${minutes}m`;
|
|
101512
|
+
}
|
|
101513
|
+
function countSavedRules(dir) {
|
|
101514
|
+
try {
|
|
101515
|
+
return loadPermissionsView(dir !== undefined ? { dir } : {}).rows.filter((row) => row.kind === "active").length;
|
|
101516
|
+
} catch {
|
|
101517
|
+
return 0;
|
|
101518
|
+
}
|
|
101519
|
+
}
|
|
101520
|
+
function postureLines(posture) {
|
|
101521
|
+
const lines = [
|
|
101522
|
+
`Mode: ${posture.defaultMode} - a Telegram turn starts with it until /mode changes the shell's mode`,
|
|
101523
|
+
`Run limit: ${formatPolicyDuration(posture.runTimeoutMs)}${posture.runTimeoutMs === 0 ? " (/stop ends a run)" : ""}`,
|
|
101524
|
+
`Approval wait: ${formatPolicyDuration(posture.approvalTimeoutMs)}`,
|
|
101525
|
+
`Saved shell rules: ${posture.savedRules} (${"/permissions"} lists and removes them)`
|
|
101526
|
+
];
|
|
101527
|
+
if (posture.inForce !== undefined)
|
|
101528
|
+
lines.unshift(`In force now: ${posture.inForce}`);
|
|
101529
|
+
return lines;
|
|
101530
|
+
}
|
|
101531
|
+
function postureSidebarText(posture) {
|
|
101532
|
+
const limit = posture.runTimeoutMs > 0 ? `limit ${formatPolicyDuration(posture.runTimeoutMs)}` : "no limit";
|
|
101533
|
+
const first = `${posture.defaultMode} \xB7 ${limit} \xB7 wait ${formatPolicyDuration(posture.approvalTimeoutMs)}`;
|
|
101534
|
+
return posture.savedRules > 0 ? `${first}
|
|
101535
|
+
${posture.savedRules} saved shell rule${posture.savedRules === 1 ? "" : "s"}` : first;
|
|
101536
|
+
}
|
|
101537
|
+
function wholeMinutes(word) {
|
|
101538
|
+
if (word === undefined || !/^\d{1,6}$/.test(word))
|
|
101539
|
+
return;
|
|
101540
|
+
return Number(word);
|
|
101541
|
+
}
|
|
101542
|
+
function parseRemotePolicyArgs(rest) {
|
|
101543
|
+
const words = rest.trim().split(/\s+/).filter((word) => word.length > 0);
|
|
101544
|
+
if (words.length === 0)
|
|
101545
|
+
return { action: "show" };
|
|
101546
|
+
const patch = {};
|
|
101547
|
+
let index = 0;
|
|
101548
|
+
while (index < words.length) {
|
|
101549
|
+
const key = (words[index] ?? "").toLowerCase();
|
|
101550
|
+
const value = words[index + 1];
|
|
101551
|
+
if (value === undefined)
|
|
101552
|
+
return { action: "invalid", message: REMOTE_POLICY_USAGE };
|
|
101553
|
+
const lower = value.toLowerCase();
|
|
101554
|
+
if (key === "mode") {
|
|
101555
|
+
if (patch.permissionMode !== undefined)
|
|
101556
|
+
return { action: "invalid", message: REMOTE_POLICY_USAGE };
|
|
101557
|
+
if (lower === "auto") {
|
|
101558
|
+
return { action: "invalid", message: "auto is not available as a Telegram default: it skips every confirmation. Use /mode auto in the shell if you mean it." };
|
|
101559
|
+
}
|
|
101560
|
+
if (lower !== "ask" && lower !== "trust")
|
|
101561
|
+
return { action: "invalid", message: `Unknown mode '${value}'. Choose ask or trust.` };
|
|
101562
|
+
patch.permissionMode = lower;
|
|
101563
|
+
} else if (key === "limit") {
|
|
101564
|
+
if (patch.runTimeoutMs !== undefined)
|
|
101565
|
+
return { action: "invalid", message: REMOTE_POLICY_USAGE };
|
|
101566
|
+
if (lower === "none" || lower === "off" || lower === "0") {
|
|
101567
|
+
patch.runTimeoutMs = 0;
|
|
101568
|
+
} else {
|
|
101569
|
+
const minutes = wholeMinutes(value);
|
|
101570
|
+
if (minutes === undefined || minutes < 1 || minutes > MAX_LIMIT_MINUTES) {
|
|
101571
|
+
return { action: "invalid", message: `limit must be none or a whole number of minutes from 1 to ${MAX_LIMIT_MINUTES}.` };
|
|
101572
|
+
}
|
|
101573
|
+
patch.runTimeoutMs = minutes * 60000;
|
|
101574
|
+
}
|
|
101575
|
+
} else if (key === "wait") {
|
|
101576
|
+
if (patch.approvalTimeoutMs !== undefined)
|
|
101577
|
+
return { action: "invalid", message: REMOTE_POLICY_USAGE };
|
|
101578
|
+
const minutes = wholeMinutes(value);
|
|
101579
|
+
if (minutes === undefined || minutes < 1 || minutes > MAX_WAIT_MINUTES) {
|
|
101580
|
+
return { action: "invalid", message: `wait must be a whole number of minutes from 1 to ${MAX_WAIT_MINUTES}.` };
|
|
101581
|
+
}
|
|
101582
|
+
patch.approvalTimeoutMs = minutes * 60000;
|
|
101583
|
+
} else {
|
|
101584
|
+
return { action: "invalid", message: REMOTE_POLICY_USAGE };
|
|
101585
|
+
}
|
|
101586
|
+
index += 2;
|
|
101587
|
+
}
|
|
101588
|
+
return { action: "set", patch };
|
|
101589
|
+
}
|
|
101590
|
+
function loadPosture(deps = {}) {
|
|
101591
|
+
const base = deps.running?.() ?? (() => {
|
|
101592
|
+
const loaded = loadRemoteConfig(deps.dir);
|
|
101593
|
+
return loaded.ok ? { defaultMode: loaded.value.permissionMode, runTimeoutMs: loaded.value.runTimeoutMs, approvalTimeoutMs: loaded.value.approvalTimeoutMs } : undefined;
|
|
101594
|
+
})();
|
|
101595
|
+
if (base === undefined)
|
|
101596
|
+
return;
|
|
101597
|
+
const inForce = deps.inForce?.();
|
|
101598
|
+
return { ...base, ...inForce !== undefined ? { inForce } : {}, savedRules: countSavedRules(deps.dir) };
|
|
101599
|
+
}
|
|
101600
|
+
function remotePolicyText(rest, deps = {}) {
|
|
101601
|
+
const request = parseRemotePolicyArgs(rest);
|
|
101602
|
+
if (request.action === "invalid")
|
|
101603
|
+
return `${request.message}
|
|
101604
|
+
`;
|
|
101605
|
+
if (request.action === "set") {
|
|
101606
|
+
const saved = updateRemotePolicy(request.patch, deps.dir);
|
|
101607
|
+
if (!saved.ok)
|
|
101608
|
+
return `Not changed: ${saved.reason}
|
|
101609
|
+
`;
|
|
101610
|
+
deps.applyPolicy?.(request.patch);
|
|
101611
|
+
const posture = loadPosture(deps);
|
|
101612
|
+
const lines = [
|
|
101613
|
+
"Saved. Telegram defaults:",
|
|
101614
|
+
...posture !== undefined ? postureLines(posture) : [],
|
|
101615
|
+
deps.applyPolicy !== undefined ? "This shell uses them from the next Telegram turn; a shell that registers later gets them when keryx serve restarts." : "Open shells keep their values until they are restarted; keryx serve delivers these to a shell that registers after it restarts.",
|
|
101616
|
+
SHELL_MODE_NOTE
|
|
101617
|
+
];
|
|
101618
|
+
return `${lines.join(`
|
|
101619
|
+
`)}
|
|
101620
|
+
`;
|
|
101621
|
+
}
|
|
101622
|
+
const posture = loadPosture(deps);
|
|
101623
|
+
if (posture === undefined) {
|
|
101624
|
+
return `Telegram is not connected, so there is nothing to change yet. Connect it with /channels.
|
|
101625
|
+
${REMOTE_POLICY_USAGE}
|
|
101626
|
+
`;
|
|
101627
|
+
}
|
|
101628
|
+
return `${["Telegram permissions:", ...postureLines(posture), SHELL_MODE_NOTE, REMOTE_POLICY_USAGE].join(`
|
|
101629
|
+
`)}
|
|
101630
|
+
`;
|
|
101631
|
+
}
|
|
101632
|
+
var REMOTE_POLICY_COMMAND = "/remote-policy", REMOTE_POLICY_USAGE, MAX_LIMIT_MINUTES, MAX_WAIT_MINUTES = 60, SHELL_MODE_NOTE = "A /mode in the shell (or typed in the topic) changes the shell's mode and wins for every turn after that; /remote-policy never changes it.";
|
|
101633
|
+
var init_remote_policy_command = __esm(() => {
|
|
101634
|
+
init_config11();
|
|
101635
|
+
init_permissions_command();
|
|
101636
|
+
REMOTE_POLICY_USAGE = `Usage: ${REMOTE_POLICY_COMMAND} [mode ask|trust] [limit none|<minutes>] [wait <minutes>]`;
|
|
101637
|
+
MAX_LIMIT_MINUTES = 7 * 24 * 60;
|
|
101638
|
+
});
|
|
101639
|
+
|
|
101222
101640
|
// src/tui/reasoning-display.ts
|
|
101223
101641
|
function parseThinkDisplayMode(raw) {
|
|
101224
101642
|
if (raw === undefined) {
|
|
@@ -101798,6 +102216,63 @@ function reasoningScope(reasoning) {
|
|
|
101798
102216
|
return { scope: "saved" };
|
|
101799
102217
|
}
|
|
101800
102218
|
}
|
|
102219
|
+
function telegramRows(state) {
|
|
102220
|
+
if (state.telegram === undefined)
|
|
102221
|
+
return [];
|
|
102222
|
+
const posture = state.telegram.posture;
|
|
102223
|
+
const notConnected = "not connected (/channels)";
|
|
102224
|
+
const modeDetail = posture === undefined ? "connect Telegram first" : `${posture.inForce !== undefined ? `in force: ${posture.inForce}; ` : ""}a /mode in the shell wins once used`;
|
|
102225
|
+
const minutes = (ms) => Math.round(ms / 60000);
|
|
102226
|
+
return [
|
|
102227
|
+
{
|
|
102228
|
+
id: "tg-mode",
|
|
102229
|
+
group: "Telegram",
|
|
102230
|
+
label: "Telegram mode",
|
|
102231
|
+
value: posture?.defaultMode ?? notConnected,
|
|
102232
|
+
scope: "saved",
|
|
102233
|
+
detail: modeDetail,
|
|
102234
|
+
command: "/remote-policy",
|
|
102235
|
+
usage: "/remote-policy mode ask|trust",
|
|
102236
|
+
actions: posture === undefined ? [] : ["ask", "trust"].map((mode) => action(mode, `/remote-policy mode ${mode}`, mode === posture.defaultMode))
|
|
102237
|
+
},
|
|
102238
|
+
{
|
|
102239
|
+
id: "tg-limit",
|
|
102240
|
+
group: "Telegram",
|
|
102241
|
+
label: "Telegram run limit",
|
|
102242
|
+
value: posture === undefined ? notConnected : formatPolicyDuration(posture.runTimeoutMs),
|
|
102243
|
+
scope: "saved",
|
|
102244
|
+
detail: "/stop in the topic always ends a run",
|
|
102245
|
+
command: "/remote-policy",
|
|
102246
|
+
usage: "/remote-policy limit none|<minutes>",
|
|
102247
|
+
actions: posture === undefined ? [] : [
|
|
102248
|
+
action("None", "/remote-policy limit none", posture.runTimeoutMs === 0),
|
|
102249
|
+
...LIMIT_CHOICES_MIN.map((m) => action(`${m} min`, `/remote-policy limit ${m}`, minutes(posture.runTimeoutMs) === m && posture.runTimeoutMs > 0))
|
|
102250
|
+
]
|
|
102251
|
+
},
|
|
102252
|
+
{
|
|
102253
|
+
id: "tg-wait",
|
|
102254
|
+
group: "Telegram",
|
|
102255
|
+
label: "Telegram approval wait",
|
|
102256
|
+
value: posture === undefined ? notConnected : formatPolicyDuration(posture.approvalTimeoutMs),
|
|
102257
|
+
scope: "saved",
|
|
102258
|
+
detail: "a prompt nobody answers in this time is denied",
|
|
102259
|
+
command: "/remote-policy",
|
|
102260
|
+
usage: "/remote-policy wait <minutes>",
|
|
102261
|
+
actions: posture === undefined ? [] : WAIT_CHOICES_MIN.map((m) => action(`${m} min`, `/remote-policy wait ${m}`, posture.approvalTimeoutMs === m * 60000))
|
|
102262
|
+
},
|
|
102263
|
+
{
|
|
102264
|
+
id: "tg-rules",
|
|
102265
|
+
group: "Telegram",
|
|
102266
|
+
label: "Saved shell rules",
|
|
102267
|
+
value: posture === undefined ? "none" : String(posture.savedRules),
|
|
102268
|
+
scope: "saved",
|
|
102269
|
+
detail: "what Always remembered, from the shell or Telegram",
|
|
102270
|
+
command: "/permissions",
|
|
102271
|
+
usage: "/permissions [list | remove <number | pattern>]",
|
|
102272
|
+
actions: []
|
|
102273
|
+
}
|
|
102274
|
+
];
|
|
102275
|
+
}
|
|
101801
102276
|
function buildSettingsRows(state) {
|
|
101802
102277
|
const reasoning = reasoningScope(state.reasoning);
|
|
101803
102278
|
return [
|
|
@@ -101931,7 +102406,8 @@ function buildSettingsRows(state) {
|
|
|
101931
102406
|
command: "/jevprofile",
|
|
101932
102407
|
usage: "/jevprofile",
|
|
101933
102408
|
actions: []
|
|
101934
|
-
}
|
|
102409
|
+
},
|
|
102410
|
+
...telegramRows(state)
|
|
101935
102411
|
];
|
|
101936
102412
|
}
|
|
101937
102413
|
function formatSettingsTable(rows, options = {}) {
|
|
@@ -101955,19 +102431,22 @@ function formatSettingsTable(rows, options = {}) {
|
|
|
101955
102431
|
`)}
|
|
101956
102432
|
`;
|
|
101957
102433
|
}
|
|
101958
|
-
var SETTING_GROUPS, onOff = (value) => value ? "on" : "off", EXTERNAL_PRIVACY_DETAIL;
|
|
102434
|
+
var SETTING_GROUPS, onOff = (value) => value ? "on" : "off", EXTERNAL_PRIVACY_DETAIL, LIMIT_CHOICES_MIN, WAIT_CHOICES_MIN;
|
|
101959
102435
|
var init_settings_model = __esm(() => {
|
|
101960
102436
|
init_agent();
|
|
101961
102437
|
init_permission_mode();
|
|
101962
102438
|
init_rendering_mode();
|
|
102439
|
+
init_remote_policy_command();
|
|
101963
102440
|
init_reasoning_display();
|
|
101964
102441
|
init_theme();
|
|
101965
|
-
SETTING_GROUPS = ["Safety", "Routing", "Display", "External"];
|
|
102442
|
+
SETTING_GROUPS = ["Safety", "Routing", "Display", "External", "Telegram"];
|
|
101966
102443
|
EXTERNAL_PRIVACY_DETAIL = {
|
|
101967
102444
|
project: "this project overrides the per-user setting",
|
|
101968
102445
|
user: undefined,
|
|
101969
102446
|
default: "default"
|
|
101970
102447
|
};
|
|
102448
|
+
LIMIT_CHOICES_MIN = [30, 60];
|
|
102449
|
+
WAIT_CHOICES_MIN = [5, 15, 30];
|
|
101971
102450
|
});
|
|
101972
102451
|
|
|
101973
102452
|
// src/review/jev-profile.ts
|
|
@@ -102630,6 +103109,25 @@ async function orElse(read, fallback) {
|
|
|
102630
103109
|
return fallback;
|
|
102631
103110
|
}
|
|
102632
103111
|
}
|
|
103112
|
+
function readTelegram(live, configDir) {
|
|
103113
|
+
try {
|
|
103114
|
+
const running = live.telegram;
|
|
103115
|
+
const posture = loadPosture({
|
|
103116
|
+
...configDir !== undefined ? { dir: configDir } : {},
|
|
103117
|
+
...running !== undefined ? {
|
|
103118
|
+
running: () => ({
|
|
103119
|
+
defaultMode: running.defaultMode,
|
|
103120
|
+
runTimeoutMs: running.runTimeoutMs,
|
|
103121
|
+
approvalTimeoutMs: running.approvalTimeoutMs
|
|
103122
|
+
}),
|
|
103123
|
+
...running.inForce !== undefined ? { inForce: () => running.inForce } : {}
|
|
103124
|
+
} : {}
|
|
103125
|
+
});
|
|
103126
|
+
return posture === undefined ? {} : { posture };
|
|
103127
|
+
} catch {
|
|
103128
|
+
return {};
|
|
103129
|
+
}
|
|
103130
|
+
}
|
|
102633
103131
|
async function loadSettingsSnapshot(cwd, live, configDir) {
|
|
102634
103132
|
const saved = loadShellConfig(configDir);
|
|
102635
103133
|
const projectMode = getProjectPermissionMode(cwd, configDir);
|
|
@@ -102655,12 +103153,14 @@ async function loadSettingsSnapshot(cwd, live, configDir) {
|
|
|
102655
103153
|
total: RECOMMENDED_JEV_PROFILE.length
|
|
102656
103154
|
},
|
|
102657
103155
|
externalAgents: agents.ok ? { on: true } : { on: false, reason: agents.reason },
|
|
102658
|
-
rendering: readRendering(configDir)
|
|
103156
|
+
rendering: readRendering(configDir),
|
|
103157
|
+
telegram: readTelegram(live, configDir)
|
|
102659
103158
|
};
|
|
102660
103159
|
}
|
|
102661
103160
|
var init_settings_state = __esm(() => {
|
|
102662
103161
|
init_external_agents();
|
|
102663
103162
|
init_agent();
|
|
103163
|
+
init_remote_policy_command();
|
|
102664
103164
|
init_review_jev_profile();
|
|
102665
103165
|
init_external_switch();
|
|
102666
103166
|
init_permission_mode_config();
|
|
@@ -104124,6 +104624,12 @@ var init_help_groups = __esm(() => {
|
|
|
104124
104624
|
group: "Automation",
|
|
104125
104625
|
summary: "Pending remote approvals: list them, allow or deny one call, once (the local answer path of keryx serve)."
|
|
104126
104626
|
},
|
|
104627
|
+
{
|
|
104628
|
+
kind: "cli",
|
|
104629
|
+
name: "permissions",
|
|
104630
|
+
group: "Automation",
|
|
104631
|
+
summary: "The saved shell rules an Always answer leaves behind: list them (honoured or not, with the reason) and remove one."
|
|
104632
|
+
},
|
|
104127
104633
|
{
|
|
104128
104634
|
kind: "cli",
|
|
104129
104635
|
name: "governance",
|
|
@@ -104178,6 +104684,12 @@ var init_help_groups = __esm(() => {
|
|
|
104178
104684
|
group: "Automation",
|
|
104179
104685
|
summary: "Pending remote approvals with scope, consequence and expiry \u2014 allow or deny one call, once."
|
|
104180
104686
|
},
|
|
104687
|
+
{
|
|
104688
|
+
kind: "slash",
|
|
104689
|
+
name: "/permissions",
|
|
104690
|
+
group: "Automation",
|
|
104691
|
+
summary: "The saved shell rules (what Always remembered): honoured or not, session grants \u2014 remove one."
|
|
104692
|
+
},
|
|
104181
104693
|
{
|
|
104182
104694
|
kind: "slash",
|
|
104183
104695
|
name: "/external-diff",
|
|
@@ -104226,6 +104738,12 @@ var init_help_groups = __esm(() => {
|
|
|
104226
104738
|
group: "Automation",
|
|
104227
104739
|
summary: "How Telegram replies are written: tables and lists as rich messages or HTML, with a fallback. [auto|rich|html|plain]."
|
|
104228
104740
|
},
|
|
104741
|
+
{
|
|
104742
|
+
kind: "slash",
|
|
104743
|
+
name: "/remote-policy",
|
|
104744
|
+
group: "Automation",
|
|
104745
|
+
summary: "Telegram defaults for a turn started there: mode ask|trust, run limit, approval wait. Never changes the shell's /mode."
|
|
104746
|
+
},
|
|
104229
104747
|
{
|
|
104230
104748
|
kind: "cli",
|
|
104231
104749
|
name: "acp",
|
|
@@ -114453,6 +114971,155 @@ var init_reviews_inspector = __esm(() => {
|
|
|
114453
114971
|
];
|
|
114454
114972
|
});
|
|
114455
114973
|
|
|
114974
|
+
// src/tui/permissions-inspector.ts
|
|
114975
|
+
function formatPermissionsModal(view, selected, notice) {
|
|
114976
|
+
const lines = [];
|
|
114977
|
+
const rowStart = [];
|
|
114978
|
+
const active = view.rows.filter((row) => row.kind === "active").length;
|
|
114979
|
+
lines.push(view.rows.length === 0 ? "No saved shell rules: every command that is not read-only asks first." : `Saved shell rules (${active}) - d removes the selected one, it asks again from then on`);
|
|
114980
|
+
if (notice !== undefined && notice.length > 0)
|
|
114981
|
+
lines.push(notice);
|
|
114982
|
+
lines.push("");
|
|
114983
|
+
let section;
|
|
114984
|
+
view.rows.forEach((row, index) => {
|
|
114985
|
+
if (row.kind !== section) {
|
|
114986
|
+
section = row.kind;
|
|
114987
|
+
if (row.kind === "inactive")
|
|
114988
|
+
lines.push("Not honoured: kept in the file, they approve nothing", "");
|
|
114989
|
+
if (row.kind === "session")
|
|
114990
|
+
lines.push("Granted for this session only", "");
|
|
114991
|
+
}
|
|
114992
|
+
rowStart.push(lines.length);
|
|
114993
|
+
lines.push(`${index === selected ? ">" : " "} ${row.n}. ${patternOnOneLine(row.pattern)}`);
|
|
114994
|
+
if (row.reason !== undefined)
|
|
114995
|
+
lines.push(` ${row.reason}`);
|
|
114996
|
+
lines.push("");
|
|
114997
|
+
});
|
|
114998
|
+
lines.push(`File: ${view.path}`);
|
|
114999
|
+
return { rows: view.rows, lines, rowStart };
|
|
115000
|
+
}
|
|
115001
|
+
function openPermissions(otui, chrome, options) {
|
|
115002
|
+
const core = otui;
|
|
115003
|
+
const r = chrome?.renderer;
|
|
115004
|
+
const rendererHint = options.renderer ?? chrome?.renderer;
|
|
115005
|
+
const panelRows = typeof rendererHint?.width === "number" && typeof rendererHint.height === "number" ? modalBodyRows(resolveModalPanelSize(rendererHint.width, rendererHint.height).height) : 13;
|
|
115006
|
+
const bodyRows = Math.max(1, options.visibleRows ?? panelRows);
|
|
115007
|
+
const load = options.load ?? (() => loadPermissionsView(options.sessionAllow !== undefined ? { sessionAllow: options.sessionAllow } : {}));
|
|
115008
|
+
const remove = options.remove ?? ((target) => removePermission(target, {
|
|
115009
|
+
...options.sessionAllow !== undefined ? { sessionAllow: options.sessionAllow } : {},
|
|
115010
|
+
...options.onChanged !== undefined ? { onChanged: options.onChanged } : {}
|
|
115011
|
+
}));
|
|
115012
|
+
let selected = 0;
|
|
115013
|
+
let scroll = 0;
|
|
115014
|
+
let width;
|
|
115015
|
+
let bodyNode;
|
|
115016
|
+
let closed = false;
|
|
115017
|
+
let notice;
|
|
115018
|
+
let armed;
|
|
115019
|
+
const keys = {};
|
|
115020
|
+
const model = () => formatPermissionsModal(load(), selected, armed !== undefined ? `Remove ${patternOnOneLine(armed)}? press y to confirm, any other key cancels` : notice);
|
|
115021
|
+
const clampSelected = (rows) => {
|
|
115022
|
+
selected = Math.min(Math.max(0, rows - 1), Math.max(0, selected));
|
|
115023
|
+
};
|
|
115024
|
+
const visible = () => {
|
|
115025
|
+
const view = model();
|
|
115026
|
+
clampSelected(view.rows.length);
|
|
115027
|
+
return windowLines(wrapLines(view.lines.join(`
|
|
115028
|
+
`), width).split(`
|
|
115029
|
+
`), scroll, bodyRows);
|
|
115030
|
+
};
|
|
115031
|
+
const paint = () => {
|
|
115032
|
+
if (closed)
|
|
115033
|
+
return;
|
|
115034
|
+
const view = model();
|
|
115035
|
+
clampSelected(view.rows.length);
|
|
115036
|
+
const wrapped = wrapLines(view.lines.join(`
|
|
115037
|
+
`), width).split(`
|
|
115038
|
+
`);
|
|
115039
|
+
const start = view.rowStart[selected] ?? 0;
|
|
115040
|
+
scroll = clampScroll(scrollToReveal(start, scroll, bodyRows), wrapped.length, bodyRows);
|
|
115041
|
+
if (bodyNode !== undefined)
|
|
115042
|
+
bodyNode.content = core.t`${dimChunk(core, windowLines(wrapped, scroll, bodyRows).join(`
|
|
115043
|
+
`))}`;
|
|
115044
|
+
};
|
|
115045
|
+
let unsubscribeTheme = () => {};
|
|
115046
|
+
const handle = openModal(core, chrome, {
|
|
115047
|
+
title: PERMISSIONS_COMMAND,
|
|
115048
|
+
tabs: [{ id: "rules", label: "Shell rules" }],
|
|
115049
|
+
footer: PERMISSIONS_FOOTER,
|
|
115050
|
+
renderTab: (_tabId, body, ctx) => {
|
|
115051
|
+
width = ctx.width;
|
|
115052
|
+
const parent = body;
|
|
115053
|
+
bodyNode = new core.TextRenderable(r, { id: "permissions-body", content: "" });
|
|
115054
|
+
parent.add(bodyNode);
|
|
115055
|
+
paint();
|
|
115056
|
+
},
|
|
115057
|
+
onClose: () => {
|
|
115058
|
+
closed = true;
|
|
115059
|
+
keys.off?.();
|
|
115060
|
+
unsubscribeTheme();
|
|
115061
|
+
}
|
|
115062
|
+
});
|
|
115063
|
+
if (handle === undefined)
|
|
115064
|
+
return;
|
|
115065
|
+
const modal = handle;
|
|
115066
|
+
unsubscribeTheme = onThemeChange(guardedThemeRepaint("permissions-modal", paint, () => closed || isRenderableGone(bodyNode) || r?.isDestroyed === true));
|
|
115067
|
+
const selectedRow = () => model().rows[selected];
|
|
115068
|
+
keys.off = options.onKeypress((key) => {
|
|
115069
|
+
if (closed || options.inputBlocked?.() === true)
|
|
115070
|
+
return;
|
|
115071
|
+
const token = key.name || key.sequence;
|
|
115072
|
+
if (armed !== undefined) {
|
|
115073
|
+
const pattern = armed;
|
|
115074
|
+
armed = undefined;
|
|
115075
|
+
if (token === "y") {
|
|
115076
|
+
notice = remove(pattern).text;
|
|
115077
|
+
}
|
|
115078
|
+
paint();
|
|
115079
|
+
return;
|
|
115080
|
+
}
|
|
115081
|
+
if (token === "up" || token === "k") {
|
|
115082
|
+
selected = Math.max(0, selected - 1);
|
|
115083
|
+
notice = undefined;
|
|
115084
|
+
} else if (token === "down" || token === "j") {
|
|
115085
|
+
selected += 1;
|
|
115086
|
+
notice = undefined;
|
|
115087
|
+
} else if (token === "d") {
|
|
115088
|
+
const row = selectedRow();
|
|
115089
|
+
if (row === undefined)
|
|
115090
|
+
notice = "Nothing to remove.";
|
|
115091
|
+
else
|
|
115092
|
+
armed = row.pattern;
|
|
115093
|
+
} else {
|
|
115094
|
+
return;
|
|
115095
|
+
}
|
|
115096
|
+
paint();
|
|
115097
|
+
});
|
|
115098
|
+
return {
|
|
115099
|
+
...modal,
|
|
115100
|
+
visibleLines: visible,
|
|
115101
|
+
selected: selectedRow,
|
|
115102
|
+
reload: () => {
|
|
115103
|
+
paint();
|
|
115104
|
+
}
|
|
115105
|
+
};
|
|
115106
|
+
}
|
|
115107
|
+
var PERMISSIONS_COMMAND = "/permissions", PERMISSIONS_FOOTER;
|
|
115108
|
+
var init_permissions_inspector = __esm(() => {
|
|
115109
|
+
init_permissions_command();
|
|
115110
|
+
init_flow_inspector();
|
|
115111
|
+
init_modal_host();
|
|
115112
|
+
init_theme();
|
|
115113
|
+
init_theme_repaint();
|
|
115114
|
+
init_theme_text();
|
|
115115
|
+
PERMISSIONS_FOOTER = [
|
|
115116
|
+
{ key: "\u2191/\u2193", label: "move" },
|
|
115117
|
+
{ key: "d", label: "remove" },
|
|
115118
|
+
{ key: "y", label: "confirm" },
|
|
115119
|
+
{ key: "esc", label: "close" }
|
|
115120
|
+
];
|
|
115121
|
+
});
|
|
115122
|
+
|
|
114456
115123
|
// src/lib/serve-config.ts
|
|
114457
115124
|
import { existsSync as existsSync42 } from "fs";
|
|
114458
115125
|
import path249 from "path";
|
|
@@ -114864,7 +115531,7 @@ class SseParser {
|
|
|
114864
115531
|
return frames;
|
|
114865
115532
|
}
|
|
114866
115533
|
}
|
|
114867
|
-
var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_APPROVAL_ACK_MS = 5000, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
|
|
115534
|
+
var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_APPROVAL_ACK_MS = 5000, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MAX_REMEMBER_PATTERN_CHARS = 300, APPROVAL_INPUT_PREVIEW_CHARS = 1500, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
|
|
114868
115535
|
|
|
114869
115536
|
`;
|
|
114870
115537
|
var init_protocol2 = __esm(() => {
|
|
@@ -114874,6 +115541,7 @@ var init_protocol2 = __esm(() => {
|
|
|
114874
115541
|
heartbeat: "POST",
|
|
114875
115542
|
reply: "POST",
|
|
114876
115543
|
approval: "POST",
|
|
115544
|
+
"approval-result": "POST",
|
|
114877
115545
|
prompt: "POST",
|
|
114878
115546
|
state: "POST",
|
|
114879
115547
|
ack: "POST",
|
|
@@ -114894,7 +115562,7 @@ var init_protocol2 = __esm(() => {
|
|
|
114894
115562
|
MAX_APPROVAL_TIMEOUT_MS = 60 * 60000;
|
|
114895
115563
|
SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
114896
115564
|
APPROVAL_ID_PATTERN = /^ap[0-9a-f]{12}$/;
|
|
114897
|
-
APPROVAL_CALLBACK_PATTERN = /^ap:(ap[0-9a-f]{12}):(allow|deny)$/;
|
|
115565
|
+
APPROVAL_CALLBACK_PATTERN = /^ap:(ap[0-9a-f]{12}):(allow|deny|always)$/;
|
|
114898
115566
|
CHOICE_CALLBACK_PATTERN = /^pk:(pk[0-9a-f]{12}):(\d{1,3})$/;
|
|
114899
115567
|
MESSAGE_STATES = ["reading", "working", "done", "failed"];
|
|
114900
115568
|
});
|
|
@@ -115202,6 +115870,16 @@ var init_shell_token = __esm(() => {
|
|
|
115202
115870
|
});
|
|
115203
115871
|
|
|
115204
115872
|
// src/remote/client.ts
|
|
115873
|
+
function readApprovalEvent(parsed) {
|
|
115874
|
+
if (parsed === null || typeof parsed !== "object")
|
|
115875
|
+
return;
|
|
115876
|
+
const o = parsed;
|
|
115877
|
+
if (typeof o.approvalId !== "string" || o.approvalId.length === 0)
|
|
115878
|
+
return;
|
|
115879
|
+
const decision = o.decision === "allow" || o.decision === "always" ? o.decision : "deny";
|
|
115880
|
+
const fromId = typeof o.fromId === "number" && Number.isSafeInteger(o.fromId) ? o.fromId : undefined;
|
|
115881
|
+
return { approvalId: o.approvalId, pressed: { decision, ...fromId === undefined ? {} : { fromId } } };
|
|
115882
|
+
}
|
|
115205
115883
|
function ownProcessIsAlive(pid) {
|
|
115206
115884
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
115207
115885
|
return false;
|
|
@@ -115268,6 +115946,8 @@ class RemoteClient {
|
|
|
115268
115946
|
name;
|
|
115269
115947
|
threadId;
|
|
115270
115948
|
runTimeoutMs;
|
|
115949
|
+
permissionMode;
|
|
115950
|
+
approvalTimeoutMs;
|
|
115271
115951
|
lastHeartbeatAt;
|
|
115272
115952
|
constructor(options) {
|
|
115273
115953
|
this.options = options;
|
|
@@ -115363,38 +116043,71 @@ class RemoteClient {
|
|
|
115363
116043
|
}
|
|
115364
116044
|
}
|
|
115365
116045
|
async requestApproval(prompt, timeoutMs) {
|
|
115366
|
-
|
|
115367
|
-
|
|
116046
|
+
return (await this.askApproval(prompt, timeoutMs)).decision;
|
|
116047
|
+
}
|
|
116048
|
+
async askApproval(prompt, timeoutMs, options = {}) {
|
|
116049
|
+
if (!this.connected || options.signal?.aborted === true) {
|
|
116050
|
+
return { decision: "deny" };
|
|
115368
116051
|
}
|
|
115369
|
-
const body = {
|
|
116052
|
+
const body = {
|
|
116053
|
+
sessionId: this.options.sessionId,
|
|
116054
|
+
prompt,
|
|
116055
|
+
timeoutMs,
|
|
116056
|
+
...options.remember === undefined ? {} : { remember: options.remember }
|
|
116057
|
+
};
|
|
115370
116058
|
let approvalId;
|
|
115371
116059
|
try {
|
|
115372
116060
|
const response = await this.post("approval", body);
|
|
115373
116061
|
if (!response.ok) {
|
|
115374
|
-
return "deny";
|
|
116062
|
+
return { decision: "deny" };
|
|
116063
|
+
}
|
|
116064
|
+
const id = (await response.json())?.approvalId;
|
|
116065
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
116066
|
+
return { decision: "deny" };
|
|
115375
116067
|
}
|
|
115376
|
-
approvalId =
|
|
116068
|
+
approvalId = id;
|
|
115377
116069
|
} catch {
|
|
115378
|
-
return "deny";
|
|
116070
|
+
return { decision: "deny" };
|
|
115379
116071
|
}
|
|
115380
116072
|
const early = this.earlyDecisions.get(approvalId);
|
|
115381
116073
|
if (early !== undefined) {
|
|
115382
116074
|
clearTimeout(early.timer);
|
|
115383
116075
|
this.earlyDecisions.delete(approvalId);
|
|
115384
|
-
this.settleApproval(approvalId, early.decision, true);
|
|
115385
|
-
return early.
|
|
116076
|
+
this.settleApproval(approvalId, early.pressed.decision, true);
|
|
116077
|
+
return { ...early.pressed, approvalId };
|
|
115386
116078
|
}
|
|
115387
116079
|
if (!this.connected) {
|
|
115388
|
-
return "deny";
|
|
116080
|
+
return { decision: "deny" };
|
|
115389
116081
|
}
|
|
115390
116082
|
return new Promise((resolve) => {
|
|
116083
|
+
const signal = options.signal;
|
|
116084
|
+
const onAbort = () => {
|
|
116085
|
+
const waiter = this.waiters.get(approvalId);
|
|
116086
|
+
if (waiter !== undefined)
|
|
116087
|
+
clearTimeout(waiter.timer);
|
|
116088
|
+
this.waiters.delete(approvalId);
|
|
116089
|
+
resolve({ decision: "deny", approvalId });
|
|
116090
|
+
};
|
|
115391
116091
|
const timer = setTimeout(() => {
|
|
116092
|
+
signal?.removeEventListener("abort", onAbort);
|
|
115392
116093
|
this.waiters.delete(approvalId);
|
|
115393
|
-
resolve("deny");
|
|
116094
|
+
resolve({ decision: "deny" });
|
|
115394
116095
|
}, timeoutMs);
|
|
115395
|
-
this.waiters.set(approvalId, {
|
|
116096
|
+
this.waiters.set(approvalId, {
|
|
116097
|
+
resolve: (pressed) => {
|
|
116098
|
+
signal?.removeEventListener("abort", onAbort);
|
|
116099
|
+
resolve({ ...pressed, approvalId });
|
|
116100
|
+
},
|
|
116101
|
+
timer
|
|
116102
|
+
});
|
|
116103
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
115396
116104
|
});
|
|
115397
116105
|
}
|
|
116106
|
+
async reportApprovalResult(approvalId, remembered) {
|
|
116107
|
+
try {
|
|
116108
|
+
await this.post("approval-result", { sessionId: this.options.sessionId, approvalId, remembered });
|
|
116109
|
+
} catch {}
|
|
116110
|
+
}
|
|
115398
116111
|
async requestChoice(text, rows, timeoutMs, forUserId) {
|
|
115399
116112
|
if (!this.connected) {
|
|
115400
116113
|
return;
|
|
@@ -115565,7 +116278,14 @@ class RemoteClient {
|
|
|
115565
116278
|
this.lastHeartbeatAt = Date.now();
|
|
115566
116279
|
this.options.onStatus?.({ state: "connected" });
|
|
115567
116280
|
if (this.name !== undefined && this.threadId !== undefined && this.runTimeoutMs !== undefined) {
|
|
115568
|
-
this.settleFirst({
|
|
116281
|
+
this.settleFirst({
|
|
116282
|
+
ok: true,
|
|
116283
|
+
name: this.name,
|
|
116284
|
+
threadId: this.threadId,
|
|
116285
|
+
runTimeoutMs: this.runTimeoutMs,
|
|
116286
|
+
...this.permissionMode === undefined ? {} : { permissionMode: this.permissionMode },
|
|
116287
|
+
...this.approvalTimeoutMs === undefined ? {} : { approvalTimeoutMs: this.approvalTimeoutMs }
|
|
116288
|
+
});
|
|
115569
116289
|
}
|
|
115570
116290
|
} else if (status.kind === "superseded") {
|
|
115571
116291
|
throw new FatalClientError("superseded", "another connection for this session replaced this one");
|
|
@@ -115601,6 +116321,8 @@ class RemoteClient {
|
|
|
115601
116321
|
this.name = result.name;
|
|
115602
116322
|
this.threadId = result.threadId;
|
|
115603
116323
|
this.runTimeoutMs = result.runTimeoutMs;
|
|
116324
|
+
this.permissionMode = result.permissionMode;
|
|
116325
|
+
this.approvalTimeoutMs = result.approvalTimeoutMs;
|
|
115604
116326
|
return;
|
|
115605
116327
|
}
|
|
115606
116328
|
const error = await response.json().catch(() => ({}));
|
|
@@ -115669,12 +116391,12 @@ class RemoteClient {
|
|
|
115669
116391
|
await this.post("ack", { sessionId: this.options.sessionId, updateId });
|
|
115670
116392
|
} catch {}
|
|
115671
116393
|
}
|
|
115672
|
-
handleApprovalFrame(
|
|
115673
|
-
const
|
|
115674
|
-
if (
|
|
116394
|
+
handleApprovalFrame(parsed) {
|
|
116395
|
+
const read = readApprovalEvent(parsed);
|
|
116396
|
+
if (read === undefined || !isApprovalId(read.approvalId)) {
|
|
115675
116397
|
return;
|
|
115676
116398
|
}
|
|
115677
|
-
const
|
|
116399
|
+
const { approvalId, pressed } = read;
|
|
115678
116400
|
const seen = this.seenApprovals.get(approvalId);
|
|
115679
116401
|
if (seen === "pending") {
|
|
115680
116402
|
return;
|
|
@@ -115685,11 +116407,11 @@ class RemoteClient {
|
|
|
115685
116407
|
return;
|
|
115686
116408
|
}
|
|
115687
116409
|
if (this.waiters.has(approvalId)) {
|
|
115688
|
-
this.settleApproval(approvalId, decision, true, () => this.resolveApproval(approvalId,
|
|
116410
|
+
this.settleApproval(approvalId, pressed.decision, true, () => this.resolveApproval(approvalId, pressed));
|
|
115689
116411
|
return;
|
|
115690
116412
|
}
|
|
115691
116413
|
this.rememberSeen(approvalId, "pending");
|
|
115692
|
-
this.parkEarlyDecision(approvalId,
|
|
116414
|
+
this.parkEarlyDecision(approvalId, pressed);
|
|
115693
116415
|
}
|
|
115694
116416
|
rememberSeen(approvalId, outcome) {
|
|
115695
116417
|
this.seenApprovals.delete(approvalId);
|
|
@@ -115721,7 +116443,7 @@ class RemoteClient {
|
|
|
115721
116443
|
apply?.();
|
|
115722
116444
|
this.ackApproval(approvalId, applied);
|
|
115723
116445
|
}
|
|
115724
|
-
parkEarlyDecision(approvalId,
|
|
116446
|
+
parkEarlyDecision(approvalId, pressed) {
|
|
115725
116447
|
while (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
|
|
115726
116448
|
const oldest = this.earlyDecisions.keys().next().value;
|
|
115727
116449
|
if (oldest === undefined) {
|
|
@@ -115731,7 +116453,7 @@ class RemoteClient {
|
|
|
115731
116453
|
}
|
|
115732
116454
|
const timer = setTimeout(() => this.dropEarlyDecision(approvalId), this.earlyDecisionMs);
|
|
115733
116455
|
timer.unref?.();
|
|
115734
|
-
this.earlyDecisions.set(approvalId, {
|
|
116456
|
+
this.earlyDecisions.set(approvalId, { pressed, timer });
|
|
115735
116457
|
}
|
|
115736
116458
|
dropEarlyDecision(approvalId) {
|
|
115737
116459
|
const early = this.earlyDecisions.get(approvalId);
|
|
@@ -115741,7 +116463,7 @@ class RemoteClient {
|
|
|
115741
116463
|
clearTimeout(early.timer);
|
|
115742
116464
|
this.earlyDecisions.delete(approvalId);
|
|
115743
116465
|
if (!this.stopped) {
|
|
115744
|
-
this.settleApproval(approvalId, early.decision, false);
|
|
116466
|
+
this.settleApproval(approvalId, early.pressed.decision, false);
|
|
115745
116467
|
}
|
|
115746
116468
|
}
|
|
115747
116469
|
async ackApproval(approvalId, applied) {
|
|
@@ -115812,7 +116534,7 @@ class RemoteClient {
|
|
|
115812
116534
|
for (const [id, waiter] of [...this.waiters.entries()]) {
|
|
115813
116535
|
clearTimeout(waiter.timer);
|
|
115814
116536
|
this.waiters.delete(id);
|
|
115815
|
-
waiter.resolve("deny");
|
|
116537
|
+
waiter.resolve({ decision: "deny" });
|
|
115816
116538
|
}
|
|
115817
116539
|
for (const id of [...this.earlyDecisions.keys()]) {
|
|
115818
116540
|
this.dropEarlyDecision(id);
|
|
@@ -115976,6 +116698,7 @@ var init_command_gateway = __esm(() => {
|
|
|
115976
116698
|
{ name: "connect", kind: "picker", description: "Switch to a connected provider with buttons" },
|
|
115977
116699
|
{ name: "resume", kind: "picker", description: "Return to an earlier session with buttons" },
|
|
115978
116700
|
{ name: "sessions", kind: "builtin", description: "List recent sessions" },
|
|
116701
|
+
{ name: "stop", kind: "builtin", description: "Stop the run you started from Telegram" },
|
|
115979
116702
|
{ name: "new", kind: "text", description: "Start a new session; this topic stays bound to it" },
|
|
115980
116703
|
{ name: "clear", kind: "text", description: "Same as /new" },
|
|
115981
116704
|
{ name: "compact", kind: "text", description: "Compact the conversation: /compact [focus]" },
|
|
@@ -116021,7 +116744,9 @@ var init_command_gateway = __esm(() => {
|
|
|
116021
116744
|
governance: "it needs the shell's screen",
|
|
116022
116745
|
decisions: "it opens a panel in the shell",
|
|
116023
116746
|
expand: "it needs the shell's screen",
|
|
116024
|
-
interrupt: "use
|
|
116747
|
+
interrupt: "use /stop here to end a run you started from Telegram, or stop it in the shell",
|
|
116748
|
+
permissions: "it opens a panel in the shell; saved rules are changed there, never from a chat",
|
|
116749
|
+
"remote-policy": "it changes what Telegram may do without asking; use the shell",
|
|
116025
116750
|
demote: "it works on the shell's own tasks",
|
|
116026
116751
|
models: "use /model",
|
|
116027
116752
|
"external-diff": "listing and applying external patches stays in the shell, where the patch hash is typed",
|
|
@@ -116072,6 +116797,8 @@ class RemoteCommandRouter {
|
|
|
116072
116797
|
await this.listSessionsText();
|
|
116073
116798
|
ended(end, "done");
|
|
116074
116799
|
});
|
|
116800
|
+
} else if (decision.command === "stop") {
|
|
116801
|
+
this.stop(end);
|
|
116075
116802
|
} else {
|
|
116076
116803
|
this.ctx.reply(this.ctx.compose(remoteHelpText())).catch(() => false);
|
|
116077
116804
|
ended(end, "done");
|
|
@@ -116098,6 +116825,17 @@ class RemoteCommandRouter {
|
|
|
116098
116825
|
return;
|
|
116099
116826
|
}
|
|
116100
116827
|
}
|
|
116828
|
+
stop(end) {
|
|
116829
|
+
const result = this.ctx.host.stopTelegramTurn?.() ?? "idle";
|
|
116830
|
+
if (result === "stopped") {
|
|
116831
|
+
ended(end, "done");
|
|
116832
|
+
return;
|
|
116833
|
+
}
|
|
116834
|
+
const text = result === "operator" ? "/stop did nothing: the running turn was started in the shell, so only the shell can stop it." : "/stop did nothing: no run started from Telegram is going on.";
|
|
116835
|
+
this.ctx.record(`/stop: ${result === "operator" ? "turn belongs to the shell" : "nothing to stop"}`);
|
|
116836
|
+
this.ctx.reply(text).catch(() => false);
|
|
116837
|
+
ended(end, "failed");
|
|
116838
|
+
}
|
|
116101
116839
|
async idle() {
|
|
116102
116840
|
await this.chain;
|
|
116103
116841
|
if (this.detached > 0) {
|
|
@@ -116384,9 +117122,27 @@ function composeReply(text) {
|
|
|
116384
117122
|
return `${safe.slice(0, REMOTE_REPLY_MAX_CHARS)}
|
|
116385
117123
|
[cut: the full reply is in the shell]`;
|
|
116386
117124
|
}
|
|
117125
|
+
function mapApprovalAnswer(got, offered) {
|
|
117126
|
+
if (got === null || typeof got !== "object")
|
|
117127
|
+
return { decision: "deny", always: false };
|
|
117128
|
+
const o = got;
|
|
117129
|
+
const yes = o.decision === "allow" || o.decision === "always";
|
|
117130
|
+
const approvalId = typeof o.approvalId === "string" && o.approvalId.length > 0 ? o.approvalId : undefined;
|
|
117131
|
+
const fromId = typeof o.fromId === "number" && Number.isSafeInteger(o.fromId) ? o.fromId : undefined;
|
|
117132
|
+
return {
|
|
117133
|
+
decision: yes ? "allow" : "deny",
|
|
117134
|
+
always: o.decision === "always" && offered,
|
|
117135
|
+
...approvalId === undefined ? {} : { approvalId },
|
|
117136
|
+
...fromId === undefined ? {} : { fromId }
|
|
117137
|
+
};
|
|
117138
|
+
}
|
|
116387
117139
|
function composeApprovalPrompt(prompt) {
|
|
116388
117140
|
const safe = redactSensitiveText(prompt).trim();
|
|
116389
|
-
|
|
117141
|
+
if (safe.length <= MAX_APPROVAL_PROMPT_CHARS)
|
|
117142
|
+
return safe;
|
|
117143
|
+
const note = `
|
|
117144
|
+
[cut: the rest of this prompt is in the shell]`;
|
|
117145
|
+
return `${safe.slice(0, MAX_APPROVAL_PROMPT_CHARS - note.length - 1)}\u2026${note}`;
|
|
116390
117146
|
}
|
|
116391
117147
|
function describeApprovalForTopic(tool, inputJson, meta) {
|
|
116392
117148
|
const lines = [];
|
|
@@ -116406,8 +117162,12 @@ function describeApprovalForTopic(tool, inputJson, meta) {
|
|
|
116406
117162
|
shown = o.patch;
|
|
116407
117163
|
}
|
|
116408
117164
|
} catch {}
|
|
116409
|
-
|
|
117165
|
+
const total = shown.length;
|
|
117166
|
+
shown = total > APPROVAL_INPUT_PREVIEW_CHARS ? `${shown.slice(0, APPROVAL_INPUT_PREVIEW_CHARS)}\u2026` : shown;
|
|
116410
117167
|
lines.push(`Approve ${tool}?`, shown);
|
|
117168
|
+
if (total > APPROVAL_INPUT_PREVIEW_CHARS) {
|
|
117169
|
+
lines.push(`[cut: only the first ${APPROVAL_INPUT_PREVIEW_CHARS} of ${total} characters are shown; the rest is in the shell]`);
|
|
117170
|
+
}
|
|
116411
117171
|
}
|
|
116412
117172
|
if (meta?.destructive === true)
|
|
116413
117173
|
lines.push("Warning: this can delete or overwrite files.");
|
|
@@ -116425,28 +117185,33 @@ class RemoteBridge {
|
|
|
116425
117185
|
events = [];
|
|
116426
117186
|
host;
|
|
116427
117187
|
now;
|
|
116428
|
-
|
|
117188
|
+
approvalOverrideMs;
|
|
117189
|
+
policyOverride = {};
|
|
116429
117190
|
connectedOnce = false;
|
|
116430
117191
|
clientConnected = false;
|
|
116431
117192
|
tgTurn = false;
|
|
116432
117193
|
lastAssistantText = "";
|
|
116433
117194
|
runTimer;
|
|
116434
117195
|
timedOut = false;
|
|
117196
|
+
stoppedByUser = false;
|
|
117197
|
+
turnAbort;
|
|
116435
117198
|
closing;
|
|
116436
117199
|
router;
|
|
116437
117200
|
keepTopic = false;
|
|
116438
117201
|
switched = false;
|
|
116439
117202
|
waiting = [];
|
|
116440
117203
|
currentUpdate;
|
|
117204
|
+
currentFromId;
|
|
116441
117205
|
constructor(options) {
|
|
116442
117206
|
this.options = options;
|
|
116443
117207
|
this.host = options.host;
|
|
116444
117208
|
this.now = options.now ?? Date.now;
|
|
116445
|
-
this.
|
|
117209
|
+
this.approvalOverrideMs = options.approvalTimeoutMs;
|
|
116446
117210
|
this.router = new RemoteCommandRouter({
|
|
116447
117211
|
host: {
|
|
116448
117212
|
isBusy: () => this.host.isBusy(),
|
|
116449
117213
|
cancelTurn: () => this.host.cancelTurn(),
|
|
117214
|
+
stopTelegramTurn: () => this.stopTelegramTurn(),
|
|
116450
117215
|
...this.host.runCommand !== undefined ? { runCommand: (line) => this.host.runCommand(line) } : {},
|
|
116451
117216
|
...this.host.busyRefusal !== undefined ? { busyRefusal: (line) => this.host.busyRefusal(line) } : {},
|
|
116452
117217
|
...this.host.listModels !== undefined ? { listModels: (id) => this.host.listModels(id) } : {},
|
|
@@ -116493,6 +117258,25 @@ class RemoteBridge {
|
|
|
116493
117258
|
get telegramTurnActive() {
|
|
116494
117259
|
return this.tgTurn;
|
|
116495
117260
|
}
|
|
117261
|
+
get telegramTurnUserId() {
|
|
117262
|
+
return this.tgTurn ? this.currentFromId : undefined;
|
|
117263
|
+
}
|
|
117264
|
+
get configuredPermissionMode() {
|
|
117265
|
+
return this.policyOverride.permissionMode ?? this.client?.permissionMode ?? "ask";
|
|
117266
|
+
}
|
|
117267
|
+
get approvalTimeoutMs() {
|
|
117268
|
+
return this.policyOverride.approvalTimeoutMs ?? this.approvalOverrideMs ?? this.client?.approvalTimeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
|
|
117269
|
+
}
|
|
117270
|
+
get runTimeoutMs() {
|
|
117271
|
+
return this.policyOverride.runTimeoutMs ?? this.client?.runTimeoutMs ?? 0;
|
|
117272
|
+
}
|
|
117273
|
+
applyPolicy(next) {
|
|
117274
|
+
this.policyOverride = { ...this.policyOverride, ...next };
|
|
117275
|
+
this.host.onChange?.();
|
|
117276
|
+
}
|
|
117277
|
+
recordApproval(text) {
|
|
117278
|
+
this.push("approval", preview2(text));
|
|
117279
|
+
}
|
|
116496
117280
|
status() {
|
|
116497
117281
|
const client = this.client;
|
|
116498
117282
|
if (client === undefined) {
|
|
@@ -116615,7 +117399,7 @@ class RemoteBridge {
|
|
|
116615
117399
|
return;
|
|
116616
117400
|
}
|
|
116617
117401
|
this.push("line", `Telegram: ${preview2(line)}`);
|
|
116618
|
-
this.waiting.push({ updateId: meta.updateId, line });
|
|
117402
|
+
this.waiting.push({ updateId: meta.updateId, line, ...meta.fromId === undefined ? {} : { fromId: meta.fromId } });
|
|
116619
117403
|
if (this.host.isBusy()) {
|
|
116620
117404
|
this.host.enqueue(line);
|
|
116621
117405
|
} else {
|
|
@@ -116670,20 +117454,37 @@ class RemoteBridge {
|
|
|
116670
117454
|
return;
|
|
116671
117455
|
const next = this.waiting.shift();
|
|
116672
117456
|
this.currentUpdate = next?.updateId;
|
|
117457
|
+
this.currentFromId = next?.fromId;
|
|
116673
117458
|
if (next !== undefined)
|
|
116674
117459
|
this.report(next.updateId, "working");
|
|
116675
117460
|
this.tgTurn = true;
|
|
116676
117461
|
this.lastAssistantText = "";
|
|
116677
117462
|
this.timedOut = false;
|
|
116678
|
-
|
|
116679
|
-
|
|
117463
|
+
this.stoppedByUser = false;
|
|
117464
|
+
this.turnAbort = new AbortController;
|
|
117465
|
+
const limit = this.runTimeoutMs;
|
|
117466
|
+
if (limit > 0) {
|
|
116680
117467
|
this.runTimer = setTimeout(() => {
|
|
116681
117468
|
this.timedOut = true;
|
|
116682
117469
|
this.push("error", "run time limit reached; stopping the turn");
|
|
117470
|
+
this.turnAbort?.abort();
|
|
116683
117471
|
this.host.cancelTurn();
|
|
116684
117472
|
}, limit);
|
|
116685
117473
|
}
|
|
116686
117474
|
}
|
|
117475
|
+
stopTelegramTurn() {
|
|
117476
|
+
if (this.client === undefined)
|
|
117477
|
+
return "idle";
|
|
117478
|
+
if (!this.tgTurn)
|
|
117479
|
+
return this.host.isBusy() ? "operator" : "idle";
|
|
117480
|
+
if (this.stoppedByUser)
|
|
117481
|
+
return "stopped";
|
|
117482
|
+
this.stoppedByUser = true;
|
|
117483
|
+
this.push("command", "/stop: stopping the turn");
|
|
117484
|
+
this.turnAbort?.abort();
|
|
117485
|
+
this.host.cancelTurn();
|
|
117486
|
+
return "stopped";
|
|
117487
|
+
}
|
|
116687
117488
|
assistantText(text) {
|
|
116688
117489
|
if (!this.tgTurn)
|
|
116689
117490
|
return;
|
|
@@ -116714,15 +117515,18 @@ class RemoteBridge {
|
|
|
116714
117515
|
const client = this.client;
|
|
116715
117516
|
const text = this.lastAssistantText;
|
|
116716
117517
|
const timedOut = this.timedOut;
|
|
117518
|
+
const stopped = this.stoppedByUser && !timedOut;
|
|
116717
117519
|
const updateId = this.currentUpdate;
|
|
116718
117520
|
this.endTurnState();
|
|
116719
117521
|
if (client === undefined)
|
|
116720
117522
|
return;
|
|
116721
117523
|
if (updateId !== undefined)
|
|
116722
|
-
this.report(updateId, outcome.failed || timedOut ? "failed" : "done");
|
|
117524
|
+
this.report(updateId, outcome.failed || timedOut || stopped ? "failed" : "done");
|
|
116723
117525
|
let body;
|
|
116724
|
-
if (
|
|
116725
|
-
body = "Stopped
|
|
117526
|
+
if (stopped) {
|
|
117527
|
+
body = "Stopped by you.";
|
|
117528
|
+
} else if (timedOut) {
|
|
117529
|
+
body = "Stopped: this run went over the time limit you set for runs started from Telegram (runTimeoutMs).";
|
|
116726
117530
|
} else if (outcome.failed && text.trim().length === 0) {
|
|
116727
117531
|
body = "The run failed. The details are in the shell.";
|
|
116728
117532
|
} else if (text.trim().length === 0) {
|
|
@@ -116736,31 +117540,56 @@ class RemoteBridge {
|
|
|
116736
117540
|
endTurnState() {
|
|
116737
117541
|
this.tgTurn = false;
|
|
116738
117542
|
this.currentUpdate = undefined;
|
|
117543
|
+
this.currentFromId = undefined;
|
|
116739
117544
|
this.lastAssistantText = "";
|
|
116740
117545
|
this.timedOut = false;
|
|
117546
|
+
this.stoppedByUser = false;
|
|
117547
|
+
this.turnAbort?.abort();
|
|
117548
|
+
this.turnAbort = undefined;
|
|
116741
117549
|
if (this.runTimer !== undefined) {
|
|
116742
117550
|
clearTimeout(this.runTimer);
|
|
116743
117551
|
this.runTimer = undefined;
|
|
116744
117552
|
}
|
|
116745
117553
|
}
|
|
116746
117554
|
async requestApproval(prompt) {
|
|
117555
|
+
return (await this.askApproval(prompt)).decision;
|
|
117556
|
+
}
|
|
117557
|
+
async askApproval(prompt, options = {}) {
|
|
116747
117558
|
const client = this.client;
|
|
116748
117559
|
if (client === undefined || !client.connected) {
|
|
116749
117560
|
this.push("approval", "denied: not connected");
|
|
116750
|
-
return "deny";
|
|
117561
|
+
return { decision: "deny", always: false };
|
|
116751
117562
|
}
|
|
116752
117563
|
this.push("approval", "asked in the topic");
|
|
116753
|
-
let
|
|
117564
|
+
let answer;
|
|
116754
117565
|
try {
|
|
116755
|
-
|
|
117566
|
+
const text = composeApprovalPrompt(prompt);
|
|
117567
|
+
const timeoutMs = this.approvalTimeoutMs;
|
|
117568
|
+
if (client.askApproval !== undefined) {
|
|
117569
|
+
const signal = this.turnAbort?.signal;
|
|
117570
|
+
const got = await client.askApproval(text, timeoutMs, {
|
|
117571
|
+
...options.remember === undefined ? {} : { remember: options.remember },
|
|
117572
|
+
...signal === undefined ? {} : { signal }
|
|
117573
|
+
});
|
|
117574
|
+
answer = mapApprovalAnswer(got, options.remember !== undefined);
|
|
117575
|
+
} else {
|
|
117576
|
+
const decision = await client.requestApproval(text, timeoutMs);
|
|
117577
|
+
answer = { decision: decision === "allow" || decision === "always" ? "allow" : "deny", always: false };
|
|
117578
|
+
}
|
|
116756
117579
|
} catch {
|
|
116757
|
-
|
|
117580
|
+
answer = { decision: "deny", always: false };
|
|
116758
117581
|
}
|
|
116759
|
-
|
|
116760
|
-
|
|
117582
|
+
const by = answer.fromId === undefined ? "" : ` by user ${answer.fromId}`;
|
|
117583
|
+
this.push("approval", answer.decision === "allow" ? `${answer.always ? "allowed (always)" : "allowed"} in the topic${by}` : `denied${by === "" ? " (or no answer)" : by}`);
|
|
117584
|
+
return answer;
|
|
117585
|
+
}
|
|
117586
|
+
async reportRemembered(approvalId, remembered) {
|
|
117587
|
+
if (approvalId === undefined)
|
|
117588
|
+
return;
|
|
117589
|
+
await this.client?.reportApprovalResult?.(approvalId, remembered);
|
|
116761
117590
|
}
|
|
116762
117591
|
}
|
|
116763
|
-
var TG_SOURCE = "tg", REMOTE_EVENT_LIMIT = 20, REMOTE_REPLY_MAX_CHARS = 16000, EVENT_PREVIEW_CHARS = 80
|
|
117592
|
+
var TG_SOURCE = "tg", REMOTE_EVENT_LIMIT = 20, REMOTE_REPLY_MAX_CHARS = 16000, EVENT_PREVIEW_CHARS = 80;
|
|
116764
117593
|
var init_shell_bridge = __esm(() => {
|
|
116765
117594
|
init_service2();
|
|
116766
117595
|
init_client3();
|
|
@@ -118152,7 +118981,7 @@ function formatAge(ms) {
|
|
|
118152
118981
|
return `${minutes}m`;
|
|
118153
118982
|
return `${Math.round(minutes / 60)}h`;
|
|
118154
118983
|
}
|
|
118155
|
-
function formatRemoteStatusLines(status) {
|
|
118984
|
+
function formatRemoteStatusLines(status, posture) {
|
|
118156
118985
|
const lines = [`Remote control: ${STATE_WORDS[status.state]}`];
|
|
118157
118986
|
if (status.state === "off") {
|
|
118158
118987
|
lines.push("Topic: none");
|
|
@@ -118168,6 +118997,9 @@ function formatRemoteStatusLines(status) {
|
|
|
118168
118997
|
lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
|
|
118169
118998
|
}
|
|
118170
118999
|
lines.push(`Turn it off with ${REMOTE_CONTROL_COMMAND} off, or press o here. Rename: ${REMOTE_CONTROL_COMMAND} off, then ${REMOTE_CONTROL_COMMAND} <name>.`);
|
|
119000
|
+
if (posture !== undefined && posture.length > 0) {
|
|
119001
|
+
lines.push("", "Telegram permissions (change with /remote-policy):", ...posture.map((line) => ` ${line}`));
|
|
119002
|
+
}
|
|
118171
119003
|
return lines;
|
|
118172
119004
|
}
|
|
118173
119005
|
function clock2(at) {
|
|
@@ -118224,7 +119056,7 @@ function presentRemoteControl(open, otui, chrome, options) {
|
|
|
118224
119056
|
return formatRemoteEventLines(status);
|
|
118225
119057
|
if (tab === "commands")
|
|
118226
119058
|
return formatRemoteCommandLines(status);
|
|
118227
|
-
return formatRemoteStatusLines(status);
|
|
119059
|
+
return formatRemoteStatusLines(status, options.getPostureLines?.());
|
|
118228
119060
|
};
|
|
118229
119061
|
const content = (tab) => {
|
|
118230
119062
|
const lines = linesFor(tab);
|
|
@@ -118293,6 +119125,13 @@ function projectRemoteRow(status, width) {
|
|
|
118293
119125
|
return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open" };
|
|
118294
119126
|
return { text: fit3(topic, width), role: "ok", action: "open" };
|
|
118295
119127
|
}
|
|
119128
|
+
function projectPostureLine(status, posture, width) {
|
|
119129
|
+
if (status === undefined || status.state === "off" || posture === undefined || posture.length === 0)
|
|
119130
|
+
return;
|
|
119131
|
+
return posture.split(`
|
|
119132
|
+
`).map((line) => fit3(line, width)).join(`
|
|
119133
|
+
`);
|
|
119134
|
+
}
|
|
118296
119135
|
function mountRemotePanel(otui, renderer, parent, options) {
|
|
118297
119136
|
const core = otui;
|
|
118298
119137
|
const box = new core.BoxRenderable(renderer, { id: "sb-remote", flexDirection: "column", flexShrink: 0 });
|
|
@@ -118306,6 +119145,8 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118306
119145
|
const value = new core.TextRenderable(renderer, { id: "sb-remote-v", content: "" });
|
|
118307
119146
|
box.add(label);
|
|
118308
119147
|
box.add(value);
|
|
119148
|
+
const posture = new core.TextRenderable(renderer, { id: "sb-remote-p", content: "" });
|
|
119149
|
+
box.add(posture);
|
|
118309
119150
|
const currentRow = () => projectRemoteRow(options.getStatus(), options.width);
|
|
118310
119151
|
const draw = () => {
|
|
118311
119152
|
if (disposed)
|
|
@@ -118313,6 +119154,9 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118313
119154
|
const row = currentRow();
|
|
118314
119155
|
label.content = core.t`${dimChunk(core, "Remote")}`;
|
|
118315
119156
|
value.content = core.t`${roleChunk(core, row.role, row.text)}`;
|
|
119157
|
+
const line = projectPostureLine(options.getStatus(), options.getPosture?.(), options.width);
|
|
119158
|
+
posture.content = line === undefined ? "" : core.t`${dimChunk(core, line)}`;
|
|
119159
|
+
posture.visible = line !== undefined;
|
|
118316
119160
|
};
|
|
118317
119161
|
const activate = () => {
|
|
118318
119162
|
options.onOpen();
|
|
@@ -118324,6 +119168,9 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118324
119168
|
value.onMouseDown = () => {
|
|
118325
119169
|
activate();
|
|
118326
119170
|
};
|
|
119171
|
+
posture.onMouseDown = () => {
|
|
119172
|
+
options.onOpenPermissions?.();
|
|
119173
|
+
};
|
|
118327
119174
|
const unsubscribeTheme = onThemeChange(guardedThemeRepaint("remote-panel", draw, () => disposed || isRenderableGone(box)));
|
|
118328
119175
|
draw();
|
|
118329
119176
|
return {
|
|
@@ -118587,6 +119434,11 @@ var init_agent_commands = __esm(() => {
|
|
|
118587
119434
|
description: "How Telegram replies are written: /rendering [auto|rich|html|plain]",
|
|
118588
119435
|
modes: AGENT_ONLY
|
|
118589
119436
|
},
|
|
119437
|
+
{
|
|
119438
|
+
name: "/remote-policy",
|
|
119439
|
+
description: "Telegram defaults: /remote-policy [mode ask|trust] [limit none|<minutes>] [wait <minutes>]. Never changes the shell's /mode",
|
|
119440
|
+
modes: AGENT_ONLY
|
|
119441
|
+
},
|
|
118590
119442
|
{
|
|
118591
119443
|
name: "/product",
|
|
118592
119444
|
description: "Intents closed in code that nobody looked back at, with their outcome criteria",
|
|
@@ -118622,6 +119474,11 @@ var init_agent_commands = __esm(() => {
|
|
|
118622
119474
|
description: "Pending remote approvals: summary, scope, consequence, expiry \u2014 allow or deny one call, once",
|
|
118623
119475
|
modes: AGENT_ONLY
|
|
118624
119476
|
},
|
|
119477
|
+
{
|
|
119478
|
+
name: "/permissions",
|
|
119479
|
+
description: "The saved shell rules (what Always remembered): honoured or not, session grants; remove one",
|
|
119480
|
+
modes: AGENT_ONLY
|
|
119481
|
+
},
|
|
118625
119482
|
{
|
|
118626
119483
|
name: "/decisions",
|
|
118627
119484
|
description: "Recommendation journal: how often you follow the agent's recommendation, by mode and stage, and the deviations; /decisions reason <why> and /decisions change <option> follow up on the last answer",
|
|
@@ -118832,10 +119689,14 @@ function classifyBusyDispatch(params) {
|
|
|
118832
119689
|
return "remote-control";
|
|
118833
119690
|
if (commandName === "/channels")
|
|
118834
119691
|
return "channels";
|
|
119692
|
+
if (commandName === "/remote-policy")
|
|
119693
|
+
return "remote-policy";
|
|
118835
119694
|
if (commandName === "/schedules")
|
|
118836
119695
|
return "schedules";
|
|
118837
119696
|
if (commandName === "/approvals")
|
|
118838
119697
|
return "approvals";
|
|
119698
|
+
if (commandName === "/permissions")
|
|
119699
|
+
return "permissions";
|
|
118839
119700
|
if (commandName === "/decisions")
|
|
118840
119701
|
return "decisions";
|
|
118841
119702
|
if (commandName === "/external-diff")
|
|
@@ -138664,6 +139525,10 @@ async function runSettingsCommand(command, handlers) {
|
|
|
138664
139525
|
case "/theme":
|
|
138665
139526
|
handlers.theme(arg);
|
|
138666
139527
|
return;
|
|
139528
|
+
case "/remote-policy":
|
|
139529
|
+
if (handlers.remotePolicy !== undefined)
|
|
139530
|
+
handlers.onSystem(handlers.remotePolicy(arg));
|
|
139531
|
+
return;
|
|
138667
139532
|
}
|
|
138668
139533
|
} catch (error) {
|
|
138669
139534
|
handlers.onSystem(`${name}: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -139738,6 +140603,58 @@ var init_games = __esm(() => {
|
|
|
139738
140603
|
init_tic_tac_toe();
|
|
139739
140604
|
});
|
|
139740
140605
|
|
|
140606
|
+
// src/remote/telegram-permission.ts
|
|
140607
|
+
function modeInForce(input) {
|
|
140608
|
+
if (input.changedThisSession)
|
|
140609
|
+
return { mode: input.shellMode, source: "shell-mode" };
|
|
140610
|
+
if (input.telegramTurn)
|
|
140611
|
+
return { mode: input.telegramDefault, source: "telegram-default" };
|
|
140612
|
+
return { mode: input.shellMode, source: "shell-default" };
|
|
140613
|
+
}
|
|
140614
|
+
function formatModeInForce(value) {
|
|
140615
|
+
const source = value.source === "telegram-default" ? "Telegram default" : value.source === "shell-mode" ? "shell /mode" : "shell default";
|
|
140616
|
+
return `${value.mode} (${source})`;
|
|
140617
|
+
}
|
|
140618
|
+
function telegramRememberOffer(ev) {
|
|
140619
|
+
if (ev.destructive || ev.credentials || ev.sacReviewConfirmation || ev.publishLease || ev.hookAsk || ev.untrustedOrigin) {
|
|
140620
|
+
return;
|
|
140621
|
+
}
|
|
140622
|
+
if (ev.command.length > APPROVAL_INPUT_PREVIEW_CHARS)
|
|
140623
|
+
return;
|
|
140624
|
+
const { exact, prefix, offerExact, offerPrefix } = suggestShellPatterns(ev.command);
|
|
140625
|
+
const shownAsStored = (pattern) => redactSensitiveText(pattern) === pattern;
|
|
140626
|
+
if (offerExact) {
|
|
140627
|
+
return exact.length <= MAX_REMEMBER_PATTERN_CHARS && shownAsStored(exact) ? { pattern: exact, kind: "exact" } : undefined;
|
|
140628
|
+
}
|
|
140629
|
+
if (offerPrefix && prefix.length <= MAX_REMEMBER_PATTERN_CHARS && shownAsStored(prefix))
|
|
140630
|
+
return { pattern: prefix, kind: "prefix" };
|
|
140631
|
+
return;
|
|
140632
|
+
}
|
|
140633
|
+
function evaluateTelegramShellApproval(input) {
|
|
140634
|
+
const evaluation = evaluateShellApproval(input);
|
|
140635
|
+
return {
|
|
140636
|
+
evaluation,
|
|
140637
|
+
autoApprove: evaluation.autoApprove,
|
|
140638
|
+
offer: evaluation.autoApprove ? undefined : telegramRememberOffer(evaluation)
|
|
140639
|
+
};
|
|
140640
|
+
}
|
|
140641
|
+
function userSuffix(userId) {
|
|
140642
|
+
return userId === undefined ? "" : `, user ${userId}`;
|
|
140643
|
+
}
|
|
140644
|
+
function modeAutoApprovalAudit(input) {
|
|
140645
|
+
return `auto-approved (${input.mode}${userSuffix(input.userId)}): ${input.preview}`;
|
|
140646
|
+
}
|
|
140647
|
+
function savedRuleAutoApprovalAudit(input) {
|
|
140648
|
+
const shown = input.command.length > 200 ? `${input.command.slice(0, 199)}\u2026` : input.command;
|
|
140649
|
+
return `auto-approved by a saved rule${input.userId === undefined ? "" : ` (user ${input.userId})`}: ${shown}`;
|
|
140650
|
+
}
|
|
140651
|
+
var init_telegram_permission = __esm(() => {
|
|
140652
|
+
init_shell_approval();
|
|
140653
|
+
init_shell_permissions();
|
|
140654
|
+
init_service2();
|
|
140655
|
+
init_protocol2();
|
|
140656
|
+
});
|
|
140657
|
+
|
|
139741
140658
|
// src/mcp-client/elicitation.ts
|
|
139742
140659
|
function extractCodexCommand(vendor) {
|
|
139743
140660
|
const raw = vendor.codex_command;
|
|
@@ -141383,8 +142300,12 @@ async function forceForegroundQueueItem(owner, handoff, item, deps) {
|
|
|
141383
142300
|
}
|
|
141384
142301
|
});
|
|
141385
142302
|
}
|
|
141386
|
-
function createForegroundAgentIoFacade(owner, token, io) {
|
|
142303
|
+
function createForegroundAgentIoFacade(owner, token, io, turn = {}) {
|
|
141387
142304
|
const accepts = () => owner.accepts(token);
|
|
142305
|
+
const approve = () => turn.requestApproval ?? io.requestApproval;
|
|
142306
|
+
const autoApproved = () => turn.onAutoApproved ?? io.onAutoApproved;
|
|
142307
|
+
const modeOf = () => turn.permissionMode ?? io.permissionMode;
|
|
142308
|
+
const mcpGrantsApply = turn.mcpGrantsApply ?? io.mcpGrantsApply;
|
|
141388
142309
|
return {
|
|
141389
142310
|
write: (text) => {
|
|
141390
142311
|
if (accepts())
|
|
@@ -141433,15 +142354,16 @@ function createForegroundAgentIoFacade(owner, token, io) {
|
|
|
141433
142354
|
requestApproval: async (tool, input, meta) => {
|
|
141434
142355
|
if (!accepts())
|
|
141435
142356
|
return false;
|
|
141436
|
-
const response = await
|
|
142357
|
+
const response = await approve()?.(tool, input, meta);
|
|
141437
142358
|
return accepts() ? response ?? false : false;
|
|
141438
142359
|
},
|
|
141439
142360
|
onAutoApproved: (tool, input, meta) => {
|
|
141440
142361
|
if (accepts())
|
|
141441
|
-
|
|
142362
|
+
autoApproved()?.(tool, input, meta);
|
|
141442
142363
|
},
|
|
141443
|
-
permissionMode: () => accepts() ?
|
|
142364
|
+
permissionMode: () => accepts() ? modeOf()?.() ?? "ask" : "ask",
|
|
141444
142365
|
...io.trustedMcpTools === undefined ? {} : { trustedMcpTools: io.trustedMcpTools },
|
|
142366
|
+
...mcpGrantsApply === undefined ? {} : { mcpGrantsApply },
|
|
141445
142367
|
...io.mcpToolFingerprint === undefined ? {} : { mcpToolFingerprint: io.mcpToolFingerprint },
|
|
141446
142368
|
...io.mcpToolDestructive === undefined ? {} : { mcpToolDestructive: io.mcpToolDestructive },
|
|
141447
142369
|
...io.beforeMutation === undefined ? {} : {
|
|
@@ -151349,7 +152271,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
151349
152271
|
});
|
|
151350
152272
|
let uid = 0;
|
|
151351
152273
|
const herdr = createHerdrReporter();
|
|
151352
|
-
const sessionShellAllow = new Set
|
|
152274
|
+
const sessionShellAllow = new Set;
|
|
151353
152275
|
let permissionMigrationShown = false;
|
|
151354
152276
|
let permissionsFingerprintAtStart = shellPermissionsFingerprint();
|
|
151355
152277
|
let permissionTamperShown = false;
|
|
@@ -151694,7 +152616,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
151694
152616
|
liveRemotePanel = mountRemotePanel(otui, r, sidebar, {
|
|
151695
152617
|
width: SIDEBAR_TEXT_WIDTH,
|
|
151696
152618
|
getStatus: remoteStatus,
|
|
151697
|
-
onOpen: () => showRemoteControl()
|
|
152619
|
+
onOpen: () => showRemoteControl(),
|
|
152620
|
+
onOpenPermissions: () => showPermissions(),
|
|
152621
|
+
getPosture: () => {
|
|
152622
|
+
const posture = remoteBridge?.active === true ? telegramPosture() : undefined;
|
|
152623
|
+
return posture === undefined ? undefined : postureSidebarText(posture);
|
|
152624
|
+
}
|
|
151698
152625
|
});
|
|
151699
152626
|
liveChannelsPanel = mountChannelsPanel(otui, r, sidebar, {
|
|
151700
152627
|
width: SIDEBAR_TEXT_WIDTH,
|
|
@@ -152509,24 +153436,60 @@ async function launchTuiAgentShell(opts) {
|
|
|
152509
153436
|
setBusyPhase("running approved shell");
|
|
152510
153437
|
return true;
|
|
152511
153438
|
};
|
|
152512
|
-
|
|
153439
|
+
const requestApprovalForTelegramTurn = async (tool, inputJson, meta) => {
|
|
152513
153440
|
const bridge = remoteBridge;
|
|
152514
153441
|
if (bridge === undefined || !bridge.telegramTurnActive) {
|
|
152515
153442
|
return requestApprovalLocally(tool, inputJson, meta);
|
|
152516
153443
|
}
|
|
152517
153444
|
const brief = tool.length > 60 ? `${tool.slice(0, 59)}\u2026` : tool;
|
|
153445
|
+
const approvedAnswer = () => meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint } : true;
|
|
153446
|
+
let offer;
|
|
153447
|
+
if (tool === "shell_exec") {
|
|
153448
|
+
const judged = evaluateTelegramShellApproval({
|
|
153449
|
+
inputJson,
|
|
153450
|
+
...meta !== undefined ? { meta } : {},
|
|
153451
|
+
sessionAllow: sessionShellAllow,
|
|
153452
|
+
fingerprintAtStart: permissionsFingerprintAtStart
|
|
153453
|
+
});
|
|
153454
|
+
if (!permissionTamperShown && judged.evaluation.tampered) {
|
|
153455
|
+
permissionTamperShown = true;
|
|
153456
|
+
io.onSystem?.(`\u26A0 the saved shell permissions changed outside this approval UI \u2014 review them before trusting an auto-approve
|
|
153457
|
+
`);
|
|
153458
|
+
}
|
|
153459
|
+
if (judged.autoApprove) {
|
|
153460
|
+
const shown = judged.evaluation.command.length > 200 ? `${judged.evaluation.command.slice(0, 199)}\u2026` : judged.evaluation.command;
|
|
153461
|
+
io.onSystem?.(`\u2713 auto-approved shell (saved rule): ${shown}
|
|
153462
|
+
`);
|
|
153463
|
+
bridge.recordApproval(savedRuleAutoApprovalAudit({ userId: bridge.telegramTurnUserId, command: judged.evaluation.command }));
|
|
153464
|
+
return approvedAnswer();
|
|
153465
|
+
}
|
|
153466
|
+
offer = judged.offer;
|
|
153467
|
+
}
|
|
152518
153468
|
io.onSystem?.(`\u25C7 approval for ${brief} sent to the Telegram topic; waiting for the answer there.
|
|
152519
153469
|
`);
|
|
152520
153470
|
setMainAgent("blocked", "approval (telegram)");
|
|
152521
|
-
const
|
|
153471
|
+
const answer = await bridge.askApproval(describeApprovalForTopic(tool, inputJson, meta), offer === undefined ? {} : { remember: offer.pattern });
|
|
153472
|
+
const decision = answer.decision;
|
|
153473
|
+
let rememberedNote = "";
|
|
153474
|
+
if (decision === "allow" && answer.always && offer !== undefined) {
|
|
153475
|
+
const stored = allowShellPattern(offer.pattern);
|
|
153476
|
+
if (stored.length > 0) {
|
|
153477
|
+
sessionShellAllow.add(stored);
|
|
153478
|
+
permissionsFingerprintAtStart = shellPermissionsFingerprint();
|
|
153479
|
+
}
|
|
153480
|
+
rememberedNote = stored.length > 0 ? ` and remembered \u201C${stored}\u201D` : ` once; \u201C${offer.pattern}\u201D cannot be remembered`;
|
|
153481
|
+
bridge.recordApproval(stored.length > 0 ? `saved a rule: ${stored}` : "a rule was asked for and refused by the store");
|
|
153482
|
+
await bridge.reportRemembered(answer.approvalId, stored.length > 0);
|
|
153483
|
+
}
|
|
152522
153484
|
setMainAgent("running", decision === "allow" ? "approved (telegram)" : "denied");
|
|
152523
|
-
io.onSystem?.(decision === "allow" ? `\u25C7 ${brief} approved in Telegram.
|
|
153485
|
+
io.onSystem?.(decision === "allow" ? `\u25C7 ${brief} approved in Telegram${rememberedNote}.
|
|
152524
153486
|
` : `\u25C7 ${brief} denied (Telegram answer, timeout or no connection).
|
|
152525
153487
|
`);
|
|
152526
153488
|
if (decision !== "allow")
|
|
152527
153489
|
return false;
|
|
152528
|
-
return
|
|
153490
|
+
return approvedAnswer();
|
|
152529
153491
|
};
|
|
153492
|
+
io.requestApproval = requestApprovalLocally;
|
|
152530
153493
|
const askUserInteractive = async (req) => {
|
|
152531
153494
|
chrome.hideMenu();
|
|
152532
153495
|
setMainAgent("blocked", "ask");
|
|
@@ -152607,7 +153570,15 @@ async function launchTuiAgentShell(opts) {
|
|
|
152607
153570
|
});
|
|
152608
153571
|
const sessionCwd = opts.session?.cwd ?? process.cwd();
|
|
152609
153572
|
let permissionMode = opts.initialPermissionMode ?? getProjectPermissionMode(sessionCwd) ?? DEFAULT_PERMISSION_MODE;
|
|
152610
|
-
|
|
153573
|
+
let modeChangedThisSession = false;
|
|
153574
|
+
const modeForTurn = (telegramTurn) => modeInForce({
|
|
153575
|
+
shellMode: permissionMode,
|
|
153576
|
+
changedThisSession: modeChangedThisSession,
|
|
153577
|
+
telegramTurn,
|
|
153578
|
+
telegramDefault: remoteBridge?.configuredPermissionMode ?? "ask"
|
|
153579
|
+
});
|
|
153580
|
+
const modeNow = () => modeForTurn(remoteBridge?.telegramTurnActive === true);
|
|
153581
|
+
io.permissionMode = () => modeForTurn(false).mode;
|
|
152611
153582
|
io.trustedMcpTools = new Map;
|
|
152612
153583
|
io.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
152613
153584
|
io.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
@@ -152615,18 +153586,30 @@ async function launchTuiAgentShell(opts) {
|
|
|
152615
153586
|
io.readOnly = () => readOnly;
|
|
152616
153587
|
const paintModeRow = () => {
|
|
152617
153588
|
const row = describeModeRow(permissionMode, readOnly);
|
|
153589
|
+
const inForce = remoteBridge?.active === true ? formatModeInForce(modeNow()) : undefined;
|
|
152618
153590
|
const hint = settingsSidebarHint(row.readOnly !== undefined);
|
|
152619
|
-
sbModeV.content = row.readOnly !== undefined ? otui.t`${dimChunk(otui, `mode ${row.mode}`)} ${roleChunk(otui, "attention", row.readOnly)}` : otui.t`${dimChunk(otui, `mode ${row.mode}${hint === undefined ? "" : ` ${hint}`}`)}`;
|
|
153591
|
+
sbModeV.content = row.readOnly !== undefined ? otui.t`${dimChunk(otui, `mode ${inForce ?? row.mode}`)} ${roleChunk(otui, "attention", row.readOnly)}` : otui.t`${dimChunk(otui, `mode ${inForce ?? row.mode}${hint === undefined || inForce !== undefined ? "" : ` ${hint}`}`)}`;
|
|
152620
153592
|
};
|
|
152621
153593
|
paintModeRow();
|
|
152622
|
-
|
|
153594
|
+
const autoApprovedFor = (telegramTurn) => (tool, input, meta) => {
|
|
152623
153595
|
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
152624
|
-
const
|
|
153596
|
+
const inForce = modeForTurn(telegramTurn);
|
|
153597
|
+
const label = `\u25C7 auto-approved (${inForce.mode})` + (meta.destructive ? " [destructive]" : "") + (meta.mcpTrusted === true ? ` ${TRUSTED_MARKER}` : "");
|
|
153598
|
+
if (telegramTurn && remoteBridge !== undefined) {
|
|
153599
|
+
remoteBridge.recordApproval(modeAutoApprovalAudit({ mode: inForce.mode, userId: remoteBridge.telegramTurnUserId, preview }));
|
|
153600
|
+
}
|
|
152625
153601
|
transcript.add(new otui.TextRenderable(r, {
|
|
152626
153602
|
id: `ap${uid++}`,
|
|
152627
153603
|
content: otui.t`${roleChunk(otui, "attention", label)} ${dimChunk(otui, preview)}`
|
|
152628
153604
|
}));
|
|
152629
153605
|
};
|
|
153606
|
+
io.onAutoApproved = autoApprovedFor(false);
|
|
153607
|
+
const telegramTurnIo = {
|
|
153608
|
+
requestApproval: requestApprovalForTelegramTurn,
|
|
153609
|
+
permissionMode: () => modeForTurn(true).mode,
|
|
153610
|
+
onAutoApproved: autoApprovedFor(true),
|
|
153611
|
+
mcpGrantsApply: () => false
|
|
153612
|
+
};
|
|
152630
153613
|
let liveSession;
|
|
152631
153614
|
let history = [];
|
|
152632
153615
|
let archive = [];
|
|
@@ -153140,6 +154123,50 @@ Staying in the current session.
|
|
|
153140
154123
|
io.onSystem?.("The product index could not be used. Run `keryx product index`.\n");
|
|
153141
154124
|
});
|
|
153142
154125
|
};
|
|
154126
|
+
const permissionsChanged = () => {
|
|
154127
|
+
permissionsFingerprintAtStart = shellPermissionsFingerprint();
|
|
154128
|
+
};
|
|
154129
|
+
const showPermissions = () => {
|
|
154130
|
+
openPermissions(otui, chrome, {
|
|
154131
|
+
sessionAllow: sessionShellAllow,
|
|
154132
|
+
onChanged: permissionsChanged,
|
|
154133
|
+
onKeypress: (handler) => onKeypress4(r, handler),
|
|
154134
|
+
renderer: r,
|
|
154135
|
+
inputBlocked: () => chrome.keyboardOwnedElsewhere()
|
|
154136
|
+
});
|
|
154137
|
+
};
|
|
154138
|
+
const runPermissionsCommand = (line) => {
|
|
154139
|
+
const rest = line.trim().slice(PERMISSIONS_COMMAND.length).trim();
|
|
154140
|
+
if (rest.length === 0) {
|
|
154141
|
+
showPermissions();
|
|
154142
|
+
return;
|
|
154143
|
+
}
|
|
154144
|
+
io.onSystem?.(permissionsSlashText(rest, { sessionAllow: sessionShellAllow, onChanged: permissionsChanged }));
|
|
154145
|
+
};
|
|
154146
|
+
const runningTelegramPolicy = () => {
|
|
154147
|
+
const bridge = remoteBridge;
|
|
154148
|
+
if (bridge === undefined || !bridge.active)
|
|
154149
|
+
return;
|
|
154150
|
+
return {
|
|
154151
|
+
defaultMode: bridge.configuredPermissionMode,
|
|
154152
|
+
runTimeoutMs: bridge.runTimeoutMs,
|
|
154153
|
+
approvalTimeoutMs: bridge.approvalTimeoutMs
|
|
154154
|
+
};
|
|
154155
|
+
};
|
|
154156
|
+
const telegramModeInForce = () => remoteBridge?.active === true ? formatModeInForce(modeNow()) : undefined;
|
|
154157
|
+
const remotePolicyDeps = () => ({
|
|
154158
|
+
running: runningTelegramPolicy,
|
|
154159
|
+
inForce: telegramModeInForce,
|
|
154160
|
+
applyPolicy: (patch) => {
|
|
154161
|
+
remoteBridge?.applyPolicy(patch);
|
|
154162
|
+
paintModeRow();
|
|
154163
|
+
liveRemotePanel?.refresh();
|
|
154164
|
+
}
|
|
154165
|
+
});
|
|
154166
|
+
const telegramPosture = () => loadPosture({ running: runningTelegramPolicy, inForce: telegramModeInForce });
|
|
154167
|
+
const runRemotePolicyCommand = (line) => {
|
|
154168
|
+
io.onSystem?.(remotePolicyText(line.trim().slice(REMOTE_POLICY_COMMAND.length), remotePolicyDeps()));
|
|
154169
|
+
};
|
|
153143
154170
|
const showReviews = () => {
|
|
153144
154171
|
openReviews(otui, chrome, { cwd: inspectorCwd(), renderer: r, ...inspectorKeys }).then(() => liveReviewsPanel?.refresh()).catch(() => {
|
|
153145
154172
|
io.onSystem?.(`The managed reviews could not be read.
|
|
@@ -153170,6 +154197,10 @@ Staying in the current session.
|
|
|
153170
154197
|
const showRemoteControl = () => {
|
|
153171
154198
|
openRemoteControl(otui, chrome, {
|
|
153172
154199
|
getStatus: remoteStatus,
|
|
154200
|
+
getPostureLines: () => {
|
|
154201
|
+
const posture = remoteBridge?.active === true ? telegramPosture() : undefined;
|
|
154202
|
+
return posture === undefined ? undefined : postureLines(posture);
|
|
154203
|
+
},
|
|
153173
154204
|
onToggle: () => {
|
|
153174
154205
|
remoteBridge?.active === true ? disableRemoteControl() : enableRemoteControl();
|
|
153175
154206
|
},
|
|
@@ -153567,6 +154598,7 @@ Staying in the current session.
|
|
|
153567
154598
|
};
|
|
153568
154599
|
const commitPermissionMode = (next) => {
|
|
153569
154600
|
permissionMode = next;
|
|
154601
|
+
modeChangedThisSession = true;
|
|
153570
154602
|
paintModeRow();
|
|
153571
154603
|
chrome.showToast(`Permission mode: ${next}`);
|
|
153572
154604
|
};
|
|
@@ -153737,7 +154769,12 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153737
154769
|
guard: guardEnabled,
|
|
153738
154770
|
routing: routingEnabled,
|
|
153739
154771
|
thinkDisplay: thinkDisplayMode,
|
|
153740
|
-
reasoningOverride
|
|
154772
|
+
reasoningOverride,
|
|
154773
|
+
...(() => {
|
|
154774
|
+
const running = runningTelegramPolicy();
|
|
154775
|
+
const inForce = telegramModeInForce();
|
|
154776
|
+
return running === undefined ? {} : { telegram: { ...running, ...inForce !== undefined ? { inForce } : {} } };
|
|
154777
|
+
})()
|
|
153741
154778
|
}));
|
|
153742
154779
|
const runSettingsAction = (command) => runSettingsCommand(command, {
|
|
153743
154780
|
mode: runModeCommand,
|
|
@@ -153752,6 +154789,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153752
154789
|
reasoning: runReasoningCommand,
|
|
153753
154790
|
think: runThinkCommand,
|
|
153754
154791
|
theme: runThemeCommand,
|
|
154792
|
+
remotePolicy: (arg) => remotePolicyText(arg, remotePolicyDeps()),
|
|
153755
154793
|
onSystem: (text) => io.onSystem?.(text)
|
|
153756
154794
|
});
|
|
153757
154795
|
const showSettings = () => {
|
|
@@ -154294,6 +155332,14 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154294
155332
|
routeApprovalsCommand(line, true, approvals);
|
|
154295
155333
|
return;
|
|
154296
155334
|
}
|
|
155335
|
+
case "permissions": {
|
|
155336
|
+
runPermissionsCommand(line);
|
|
155337
|
+
return;
|
|
155338
|
+
}
|
|
155339
|
+
case "remote-policy": {
|
|
155340
|
+
runRemotePolicyCommand(line);
|
|
155341
|
+
return;
|
|
155342
|
+
}
|
|
154297
155343
|
case "decisions": {
|
|
154298
155344
|
routeDecisionsCommand(line, decisionsPanel);
|
|
154299
155345
|
return;
|
|
@@ -154618,6 +155664,14 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154618
155664
|
if (routeApprovalsCommand(line, false, approvals)) {
|
|
154619
155665
|
return;
|
|
154620
155666
|
}
|
|
155667
|
+
if (command.name === PERMISSIONS_COMMAND) {
|
|
155668
|
+
runPermissionsCommand(line);
|
|
155669
|
+
return;
|
|
155670
|
+
}
|
|
155671
|
+
if (command.name === REMOTE_POLICY_COMMAND) {
|
|
155672
|
+
runRemotePolicyCommand(line);
|
|
155673
|
+
return;
|
|
155674
|
+
}
|
|
154621
155675
|
if (routeDecisionsCommand(line, decisionsPanel)) {
|
|
154622
155676
|
return;
|
|
154623
155677
|
}
|
|
@@ -155160,6 +156214,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155160
156214
|
deps.resetSubagentBudget?.();
|
|
155161
156215
|
const operation = foregroundOperation.begin();
|
|
155162
156216
|
remoteQueue.turnStarted(source);
|
|
156217
|
+
const telegramTurn = source === TG_SOURCE && remoteBridge?.telegramTurnActive === true;
|
|
155163
156218
|
blockIo.resetReasoningLiveState();
|
|
155164
156219
|
setMainAgent("running", "waiting");
|
|
155165
156220
|
startBusy("waiting for model");
|
|
@@ -155246,7 +156301,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155246
156301
|
guardCollector.reset(line);
|
|
155247
156302
|
syncArchive();
|
|
155248
156303
|
rewindRecorder.beginTurn({ archiveIndex: archive.length, prompt: line });
|
|
155249
|
-
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io);
|
|
156304
|
+
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io, telegramTurn ? telegramTurnIo : {});
|
|
155250
156305
|
const turnSignal = foregroundOperation.signal;
|
|
155251
156306
|
runAgentTurn(foregroundIo, deps, history, line, {
|
|
155252
156307
|
signal: turnSignal,
|
|
@@ -155434,7 +156489,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155434
156489
|
});
|
|
155435
156490
|
return `${formatSessionInfoText(snapshot).trimEnd()}
|
|
155436
156491
|
|
|
155437
|
-
Mode: ${
|
|
156492
|
+
Mode: ${formatModeInForce(modeForTurn(true))}${readOnly ? " (read-only on)" : ""}
|
|
155438
156493
|
Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} waiting`;
|
|
155439
156494
|
};
|
|
155440
156495
|
const runRemoteCommand = async (line) => {
|
|
@@ -155447,12 +156502,13 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155447
156502
|
`);
|
|
155448
156503
|
});
|
|
155449
156504
|
}
|
|
155450
|
-
|
|
156505
|
+
const modeWords = line.trim().split(/\s+/).slice(1).filter((word) => word.length > 0);
|
|
156506
|
+
if (name === "/mode" && (first === "" || (first === "ask" || first === "trust" || first === "auto") && modeWords.length === 1)) {
|
|
155451
156507
|
echoRemoteCommand(line);
|
|
155452
156508
|
return await captureShellOutput(() => {
|
|
155453
|
-
if (first === "auto")
|
|
155454
|
-
commitPermissionMode(
|
|
155455
|
-
io.onSystem?.(`Permission mode: ${
|
|
156509
|
+
if (first === "ask" || first === "trust" || first === "auto")
|
|
156510
|
+
commitPermissionMode(first);
|
|
156511
|
+
io.onSystem?.(`Permission mode: ${formatModeInForce(modeForTurn(true))} (ask, trust or auto)
|
|
155456
156512
|
`);
|
|
155457
156513
|
});
|
|
155458
156514
|
}
|
|
@@ -155555,7 +156611,10 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155555
156611
|
pendingResumeTarget = undefined;
|
|
155556
156612
|
return liveSession.summary.id === found.id ? { ...outcome, ok: true } : { ...outcome, ok: false };
|
|
155557
156613
|
},
|
|
155558
|
-
onChange: () =>
|
|
156614
|
+
onChange: () => {
|
|
156615
|
+
liveRemotePanel?.refresh();
|
|
156616
|
+
paintModeRow();
|
|
156617
|
+
}
|
|
155559
156618
|
}
|
|
155560
156619
|
});
|
|
155561
156620
|
onKeypress4(r, (key) => {
|
|
@@ -155607,6 +156666,9 @@ var init_tui_shell = __esm(() => {
|
|
|
155607
156666
|
init_flow_inspector();
|
|
155608
156667
|
init_product_open_surface();
|
|
155609
156668
|
init_reviews_inspector();
|
|
156669
|
+
init_permissions_command();
|
|
156670
|
+
init_remote_policy_command();
|
|
156671
|
+
init_permissions_inspector();
|
|
155610
156672
|
init_shell_bridge();
|
|
155611
156673
|
init_command_gateway();
|
|
155612
156674
|
init_channels_client();
|
|
@@ -155696,6 +156758,7 @@ var init_tui_shell = __esm(() => {
|
|
|
155696
156758
|
init_provider_config();
|
|
155697
156759
|
init_shell_permissions();
|
|
155698
156760
|
init_shell_approval();
|
|
156761
|
+
init_telegram_permission();
|
|
155699
156762
|
init_elicitation();
|
|
155700
156763
|
init_permission_mode_config();
|
|
155701
156764
|
init_permission_mode();
|
|
@@ -157536,7 +158599,7 @@ async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts, ini
|
|
|
157536
158599
|
}
|
|
157537
158600
|
};
|
|
157538
158601
|
const searchProviderController = createDefaultSearchProviderController();
|
|
157539
|
-
const sessionShellAllow = new Set
|
|
158602
|
+
const sessionShellAllow = new Set;
|
|
157540
158603
|
let fingerprintAtStart = shellPermissionsFingerprint(configDir);
|
|
157541
158604
|
let permissionMigrationShown = false;
|
|
157542
158605
|
let permissionTamperShown = false;
|
|
@@ -158228,6 +159291,8 @@ ${GUTTER}${turnSeparator()}
|
|
|
158228
159291
|
agentIo.onSystem?.(readlineChannelsText(line, await loadChannelsSnapshot(new ChannelsClient)));
|
|
158229
159292
|
} else if (isRenderingCommand(command)) {
|
|
158230
159293
|
agentIo.onSystem?.(runRenderingCommand(rest));
|
|
159294
|
+
} else if (command === "/remote-policy") {
|
|
159295
|
+
agentIo.onSystem?.(remotePolicyText(rest, { ...configDir !== undefined ? { dir: configDir } : {} }));
|
|
158231
159296
|
} else if (isSessionInfoCommand(command)) {
|
|
158232
159297
|
const cwd = sessionCwd;
|
|
158233
159298
|
const [workspaces, flows] = await Promise.all([
|
|
@@ -158251,6 +159316,13 @@ ${GUTTER}${turnSeparator()}
|
|
|
158251
159316
|
})));
|
|
158252
159317
|
} else if (command === "/approvals") {
|
|
158253
159318
|
agentIo.onSystem?.(approvalsSlashText(rest));
|
|
159319
|
+
} else if (command === "/permissions") {
|
|
159320
|
+
agentIo.onSystem?.(permissionsSlashText(rest, {
|
|
159321
|
+
sessionAllow: sessionShellAllow,
|
|
159322
|
+
onChanged: () => {
|
|
159323
|
+
fingerprintAtStart = shellPermissionsFingerprint(configDir);
|
|
159324
|
+
}
|
|
159325
|
+
}));
|
|
158254
159326
|
} else if (command === "/external-diff") {
|
|
158255
159327
|
agentIo.onSystem?.(externalDiffSlashText(defaultExternalDiffDeps(sessionCwd)));
|
|
158256
159328
|
} else if (isFlowsCommand(command)) {
|
|
@@ -159359,6 +160431,8 @@ var init_shell = __esm(() => {
|
|
|
159359
160431
|
init_flow_inspector();
|
|
159360
160432
|
init_inspector_sources();
|
|
159361
160433
|
init_approvals();
|
|
160434
|
+
init_permissions_command();
|
|
160435
|
+
init_remote_policy_command();
|
|
159362
160436
|
init_external_diff_modal();
|
|
159363
160437
|
init_doctor2();
|
|
159364
160438
|
init_setup_guide();
|
|
@@ -159425,6 +160499,7 @@ var init_shell = __esm(() => {
|
|
|
159425
160499
|
"/status",
|
|
159426
160500
|
"/flows",
|
|
159427
160501
|
"/approvals",
|
|
160502
|
+
"/permissions",
|
|
159428
160503
|
"/external-diff",
|
|
159429
160504
|
"/doctor",
|
|
159430
160505
|
"/setup",
|
|
@@ -159432,6 +160507,7 @@ var init_shell = __esm(() => {
|
|
|
159432
160507
|
"/remote-control",
|
|
159433
160508
|
"/channels",
|
|
159434
160509
|
"/rendering",
|
|
160510
|
+
"/remote-policy",
|
|
159435
160511
|
"/theme",
|
|
159436
160512
|
"/settings",
|
|
159437
160513
|
"/mode",
|
|
@@ -193371,6 +194447,7 @@ var init_serve_server = __esm(() => {
|
|
|
193371
194447
|
["heartbeat", "POST"],
|
|
193372
194448
|
["reply", "POST"],
|
|
193373
194449
|
["approval", "POST"],
|
|
194450
|
+
["approval-result", "POST"],
|
|
193374
194451
|
["prompt", "POST"],
|
|
193375
194452
|
["state", "POST"],
|
|
193376
194453
|
["ack", "POST"],
|
|
@@ -196086,7 +197163,12 @@ class RemoteHub {
|
|
|
196086
197163
|
}));
|
|
196087
197164
|
}
|
|
196088
197165
|
limits() {
|
|
196089
|
-
return {
|
|
197166
|
+
return {
|
|
197167
|
+
runTimeoutMs: this.config.runTimeoutMs,
|
|
197168
|
+
orphanMs: this.config.orphanMs,
|
|
197169
|
+
permissionMode: this.config.permissionMode,
|
|
197170
|
+
approvalTimeoutMs: this.config.approvalTimeoutMs
|
|
197171
|
+
};
|
|
196090
197172
|
}
|
|
196091
197173
|
hasSession(sessionId) {
|
|
196092
197174
|
return this.registry.bySession(sessionId) !== undefined;
|
|
@@ -196942,6 +198024,8 @@ class RemoteHttpSurface {
|
|
|
196942
198024
|
return this.reply(hub, request);
|
|
196943
198025
|
case "approval":
|
|
196944
198026
|
return this.requestApproval(hub, request);
|
|
198027
|
+
case "approval-result":
|
|
198028
|
+
return this.approvalResult(hub, request);
|
|
196945
198029
|
case "prompt":
|
|
196946
198030
|
return this.requestPrompt(hub, request);
|
|
196947
198031
|
case "state":
|
|
@@ -197005,7 +198089,15 @@ class RemoteHttpSurface {
|
|
|
197005
198089
|
this.sent.delete(checked.sessionId);
|
|
197006
198090
|
this.bound.set(checked.sessionId, binding);
|
|
197007
198091
|
}
|
|
197008
|
-
|
|
198092
|
+
const limits = hub.limits();
|
|
198093
|
+
return ok2({
|
|
198094
|
+
name: result.name,
|
|
198095
|
+
threadId: result.threadId,
|
|
198096
|
+
reused: result.reused,
|
|
198097
|
+
runTimeoutMs: limits.runTimeoutMs,
|
|
198098
|
+
permissionMode: limits.permissionMode,
|
|
198099
|
+
approvalTimeoutMs: limits.approvalTimeoutMs
|
|
198100
|
+
});
|
|
197009
198101
|
}
|
|
197010
198102
|
const status = result.code === "name-taken" ? 409 : result.code === "invalid-name" ? 400 : 502;
|
|
197011
198103
|
return fail4(status, result.code, result.message);
|
|
@@ -197132,6 +198224,13 @@ class RemoteHttpSurface {
|
|
|
197132
198224
|
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < MIN_APPROVAL_TIMEOUT_MS || timeoutMs > MAX_APPROVAL_TIMEOUT_MS) {
|
|
197133
198225
|
return invalid2(`timeoutMs must be an integer from ${MIN_APPROVAL_TIMEOUT_MS} to ${MAX_APPROVAL_TIMEOUT_MS}.`);
|
|
197134
198226
|
}
|
|
198227
|
+
let remember;
|
|
198228
|
+
if (value.remember !== undefined) {
|
|
198229
|
+
if (typeof value.remember !== "string" || value.remember.length === 0 || value.remember.length > MAX_REMEMBER_PATTERN_CHARS || CONTROL_CHARS2.test(value.remember)) {
|
|
198230
|
+
return invalid2(`remember must be a string of 1 to ${MAX_REMEMBER_PATTERN_CHARS} characters without control characters.`);
|
|
198231
|
+
}
|
|
198232
|
+
remember = redactSensitiveText(value.remember);
|
|
198233
|
+
}
|
|
197135
198234
|
const sessionId = value.sessionId;
|
|
197136
198235
|
if (!hub.hasSession(sessionId)) {
|
|
197137
198236
|
return fail4(404, "unknown-session", "This session is not registered for remote control; register again.");
|
|
@@ -197153,15 +198252,20 @@ class RemoteHttpSurface {
|
|
|
197153
198252
|
this.approvals.set(approvalId, {
|
|
197154
198253
|
sessionId,
|
|
197155
198254
|
expiresAt,
|
|
197156
|
-
timer: this.timers.setTimeout(() => this.expireApproval(approvalId), timeoutMs)
|
|
198255
|
+
timer: this.timers.setTimeout(() => this.expireApproval(approvalId), timeoutMs),
|
|
198256
|
+
...remember === undefined ? {} : { remember }
|
|
197157
198257
|
});
|
|
198258
|
+
const offer = remember === undefined ? "" : `
|
|
198259
|
+
Always would remember:
|
|
198260
|
+
${asCodeBlock(remember)}`;
|
|
197158
198261
|
const sent = await hub.send(sessionId, `Approval needed:
|
|
197159
|
-
${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
198262
|
+
${asCodeBlock(redactSensitiveText(value.prompt))}${offer}`, {
|
|
197160
198263
|
keyboard: [
|
|
197161
198264
|
[
|
|
197162
198265
|
{ text: "Allow", callback_data: approvalCallbackData(approvalId, "allow") },
|
|
197163
198266
|
{ text: "Deny", callback_data: approvalCallbackData(approvalId, "deny") }
|
|
197164
|
-
]
|
|
198267
|
+
],
|
|
198268
|
+
...remember === undefined ? [] : [[{ text: alwaysButtonText(remember), callback_data: approvalCallbackData(approvalId, "always") }]]
|
|
197165
198269
|
],
|
|
197166
198270
|
onSent: (info) => this.onApprovalSent(approvalId, sessionId, info.messageId, info.text)
|
|
197167
198271
|
});
|
|
@@ -197171,6 +198275,45 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197171
198275
|
}
|
|
197172
198276
|
return ok2({ approvalId, expiresAt });
|
|
197173
198277
|
}
|
|
198278
|
+
async approvalResult(hub, request) {
|
|
198279
|
+
const body = await readJsonBody(request);
|
|
198280
|
+
if (!body.ok) {
|
|
198281
|
+
return body.response;
|
|
198282
|
+
}
|
|
198283
|
+
const value = body.value;
|
|
198284
|
+
if (!isSessionId(value.sessionId)) {
|
|
198285
|
+
return invalid2("sessionId must be 1 to 64 characters of letters, digits, '-' and '_'.");
|
|
198286
|
+
}
|
|
198287
|
+
if (!isApprovalId(value.approvalId) || typeof value.remembered !== "boolean") {
|
|
198288
|
+
return invalid2("approvalId must be an approval id and remembered a boolean.");
|
|
198289
|
+
}
|
|
198290
|
+
const waiting = this.awaiting.get(value.approvalId);
|
|
198291
|
+
if (waiting !== undefined && waiting.sessionId === value.sessionId && waiting.remember !== undefined) {
|
|
198292
|
+
waiting.reported ??= value.remembered;
|
|
198293
|
+
return ok2({ settled: true });
|
|
198294
|
+
}
|
|
198295
|
+
const done = this.finished.get(value.approvalId);
|
|
198296
|
+
if (done === undefined || done.sessionId !== value.sessionId || done.rememberOf === undefined) {
|
|
198297
|
+
return fail4(404, "unknown-approval", "No approval of this session is waiting for that report.");
|
|
198298
|
+
}
|
|
198299
|
+
this.settleRemembered(hub, value.sessionId, done, value.remembered);
|
|
198300
|
+
return ok2({ settled: true });
|
|
198301
|
+
}
|
|
198302
|
+
settleRemembered(hub, sessionId, done, remembered) {
|
|
198303
|
+
const info = done.rememberOf;
|
|
198304
|
+
if (info === undefined) {
|
|
198305
|
+
return;
|
|
198306
|
+
}
|
|
198307
|
+
done.rememberOf = undefined;
|
|
198308
|
+
const result = remembered ? `Allowed by ${info.who} at ${info.when}. Remembered: ${info.pattern}` : `Allowed by ${info.who} at ${info.when}. Not remembered: ${info.pattern} (approved this once).`;
|
|
198309
|
+
done.shortReply = remembered ? "Approval granted and remembered." : "Approval granted once; the rule was not remembered.";
|
|
198310
|
+
done.finalText = done.messageId === undefined ? result : settledText(info.original, result);
|
|
198311
|
+
if (done.messageId !== undefined) {
|
|
198312
|
+
hub.settleMessage(sessionId, done.messageId, done.finalText, done.shortReply).catch(() => {
|
|
198313
|
+
return;
|
|
198314
|
+
});
|
|
198315
|
+
}
|
|
198316
|
+
}
|
|
197174
198317
|
async requestPrompt(hub, request) {
|
|
197175
198318
|
const body = await readJsonBody(request);
|
|
197176
198319
|
if (!body.ok) {
|
|
@@ -197320,11 +198463,25 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197320
198463
|
this.rememberDecided(id, sessionId);
|
|
197321
198464
|
if (value.applied === false) {
|
|
197322
198465
|
this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
|
|
197323
|
-
result: `Not applied at ${waiting.sentAt}: the shell was no longer waiting for this question, so the ${waiting.decision === "
|
|
198466
|
+
result: `Not applied at ${waiting.sentAt}: the shell was no longer waiting for this question, so the ${waiting.decision === "deny" ? "Deny" : "Allow"} from ${waiting.who} changed nothing.`,
|
|
197324
198467
|
shortReply: "Not applied: the shell was no longer waiting for this question."
|
|
197325
198468
|
});
|
|
197326
198469
|
return ok2({ acknowledged: true });
|
|
197327
198470
|
}
|
|
198471
|
+
if (waiting.decision === "always" && waiting.remember !== undefined) {
|
|
198472
|
+
this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
|
|
198473
|
+
result: `Allowed by ${waiting.who} at ${waiting.sentAt}. Saving the rule: ${waiting.remember}`,
|
|
198474
|
+
shortReply: "Approval granted; saving the rule."
|
|
198475
|
+
});
|
|
198476
|
+
const done = this.finished.get(id);
|
|
198477
|
+
if (done !== undefined) {
|
|
198478
|
+
done.rememberOf = { original: waiting.text, who: waiting.who, when: waiting.sentAt, pattern: waiting.remember };
|
|
198479
|
+
if (waiting.reported !== undefined) {
|
|
198480
|
+
this.settleRemembered(hub, sessionId, done, waiting.reported);
|
|
198481
|
+
}
|
|
198482
|
+
}
|
|
198483
|
+
return ok2({ acknowledged: true });
|
|
198484
|
+
}
|
|
197328
198485
|
this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
|
|
197329
198486
|
result: waiting.decision === "allow" ? `Allowed by ${waiting.who} at ${waiting.sentAt}.` : `Denied by ${waiting.who} at ${waiting.sentAt}.`,
|
|
197330
198487
|
shortReply: waiting.decision === "allow" ? "Approval granted." : "Approval denied."
|
|
@@ -197480,8 +198637,11 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197480
198637
|
return;
|
|
197481
198638
|
}
|
|
197482
198639
|
if (entry !== undefined && entry.expiresAt > this.now()) {
|
|
198640
|
+
if (approval.decision === "always" && entry.remember === undefined) {
|
|
198641
|
+
return;
|
|
198642
|
+
}
|
|
197483
198643
|
this.dropApproval(id);
|
|
197484
|
-
const event = { updateId: callback.updateId, approvalId: id, decision: approval.decision };
|
|
198644
|
+
const event = { updateId: callback.updateId, approvalId: id, decision: approval.decision, fromId: callback.fromId };
|
|
197485
198645
|
const told = stream?.write(encodeSseEvent("approval", event, callback.updateId)) === true;
|
|
197486
198646
|
const messageId = entry.messageId ?? callback.messageId;
|
|
197487
198647
|
const when = clockText(this.now());
|
|
@@ -197499,6 +198659,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197499
198659
|
sentAt: when,
|
|
197500
198660
|
...messageId === undefined ? {} : { messageId },
|
|
197501
198661
|
...entry.text === undefined ? {} : { text: entry.text },
|
|
198662
|
+
...approval.decision === "always" && entry.remember !== undefined ? { remember: entry.remember } : {},
|
|
197502
198663
|
timer: this.timers.setTimeout(() => this.giveUpOnAck(id), this.approvalAckMs)
|
|
197503
198664
|
});
|
|
197504
198665
|
return;
|
|
@@ -197754,6 +198915,10 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197754
198915
|
}
|
|
197755
198916
|
}
|
|
197756
198917
|
}
|
|
198918
|
+
function alwaysButtonText(pattern) {
|
|
198919
|
+
const text = `Always: ${pattern.replace(/\s+/g, " ")}`;
|
|
198920
|
+
return text.length <= MAX_BUTTON_TEXT_CHARS ? text : `${text.slice(0, MAX_BUTTON_TEXT_CHARS - 1)}\u2026`;
|
|
198921
|
+
}
|
|
197757
198922
|
function asCodeBlock(text) {
|
|
197758
198923
|
const safe = (text.trim().length === 0 ? "(empty)" : text).replace(/\r\n?/g, `
|
|
197759
198924
|
`).replace(/`{9,}/g, (run) => run.match(/`{1,8}/g).join("\u200B"));
|
|
@@ -198903,11 +200068,24 @@ function runStatus7(args) {
|
|
|
198903
200068
|
};
|
|
198904
200069
|
const credentialState = credential.status === "ok" ? "present" : credential.status;
|
|
198905
200070
|
const fingerprint = credential.status === "ok" ? credentialFingerprint(credential.record) : undefined;
|
|
200071
|
+
const telegram = loadPosture();
|
|
200072
|
+
const remoteConfig = telegram === undefined ? undefined : loadRemoteConfig();
|
|
200073
|
+
const allowedUsers = remoteConfig?.ok === true ? remoteConfig.value.allowedUserIds.length : undefined;
|
|
198906
200074
|
if (asJson) {
|
|
198907
200075
|
console.log(JSON.stringify({
|
|
198908
200076
|
...report,
|
|
198909
200077
|
credential: credentialState,
|
|
198910
200078
|
...fingerprint === undefined ? {} : { credentialFingerprint: fingerprint },
|
|
200079
|
+
...telegram === undefined ? {} : {
|
|
200080
|
+
telegramPermissions: {
|
|
200081
|
+
defaultMode: telegram.defaultMode,
|
|
200082
|
+
runTimeoutMs: telegram.runTimeoutMs,
|
|
200083
|
+
approvalTimeoutMs: telegram.approvalTimeoutMs,
|
|
200084
|
+
savedRules: telegram.savedRules,
|
|
200085
|
+
...allowedUsers !== undefined ? { allowedUsers } : {},
|
|
200086
|
+
shellModeOverridesDefault: true
|
|
200087
|
+
}
|
|
200088
|
+
},
|
|
198911
200089
|
warnings: [...warnings].sort()
|
|
198912
200090
|
}, null, 2));
|
|
198913
200091
|
return;
|
|
@@ -198925,6 +200103,10 @@ function runStatus7(args) {
|
|
|
198925
200103
|
}
|
|
198926
200104
|
console.log(` credential: ${credentialState}${fingerprint === undefined ? "" : ` (fingerprint ${fingerprint})`}`);
|
|
198927
200105
|
console.log(` pending approvals: ${report.pendingApprovals}`);
|
|
200106
|
+
if (telegram !== undefined) {
|
|
200107
|
+
console.log(` telegram: default mode ${telegram.defaultMode}, run limit ${formatPolicyDuration(telegram.runTimeoutMs)}, approval wait ${formatPolicyDuration(telegram.approvalTimeoutMs)}, ${telegram.savedRules} saved shell rule${telegram.savedRules === 1 ? "" : "s"}${allowedUsers !== undefined ? `, ${allowedUsers} allowed user${allowedUsers === 1 ? "" : "s"}` : ""}`);
|
|
200108
|
+
console.log(" a running shell's /mode overrides the default mode; /remote-policy changes the defaults");
|
|
200109
|
+
}
|
|
198928
200110
|
if (report.message !== undefined) {
|
|
198929
200111
|
console.log("");
|
|
198930
200112
|
console.log(` ${style.yellow(symbols.bullet)} ${sanitizeForDisplay(report.message)}`);
|
|
@@ -199182,6 +200364,8 @@ function printHelp23() {
|
|
|
199182
200364
|
}
|
|
199183
200365
|
var BIND_FLAGS, ACK_FLAG = "--acknowledge-non-loopback", FORCE_FLAG = "--force", NO_ACK_FLAG = "--no-acknowledge-non-loopback", ENABLE_FLAG = "--enable", DISABLE_FLAG = "--disable";
|
|
199184
200366
|
var init_serve = __esm(() => {
|
|
200367
|
+
init_config11();
|
|
200368
|
+
init_remote_policy_command();
|
|
199185
200369
|
init_project_registry();
|
|
199186
200370
|
init_serve_config();
|
|
199187
200371
|
init_serve_credential();
|
|
@@ -210895,6 +212079,7 @@ var init_group_subcommands = __esm(() => {
|
|
|
210895
212079
|
["external", ["on", "off", "status", "list"]],
|
|
210896
212080
|
["auth", ["list", "login", "logout", "status"]],
|
|
210897
212081
|
["approvals", ["list", "allow", "deny"]],
|
|
212082
|
+
["permissions", ["list", "remove"]],
|
|
210898
212083
|
["version", ["check"]],
|
|
210899
212084
|
["gdgraph", ["build", "query", "find", "symbol", "symbols", "path", "affected", "repomap", "context", "assets"]],
|
|
210900
212085
|
["ctx", ["status", "diff", "rg", "read", "run", "show", "hook", "install-hook", "uninstall-hook"]],
|
|
@@ -211219,6 +212404,8 @@ var VERSION3, CLI_ROUTES, MCP_KNOWN_SUBCOMMANDS, USAGE_BODY = `Usage:
|
|
|
211219
212404
|
keryx serve config init|set|show
|
|
211220
212405
|
keryx approvals list [--all] [--json] | allow <id> | deny <id>
|
|
211221
212406
|
Answer, from this machine, a call a remote turn is waiting on (once, that call only)
|
|
212407
|
+
keryx permissions list [--json] | remove <number|pattern>
|
|
212408
|
+
The saved shell rules an Always answer left behind; take one back
|
|
211222
212409
|
keryx update [--skip-runtime] [--hooks]
|
|
211223
212410
|
keryx dashboard build
|
|
211224
212411
|
keryx dashboard open
|
|
@@ -211399,6 +212586,7 @@ Commands:
|
|
|
211399
212586
|
projects Inspect the user-global registry of initialized projects
|
|
211400
212587
|
serve Loopback-bound authenticated HTTP entry (off by default; read-only routes)
|
|
211401
212588
|
approvals Pending remote approvals: list them, allow or deny one call, once (the local answer path of the serve entry)
|
|
212589
|
+
permissions The saved shell rules (what Always remembered): list them, remove one
|
|
211402
212590
|
update Refresh managed service files without touching data artifacts
|
|
211403
212591
|
dashboard Build or open the project admin dashboard
|
|
211404
212592
|
dash Rebuild and open .metaproject/keryx-dashboard.html
|
|
@@ -211479,6 +212667,7 @@ var init_cli_registry = __esm(() => {
|
|
|
211479
212667
|
init_projects();
|
|
211480
212668
|
init_serve();
|
|
211481
212669
|
init_approvals();
|
|
212670
|
+
init_permissions_command();
|
|
211482
212671
|
init_update();
|
|
211483
212672
|
init_dashboard();
|
|
211484
212673
|
init_agents2();
|
|
@@ -211522,6 +212711,7 @@ var init_cli_registry = __esm(() => {
|
|
|
211522
212711
|
auth: authCommand2,
|
|
211523
212712
|
serve: serveCommand,
|
|
211524
212713
|
approvals: approvalsCommand,
|
|
212714
|
+
permissions: permissionsCommand,
|
|
211525
212715
|
update: updateCommand,
|
|
211526
212716
|
dashboard: dashboardCommand,
|
|
211527
212717
|
dash: (rest) => dashboardCommand(rest.length > 0 ? rest : ["open"]),
|