@mrciphersmith/keryx 0.3.66 → 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 +22 -2
- package/dist/cli.js +1653 -172
- 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";
|
|
@@ -114789,6 +115456,9 @@ function channelsRoutePath(route) {
|
|
|
114789
115456
|
function isSessionId(value) {
|
|
114790
115457
|
return typeof value === "string" && SESSION_ID_PATTERN.test(value);
|
|
114791
115458
|
}
|
|
115459
|
+
function isApprovalId(value) {
|
|
115460
|
+
return typeof value === "string" && APPROVAL_ID_PATTERN.test(value);
|
|
115461
|
+
}
|
|
114792
115462
|
function approvalCallbackData(approvalId, decision) {
|
|
114793
115463
|
return `ap:${approvalId}:${decision}`;
|
|
114794
115464
|
}
|
|
@@ -114861,7 +115531,7 @@ class SseParser {
|
|
|
114861
115531
|
return frames;
|
|
114862
115532
|
}
|
|
114863
115533
|
}
|
|
114864
|
-
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_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_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
|
|
114865
115535
|
|
|
114866
115536
|
`;
|
|
114867
115537
|
var init_protocol2 = __esm(() => {
|
|
@@ -114871,9 +115541,11 @@ var init_protocol2 = __esm(() => {
|
|
|
114871
115541
|
heartbeat: "POST",
|
|
114872
115542
|
reply: "POST",
|
|
114873
115543
|
approval: "POST",
|
|
115544
|
+
"approval-result": "POST",
|
|
114874
115545
|
prompt: "POST",
|
|
114875
115546
|
state: "POST",
|
|
114876
115547
|
ack: "POST",
|
|
115548
|
+
"approval-ack": "POST",
|
|
114877
115549
|
stream: "GET"
|
|
114878
115550
|
};
|
|
114879
115551
|
CHANNELS_ROUTE_METHODS = {
|
|
@@ -114889,7 +115561,8 @@ var init_protocol2 = __esm(() => {
|
|
|
114889
115561
|
DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60000;
|
|
114890
115562
|
MAX_APPROVAL_TIMEOUT_MS = 60 * 60000;
|
|
114891
115563
|
SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
114892
|
-
|
|
115564
|
+
APPROVAL_ID_PATTERN = /^ap[0-9a-f]{12}$/;
|
|
115565
|
+
APPROVAL_CALLBACK_PATTERN = /^ap:(ap[0-9a-f]{12}):(allow|deny|always)$/;
|
|
114893
115566
|
CHOICE_CALLBACK_PATTERN = /^pk:(pk[0-9a-f]{12}):(\d{1,3})$/;
|
|
114894
115567
|
MESSAGE_STATES = ["reading", "working", "done", "failed"];
|
|
114895
115568
|
});
|
|
@@ -115197,6 +115870,16 @@ var init_shell_token = __esm(() => {
|
|
|
115197
115870
|
});
|
|
115198
115871
|
|
|
115199
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
|
+
}
|
|
115200
115883
|
function ownProcessIsAlive(pid) {
|
|
115201
115884
|
if (!Number.isInteger(pid) || pid <= 0) {
|
|
115202
115885
|
return false;
|
|
@@ -115253,12 +115936,18 @@ class RemoteClient {
|
|
|
115253
115936
|
inboundChain = Promise.resolve();
|
|
115254
115937
|
waiters = new Map;
|
|
115255
115938
|
earlyDecisions = new Map;
|
|
115939
|
+
seenApprovals = new Map;
|
|
115940
|
+
earlyDecisionMs;
|
|
115941
|
+
unackedApprovals = new Map;
|
|
115942
|
+
lingerTimers = new Set;
|
|
115256
115943
|
choiceWaiters = new Map;
|
|
115257
115944
|
earlyChoices = new Map;
|
|
115258
115945
|
firstResult;
|
|
115259
115946
|
name;
|
|
115260
115947
|
threadId;
|
|
115261
115948
|
runTimeoutMs;
|
|
115949
|
+
permissionMode;
|
|
115950
|
+
approvalTimeoutMs;
|
|
115262
115951
|
lastHeartbeatAt;
|
|
115263
115952
|
constructor(options) {
|
|
115264
115953
|
this.options = options;
|
|
@@ -115269,10 +115958,20 @@ class RemoteClient {
|
|
|
115269
115958
|
this.sleep = options.sleep ?? abortableSleep2;
|
|
115270
115959
|
this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
115271
115960
|
this.isAlive = options.isAlive ?? ownProcessIsAlive;
|
|
115961
|
+
this.earlyDecisionMs = options.earlyDecisionMs ?? DEFAULT_EARLY_DECISION_MS;
|
|
115272
115962
|
}
|
|
115273
115963
|
get connected() {
|
|
115274
115964
|
return this.streamOpen && !this.stopped;
|
|
115275
115965
|
}
|
|
115966
|
+
get unconfirmedApprovals() {
|
|
115967
|
+
const cutoff = Date.now() - UNCONFIRMED_APPROVAL_LINGER_MS;
|
|
115968
|
+
for (const [id, at] of [...this.unackedApprovals.entries()]) {
|
|
115969
|
+
if (at < cutoff) {
|
|
115970
|
+
this.unackedApprovals.delete(id);
|
|
115971
|
+
}
|
|
115972
|
+
}
|
|
115973
|
+
return this.unackedApprovals.size;
|
|
115974
|
+
}
|
|
115276
115975
|
start() {
|
|
115277
115976
|
if (this.started) {
|
|
115278
115977
|
return Promise.resolve({ ok: false, code: "already-started", message: "this client was already started", retrying: false });
|
|
@@ -115323,6 +116022,10 @@ class RemoteClient {
|
|
|
115323
116022
|
}
|
|
115324
116023
|
this.lifetime.abort();
|
|
115325
116024
|
this.streamAbort?.abort();
|
|
116025
|
+
for (const timer of this.lingerTimers) {
|
|
116026
|
+
clearTimeout(timer);
|
|
116027
|
+
}
|
|
116028
|
+
this.lingerTimers.clear();
|
|
115326
116029
|
this.failApprovals();
|
|
115327
116030
|
this.firstResult?.({ ok: false, code: "stopped", message: "the client was stopped before it connected", retrying: false });
|
|
115328
116031
|
this.firstResult = undefined;
|
|
@@ -115340,36 +116043,71 @@ class RemoteClient {
|
|
|
115340
116043
|
}
|
|
115341
116044
|
}
|
|
115342
116045
|
async requestApproval(prompt, timeoutMs) {
|
|
115343
|
-
|
|
115344
|
-
|
|
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" };
|
|
115345
116051
|
}
|
|
115346
|
-
const body = {
|
|
116052
|
+
const body = {
|
|
116053
|
+
sessionId: this.options.sessionId,
|
|
116054
|
+
prompt,
|
|
116055
|
+
timeoutMs,
|
|
116056
|
+
...options.remember === undefined ? {} : { remember: options.remember }
|
|
116057
|
+
};
|
|
115347
116058
|
let approvalId;
|
|
115348
116059
|
try {
|
|
115349
116060
|
const response = await this.post("approval", body);
|
|
115350
116061
|
if (!response.ok) {
|
|
115351
|
-
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" };
|
|
115352
116067
|
}
|
|
115353
|
-
approvalId =
|
|
116068
|
+
approvalId = id;
|
|
115354
116069
|
} catch {
|
|
115355
|
-
return "deny";
|
|
116070
|
+
return { decision: "deny" };
|
|
115356
116071
|
}
|
|
115357
116072
|
const early = this.earlyDecisions.get(approvalId);
|
|
115358
116073
|
if (early !== undefined) {
|
|
116074
|
+
clearTimeout(early.timer);
|
|
115359
116075
|
this.earlyDecisions.delete(approvalId);
|
|
115360
|
-
|
|
116076
|
+
this.settleApproval(approvalId, early.pressed.decision, true);
|
|
116077
|
+
return { ...early.pressed, approvalId };
|
|
115361
116078
|
}
|
|
115362
116079
|
if (!this.connected) {
|
|
115363
|
-
return "deny";
|
|
116080
|
+
return { decision: "deny" };
|
|
115364
116081
|
}
|
|
115365
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
|
+
};
|
|
115366
116091
|
const timer = setTimeout(() => {
|
|
116092
|
+
signal?.removeEventListener("abort", onAbort);
|
|
115367
116093
|
this.waiters.delete(approvalId);
|
|
115368
|
-
resolve("deny");
|
|
116094
|
+
resolve({ decision: "deny" });
|
|
115369
116095
|
}, timeoutMs);
|
|
115370
|
-
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 });
|
|
115371
116104
|
});
|
|
115372
116105
|
}
|
|
116106
|
+
async reportApprovalResult(approvalId, remembered) {
|
|
116107
|
+
try {
|
|
116108
|
+
await this.post("approval-result", { sessionId: this.options.sessionId, approvalId, remembered });
|
|
116109
|
+
} catch {}
|
|
116110
|
+
}
|
|
115373
116111
|
async requestChoice(text, rows, timeoutMs, forUserId) {
|
|
115374
116112
|
if (!this.connected) {
|
|
115375
116113
|
return;
|
|
@@ -115540,7 +116278,14 @@ class RemoteClient {
|
|
|
115540
116278
|
this.lastHeartbeatAt = Date.now();
|
|
115541
116279
|
this.options.onStatus?.({ state: "connected" });
|
|
115542
116280
|
if (this.name !== undefined && this.threadId !== undefined && this.runTimeoutMs !== undefined) {
|
|
115543
|
-
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
|
+
});
|
|
115544
116289
|
}
|
|
115545
116290
|
} else if (status.kind === "superseded") {
|
|
115546
116291
|
throw new FatalClientError("superseded", "another connection for this session replaced this one");
|
|
@@ -115576,6 +116321,8 @@ class RemoteClient {
|
|
|
115576
116321
|
this.name = result.name;
|
|
115577
116322
|
this.threadId = result.threadId;
|
|
115578
116323
|
this.runTimeoutMs = result.runTimeoutMs;
|
|
116324
|
+
this.permissionMode = result.permissionMode;
|
|
116325
|
+
this.approvalTimeoutMs = result.approvalTimeoutMs;
|
|
115579
116326
|
return;
|
|
115580
116327
|
}
|
|
115581
116328
|
const error = await response.json().catch(() => ({}));
|
|
@@ -115599,8 +116346,7 @@ class RemoteClient {
|
|
|
115599
116346
|
return;
|
|
115600
116347
|
});
|
|
115601
116348
|
} else if (event === "approval") {
|
|
115602
|
-
|
|
115603
|
-
this.resolveApproval(approval.approvalId, approval.decision);
|
|
116349
|
+
this.handleApprovalFrame(parsed);
|
|
115604
116350
|
} else if (event === "choice") {
|
|
115605
116351
|
const choice = parsed;
|
|
115606
116352
|
this.resolveChoice(choice.promptId, choice.index);
|
|
@@ -115645,16 +116391,115 @@ class RemoteClient {
|
|
|
115645
116391
|
await this.post("ack", { sessionId: this.options.sessionId, updateId });
|
|
115646
116392
|
} catch {}
|
|
115647
116393
|
}
|
|
116394
|
+
handleApprovalFrame(parsed) {
|
|
116395
|
+
const read = readApprovalEvent(parsed);
|
|
116396
|
+
if (read === undefined || !isApprovalId(read.approvalId)) {
|
|
116397
|
+
return;
|
|
116398
|
+
}
|
|
116399
|
+
const { approvalId, pressed } = read;
|
|
116400
|
+
const seen = this.seenApprovals.get(approvalId);
|
|
116401
|
+
if (seen === "pending") {
|
|
116402
|
+
return;
|
|
116403
|
+
}
|
|
116404
|
+
this.countUnconfirmed(approvalId);
|
|
116405
|
+
if (seen !== undefined) {
|
|
116406
|
+
this.ackApproval(approvalId, seen === "applied");
|
|
116407
|
+
return;
|
|
116408
|
+
}
|
|
116409
|
+
if (this.waiters.has(approvalId)) {
|
|
116410
|
+
this.settleApproval(approvalId, pressed.decision, true, () => this.resolveApproval(approvalId, pressed));
|
|
116411
|
+
return;
|
|
116412
|
+
}
|
|
116413
|
+
this.rememberSeen(approvalId, "pending");
|
|
116414
|
+
this.parkEarlyDecision(approvalId, pressed);
|
|
116415
|
+
}
|
|
116416
|
+
rememberSeen(approvalId, outcome) {
|
|
116417
|
+
this.seenApprovals.delete(approvalId);
|
|
116418
|
+
this.seenApprovals.set(approvalId, outcome);
|
|
116419
|
+
if (this.seenApprovals.size > MAX_SEEN_APPROVALS) {
|
|
116420
|
+
const oldest = this.seenApprovals.keys().next().value;
|
|
116421
|
+
if (oldest !== undefined) {
|
|
116422
|
+
this.seenApprovals.delete(oldest);
|
|
116423
|
+
}
|
|
116424
|
+
}
|
|
116425
|
+
}
|
|
116426
|
+
countUnconfirmed(approvalId) {
|
|
116427
|
+
this.unackedApprovals.delete(approvalId);
|
|
116428
|
+
this.unackedApprovals.set(approvalId, Date.now());
|
|
116429
|
+
this.notifyUnconfirmed();
|
|
116430
|
+
while (this.unackedApprovals.size > MAX_UNCONFIRMED_APPROVALS) {
|
|
116431
|
+
const oldest = this.unackedApprovals.keys().next().value;
|
|
116432
|
+
if (oldest === undefined) {
|
|
116433
|
+
break;
|
|
116434
|
+
}
|
|
116435
|
+
this.unackedApprovals.delete(oldest);
|
|
116436
|
+
}
|
|
116437
|
+
}
|
|
116438
|
+
settleApproval(approvalId, decision, applied, apply) {
|
|
116439
|
+
this.rememberSeen(approvalId, applied ? "applied" : "not-applied");
|
|
116440
|
+
try {
|
|
116441
|
+
this.options.onApprovalFrame?.({ approvalId, decision, applied });
|
|
116442
|
+
} catch {}
|
|
116443
|
+
apply?.();
|
|
116444
|
+
this.ackApproval(approvalId, applied);
|
|
116445
|
+
}
|
|
116446
|
+
parkEarlyDecision(approvalId, pressed) {
|
|
116447
|
+
while (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
|
|
116448
|
+
const oldest = this.earlyDecisions.keys().next().value;
|
|
116449
|
+
if (oldest === undefined) {
|
|
116450
|
+
break;
|
|
116451
|
+
}
|
|
116452
|
+
this.dropEarlyDecision(oldest);
|
|
116453
|
+
}
|
|
116454
|
+
const timer = setTimeout(() => this.dropEarlyDecision(approvalId), this.earlyDecisionMs);
|
|
116455
|
+
timer.unref?.();
|
|
116456
|
+
this.earlyDecisions.set(approvalId, { pressed, timer });
|
|
116457
|
+
}
|
|
116458
|
+
dropEarlyDecision(approvalId) {
|
|
116459
|
+
const early = this.earlyDecisions.get(approvalId);
|
|
116460
|
+
if (early === undefined) {
|
|
116461
|
+
return;
|
|
116462
|
+
}
|
|
116463
|
+
clearTimeout(early.timer);
|
|
116464
|
+
this.earlyDecisions.delete(approvalId);
|
|
116465
|
+
if (!this.stopped) {
|
|
116466
|
+
this.settleApproval(approvalId, early.pressed.decision, false);
|
|
116467
|
+
}
|
|
116468
|
+
}
|
|
116469
|
+
async ackApproval(approvalId, applied) {
|
|
116470
|
+
let confirmed = false;
|
|
116471
|
+
try {
|
|
116472
|
+
const response = await this.post("approval-ack", { sessionId: this.options.sessionId, approvalId, applied });
|
|
116473
|
+
confirmed = response.ok;
|
|
116474
|
+
} catch {}
|
|
116475
|
+
if (this.stopped) {
|
|
116476
|
+
return;
|
|
116477
|
+
}
|
|
116478
|
+
if (confirmed) {
|
|
116479
|
+
this.unackedApprovals.delete(approvalId);
|
|
116480
|
+
this.notifyUnconfirmed();
|
|
116481
|
+
return;
|
|
116482
|
+
}
|
|
116483
|
+
this.lingerTimers.add(this.scheduleUnref(() => {
|
|
116484
|
+
this.notifyUnconfirmed();
|
|
116485
|
+
}, UNCONFIRMED_APPROVAL_LINGER_MS + 50));
|
|
116486
|
+
}
|
|
116487
|
+
notifyUnconfirmed() {
|
|
116488
|
+
try {
|
|
116489
|
+
this.options.onUnconfirmedChange?.();
|
|
116490
|
+
} catch {}
|
|
116491
|
+
}
|
|
116492
|
+
scheduleUnref(fn, ms) {
|
|
116493
|
+
const timer = setTimeout(() => {
|
|
116494
|
+
this.lingerTimers.delete(timer);
|
|
116495
|
+
fn();
|
|
116496
|
+
}, ms);
|
|
116497
|
+
timer.unref?.();
|
|
116498
|
+
return timer;
|
|
116499
|
+
}
|
|
115648
116500
|
resolveApproval(approvalId, decision) {
|
|
115649
116501
|
const waiter = this.waiters.get(approvalId);
|
|
115650
116502
|
if (waiter === undefined) {
|
|
115651
|
-
if (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
|
|
115652
|
-
const oldest = this.earlyDecisions.keys().next().value;
|
|
115653
|
-
if (oldest !== undefined) {
|
|
115654
|
-
this.earlyDecisions.delete(oldest);
|
|
115655
|
-
}
|
|
115656
|
-
}
|
|
115657
|
-
this.earlyDecisions.set(approvalId, decision);
|
|
115658
116503
|
return;
|
|
115659
116504
|
}
|
|
115660
116505
|
clearTimeout(waiter.timer);
|
|
@@ -115689,13 +116534,15 @@ class RemoteClient {
|
|
|
115689
116534
|
for (const [id, waiter] of [...this.waiters.entries()]) {
|
|
115690
116535
|
clearTimeout(waiter.timer);
|
|
115691
116536
|
this.waiters.delete(id);
|
|
115692
|
-
waiter.resolve("deny");
|
|
116537
|
+
waiter.resolve({ decision: "deny" });
|
|
116538
|
+
}
|
|
116539
|
+
for (const id of [...this.earlyDecisions.keys()]) {
|
|
116540
|
+
this.dropEarlyDecision(id);
|
|
115693
116541
|
}
|
|
115694
|
-
this.earlyDecisions.clear();
|
|
115695
116542
|
this.failChoices();
|
|
115696
116543
|
}
|
|
115697
116544
|
}
|
|
115698
|
-
var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, MAX_COMPLETED_IDS = 256;
|
|
116545
|
+
var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, DEFAULT_EARLY_DECISION_MS = 2000, MAX_SEEN_APPROVALS = 256, UNCONFIRMED_APPROVAL_LINGER_MS = 60000, MAX_UNCONFIRMED_APPROVALS = 64, MAX_COMPLETED_IDS = 256;
|
|
115699
116546
|
var init_client3 = __esm(() => {
|
|
115700
116547
|
init_serve_config();
|
|
115701
116548
|
init_lease();
|
|
@@ -115851,6 +116698,7 @@ var init_command_gateway = __esm(() => {
|
|
|
115851
116698
|
{ name: "connect", kind: "picker", description: "Switch to a connected provider with buttons" },
|
|
115852
116699
|
{ name: "resume", kind: "picker", description: "Return to an earlier session with buttons" },
|
|
115853
116700
|
{ name: "sessions", kind: "builtin", description: "List recent sessions" },
|
|
116701
|
+
{ name: "stop", kind: "builtin", description: "Stop the run you started from Telegram" },
|
|
115854
116702
|
{ name: "new", kind: "text", description: "Start a new session; this topic stays bound to it" },
|
|
115855
116703
|
{ name: "clear", kind: "text", description: "Same as /new" },
|
|
115856
116704
|
{ name: "compact", kind: "text", description: "Compact the conversation: /compact [focus]" },
|
|
@@ -115896,7 +116744,9 @@ var init_command_gateway = __esm(() => {
|
|
|
115896
116744
|
governance: "it needs the shell's screen",
|
|
115897
116745
|
decisions: "it opens a panel in the shell",
|
|
115898
116746
|
expand: "it needs the shell's screen",
|
|
115899
|
-
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",
|
|
115900
116750
|
demote: "it works on the shell's own tasks",
|
|
115901
116751
|
models: "use /model",
|
|
115902
116752
|
"external-diff": "listing and applying external patches stays in the shell, where the patch hash is typed",
|
|
@@ -115947,6 +116797,8 @@ class RemoteCommandRouter {
|
|
|
115947
116797
|
await this.listSessionsText();
|
|
115948
116798
|
ended(end, "done");
|
|
115949
116799
|
});
|
|
116800
|
+
} else if (decision.command === "stop") {
|
|
116801
|
+
this.stop(end);
|
|
115950
116802
|
} else {
|
|
115951
116803
|
this.ctx.reply(this.ctx.compose(remoteHelpText())).catch(() => false);
|
|
115952
116804
|
ended(end, "done");
|
|
@@ -115973,6 +116825,17 @@ class RemoteCommandRouter {
|
|
|
115973
116825
|
return;
|
|
115974
116826
|
}
|
|
115975
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
|
+
}
|
|
115976
116839
|
async idle() {
|
|
115977
116840
|
await this.chain;
|
|
115978
116841
|
if (this.detached > 0) {
|
|
@@ -116259,9 +117122,27 @@ function composeReply(text) {
|
|
|
116259
117122
|
return `${safe.slice(0, REMOTE_REPLY_MAX_CHARS)}
|
|
116260
117123
|
[cut: the full reply is in the shell]`;
|
|
116261
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
|
+
}
|
|
116262
117139
|
function composeApprovalPrompt(prompt) {
|
|
116263
117140
|
const safe = redactSensitiveText(prompt).trim();
|
|
116264
|
-
|
|
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}`;
|
|
116265
117146
|
}
|
|
116266
117147
|
function describeApprovalForTopic(tool, inputJson, meta) {
|
|
116267
117148
|
const lines = [];
|
|
@@ -116281,8 +117162,12 @@ function describeApprovalForTopic(tool, inputJson, meta) {
|
|
|
116281
117162
|
shown = o.patch;
|
|
116282
117163
|
}
|
|
116283
117164
|
} catch {}
|
|
116284
|
-
|
|
117165
|
+
const total = shown.length;
|
|
117166
|
+
shown = total > APPROVAL_INPUT_PREVIEW_CHARS ? `${shown.slice(0, APPROVAL_INPUT_PREVIEW_CHARS)}\u2026` : shown;
|
|
116285
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
|
+
}
|
|
116286
117171
|
}
|
|
116287
117172
|
if (meta?.destructive === true)
|
|
116288
117173
|
lines.push("Warning: this can delete or overwrite files.");
|
|
@@ -116300,28 +117185,33 @@ class RemoteBridge {
|
|
|
116300
117185
|
events = [];
|
|
116301
117186
|
host;
|
|
116302
117187
|
now;
|
|
116303
|
-
|
|
117188
|
+
approvalOverrideMs;
|
|
117189
|
+
policyOverride = {};
|
|
116304
117190
|
connectedOnce = false;
|
|
116305
117191
|
clientConnected = false;
|
|
116306
117192
|
tgTurn = false;
|
|
116307
117193
|
lastAssistantText = "";
|
|
116308
117194
|
runTimer;
|
|
116309
117195
|
timedOut = false;
|
|
117196
|
+
stoppedByUser = false;
|
|
117197
|
+
turnAbort;
|
|
116310
117198
|
closing;
|
|
116311
117199
|
router;
|
|
116312
117200
|
keepTopic = false;
|
|
116313
117201
|
switched = false;
|
|
116314
117202
|
waiting = [];
|
|
116315
117203
|
currentUpdate;
|
|
117204
|
+
currentFromId;
|
|
116316
117205
|
constructor(options) {
|
|
116317
117206
|
this.options = options;
|
|
116318
117207
|
this.host = options.host;
|
|
116319
117208
|
this.now = options.now ?? Date.now;
|
|
116320
|
-
this.
|
|
117209
|
+
this.approvalOverrideMs = options.approvalTimeoutMs;
|
|
116321
117210
|
this.router = new RemoteCommandRouter({
|
|
116322
117211
|
host: {
|
|
116323
117212
|
isBusy: () => this.host.isBusy(),
|
|
116324
117213
|
cancelTurn: () => this.host.cancelTurn(),
|
|
117214
|
+
stopTelegramTurn: () => this.stopTelegramTurn(),
|
|
116325
117215
|
...this.host.runCommand !== undefined ? { runCommand: (line) => this.host.runCommand(line) } : {},
|
|
116326
117216
|
...this.host.busyRefusal !== undefined ? { busyRefusal: (line) => this.host.busyRefusal(line) } : {},
|
|
116327
117217
|
...this.host.listModels !== undefined ? { listModels: (id) => this.host.listModels(id) } : {},
|
|
@@ -116368,6 +117258,25 @@ class RemoteBridge {
|
|
|
116368
117258
|
get telegramTurnActive() {
|
|
116369
117259
|
return this.tgTurn;
|
|
116370
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
|
+
}
|
|
116371
117280
|
status() {
|
|
116372
117281
|
const client = this.client;
|
|
116373
117282
|
if (client === undefined) {
|
|
@@ -116375,10 +117284,12 @@ class RemoteBridge {
|
|
|
116375
117284
|
}
|
|
116376
117285
|
const online = this.clientConnected && client.connected;
|
|
116377
117286
|
const heartbeat = client.lastHeartbeatAt;
|
|
117287
|
+
const unconfirmed = client.unconfirmedApprovals ?? 0;
|
|
116378
117288
|
return {
|
|
116379
117289
|
state: online ? "on" : "offline",
|
|
116380
117290
|
...client.name !== undefined ? { name: client.name } : {},
|
|
116381
117291
|
...heartbeat !== undefined ? { heartbeatAgeMs: Math.max(0, this.now() - heartbeat) } : {},
|
|
117292
|
+
...unconfirmed > 0 ? { unconfirmedApprovals: unconfirmed } : {},
|
|
116382
117293
|
events: [...this.events]
|
|
116383
117294
|
};
|
|
116384
117295
|
}
|
|
@@ -116405,7 +117316,9 @@ class RemoteBridge {
|
|
|
116405
117316
|
...name !== undefined ? { name } : {},
|
|
116406
117317
|
...this.options.dir !== undefined ? { dir: this.options.dir } : {},
|
|
116407
117318
|
onLine: (text, meta) => this.accept(text, meta),
|
|
116408
|
-
onStatus: (status) => this.onStatus(status)
|
|
117319
|
+
onStatus: (status) => this.onStatus(status),
|
|
117320
|
+
onApprovalFrame: (frame) => this.host.notice(frame.applied ? `\u25C7 approval ${frame.approvalId} ${frame.decision === "allow" ? "allowed" : "denied"} from Telegram` : `\u25C7 approval ${frame.approvalId}: ${frame.decision === "allow" ? "an allow" : "a deny"} from Telegram arrived but nothing here was waiting for it; not applied`),
|
|
117321
|
+
onUnconfirmedChange: () => this.host.onChange?.()
|
|
116409
117322
|
};
|
|
116410
117323
|
const client = this.options.makeClient !== undefined ? this.options.makeClient(clientOptions) : new RemoteClient(clientOptions);
|
|
116411
117324
|
this.client = client;
|
|
@@ -116486,7 +117399,7 @@ class RemoteBridge {
|
|
|
116486
117399
|
return;
|
|
116487
117400
|
}
|
|
116488
117401
|
this.push("line", `Telegram: ${preview2(line)}`);
|
|
116489
|
-
this.waiting.push({ updateId: meta.updateId, line });
|
|
117402
|
+
this.waiting.push({ updateId: meta.updateId, line, ...meta.fromId === undefined ? {} : { fromId: meta.fromId } });
|
|
116490
117403
|
if (this.host.isBusy()) {
|
|
116491
117404
|
this.host.enqueue(line);
|
|
116492
117405
|
} else {
|
|
@@ -116541,20 +117454,37 @@ class RemoteBridge {
|
|
|
116541
117454
|
return;
|
|
116542
117455
|
const next = this.waiting.shift();
|
|
116543
117456
|
this.currentUpdate = next?.updateId;
|
|
117457
|
+
this.currentFromId = next?.fromId;
|
|
116544
117458
|
if (next !== undefined)
|
|
116545
117459
|
this.report(next.updateId, "working");
|
|
116546
117460
|
this.tgTurn = true;
|
|
116547
117461
|
this.lastAssistantText = "";
|
|
116548
117462
|
this.timedOut = false;
|
|
116549
|
-
|
|
116550
|
-
|
|
117463
|
+
this.stoppedByUser = false;
|
|
117464
|
+
this.turnAbort = new AbortController;
|
|
117465
|
+
const limit = this.runTimeoutMs;
|
|
117466
|
+
if (limit > 0) {
|
|
116551
117467
|
this.runTimer = setTimeout(() => {
|
|
116552
117468
|
this.timedOut = true;
|
|
116553
117469
|
this.push("error", "run time limit reached; stopping the turn");
|
|
117470
|
+
this.turnAbort?.abort();
|
|
116554
117471
|
this.host.cancelTurn();
|
|
116555
117472
|
}, limit);
|
|
116556
117473
|
}
|
|
116557
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
|
+
}
|
|
116558
117488
|
assistantText(text) {
|
|
116559
117489
|
if (!this.tgTurn)
|
|
116560
117490
|
return;
|
|
@@ -116585,15 +117515,18 @@ class RemoteBridge {
|
|
|
116585
117515
|
const client = this.client;
|
|
116586
117516
|
const text = this.lastAssistantText;
|
|
116587
117517
|
const timedOut = this.timedOut;
|
|
117518
|
+
const stopped = this.stoppedByUser && !timedOut;
|
|
116588
117519
|
const updateId = this.currentUpdate;
|
|
116589
117520
|
this.endTurnState();
|
|
116590
117521
|
if (client === undefined)
|
|
116591
117522
|
return;
|
|
116592
117523
|
if (updateId !== undefined)
|
|
116593
|
-
this.report(updateId, outcome.failed || timedOut ? "failed" : "done");
|
|
117524
|
+
this.report(updateId, outcome.failed || timedOut || stopped ? "failed" : "done");
|
|
116594
117525
|
let body;
|
|
116595
|
-
if (
|
|
116596
|
-
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).";
|
|
116597
117530
|
} else if (outcome.failed && text.trim().length === 0) {
|
|
116598
117531
|
body = "The run failed. The details are in the shell.";
|
|
116599
117532
|
} else if (text.trim().length === 0) {
|
|
@@ -116607,31 +117540,56 @@ class RemoteBridge {
|
|
|
116607
117540
|
endTurnState() {
|
|
116608
117541
|
this.tgTurn = false;
|
|
116609
117542
|
this.currentUpdate = undefined;
|
|
117543
|
+
this.currentFromId = undefined;
|
|
116610
117544
|
this.lastAssistantText = "";
|
|
116611
117545
|
this.timedOut = false;
|
|
117546
|
+
this.stoppedByUser = false;
|
|
117547
|
+
this.turnAbort?.abort();
|
|
117548
|
+
this.turnAbort = undefined;
|
|
116612
117549
|
if (this.runTimer !== undefined) {
|
|
116613
117550
|
clearTimeout(this.runTimer);
|
|
116614
117551
|
this.runTimer = undefined;
|
|
116615
117552
|
}
|
|
116616
117553
|
}
|
|
116617
117554
|
async requestApproval(prompt) {
|
|
117555
|
+
return (await this.askApproval(prompt)).decision;
|
|
117556
|
+
}
|
|
117557
|
+
async askApproval(prompt, options = {}) {
|
|
116618
117558
|
const client = this.client;
|
|
116619
117559
|
if (client === undefined || !client.connected) {
|
|
116620
117560
|
this.push("approval", "denied: not connected");
|
|
116621
|
-
return "deny";
|
|
117561
|
+
return { decision: "deny", always: false };
|
|
116622
117562
|
}
|
|
116623
117563
|
this.push("approval", "asked in the topic");
|
|
116624
|
-
let
|
|
117564
|
+
let answer;
|
|
116625
117565
|
try {
|
|
116626
|
-
|
|
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
|
+
}
|
|
116627
117579
|
} catch {
|
|
116628
|
-
|
|
117580
|
+
answer = { decision: "deny", always: false };
|
|
116629
117581
|
}
|
|
116630
|
-
|
|
116631
|
-
|
|
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);
|
|
116632
117590
|
}
|
|
116633
117591
|
}
|
|
116634
|
-
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;
|
|
116635
117593
|
var init_shell_bridge = __esm(() => {
|
|
116636
117594
|
init_service2();
|
|
116637
117595
|
init_client3();
|
|
@@ -118023,7 +118981,7 @@ function formatAge(ms) {
|
|
|
118023
118981
|
return `${minutes}m`;
|
|
118024
118982
|
return `${Math.round(minutes / 60)}h`;
|
|
118025
118983
|
}
|
|
118026
|
-
function formatRemoteStatusLines(status) {
|
|
118984
|
+
function formatRemoteStatusLines(status, posture) {
|
|
118027
118985
|
const lines = [`Remote control: ${STATE_WORDS[status.state]}`];
|
|
118028
118986
|
if (status.state === "off") {
|
|
118029
118987
|
lines.push("Topic: none");
|
|
@@ -118034,7 +118992,14 @@ function formatRemoteStatusLines(status) {
|
|
|
118034
118992
|
lines.push(`Last heartbeat: ${status.heartbeatAgeMs === undefined ? "none yet" : `${formatAge(status.heartbeatAgeMs)} ago`}`);
|
|
118035
118993
|
if (status.state === "offline")
|
|
118036
118994
|
lines.push("Serve is not reachable. The shell keeps retrying; nothing is lost from Telegram.");
|
|
118995
|
+
if (status.unconfirmedApprovals !== undefined && status.unconfirmedApprovals > 0) {
|
|
118996
|
+
const n = status.unconfirmedApprovals;
|
|
118997
|
+
lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
|
|
118998
|
+
}
|
|
118037
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
|
+
}
|
|
118038
119003
|
return lines;
|
|
118039
119004
|
}
|
|
118040
119005
|
function clock2(at) {
|
|
@@ -118091,7 +119056,7 @@ function presentRemoteControl(open, otui, chrome, options) {
|
|
|
118091
119056
|
return formatRemoteEventLines(status);
|
|
118092
119057
|
if (tab === "commands")
|
|
118093
119058
|
return formatRemoteCommandLines(status);
|
|
118094
|
-
return formatRemoteStatusLines(status);
|
|
119059
|
+
return formatRemoteStatusLines(status, options.getPostureLines?.());
|
|
118095
119060
|
};
|
|
118096
119061
|
const content = (tab) => {
|
|
118097
119062
|
const lines = linesFor(tab);
|
|
@@ -118154,7 +119119,18 @@ function projectRemoteRow(status, width) {
|
|
|
118154
119119
|
return { text: fit3("off", width), role: "muted", action: "open" };
|
|
118155
119120
|
if (status.state === "offline")
|
|
118156
119121
|
return { text: fit3("offline", width), role: "attention", action: "open" };
|
|
118157
|
-
|
|
119122
|
+
const topic = status.name ?? "connecting";
|
|
119123
|
+
const unconfirmed = status.unconfirmedApprovals ?? 0;
|
|
119124
|
+
if (unconfirmed > 0)
|
|
119125
|
+
return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open" };
|
|
119126
|
+
return { text: fit3(topic, width), role: "ok", action: "open" };
|
|
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
|
+
`);
|
|
118158
119134
|
}
|
|
118159
119135
|
function mountRemotePanel(otui, renderer, parent, options) {
|
|
118160
119136
|
const core = otui;
|
|
@@ -118169,6 +119145,8 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118169
119145
|
const value = new core.TextRenderable(renderer, { id: "sb-remote-v", content: "" });
|
|
118170
119146
|
box.add(label);
|
|
118171
119147
|
box.add(value);
|
|
119148
|
+
const posture = new core.TextRenderable(renderer, { id: "sb-remote-p", content: "" });
|
|
119149
|
+
box.add(posture);
|
|
118172
119150
|
const currentRow = () => projectRemoteRow(options.getStatus(), options.width);
|
|
118173
119151
|
const draw = () => {
|
|
118174
119152
|
if (disposed)
|
|
@@ -118176,6 +119154,9 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118176
119154
|
const row = currentRow();
|
|
118177
119155
|
label.content = core.t`${dimChunk(core, "Remote")}`;
|
|
118178
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;
|
|
118179
119160
|
};
|
|
118180
119161
|
const activate = () => {
|
|
118181
119162
|
options.onOpen();
|
|
@@ -118187,6 +119168,9 @@ function mountRemotePanel(otui, renderer, parent, options) {
|
|
|
118187
119168
|
value.onMouseDown = () => {
|
|
118188
119169
|
activate();
|
|
118189
119170
|
};
|
|
119171
|
+
posture.onMouseDown = () => {
|
|
119172
|
+
options.onOpenPermissions?.();
|
|
119173
|
+
};
|
|
118190
119174
|
const unsubscribeTheme = onThemeChange(guardedThemeRepaint("remote-panel", draw, () => disposed || isRenderableGone(box)));
|
|
118191
119175
|
draw();
|
|
118192
119176
|
return {
|
|
@@ -118450,6 +119434,11 @@ var init_agent_commands = __esm(() => {
|
|
|
118450
119434
|
description: "How Telegram replies are written: /rendering [auto|rich|html|plain]",
|
|
118451
119435
|
modes: AGENT_ONLY
|
|
118452
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
|
+
},
|
|
118453
119442
|
{
|
|
118454
119443
|
name: "/product",
|
|
118455
119444
|
description: "Intents closed in code that nobody looked back at, with their outcome criteria",
|
|
@@ -118485,6 +119474,11 @@ var init_agent_commands = __esm(() => {
|
|
|
118485
119474
|
description: "Pending remote approvals: summary, scope, consequence, expiry \u2014 allow or deny one call, once",
|
|
118486
119475
|
modes: AGENT_ONLY
|
|
118487
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
|
+
},
|
|
118488
119482
|
{
|
|
118489
119483
|
name: "/decisions",
|
|
118490
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",
|
|
@@ -118695,10 +119689,14 @@ function classifyBusyDispatch(params) {
|
|
|
118695
119689
|
return "remote-control";
|
|
118696
119690
|
if (commandName === "/channels")
|
|
118697
119691
|
return "channels";
|
|
119692
|
+
if (commandName === "/remote-policy")
|
|
119693
|
+
return "remote-policy";
|
|
118698
119694
|
if (commandName === "/schedules")
|
|
118699
119695
|
return "schedules";
|
|
118700
119696
|
if (commandName === "/approvals")
|
|
118701
119697
|
return "approvals";
|
|
119698
|
+
if (commandName === "/permissions")
|
|
119699
|
+
return "permissions";
|
|
118702
119700
|
if (commandName === "/decisions")
|
|
118703
119701
|
return "decisions";
|
|
118704
119702
|
if (commandName === "/external-diff")
|
|
@@ -121375,7 +122373,7 @@ var init_serve_turn_store = __esm(() => {
|
|
|
121375
122373
|
import { randomUUID as randomUUID34 } from "crypto";
|
|
121376
122374
|
import { existsSync as existsSync45, readdirSync as readdirSync10 } from "fs";
|
|
121377
122375
|
import path261 from "path";
|
|
121378
|
-
function
|
|
122376
|
+
function isApprovalId2(value) {
|
|
121379
122377
|
return ID_PATTERN2.test(value);
|
|
121380
122378
|
}
|
|
121381
122379
|
function root(dir) {
|
|
@@ -121401,7 +122399,7 @@ function appendEvidence(event, dir) {
|
|
|
121401
122399
|
appendOwnerOnlyLine(ledgerFile(dir), JSON.stringify(event));
|
|
121402
122400
|
}
|
|
121403
122401
|
function createApproval(input, dir, now = new Date) {
|
|
121404
|
-
if (!
|
|
122402
|
+
if (!isApprovalId2(input.approvalId)) {
|
|
121405
122403
|
throw new Error("approvalId is not an approval id");
|
|
121406
122404
|
}
|
|
121407
122405
|
ensureKeryxSubdir(["approvals"], dir);
|
|
@@ -121468,7 +122466,7 @@ function parseResolution(text) {
|
|
|
121468
122466
|
}
|
|
121469
122467
|
}
|
|
121470
122468
|
function readRaw(id, dir) {
|
|
121471
|
-
if (!
|
|
122469
|
+
if (!isApprovalId2(id)) {
|
|
121472
122470
|
return { ok: false, reason: "not-an-approval-id" };
|
|
121473
122471
|
}
|
|
121474
122472
|
const read = readConfigFile(requestFile(id, dir));
|
|
@@ -121572,7 +122570,7 @@ function approvalIds(dir) {
|
|
|
121572
122570
|
if (!existsSync45(base)) {
|
|
121573
122571
|
return [];
|
|
121574
122572
|
}
|
|
121575
|
-
return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(
|
|
122573
|
+
return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(isApprovalId2);
|
|
121576
122574
|
}
|
|
121577
122575
|
function listApprovals(dir, options = {}) {
|
|
121578
122576
|
const views = [];
|
|
@@ -138527,6 +139525,10 @@ async function runSettingsCommand(command, handlers) {
|
|
|
138527
139525
|
case "/theme":
|
|
138528
139526
|
handlers.theme(arg);
|
|
138529
139527
|
return;
|
|
139528
|
+
case "/remote-policy":
|
|
139529
|
+
if (handlers.remotePolicy !== undefined)
|
|
139530
|
+
handlers.onSystem(handlers.remotePolicy(arg));
|
|
139531
|
+
return;
|
|
138530
139532
|
}
|
|
138531
139533
|
} catch (error) {
|
|
138532
139534
|
handlers.onSystem(`${name}: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -139601,6 +140603,58 @@ var init_games = __esm(() => {
|
|
|
139601
140603
|
init_tic_tac_toe();
|
|
139602
140604
|
});
|
|
139603
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
|
+
|
|
139604
140658
|
// src/mcp-client/elicitation.ts
|
|
139605
140659
|
function extractCodexCommand(vendor) {
|
|
139606
140660
|
const raw = vendor.codex_command;
|
|
@@ -141246,8 +142300,12 @@ async function forceForegroundQueueItem(owner, handoff, item, deps) {
|
|
|
141246
142300
|
}
|
|
141247
142301
|
});
|
|
141248
142302
|
}
|
|
141249
|
-
function createForegroundAgentIoFacade(owner, token, io) {
|
|
142303
|
+
function createForegroundAgentIoFacade(owner, token, io, turn = {}) {
|
|
141250
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;
|
|
141251
142309
|
return {
|
|
141252
142310
|
write: (text) => {
|
|
141253
142311
|
if (accepts())
|
|
@@ -141296,15 +142354,16 @@ function createForegroundAgentIoFacade(owner, token, io) {
|
|
|
141296
142354
|
requestApproval: async (tool, input, meta) => {
|
|
141297
142355
|
if (!accepts())
|
|
141298
142356
|
return false;
|
|
141299
|
-
const response = await
|
|
142357
|
+
const response = await approve()?.(tool, input, meta);
|
|
141300
142358
|
return accepts() ? response ?? false : false;
|
|
141301
142359
|
},
|
|
141302
142360
|
onAutoApproved: (tool, input, meta) => {
|
|
141303
142361
|
if (accepts())
|
|
141304
|
-
|
|
142362
|
+
autoApproved()?.(tool, input, meta);
|
|
141305
142363
|
},
|
|
141306
|
-
permissionMode: () => accepts() ?
|
|
142364
|
+
permissionMode: () => accepts() ? modeOf()?.() ?? "ask" : "ask",
|
|
141307
142365
|
...io.trustedMcpTools === undefined ? {} : { trustedMcpTools: io.trustedMcpTools },
|
|
142366
|
+
...mcpGrantsApply === undefined ? {} : { mcpGrantsApply },
|
|
141308
142367
|
...io.mcpToolFingerprint === undefined ? {} : { mcpToolFingerprint: io.mcpToolFingerprint },
|
|
141309
142368
|
...io.mcpToolDestructive === undefined ? {} : { mcpToolDestructive: io.mcpToolDestructive },
|
|
141310
142369
|
...io.beforeMutation === undefined ? {} : {
|
|
@@ -141950,6 +143009,98 @@ function drainIdleMainQueue(items, options) {
|
|
|
141950
143009
|
return true;
|
|
141951
143010
|
}
|
|
141952
143011
|
|
|
143012
|
+
// src/tui/remote-queue-wiring.ts
|
|
143013
|
+
function createRemoteQueueWiring(deps) {
|
|
143014
|
+
let pendingEdit;
|
|
143015
|
+
return {
|
|
143016
|
+
removeMainQueue(index) {
|
|
143017
|
+
const queue = deps.getQueue();
|
|
143018
|
+
if (index < 0 || index >= queue.length)
|
|
143019
|
+
return;
|
|
143020
|
+
const removed = queue[index];
|
|
143021
|
+
if (removed?.source === TG_SOURCE) {
|
|
143022
|
+
deps.getBridge()?.queuedLineRemoved(removed.question);
|
|
143023
|
+
}
|
|
143024
|
+
deps.setQueue(removeMainQueueItem(queue, index));
|
|
143025
|
+
deps.paint();
|
|
143026
|
+
},
|
|
143027
|
+
editMainQueue(index) {
|
|
143028
|
+
const edited = editMainQueueItem(deps.getQueue(), index);
|
|
143029
|
+
if (edited === undefined)
|
|
143030
|
+
return;
|
|
143031
|
+
deps.setQueue(edited.rest);
|
|
143032
|
+
pendingEdit = pendingQueueEditFor(edited.removed, index);
|
|
143033
|
+
deps.composer.setText(edited.text);
|
|
143034
|
+
deps.composer.focus();
|
|
143035
|
+
deps.paint();
|
|
143036
|
+
},
|
|
143037
|
+
requeuePendingEdit(line, displayLine) {
|
|
143038
|
+
if (pendingEdit === undefined)
|
|
143039
|
+
return false;
|
|
143040
|
+
const edit = pendingEdit;
|
|
143041
|
+
pendingEdit = undefined;
|
|
143042
|
+
deps.setQueue(reinsertEditedMainQueueItem(deps.getQueue(), edit, line, displayLine));
|
|
143043
|
+
deps.paint();
|
|
143044
|
+
return true;
|
|
143045
|
+
},
|
|
143046
|
+
hasPendingEdit: () => pendingEdit !== undefined,
|
|
143047
|
+
stopRemoteForSessionSwitch() {
|
|
143048
|
+
const bridge = deps.getBridge();
|
|
143049
|
+
if (bridge?.keepingTopic === true) {
|
|
143050
|
+
bridge.sessionLeaving();
|
|
143051
|
+
return;
|
|
143052
|
+
}
|
|
143053
|
+
if (bridge?.active !== true)
|
|
143054
|
+
return;
|
|
143055
|
+
const closing = bridge.disable();
|
|
143056
|
+
deps.say(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
|
|
143057
|
+
`);
|
|
143058
|
+
closing.then(() => deps.say(`\u25C7 remote control off: the topic is deleted.
|
|
143059
|
+
`));
|
|
143060
|
+
},
|
|
143061
|
+
dropQueuedTelegramLines() {
|
|
143062
|
+
const { kept, dropped } = dropQueuedBySource(deps.getQueue(), TG_SOURCE);
|
|
143063
|
+
if (dropped.length === 0)
|
|
143064
|
+
return [];
|
|
143065
|
+
deps.setQueue(kept);
|
|
143066
|
+
deps.paint();
|
|
143067
|
+
return dropped.map((item) => item.question);
|
|
143068
|
+
},
|
|
143069
|
+
enqueueTelegramLine(text) {
|
|
143070
|
+
const queue = deps.getQueue();
|
|
143071
|
+
queue.push({ id: deps.nextId(), question: text, displayQuestion: deps.summarize(text), source: TG_SOURCE });
|
|
143072
|
+
deps.paint();
|
|
143073
|
+
deps.say(`\u25C7 a line from Telegram is queued as q${queue.length}.
|
|
143074
|
+
`);
|
|
143075
|
+
},
|
|
143076
|
+
runTelegramLine(text) {
|
|
143077
|
+
deps.runLine(text, "operator", TG_SOURCE);
|
|
143078
|
+
},
|
|
143079
|
+
runQueued(item) {
|
|
143080
|
+
deps.runLine(item.question, "operator", item.source);
|
|
143081
|
+
},
|
|
143082
|
+
turnStarted(source) {
|
|
143083
|
+
deps.getBridge()?.turnStarted(source);
|
|
143084
|
+
},
|
|
143085
|
+
assistantText(text) {
|
|
143086
|
+
deps.getBridge()?.assistantText(text);
|
|
143087
|
+
},
|
|
143088
|
+
wrapOnToolCall(base) {
|
|
143089
|
+
return (...args) => {
|
|
143090
|
+
deps.getBridge()?.toolCall();
|
|
143091
|
+
base?.(...args);
|
|
143092
|
+
};
|
|
143093
|
+
},
|
|
143094
|
+
turnSettled(outcome) {
|
|
143095
|
+
const bridge = deps.getBridge();
|
|
143096
|
+
return bridge === undefined ? Promise.resolve() : bridge.turnSettled({ failed: outcome.failed || outcome.aborted });
|
|
143097
|
+
}
|
|
143098
|
+
};
|
|
143099
|
+
}
|
|
143100
|
+
var init_remote_queue_wiring = __esm(() => {
|
|
143101
|
+
init_shell_bridge();
|
|
143102
|
+
});
|
|
143103
|
+
|
|
141953
143104
|
// src/tui/worker-fleet.ts
|
|
141954
143105
|
function clip4(s, max) {
|
|
141955
143106
|
if (s.length <= max) {
|
|
@@ -151120,7 +152271,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
151120
152271
|
});
|
|
151121
152272
|
let uid = 0;
|
|
151122
152273
|
const herdr = createHerdrReporter();
|
|
151123
|
-
const sessionShellAllow = new Set
|
|
152274
|
+
const sessionShellAllow = new Set;
|
|
151124
152275
|
let permissionMigrationShown = false;
|
|
151125
152276
|
let permissionsFingerprintAtStart = shellPermissionsFingerprint();
|
|
151126
152277
|
let permissionTamperShown = false;
|
|
@@ -151465,7 +152616,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
151465
152616
|
liveRemotePanel = mountRemotePanel(otui, r, sidebar, {
|
|
151466
152617
|
width: SIDEBAR_TEXT_WIDTH,
|
|
151467
152618
|
getStatus: remoteStatus,
|
|
151468
|
-
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
|
+
}
|
|
151469
152625
|
});
|
|
151470
152626
|
liveChannelsPanel = mountChannelsPanel(otui, r, sidebar, {
|
|
151471
152627
|
width: SIDEBAR_TEXT_WIDTH,
|
|
@@ -151763,17 +152919,31 @@ async function launchTuiAgentShell(opts) {
|
|
|
151763
152919
|
guardCollector.onToolResult(name, toolResult);
|
|
151764
152920
|
baseOnToolResultForGuard?.(name, toolResult);
|
|
151765
152921
|
};
|
|
152922
|
+
const remoteQueue = createRemoteQueueWiring({
|
|
152923
|
+
getBridge: () => remoteBridge,
|
|
152924
|
+
getQueue: () => mainQueue,
|
|
152925
|
+
setQueue: (next) => {
|
|
152926
|
+
mainQueue = next;
|
|
152927
|
+
},
|
|
152928
|
+
nextId: () => `mq${mainQueueSeq++}`,
|
|
152929
|
+
summarize: summarizeSubmittedLine,
|
|
152930
|
+
paint: () => paintMainQueue(),
|
|
152931
|
+
composer: {
|
|
152932
|
+
setText: (text) => {
|
|
152933
|
+
input.value = text;
|
|
152934
|
+
},
|
|
152935
|
+
focus: () => input.focus()
|
|
152936
|
+
},
|
|
152937
|
+
say: (text) => io.onSystem?.(text),
|
|
152938
|
+
runLine: (line, origin, source) => runLine(line, origin, source)
|
|
152939
|
+
});
|
|
151766
152940
|
const baseOnAssistantTextForGuard = io.onAssistantText?.bind(io);
|
|
151767
152941
|
io.onAssistantText = (text) => {
|
|
151768
152942
|
guardCollector.onAssistantText(text);
|
|
151769
|
-
|
|
152943
|
+
remoteQueue.assistantText(text);
|
|
151770
152944
|
baseOnAssistantTextForGuard?.(text);
|
|
151771
152945
|
};
|
|
151772
|
-
|
|
151773
|
-
io.onToolCall = (name, toolInput) => {
|
|
151774
|
-
remoteBridge?.toolCall();
|
|
151775
|
-
baseOnToolCallForRemote?.(name, toolInput);
|
|
151776
|
-
};
|
|
152946
|
+
io.onToolCall = remoteQueue.wrapOnToolCall(io.onToolCall?.bind(io));
|
|
151777
152947
|
const refreshGuardSidebar = () => {
|
|
151778
152948
|
clearTranscriptChildren(sbGuard);
|
|
151779
152949
|
if (!guardEnabled)
|
|
@@ -152266,24 +153436,60 @@ async function launchTuiAgentShell(opts) {
|
|
|
152266
153436
|
setBusyPhase("running approved shell");
|
|
152267
153437
|
return true;
|
|
152268
153438
|
};
|
|
152269
|
-
|
|
153439
|
+
const requestApprovalForTelegramTurn = async (tool, inputJson, meta) => {
|
|
152270
153440
|
const bridge = remoteBridge;
|
|
152271
153441
|
if (bridge === undefined || !bridge.telegramTurnActive) {
|
|
152272
153442
|
return requestApprovalLocally(tool, inputJson, meta);
|
|
152273
153443
|
}
|
|
152274
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
|
+
}
|
|
152275
153468
|
io.onSystem?.(`\u25C7 approval for ${brief} sent to the Telegram topic; waiting for the answer there.
|
|
152276
153469
|
`);
|
|
152277
153470
|
setMainAgent("blocked", "approval (telegram)");
|
|
152278
|
-
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
|
+
}
|
|
152279
153484
|
setMainAgent("running", decision === "allow" ? "approved (telegram)" : "denied");
|
|
152280
|
-
io.onSystem?.(decision === "allow" ? `\u25C7 ${brief} approved in Telegram.
|
|
153485
|
+
io.onSystem?.(decision === "allow" ? `\u25C7 ${brief} approved in Telegram${rememberedNote}.
|
|
152281
153486
|
` : `\u25C7 ${brief} denied (Telegram answer, timeout or no connection).
|
|
152282
153487
|
`);
|
|
152283
153488
|
if (decision !== "allow")
|
|
152284
153489
|
return false;
|
|
152285
|
-
return
|
|
153490
|
+
return approvedAnswer();
|
|
152286
153491
|
};
|
|
153492
|
+
io.requestApproval = requestApprovalLocally;
|
|
152287
153493
|
const askUserInteractive = async (req) => {
|
|
152288
153494
|
chrome.hideMenu();
|
|
152289
153495
|
setMainAgent("blocked", "ask");
|
|
@@ -152364,7 +153570,15 @@ async function launchTuiAgentShell(opts) {
|
|
|
152364
153570
|
});
|
|
152365
153571
|
const sessionCwd = opts.session?.cwd ?? process.cwd();
|
|
152366
153572
|
let permissionMode = opts.initialPermissionMode ?? getProjectPermissionMode(sessionCwd) ?? DEFAULT_PERMISSION_MODE;
|
|
152367
|
-
|
|
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;
|
|
152368
153582
|
io.trustedMcpTools = new Map;
|
|
152369
153583
|
io.mcpToolFingerprint = (fqn) => catalogFingerprintResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
152370
153584
|
io.mcpToolDestructive = (fqn) => catalogDestructiveResolver(deps.mcpRuntime?.()?.catalog())(fqn);
|
|
@@ -152372,18 +153586,30 @@ async function launchTuiAgentShell(opts) {
|
|
|
152372
153586
|
io.readOnly = () => readOnly;
|
|
152373
153587
|
const paintModeRow = () => {
|
|
152374
153588
|
const row = describeModeRow(permissionMode, readOnly);
|
|
153589
|
+
const inForce = remoteBridge?.active === true ? formatModeInForce(modeNow()) : undefined;
|
|
152375
153590
|
const hint = settingsSidebarHint(row.readOnly !== undefined);
|
|
152376
|
-
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}`}`)}`;
|
|
152377
153592
|
};
|
|
152378
153593
|
paintModeRow();
|
|
152379
|
-
|
|
153594
|
+
const autoApprovedFor = (telegramTurn) => (tool, input, meta) => {
|
|
152380
153595
|
const preview = tool === "shell_exec" ? parseShellExecCommand(input) : isMcpToolCall(tool) ? `${tool} ${describeUseToolApproval(input, catalogResolver(deps.mcpRuntime?.()?.catalog())).fqn}` : tool;
|
|
152381
|
-
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
|
+
}
|
|
152382
153601
|
transcript.add(new otui.TextRenderable(r, {
|
|
152383
153602
|
id: `ap${uid++}`,
|
|
152384
153603
|
content: otui.t`${roleChunk(otui, "attention", label)} ${dimChunk(otui, preview)}`
|
|
152385
153604
|
}));
|
|
152386
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
|
+
};
|
|
152387
153613
|
let liveSession;
|
|
152388
153614
|
let history = [];
|
|
152389
153615
|
let archive = [];
|
|
@@ -152419,19 +153645,7 @@ async function launchTuiAgentShell(opts) {
|
|
|
152419
153645
|
deps = { ...deps, onContextCompaction };
|
|
152420
153646
|
liveDeps = deps;
|
|
152421
153647
|
let splash = undefined;
|
|
152422
|
-
const stopRemoteForSessionSwitch = () =>
|
|
152423
|
-
if (remoteBridge?.keepingTopic === true) {
|
|
152424
|
-
remoteBridge.sessionLeaving();
|
|
152425
|
-
return;
|
|
152426
|
-
}
|
|
152427
|
-
if (remoteBridge?.active !== true)
|
|
152428
|
-
return;
|
|
152429
|
-
const closing = remoteBridge.disable();
|
|
152430
|
-
io.onSystem?.(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
|
|
152431
|
-
`);
|
|
152432
|
-
closing.then(() => io.onSystem?.(`\u25C7 remote control off: the topic is deleted.
|
|
152433
|
-
`));
|
|
152434
|
-
};
|
|
153648
|
+
const stopRemoteForSessionSwitch = () => remoteQueue.stopRemoteForSessionSwitch();
|
|
152435
153649
|
const applyOpened = (opened, previewHistory) => {
|
|
152436
153650
|
if (opened.history.length > 0) {
|
|
152437
153651
|
splash?.removeIfShown();
|
|
@@ -152909,6 +154123,50 @@ Staying in the current session.
|
|
|
152909
154123
|
io.onSystem?.("The product index could not be used. Run `keryx product index`.\n");
|
|
152910
154124
|
});
|
|
152911
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
|
+
};
|
|
152912
154170
|
const showReviews = () => {
|
|
152913
154171
|
openReviews(otui, chrome, { cwd: inspectorCwd(), renderer: r, ...inspectorKeys }).then(() => liveReviewsPanel?.refresh()).catch(() => {
|
|
152914
154172
|
io.onSystem?.(`The managed reviews could not be read.
|
|
@@ -152939,6 +154197,10 @@ Staying in the current session.
|
|
|
152939
154197
|
const showRemoteControl = () => {
|
|
152940
154198
|
openRemoteControl(otui, chrome, {
|
|
152941
154199
|
getStatus: remoteStatus,
|
|
154200
|
+
getPostureLines: () => {
|
|
154201
|
+
const posture = remoteBridge?.active === true ? telegramPosture() : undefined;
|
|
154202
|
+
return posture === undefined ? undefined : postureLines(posture);
|
|
154203
|
+
},
|
|
152942
154204
|
onToggle: () => {
|
|
152943
154205
|
remoteBridge?.active === true ? disableRemoteControl() : enableRemoteControl();
|
|
152944
154206
|
},
|
|
@@ -153336,6 +154598,7 @@ Staying in the current session.
|
|
|
153336
154598
|
};
|
|
153337
154599
|
const commitPermissionMode = (next) => {
|
|
153338
154600
|
permissionMode = next;
|
|
154601
|
+
modeChangedThisSession = true;
|
|
153339
154602
|
paintModeRow();
|
|
153340
154603
|
chrome.showToast(`Permission mode: ${next}`);
|
|
153341
154604
|
};
|
|
@@ -153506,7 +154769,12 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153506
154769
|
guard: guardEnabled,
|
|
153507
154770
|
routing: routingEnabled,
|
|
153508
154771
|
thinkDisplay: thinkDisplayMode,
|
|
153509
|
-
reasoningOverride
|
|
154772
|
+
reasoningOverride,
|
|
154773
|
+
...(() => {
|
|
154774
|
+
const running = runningTelegramPolicy();
|
|
154775
|
+
const inForce = telegramModeInForce();
|
|
154776
|
+
return running === undefined ? {} : { telegram: { ...running, ...inForce !== undefined ? { inForce } : {} } };
|
|
154777
|
+
})()
|
|
153510
154778
|
}));
|
|
153511
154779
|
const runSettingsAction = (command) => runSettingsCommand(command, {
|
|
153512
154780
|
mode: runModeCommand,
|
|
@@ -153521,6 +154789,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153521
154789
|
reasoning: runReasoningCommand,
|
|
153522
154790
|
think: runThinkCommand,
|
|
153523
154791
|
theme: runThemeCommand,
|
|
154792
|
+
remotePolicy: (arg) => remotePolicyText(arg, remotePolicyDeps()),
|
|
153524
154793
|
onSystem: (text) => io.onSystem?.(text)
|
|
153525
154794
|
});
|
|
153526
154795
|
const showSettings = () => {
|
|
@@ -153567,7 +154836,6 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153567
154836
|
const sideQueue = [];
|
|
153568
154837
|
let mainQueue = [];
|
|
153569
154838
|
let mainQueueSeq = 0;
|
|
153570
|
-
let pendingQueueEdit;
|
|
153571
154839
|
const forceHandoff = createForegroundForceHandoff();
|
|
153572
154840
|
let sideWorkerRunning = false;
|
|
153573
154841
|
let sideClearTimeout;
|
|
@@ -153675,26 +154943,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153675
154943
|
fleet.remove("agent:queue");
|
|
153676
154944
|
}
|
|
153677
154945
|
};
|
|
153678
|
-
const removeMainQueue = (index) =>
|
|
153679
|
-
|
|
153680
|
-
return;
|
|
153681
|
-
const removedItem = mainQueue[index];
|
|
153682
|
-
if (removedItem?.source === TG_SOURCE) {
|
|
153683
|
-
remoteBridge?.queuedLineRemoved(removedItem.question);
|
|
153684
|
-
}
|
|
153685
|
-
mainQueue = removeMainQueueItem(mainQueue, index);
|
|
153686
|
-
paintMainQueue();
|
|
153687
|
-
};
|
|
153688
|
-
const editMainQueue = (index) => {
|
|
153689
|
-
const edited = editMainQueueItem(mainQueue, index);
|
|
153690
|
-
if (edited === undefined)
|
|
153691
|
-
return;
|
|
153692
|
-
mainQueue = edited.rest;
|
|
153693
|
-
pendingQueueEdit = pendingQueueEditFor(edited.removed, index);
|
|
153694
|
-
input.value = edited.text;
|
|
153695
|
-
input.focus();
|
|
153696
|
-
paintMainQueue();
|
|
153697
|
-
};
|
|
154946
|
+
const removeMainQueue = (index) => remoteQueue.removeMainQueue(index);
|
|
154947
|
+
const editMainQueue = (index) => remoteQueue.editMainQueue(index);
|
|
153698
154948
|
const forceMainQueue = (index) => {
|
|
153699
154949
|
const item = mainQueue[index];
|
|
153700
154950
|
if (item === undefined)
|
|
@@ -153709,7 +154959,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
153709
154959
|
forceForegroundQueueItem(foregroundOperation, forceHandoff, item, {
|
|
153710
154960
|
announce: () => io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
|
|
153711
154961
|
`),
|
|
153712
|
-
run: (next) =>
|
|
154962
|
+
run: (next) => remoteQueue.runQueued(next)
|
|
153713
154963
|
});
|
|
153714
154964
|
};
|
|
153715
154965
|
const handleQueueNavKey = (key) => {
|
|
@@ -154082,6 +155332,14 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154082
155332
|
routeApprovalsCommand(line, true, approvals);
|
|
154083
155333
|
return;
|
|
154084
155334
|
}
|
|
155335
|
+
case "permissions": {
|
|
155336
|
+
runPermissionsCommand(line);
|
|
155337
|
+
return;
|
|
155338
|
+
}
|
|
155339
|
+
case "remote-policy": {
|
|
155340
|
+
runRemotePolicyCommand(line);
|
|
155341
|
+
return;
|
|
155342
|
+
}
|
|
154085
155343
|
case "decisions": {
|
|
154086
155344
|
routeDecisionsCommand(line, decisionsPanel);
|
|
154087
155345
|
return;
|
|
@@ -154118,11 +155376,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154118
155376
|
case "not-a-command":
|
|
154119
155377
|
break;
|
|
154120
155378
|
}
|
|
154121
|
-
if (
|
|
154122
|
-
const edit = pendingQueueEdit;
|
|
154123
|
-
pendingQueueEdit = undefined;
|
|
154124
|
-
mainQueue = reinsertEditedMainQueueItem(mainQueue, edit, line, displayLine);
|
|
154125
|
-
paintMainQueue();
|
|
155379
|
+
if (remoteQueue.requeuePendingEdit(line, displayLine)) {
|
|
154126
155380
|
return;
|
|
154127
155381
|
}
|
|
154128
155382
|
if (leaseView()?.held() === true) {
|
|
@@ -154163,7 +155417,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154163
155417
|
takeForced: () => forceHandoff.takeNext(),
|
|
154164
155418
|
dispatch: (next) => {
|
|
154165
155419
|
paintMainQueue();
|
|
154166
|
-
|
|
155420
|
+
remoteQueue.runQueued(next);
|
|
154167
155421
|
}
|
|
154168
155422
|
});
|
|
154169
155423
|
} else {
|
|
@@ -154410,6 +155664,14 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154410
155664
|
if (routeApprovalsCommand(line, false, approvals)) {
|
|
154411
155665
|
return;
|
|
154412
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
|
+
}
|
|
154413
155675
|
if (routeDecisionsCommand(line, decisionsPanel)) {
|
|
154414
155676
|
return;
|
|
154415
155677
|
}
|
|
@@ -154939,7 +156201,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154939
156201
|
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
154940
156202
|
paintMainQueue();
|
|
154941
156203
|
if (next !== undefined)
|
|
154942
|
-
|
|
156204
|
+
remoteQueue.runQueued(next);
|
|
154943
156205
|
}
|
|
154944
156206
|
}
|
|
154945
156207
|
});
|
|
@@ -154951,7 +156213,8 @@ ${formatThemeList(getThemeId())}`);
|
|
|
154951
156213
|
sessions.clear();
|
|
154952
156214
|
deps.resetSubagentBudget?.();
|
|
154953
156215
|
const operation = foregroundOperation.begin();
|
|
154954
|
-
|
|
156216
|
+
remoteQueue.turnStarted(source);
|
|
156217
|
+
const telegramTurn = source === TG_SOURCE && remoteBridge?.telegramTurnActive === true;
|
|
154955
156218
|
blockIo.resetReasoningLiveState();
|
|
154956
156219
|
setMainAgent("running", "waiting");
|
|
154957
156220
|
startBusy("waiting for model");
|
|
@@ -155038,7 +156301,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155038
156301
|
guardCollector.reset(line);
|
|
155039
156302
|
syncArchive();
|
|
155040
156303
|
rewindRecorder.beginTurn({ archiveIndex: archive.length, prompt: line });
|
|
155041
|
-
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io);
|
|
156304
|
+
const foregroundIo = createForegroundAgentIoFacade(foregroundOperation, operation, io, telegramTurn ? telegramTurnIo : {});
|
|
155042
156305
|
const turnSignal = foregroundOperation.signal;
|
|
155043
156306
|
runAgentTurn(foregroundIo, deps, history, line, {
|
|
155044
156307
|
signal: turnSignal,
|
|
@@ -155046,7 +156309,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155046
156309
|
...slateSession !== undefined ? { slateSession, pruneArchive: true } : {}
|
|
155047
156310
|
}).finally(() => {
|
|
155048
156311
|
foregroundOperation.settle(operation);
|
|
155049
|
-
|
|
156312
|
+
remoteQueue.turnSettled({ failed: turnFailed, aborted: turnSignal.aborted });
|
|
155050
156313
|
if (foregroundOperation.isDisposed)
|
|
155051
156314
|
return;
|
|
155052
156315
|
refreshRewindSidebar();
|
|
@@ -155099,7 +156362,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155099
156362
|
const next = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
155100
156363
|
paintMainQueue();
|
|
155101
156364
|
if (next !== undefined) {
|
|
155102
|
-
|
|
156365
|
+
remoteQueue.runQueued(next);
|
|
155103
156366
|
} else {
|
|
155104
156367
|
busWakeController?.onSettle();
|
|
155105
156368
|
}
|
|
@@ -155143,7 +156406,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155143
156406
|
const drained = forceHandoff.takeNext() ?? mainQueue.shift();
|
|
155144
156407
|
paintMainQueue();
|
|
155145
156408
|
if (drained !== undefined)
|
|
155146
|
-
|
|
156409
|
+
remoteQueue.runQueued(drained);
|
|
155147
156410
|
}
|
|
155148
156411
|
});
|
|
155149
156412
|
const captureShellOutput = async (run) => {
|
|
@@ -155226,7 +156489,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
155226
156489
|
});
|
|
155227
156490
|
return `${formatSessionInfoText(snapshot).trimEnd()}
|
|
155228
156491
|
|
|
155229
|
-
Mode: ${
|
|
156492
|
+
Mode: ${formatModeInForce(modeForTurn(true))}${readOnly ? " (read-only on)" : ""}
|
|
155230
156493
|
Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} waiting`;
|
|
155231
156494
|
};
|
|
155232
156495
|
const runRemoteCommand = async (line) => {
|
|
@@ -155239,12 +156502,13 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155239
156502
|
`);
|
|
155240
156503
|
});
|
|
155241
156504
|
}
|
|
155242
|
-
|
|
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)) {
|
|
155243
156507
|
echoRemoteCommand(line);
|
|
155244
156508
|
return await captureShellOutput(() => {
|
|
155245
|
-
if (first === "auto")
|
|
155246
|
-
commitPermissionMode(
|
|
155247
|
-
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)
|
|
155248
156512
|
`);
|
|
155249
156513
|
});
|
|
155250
156514
|
}
|
|
@@ -155255,14 +156519,8 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155255
156519
|
sessionId: () => liveSession.summary.id,
|
|
155256
156520
|
project: () => sessionCwd,
|
|
155257
156521
|
isBusy: () => chrome.isBusy() || foregroundOperation.isActive || forceHandoff.isAwaitingSettlement || mainQueue.length > 0 || leaseView()?.held() === true,
|
|
155258
|
-
runLine: (text) =>
|
|
155259
|
-
enqueue: (text) =>
|
|
155260
|
-
const id = `mq${mainQueueSeq++}`;
|
|
155261
|
-
mainQueue.push({ id, question: text, displayQuestion: summarizeSubmittedLine(text), source: TG_SOURCE });
|
|
155262
|
-
paintMainQueue();
|
|
155263
|
-
io.onSystem?.(`\u25C7 a line from Telegram is queued as q${mainQueue.length}.
|
|
155264
|
-
`);
|
|
155265
|
-
},
|
|
156522
|
+
runLine: (text) => remoteQueue.runTelegramLine(text),
|
|
156523
|
+
enqueue: (text) => remoteQueue.enqueueTelegramLine(text),
|
|
155266
156524
|
notice: (text) => io.onSystem?.(`${text}
|
|
155267
156525
|
`),
|
|
155268
156526
|
cancelTurn: () => foregroundOperation.cancel("remote run time limit"),
|
|
@@ -155280,14 +156538,7 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155280
156538
|
liveSession = recordRemoteOff(liveSession);
|
|
155281
156539
|
} catch {}
|
|
155282
156540
|
},
|
|
155283
|
-
dropQueuedTelegramLines: () =>
|
|
155284
|
-
const { kept, dropped } = dropQueuedBySource(mainQueue, TG_SOURCE);
|
|
155285
|
-
if (dropped.length === 0)
|
|
155286
|
-
return [];
|
|
155287
|
-
mainQueue = kept;
|
|
155288
|
-
paintMainQueue();
|
|
155289
|
-
return dropped.map((item) => item.question);
|
|
155290
|
-
},
|
|
156541
|
+
dropQueuedTelegramLines: () => remoteQueue.dropQueuedTelegramLines(),
|
|
155291
156542
|
runCommand: runRemoteCommand,
|
|
155292
156543
|
busyRefusal: (line) => {
|
|
155293
156544
|
const command = findAgentCommand(line, "agent");
|
|
@@ -155360,7 +156611,10 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
|
|
|
155360
156611
|
pendingResumeTarget = undefined;
|
|
155361
156612
|
return liveSession.summary.id === found.id ? { ...outcome, ok: true } : { ...outcome, ok: false };
|
|
155362
156613
|
},
|
|
155363
|
-
onChange: () =>
|
|
156614
|
+
onChange: () => {
|
|
156615
|
+
liveRemotePanel?.refresh();
|
|
156616
|
+
paintModeRow();
|
|
156617
|
+
}
|
|
155364
156618
|
}
|
|
155365
156619
|
});
|
|
155366
156620
|
onKeypress4(r, (key) => {
|
|
@@ -155412,6 +156666,9 @@ var init_tui_shell = __esm(() => {
|
|
|
155412
156666
|
init_flow_inspector();
|
|
155413
156667
|
init_product_open_surface();
|
|
155414
156668
|
init_reviews_inspector();
|
|
156669
|
+
init_permissions_command();
|
|
156670
|
+
init_remote_policy_command();
|
|
156671
|
+
init_permissions_inspector();
|
|
155415
156672
|
init_shell_bridge();
|
|
155416
156673
|
init_command_gateway();
|
|
155417
156674
|
init_channels_client();
|
|
@@ -155501,6 +156758,7 @@ var init_tui_shell = __esm(() => {
|
|
|
155501
156758
|
init_provider_config();
|
|
155502
156759
|
init_shell_permissions();
|
|
155503
156760
|
init_shell_approval();
|
|
156761
|
+
init_telegram_permission();
|
|
155504
156762
|
init_elicitation();
|
|
155505
156763
|
init_permission_mode_config();
|
|
155506
156764
|
init_permission_mode();
|
|
@@ -155514,6 +156772,7 @@ var init_tui_shell = __esm(() => {
|
|
|
155514
156772
|
init_composer_choice();
|
|
155515
156773
|
init_filter_list();
|
|
155516
156774
|
init_shell_chrome();
|
|
156775
|
+
init_remote_queue_wiring();
|
|
155517
156776
|
init_queue_nav();
|
|
155518
156777
|
init_subagent_inspector();
|
|
155519
156778
|
init_subagent_session();
|
|
@@ -157340,7 +158599,7 @@ async function runAgentRepl(lines, rich, deps, metaprojectPort, sessionOpts, ini
|
|
|
157340
158599
|
}
|
|
157341
158600
|
};
|
|
157342
158601
|
const searchProviderController = createDefaultSearchProviderController();
|
|
157343
|
-
const sessionShellAllow = new Set
|
|
158602
|
+
const sessionShellAllow = new Set;
|
|
157344
158603
|
let fingerprintAtStart = shellPermissionsFingerprint(configDir);
|
|
157345
158604
|
let permissionMigrationShown = false;
|
|
157346
158605
|
let permissionTamperShown = false;
|
|
@@ -158032,6 +159291,8 @@ ${GUTTER}${turnSeparator()}
|
|
|
158032
159291
|
agentIo.onSystem?.(readlineChannelsText(line, await loadChannelsSnapshot(new ChannelsClient)));
|
|
158033
159292
|
} else if (isRenderingCommand(command)) {
|
|
158034
159293
|
agentIo.onSystem?.(runRenderingCommand(rest));
|
|
159294
|
+
} else if (command === "/remote-policy") {
|
|
159295
|
+
agentIo.onSystem?.(remotePolicyText(rest, { ...configDir !== undefined ? { dir: configDir } : {} }));
|
|
158035
159296
|
} else if (isSessionInfoCommand(command)) {
|
|
158036
159297
|
const cwd = sessionCwd;
|
|
158037
159298
|
const [workspaces, flows] = await Promise.all([
|
|
@@ -158055,6 +159316,13 @@ ${GUTTER}${turnSeparator()}
|
|
|
158055
159316
|
})));
|
|
158056
159317
|
} else if (command === "/approvals") {
|
|
158057
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
|
+
}));
|
|
158058
159326
|
} else if (command === "/external-diff") {
|
|
158059
159327
|
agentIo.onSystem?.(externalDiffSlashText(defaultExternalDiffDeps(sessionCwd)));
|
|
158060
159328
|
} else if (isFlowsCommand(command)) {
|
|
@@ -159163,6 +160431,8 @@ var init_shell = __esm(() => {
|
|
|
159163
160431
|
init_flow_inspector();
|
|
159164
160432
|
init_inspector_sources();
|
|
159165
160433
|
init_approvals();
|
|
160434
|
+
init_permissions_command();
|
|
160435
|
+
init_remote_policy_command();
|
|
159166
160436
|
init_external_diff_modal();
|
|
159167
160437
|
init_doctor2();
|
|
159168
160438
|
init_setup_guide();
|
|
@@ -159229,6 +160499,7 @@ var init_shell = __esm(() => {
|
|
|
159229
160499
|
"/status",
|
|
159230
160500
|
"/flows",
|
|
159231
160501
|
"/approvals",
|
|
160502
|
+
"/permissions",
|
|
159232
160503
|
"/external-diff",
|
|
159233
160504
|
"/doctor",
|
|
159234
160505
|
"/setup",
|
|
@@ -159236,6 +160507,7 @@ var init_shell = __esm(() => {
|
|
|
159236
160507
|
"/remote-control",
|
|
159237
160508
|
"/channels",
|
|
159238
160509
|
"/rendering",
|
|
160510
|
+
"/remote-policy",
|
|
159239
160511
|
"/theme",
|
|
159240
160512
|
"/settings",
|
|
159241
160513
|
"/mode",
|
|
@@ -192911,7 +194183,7 @@ function listApprovalsRoute(request, ctx) {
|
|
|
192911
194183
|
});
|
|
192912
194184
|
}
|
|
192913
194185
|
async function answerApprovalRoute(request, approvalId, ctx) {
|
|
192914
|
-
if (!
|
|
194186
|
+
if (!isApprovalId2(approvalId)) {
|
|
192915
194187
|
return approvalNotFound();
|
|
192916
194188
|
}
|
|
192917
194189
|
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
@@ -193175,9 +194447,11 @@ var init_serve_server = __esm(() => {
|
|
|
193175
194447
|
["heartbeat", "POST"],
|
|
193176
194448
|
["reply", "POST"],
|
|
193177
194449
|
["approval", "POST"],
|
|
194450
|
+
["approval-result", "POST"],
|
|
193178
194451
|
["prompt", "POST"],
|
|
193179
194452
|
["state", "POST"],
|
|
193180
194453
|
["ack", "POST"],
|
|
194454
|
+
["approval-ack", "POST"],
|
|
193181
194455
|
["stream", "GET"],
|
|
193182
194456
|
["channels-status", "GET"],
|
|
193183
194457
|
["channels-pair", "POST"],
|
|
@@ -195889,7 +197163,12 @@ class RemoteHub {
|
|
|
195889
197163
|
}));
|
|
195890
197164
|
}
|
|
195891
197165
|
limits() {
|
|
195892
|
-
return {
|
|
197166
|
+
return {
|
|
197167
|
+
runTimeoutMs: this.config.runTimeoutMs,
|
|
197168
|
+
orphanMs: this.config.orphanMs,
|
|
197169
|
+
permissionMode: this.config.permissionMode,
|
|
197170
|
+
approvalTimeoutMs: this.config.approvalTimeoutMs
|
|
197171
|
+
};
|
|
195893
197172
|
}
|
|
195894
197173
|
hasSession(sessionId) {
|
|
195895
197174
|
return this.registry.bySession(sessionId) !== undefined;
|
|
@@ -196665,6 +197944,7 @@ class RemoteHttpSurface {
|
|
|
196665
197944
|
timers;
|
|
196666
197945
|
keepaliveMs;
|
|
196667
197946
|
ackTimeoutMs;
|
|
197947
|
+
approvalAckMs;
|
|
196668
197948
|
newApprovalId;
|
|
196669
197949
|
newPromptId;
|
|
196670
197950
|
streams = new Map;
|
|
@@ -196673,6 +197953,8 @@ class RemoteHttpSurface {
|
|
|
196673
197953
|
bound = new Map;
|
|
196674
197954
|
pendingAcks = new Map;
|
|
196675
197955
|
approvals = new Map;
|
|
197956
|
+
awaiting = new Map;
|
|
197957
|
+
decided = new Map;
|
|
196676
197958
|
choices = new Map;
|
|
196677
197959
|
finished = new Map;
|
|
196678
197960
|
constructor(options) {
|
|
@@ -196681,6 +197963,7 @@ class RemoteHttpSurface {
|
|
|
196681
197963
|
this.timers = options.timers ?? realTimers;
|
|
196682
197964
|
this.keepaliveMs = options.keepaliveMs ?? DEFAULT_KEEPALIVE_MS;
|
|
196683
197965
|
this.ackTimeoutMs = options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS;
|
|
197966
|
+
this.approvalAckMs = options.approvalAckMs ?? DEFAULT_APPROVAL_ACK_MS;
|
|
196684
197967
|
this.newApprovalId = options.approvalIds ?? (() => `ap${randomBytes15(6).toString("hex")}`);
|
|
196685
197968
|
this.newPromptId = options.promptIds ?? (() => `pk${randomBytes15(6).toString("hex")}`);
|
|
196686
197969
|
this.consumer = {
|
|
@@ -196741,12 +198024,16 @@ class RemoteHttpSurface {
|
|
|
196741
198024
|
return this.reply(hub, request);
|
|
196742
198025
|
case "approval":
|
|
196743
198026
|
return this.requestApproval(hub, request);
|
|
198027
|
+
case "approval-result":
|
|
198028
|
+
return this.approvalResult(hub, request);
|
|
196744
198029
|
case "prompt":
|
|
196745
198030
|
return this.requestPrompt(hub, request);
|
|
196746
198031
|
case "state":
|
|
196747
198032
|
return this.messageState(hub, request);
|
|
196748
198033
|
case "ack":
|
|
196749
198034
|
return this.ack(hub, request);
|
|
198035
|
+
case "approval-ack":
|
|
198036
|
+
return this.approvalAck(hub, request);
|
|
196750
198037
|
case "stream":
|
|
196751
198038
|
return this.openStream(hub, request, io);
|
|
196752
198039
|
}
|
|
@@ -196765,6 +198052,9 @@ class RemoteHttpSurface {
|
|
|
196765
198052
|
for (const id of [...this.approvals.keys()]) {
|
|
196766
198053
|
this.dropApproval(id);
|
|
196767
198054
|
}
|
|
198055
|
+
for (const id of [...this.awaiting.keys()]) {
|
|
198056
|
+
this.dropAwaiting(id);
|
|
198057
|
+
}
|
|
196768
198058
|
for (const id of [...this.choices.keys()]) {
|
|
196769
198059
|
this.dropChoice(id);
|
|
196770
198060
|
}
|
|
@@ -196799,7 +198089,15 @@ class RemoteHttpSurface {
|
|
|
196799
198089
|
this.sent.delete(checked.sessionId);
|
|
196800
198090
|
this.bound.set(checked.sessionId, binding);
|
|
196801
198091
|
}
|
|
196802
|
-
|
|
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
|
+
});
|
|
196803
198101
|
}
|
|
196804
198102
|
const status = result.code === "name-taken" ? 409 : result.code === "invalid-name" ? 400 : 502;
|
|
196805
198103
|
return fail4(status, result.code, result.message);
|
|
@@ -196926,6 +198224,13 @@ class RemoteHttpSurface {
|
|
|
196926
198224
|
if (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < MIN_APPROVAL_TIMEOUT_MS || timeoutMs > MAX_APPROVAL_TIMEOUT_MS) {
|
|
196927
198225
|
return invalid2(`timeoutMs must be an integer from ${MIN_APPROVAL_TIMEOUT_MS} to ${MAX_APPROVAL_TIMEOUT_MS}.`);
|
|
196928
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
|
+
}
|
|
196929
198234
|
const sessionId = value.sessionId;
|
|
196930
198235
|
if (!hub.hasSession(sessionId)) {
|
|
196931
198236
|
return fail4(404, "unknown-session", "This session is not registered for remote control; register again.");
|
|
@@ -196947,15 +198252,20 @@ class RemoteHttpSurface {
|
|
|
196947
198252
|
this.approvals.set(approvalId, {
|
|
196948
198253
|
sessionId,
|
|
196949
198254
|
expiresAt,
|
|
196950
|
-
timer: this.timers.setTimeout(() => this.expireApproval(approvalId), timeoutMs)
|
|
198255
|
+
timer: this.timers.setTimeout(() => this.expireApproval(approvalId), timeoutMs),
|
|
198256
|
+
...remember === undefined ? {} : { remember }
|
|
196951
198257
|
});
|
|
198258
|
+
const offer = remember === undefined ? "" : `
|
|
198259
|
+
Always would remember:
|
|
198260
|
+
${asCodeBlock(remember)}`;
|
|
196952
198261
|
const sent = await hub.send(sessionId, `Approval needed:
|
|
196953
|
-
${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
198262
|
+
${asCodeBlock(redactSensitiveText(value.prompt))}${offer}`, {
|
|
196954
198263
|
keyboard: [
|
|
196955
198264
|
[
|
|
196956
198265
|
{ text: "Allow", callback_data: approvalCallbackData(approvalId, "allow") },
|
|
196957
198266
|
{ text: "Deny", callback_data: approvalCallbackData(approvalId, "deny") }
|
|
196958
|
-
]
|
|
198267
|
+
],
|
|
198268
|
+
...remember === undefined ? [] : [[{ text: alwaysButtonText(remember), callback_data: approvalCallbackData(approvalId, "always") }]]
|
|
196959
198269
|
],
|
|
196960
198270
|
onSent: (info) => this.onApprovalSent(approvalId, sessionId, info.messageId, info.text)
|
|
196961
198271
|
});
|
|
@@ -196965,6 +198275,45 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
196965
198275
|
}
|
|
196966
198276
|
return ok2({ approvalId, expiresAt });
|
|
196967
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
|
+
}
|
|
196968
198317
|
async requestPrompt(hub, request) {
|
|
196969
198318
|
const body = await readJsonBody(request);
|
|
196970
198319
|
if (!body.ok) {
|
|
@@ -197088,6 +198437,62 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197088
198437
|
this.complete(value.sessionId, value.updateId);
|
|
197089
198438
|
return ok2({ acknowledged: true });
|
|
197090
198439
|
}
|
|
198440
|
+
async approvalAck(hub, request) {
|
|
198441
|
+
const body = await readJsonBody(request);
|
|
198442
|
+
if (!body.ok) {
|
|
198443
|
+
return body.response;
|
|
198444
|
+
}
|
|
198445
|
+
const value = body.value;
|
|
198446
|
+
if (!isSessionId(value.sessionId)) {
|
|
198447
|
+
return invalid2("sessionId must be 1 to 64 characters of letters, digits, '-' and '_'.");
|
|
198448
|
+
}
|
|
198449
|
+
if (!isApprovalId(value.approvalId)) {
|
|
198450
|
+
return invalid2("approvalId must be 'ap' and 12 lowercase hex characters.");
|
|
198451
|
+
}
|
|
198452
|
+
if (!hub.hasSession(value.sessionId)) {
|
|
198453
|
+
return fail4(404, "unknown-session", "This session is not registered for remote control; register again.");
|
|
198454
|
+
}
|
|
198455
|
+
if (value.applied !== undefined && typeof value.applied !== "boolean") {
|
|
198456
|
+
return invalid2("applied must be true or false when present.");
|
|
198457
|
+
}
|
|
198458
|
+
const sessionId = value.sessionId;
|
|
198459
|
+
const id = value.approvalId;
|
|
198460
|
+
const waiting = this.awaiting.get(id);
|
|
198461
|
+
if (waiting !== undefined && waiting.sessionId === sessionId) {
|
|
198462
|
+
this.dropAwaiting(id);
|
|
198463
|
+
this.rememberDecided(id, sessionId);
|
|
198464
|
+
if (value.applied === false) {
|
|
198465
|
+
this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
|
|
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.`,
|
|
198467
|
+
shortReply: "Not applied: the shell was no longer waiting for this question."
|
|
198468
|
+
});
|
|
198469
|
+
return ok2({ acknowledged: true });
|
|
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
|
+
}
|
|
198485
|
+
this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
|
|
198486
|
+
result: waiting.decision === "allow" ? `Allowed by ${waiting.who} at ${waiting.sentAt}.` : `Denied by ${waiting.who} at ${waiting.sentAt}.`,
|
|
198487
|
+
shortReply: waiting.decision === "allow" ? "Approval granted." : "Approval denied."
|
|
198488
|
+
});
|
|
198489
|
+
return ok2({ acknowledged: true });
|
|
198490
|
+
}
|
|
198491
|
+
if (this.decided.get(id) === sessionId) {
|
|
198492
|
+
return ok2({ acknowledged: true });
|
|
198493
|
+
}
|
|
198494
|
+
return fail4(404, "unknown-approval", "No approval with that id was sent to this session.");
|
|
198495
|
+
}
|
|
197091
198496
|
openStream(hub, request, io) {
|
|
197092
198497
|
const sessionId = new URL(request.url).searchParams.get("sessionId");
|
|
197093
198498
|
if (!isSessionId(sessionId)) {
|
|
@@ -197232,8 +198637,11 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197232
198637
|
return;
|
|
197233
198638
|
}
|
|
197234
198639
|
if (entry !== undefined && entry.expiresAt > this.now()) {
|
|
198640
|
+
if (approval.decision === "always" && entry.remember === undefined) {
|
|
198641
|
+
return;
|
|
198642
|
+
}
|
|
197235
198643
|
this.dropApproval(id);
|
|
197236
|
-
const event = { updateId: callback.updateId, approvalId: id, decision: approval.decision };
|
|
198644
|
+
const event = { updateId: callback.updateId, approvalId: id, decision: approval.decision, fromId: callback.fromId };
|
|
197237
198645
|
const told = stream?.write(encodeSseEvent("approval", event, callback.updateId)) === true;
|
|
197238
198646
|
const messageId = entry.messageId ?? callback.messageId;
|
|
197239
198647
|
const when = clockText(this.now());
|
|
@@ -197244,10 +198652,15 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197244
198652
|
});
|
|
197245
198653
|
return;
|
|
197246
198654
|
}
|
|
197247
|
-
|
|
197248
|
-
|
|
197249
|
-
|
|
197250
|
-
|
|
198655
|
+
this.awaiting.set(id, {
|
|
198656
|
+
sessionId,
|
|
198657
|
+
decision: approval.decision,
|
|
198658
|
+
who: `user ${callback.fromId}`,
|
|
198659
|
+
sentAt: when,
|
|
198660
|
+
...messageId === undefined ? {} : { messageId },
|
|
198661
|
+
...entry.text === undefined ? {} : { text: entry.text },
|
|
198662
|
+
...approval.decision === "always" && entry.remember !== undefined ? { remember: entry.remember } : {},
|
|
198663
|
+
timer: this.timers.setTimeout(() => this.giveUpOnAck(id), this.approvalAckMs)
|
|
197251
198664
|
});
|
|
197252
198665
|
return;
|
|
197253
198666
|
}
|
|
@@ -197255,6 +198668,9 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197255
198668
|
this.expireApproval(id);
|
|
197256
198669
|
return;
|
|
197257
198670
|
}
|
|
198671
|
+
if (this.awaiting.has(id)) {
|
|
198672
|
+
return;
|
|
198673
|
+
}
|
|
197258
198674
|
const done = this.finished.get(id);
|
|
197259
198675
|
if (done !== undefined) {
|
|
197260
198676
|
if (done.sessionId === sessionId) {
|
|
@@ -197275,7 +198691,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197275
198691
|
}
|
|
197276
198692
|
}
|
|
197277
198693
|
carriesLivePrompt(messageId) {
|
|
197278
|
-
return [...this.approvals.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
|
|
198694
|
+
return [...this.approvals.values(), ...this.awaiting.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
|
|
197279
198695
|
}
|
|
197280
198696
|
pressChoice(sessionId, callback, choice, stream) {
|
|
197281
198697
|
const id = choice.promptId;
|
|
@@ -197358,7 +198774,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197358
198774
|
});
|
|
197359
198775
|
}
|
|
197360
198776
|
onApprovalSent(approvalId, sessionId, messageId, text) {
|
|
197361
|
-
const live = this.approvals.get(approvalId);
|
|
198777
|
+
const live = this.approvals.get(approvalId) ?? this.awaiting.get(approvalId);
|
|
197362
198778
|
if (live !== undefined) {
|
|
197363
198779
|
live.messageId = messageId;
|
|
197364
198780
|
live.text = text;
|
|
@@ -197411,6 +198827,37 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197411
198827
|
this.timers.clearTimeout(entry.timer);
|
|
197412
198828
|
this.approvals.delete(approvalId);
|
|
197413
198829
|
}
|
|
198830
|
+
dropAwaiting(approvalId) {
|
|
198831
|
+
const entry = this.awaiting.get(approvalId);
|
|
198832
|
+
if (entry === undefined) {
|
|
198833
|
+
return;
|
|
198834
|
+
}
|
|
198835
|
+
this.timers.clearTimeout(entry.timer);
|
|
198836
|
+
this.awaiting.delete(approvalId);
|
|
198837
|
+
}
|
|
198838
|
+
rememberDecided(approvalId, sessionId) {
|
|
198839
|
+
this.decided.delete(approvalId);
|
|
198840
|
+
this.decided.set(approvalId, sessionId);
|
|
198841
|
+
while (this.decided.size > MAX_DECIDED_APPROVALS) {
|
|
198842
|
+
const oldest = this.decided.keys().next().value;
|
|
198843
|
+
if (oldest === undefined) {
|
|
198844
|
+
break;
|
|
198845
|
+
}
|
|
198846
|
+
this.decided.delete(oldest);
|
|
198847
|
+
}
|
|
198848
|
+
}
|
|
198849
|
+
giveUpOnAck(approvalId) {
|
|
198850
|
+
const entry = this.awaiting.get(approvalId);
|
|
198851
|
+
if (entry === undefined) {
|
|
198852
|
+
return;
|
|
198853
|
+
}
|
|
198854
|
+
this.dropAwaiting(approvalId);
|
|
198855
|
+
this.rememberDecided(approvalId, entry.sessionId);
|
|
198856
|
+
this.finishApproval(approvalId, entry.sessionId, entry.messageId, entry.text, {
|
|
198857
|
+
result: `Sent to the shell at ${entry.sentAt}, not confirmed; the shell denies by itself if it did not receive it.`,
|
|
198858
|
+
shortReply: "Sent to the shell, not confirmed; it denies by itself if it did not receive it."
|
|
198859
|
+
});
|
|
198860
|
+
}
|
|
197414
198861
|
expireApproval(approvalId) {
|
|
197415
198862
|
const entry = this.approvals.get(approvalId);
|
|
197416
198863
|
if (entry === undefined) {
|
|
@@ -197447,6 +198894,11 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197447
198894
|
}
|
|
197448
198895
|
}
|
|
197449
198896
|
}
|
|
198897
|
+
for (const [id, entry] of [...this.awaiting.entries()]) {
|
|
198898
|
+
if (entry.sessionId === stream.sessionId && !this.closed) {
|
|
198899
|
+
this.giveUpOnAck(id);
|
|
198900
|
+
}
|
|
198901
|
+
}
|
|
197450
198902
|
for (const [id, entry] of [...this.choices.entries()]) {
|
|
197451
198903
|
if (entry.sessionId === stream.sessionId) {
|
|
197452
198904
|
this.dropChoice(id);
|
|
@@ -197463,6 +198915,10 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
|
|
|
197463
198915
|
}
|
|
197464
198916
|
}
|
|
197465
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
|
+
}
|
|
197466
198922
|
function asCodeBlock(text) {
|
|
197467
198923
|
const safe = (text.trim().length === 0 ? "(empty)" : text).replace(/\r\n?/g, `
|
|
197468
198924
|
`).replace(/`{9,}/g, (run) => run.match(/`{1,8}/g).join("\u200B"));
|
|
@@ -197472,7 +198928,7 @@ function asCodeBlock(text) {
|
|
|
197472
198928
|
${safe}
|
|
197473
198929
|
${fence}`;
|
|
197474
198930
|
}
|
|
197475
|
-
var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
|
|
198931
|
+
var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_DECIDED_APPROVALS = 256, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
|
|
197476
198932
|
var init_http_surface = __esm(() => {
|
|
197477
198933
|
init_service2();
|
|
197478
198934
|
init_hub();
|
|
@@ -198612,11 +200068,24 @@ function runStatus7(args) {
|
|
|
198612
200068
|
};
|
|
198613
200069
|
const credentialState = credential.status === "ok" ? "present" : credential.status;
|
|
198614
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;
|
|
198615
200074
|
if (asJson) {
|
|
198616
200075
|
console.log(JSON.stringify({
|
|
198617
200076
|
...report,
|
|
198618
200077
|
credential: credentialState,
|
|
198619
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
|
+
},
|
|
198620
200089
|
warnings: [...warnings].sort()
|
|
198621
200090
|
}, null, 2));
|
|
198622
200091
|
return;
|
|
@@ -198634,6 +200103,10 @@ function runStatus7(args) {
|
|
|
198634
200103
|
}
|
|
198635
200104
|
console.log(` credential: ${credentialState}${fingerprint === undefined ? "" : ` (fingerprint ${fingerprint})`}`);
|
|
198636
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
|
+
}
|
|
198637
200110
|
if (report.message !== undefined) {
|
|
198638
200111
|
console.log("");
|
|
198639
200112
|
console.log(` ${style.yellow(symbols.bullet)} ${sanitizeForDisplay(report.message)}`);
|
|
@@ -198891,6 +200364,8 @@ function printHelp23() {
|
|
|
198891
200364
|
}
|
|
198892
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";
|
|
198893
200366
|
var init_serve = __esm(() => {
|
|
200367
|
+
init_config11();
|
|
200368
|
+
init_remote_policy_command();
|
|
198894
200369
|
init_project_registry();
|
|
198895
200370
|
init_serve_config();
|
|
198896
200371
|
init_serve_credential();
|
|
@@ -210604,6 +212079,7 @@ var init_group_subcommands = __esm(() => {
|
|
|
210604
212079
|
["external", ["on", "off", "status", "list"]],
|
|
210605
212080
|
["auth", ["list", "login", "logout", "status"]],
|
|
210606
212081
|
["approvals", ["list", "allow", "deny"]],
|
|
212082
|
+
["permissions", ["list", "remove"]],
|
|
210607
212083
|
["version", ["check"]],
|
|
210608
212084
|
["gdgraph", ["build", "query", "find", "symbol", "symbols", "path", "affected", "repomap", "context", "assets"]],
|
|
210609
212085
|
["ctx", ["status", "diff", "rg", "read", "run", "show", "hook", "install-hook", "uninstall-hook"]],
|
|
@@ -210928,6 +212404,8 @@ var VERSION3, CLI_ROUTES, MCP_KNOWN_SUBCOMMANDS, USAGE_BODY = `Usage:
|
|
|
210928
212404
|
keryx serve config init|set|show
|
|
210929
212405
|
keryx approvals list [--all] [--json] | allow <id> | deny <id>
|
|
210930
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
|
|
210931
212409
|
keryx update [--skip-runtime] [--hooks]
|
|
210932
212410
|
keryx dashboard build
|
|
210933
212411
|
keryx dashboard open
|
|
@@ -211108,6 +212586,7 @@ Commands:
|
|
|
211108
212586
|
projects Inspect the user-global registry of initialized projects
|
|
211109
212587
|
serve Loopback-bound authenticated HTTP entry (off by default; read-only routes)
|
|
211110
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
|
|
211111
212590
|
update Refresh managed service files without touching data artifacts
|
|
211112
212591
|
dashboard Build or open the project admin dashboard
|
|
211113
212592
|
dash Rebuild and open .metaproject/keryx-dashboard.html
|
|
@@ -211188,6 +212667,7 @@ var init_cli_registry = __esm(() => {
|
|
|
211188
212667
|
init_projects();
|
|
211189
212668
|
init_serve();
|
|
211190
212669
|
init_approvals();
|
|
212670
|
+
init_permissions_command();
|
|
211191
212671
|
init_update();
|
|
211192
212672
|
init_dashboard();
|
|
211193
212673
|
init_agents2();
|
|
@@ -211231,6 +212711,7 @@ var init_cli_registry = __esm(() => {
|
|
|
211231
212711
|
auth: authCommand2,
|
|
211232
212712
|
serve: serveCommand,
|
|
211233
212713
|
approvals: approvalsCommand,
|
|
212714
|
+
permissions: permissionsCommand,
|
|
211234
212715
|
update: updateCommand,
|
|
211235
212716
|
dashboard: dashboardCommand,
|
|
211236
212717
|
dash: (rest) => dashboardCommand(rest.length > 0 ? rest : ["open"]),
|