junecoder 1.0.11 → 1.0.13
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/agent.mjs +1 -1
- package/executor.mjs +10 -3
- package/package.json +1 -1
- package/tui.mjs +45 -11
package/agent.mjs
CHANGED
|
@@ -263,7 +263,7 @@ function defaultCallbacks(overrides = {}) {
|
|
|
263
263
|
onToolCall: () => {},
|
|
264
264
|
onToolResult: () => {},
|
|
265
265
|
onToolOutput: () => {},
|
|
266
|
-
onPermissionRequest: async () => true,
|
|
266
|
+
onPermissionRequest: async () => ({ allowed: true }),
|
|
267
267
|
onCompress: () => {},
|
|
268
268
|
onUsage: () => {},
|
|
269
269
|
onTurnEnd: () => {},
|
package/executor.mjs
CHANGED
|
@@ -66,10 +66,16 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
66
66
|
|
|
67
67
|
// Permission request for non-readonly tools
|
|
68
68
|
let permissionDenied = false;
|
|
69
|
+
let denyReason = '';
|
|
69
70
|
if (!tool.readonly && cb.onPermissionRequest) {
|
|
70
71
|
try {
|
|
71
|
-
const
|
|
72
|
-
if (
|
|
72
|
+
const result = await cb.onPermissionRequest(tool, args);
|
|
73
|
+
if (typeof result === 'object' && result !== null) {
|
|
74
|
+
if (!result.allowed) {
|
|
75
|
+
permissionDenied = true;
|
|
76
|
+
denyReason = result.reason || '';
|
|
77
|
+
}
|
|
78
|
+
} else if (!result) {
|
|
73
79
|
permissionDenied = true;
|
|
74
80
|
}
|
|
75
81
|
} catch {
|
|
@@ -78,11 +84,12 @@ export async function executeToolCalls(agent, toolByName, toolCalls, callbacks,
|
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
if (permissionDenied) {
|
|
87
|
+
const reasonSuffix = denyReason ? ` Reason: ${denyReason}` : '';
|
|
81
88
|
prepared.push({
|
|
82
89
|
id,
|
|
83
90
|
name,
|
|
84
91
|
denied: true,
|
|
85
|
-
output: `Permission denied for "${name}"
|
|
92
|
+
output: `Permission denied for "${name}".${reasonSuffix}`,
|
|
86
93
|
});
|
|
87
94
|
continue;
|
|
88
95
|
}
|
package/package.json
CHANGED
package/tui.mjs
CHANGED
|
@@ -227,6 +227,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
227
227
|
|
|
228
228
|
let borderColor = C.tool, title;
|
|
229
229
|
if (state.question) { borderColor = C.tool; title = " " + sliceByWidth(state.question.text, W - 6) + " "; }
|
|
230
|
+
else if (state.permission?.reasonMode) { borderColor = C.warn; title = " Denied \u2014 why? (Enter to send, Esc to skip) "; }
|
|
230
231
|
else if (state.permission) { borderColor = C.warn; title = state.permission.name === "continue" ? " Continue? (y/n) " : " Allow " + state.permission.name + "? (y/n/a) "; }
|
|
231
232
|
else if (state.processing) title = " Processing... ";
|
|
232
233
|
else if (setupMode) title = " API Key ";
|
|
@@ -271,12 +272,32 @@ export async function startTUI(agent, opts = {}) {
|
|
|
271
272
|
|
|
272
273
|
keyStream.on("keypress", (str, key) => {
|
|
273
274
|
if (state.permission) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
if (state.permission.reasonMode) {
|
|
276
|
+
// In reason mode: Escape cancels, everything else passes through for typing
|
|
277
|
+
if (key.name === "escape") {
|
|
278
|
+
state.input = []; state.cursor = 0;
|
|
279
|
+
const resolve = state.permission.resolve;
|
|
280
|
+
state.permission = null; state.permissionPreview = [];
|
|
281
|
+
state.status = state.processing ? "Processing..." : "Ready";
|
|
282
|
+
resolve({ allowed: false });
|
|
283
|
+
render(); return;
|
|
284
|
+
}
|
|
285
|
+
// Fall through to normal key handling (typing, Enter→submit, Ctrl+C, etc.)
|
|
286
|
+
} else {
|
|
287
|
+
const ch = (str || key.name || "").toLowerCase();
|
|
288
|
+
if (ch === "y" || key.name === "y") state.permission.resolve({ allowed: true });
|
|
289
|
+
else if (ch === "a" || key.name === "a") { agent.autoApprove = true; state.permission.resolve({ allowed: true }); pushLine(" [auto] Auto-approve ON.", C.warn); }
|
|
290
|
+
else if (ch === "n" || key.name === "n") {
|
|
291
|
+
// Enter reason mode
|
|
292
|
+
state.permission.reasonMode = true;
|
|
293
|
+
state.input = []; state.cursor = 0;
|
|
294
|
+
state.status = "Denied — why? (Enter to send, Esc to skip)";
|
|
295
|
+
render();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
else return;
|
|
299
|
+
state.permission = null; state.permissionPreview = []; state.status = state.processing ? "Processing..." : "Ready"; render(); return;
|
|
300
|
+
}
|
|
280
301
|
}
|
|
281
302
|
if (state.question) {
|
|
282
303
|
if (key.name === "return" || key.name === "enter") {
|
|
@@ -344,7 +365,20 @@ export async function startTUI(agent, opts = {}) {
|
|
|
344
365
|
|
|
345
366
|
async function submit() {
|
|
346
367
|
const text = state.input.join("").trim();
|
|
347
|
-
if (!text
|
|
368
|
+
if (!text && !state.permission?.reasonMode) return;
|
|
369
|
+
if (state.processing && !state.permission?.reasonMode) return;
|
|
370
|
+
|
|
371
|
+
// ─── Permission reason mode ────────────────────────────────────────────
|
|
372
|
+
if (state.permission?.reasonMode) {
|
|
373
|
+
const reason = text;
|
|
374
|
+
state.input = []; state.cursor = 0;
|
|
375
|
+
const resolve = state.permission.resolve;
|
|
376
|
+
state.permission = null; state.permissionPreview = [];
|
|
377
|
+
state.status = state.processing ? "Processing..." : "Ready";
|
|
378
|
+
pushLine(" Denied: " + (reason || "(no reason)"), C.warn);
|
|
379
|
+
resolve({ allowed: false, reason: reason || undefined });
|
|
380
|
+
render(); return;
|
|
381
|
+
}
|
|
348
382
|
|
|
349
383
|
// ─── Setup mode: first input is the API key ──────────────────────────────
|
|
350
384
|
if (setupMode) {
|
|
@@ -423,7 +457,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
423
457
|
try { await runAgent(agent, text, callbacks, { signal: state.controller.signal, resume }); flushStream(); break; }
|
|
424
458
|
catch (error) { flushStream();
|
|
425
459
|
if (error.name === "AbortError" || state.controller?.signal.aborted) { pushLine("[Aborted]", C.warn); break; }
|
|
426
|
-
if (error instanceof ContinueError) { pushLabel("\u276f Continue", ansi.bold + C.warn); pushLine("Ran " + error.turn + " turns (limit " + error.turn + "). Continue?", C.warn); const willContinue = await new Promise(resolve => { state.permission = { name: "continue", args: { turns: error.turn }, resolve }; state.status = "Continue after " + error.turn + " turns?"; render(); }); state.permission = null; if (!willContinue) { pushLine("[Cancelled]", C.warn); break; } pushLine("[Continuing\u2026]", C.tool); state.controller = new AbortController(); continue; }
|
|
460
|
+
if (error instanceof ContinueError) { pushLabel("\u276f Continue", ansi.bold + C.warn); pushLine("Ran " + error.turn + " turns (limit " + error.turn + "). Continue?", C.warn); const willContinue = await new Promise(resolve => { state.permission = { name: "continue", args: { turns: error.turn }, resolve, reasonMode: false }; state.status = "Continue after " + error.turn + " turns?"; render(); }); state.permission = null; state.permissionPreview = []; if (!willContinue.allowed) { pushLine("[Cancelled]", C.warn); break; } pushLine("[Continuing\u2026]", C.tool); state.controller = new AbortController(); continue; }
|
|
427
461
|
pushLine("[error] " + error.message, C.error); break;
|
|
428
462
|
}
|
|
429
463
|
}
|
|
@@ -432,7 +466,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
432
466
|
try { saveSession(agent, state.lines); } catch {} render();
|
|
433
467
|
}
|
|
434
468
|
function flushStream() { if (state.reasoning) { pushLine(state.reasoning, C.reason); state.reasoning = ""; } if (state.streaming) { pushLine(state.streaming, C.text); state.streaming = ""; } }
|
|
435
|
-
function askPermission(name, args) { if (agent.autoApprove) { pushLine(" [auto] " + name + " " + summarize(args), C.warn); return Promise.resolve(true); } state.permissionPreview = formatPermission(name, args); return new Promise(resolve => { state.permission = { name, args, resolve }; state.status = "Waiting: " + name; render(); }); }
|
|
469
|
+
function askPermission(name, args) { if (agent.autoApprove) { pushLine(" [auto] " + name + " " + summarize(args), C.warn); return Promise.resolve({ allowed: true }); } state.permissionPreview = formatPermission(name, args); return new Promise(resolve => { state.permission = { name, args, resolve, reasonMode: false }; state.status = "Waiting: " + name; render(); }); }
|
|
436
470
|
function askQuestion(text) { if (state.question) return Promise.resolve("(already waiting)"); pushLabel("\u276f Question", ansi.bold + C.tool); for (const line of text.split("\n")) pushLine(" " + line, C.text); return new Promise(resolve => { state.question = { text, options: [], resolve }; state.status = "Waiting..."; render(); }); }
|
|
437
471
|
|
|
438
472
|
async function handleSlash(text) {
|
|
@@ -474,8 +508,8 @@ export async function startTUI(agent, opts = {}) {
|
|
|
474
508
|
for (const c of candidates) {
|
|
475
509
|
pushLine(`\n [${c.type}] ${c.title}`, C.text);
|
|
476
510
|
pushLine(` ${c.content.slice(0, 200)}...`, C.dim);
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
511
|
+
const result = await askPermission("distill-save", { title: c.title });
|
|
512
|
+
if (result.allowed) {
|
|
479
513
|
const status = await saveCandidate(agent.memory, c, {});
|
|
480
514
|
pushLine(` ✓ ${status}`, C.tool);
|
|
481
515
|
saved++;
|