junecoder 1.0.11 → 1.0.12
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 +36 -8
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 ";
|
|
@@ -272,9 +273,16 @@ export async function startTUI(agent, opts = {}) {
|
|
|
272
273
|
keyStream.on("keypress", (str, key) => {
|
|
273
274
|
if (state.permission) {
|
|
274
275
|
const ch = (str || key.name || "").toLowerCase();
|
|
275
|
-
if (ch === "y" || key.name === "y") state.permission.resolve(true);
|
|
276
|
-
else if (ch === "
|
|
277
|
-
else if (ch === "
|
|
276
|
+
if (ch === "y" || key.name === "y") state.permission.resolve({ allowed: true });
|
|
277
|
+
else if (ch === "a" || key.name === "a") { agent.autoApprove = true; state.permission.resolve({ allowed: true }); pushLine(" [auto] Auto-approve ON.", C.warn); }
|
|
278
|
+
else if (ch === "n" || key.name === "n") {
|
|
279
|
+
// Enter reason mode
|
|
280
|
+
state.permission.reasonMode = true;
|
|
281
|
+
state.input = []; state.cursor = 0;
|
|
282
|
+
state.status = "Denied — why? (Enter to send, Esc to skip)";
|
|
283
|
+
render();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
278
286
|
else return;
|
|
279
287
|
state.permission = null; state.permissionPreview = []; state.status = state.processing ? "Processing..." : "Ready"; render(); return;
|
|
280
288
|
}
|
|
@@ -293,6 +301,14 @@ export async function startTUI(agent, opts = {}) {
|
|
|
293
301
|
if (state.question.options.length === 0) { editInput(str, key); render(); return; }
|
|
294
302
|
return;
|
|
295
303
|
}
|
|
304
|
+
if (state.permission?.reasonMode && key.name === "escape") {
|
|
305
|
+
state.input = []; state.cursor = 0;
|
|
306
|
+
const resolve = state.permission.resolve;
|
|
307
|
+
state.permission = null; state.permissionPreview = [];
|
|
308
|
+
state.status = state.processing ? "Processing..." : "Ready";
|
|
309
|
+
resolve({ allowed: false });
|
|
310
|
+
render(); return;
|
|
311
|
+
}
|
|
296
312
|
if (key.name === "c" && key.ctrl) {
|
|
297
313
|
if (state.processing && state.controller) { state.controller.abort(); state.status = "Aborting..."; render(); }
|
|
298
314
|
else { cleanup(); process.exit(0); }
|
|
@@ -344,7 +360,19 @@ export async function startTUI(agent, opts = {}) {
|
|
|
344
360
|
|
|
345
361
|
async function submit() {
|
|
346
362
|
const text = state.input.join("").trim();
|
|
347
|
-
if (!text || state.processing) return;
|
|
363
|
+
if (!text || (state.processing && !state.permission?.reasonMode)) return;
|
|
364
|
+
|
|
365
|
+
// ─── Permission reason mode ────────────────────────────────────────────
|
|
366
|
+
if (state.permission?.reasonMode) {
|
|
367
|
+
const reason = text;
|
|
368
|
+
state.input = []; state.cursor = 0;
|
|
369
|
+
const resolve = state.permission.resolve;
|
|
370
|
+
state.permission = null; state.permissionPreview = [];
|
|
371
|
+
state.status = state.processing ? "Processing..." : "Ready";
|
|
372
|
+
pushLine(" Denied: " + (reason || "(no reason)"), C.warn);
|
|
373
|
+
resolve({ allowed: false, reason: reason || undefined });
|
|
374
|
+
render(); return;
|
|
375
|
+
}
|
|
348
376
|
|
|
349
377
|
// ─── Setup mode: first input is the API key ──────────────────────────────
|
|
350
378
|
if (setupMode) {
|
|
@@ -423,7 +451,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
423
451
|
try { await runAgent(agent, text, callbacks, { signal: state.controller.signal, resume }); flushStream(); break; }
|
|
424
452
|
catch (error) { flushStream();
|
|
425
453
|
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; }
|
|
454
|
+
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
455
|
pushLine("[error] " + error.message, C.error); break;
|
|
428
456
|
}
|
|
429
457
|
}
|
|
@@ -432,7 +460,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
432
460
|
try { saveSession(agent, state.lines); } catch {} render();
|
|
433
461
|
}
|
|
434
462
|
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(); }); }
|
|
463
|
+
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
464
|
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
465
|
|
|
438
466
|
async function handleSlash(text) {
|
|
@@ -474,8 +502,8 @@ export async function startTUI(agent, opts = {}) {
|
|
|
474
502
|
for (const c of candidates) {
|
|
475
503
|
pushLine(`\n [${c.type}] ${c.title}`, C.text);
|
|
476
504
|
pushLine(` ${c.content.slice(0, 200)}...`, C.dim);
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
505
|
+
const result = await askPermission("distill-save", { title: c.title });
|
|
506
|
+
if (result.allowed) {
|
|
479
507
|
const status = await saveCandidate(agent.memory, c, {});
|
|
480
508
|
pushLine(` ✓ ${status}`, C.tool);
|
|
481
509
|
saved++;
|