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 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 allowed = await cb.onPermissionRequest(tool, args);
72
- if (!allowed) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "junecoder",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Zero Npm Dependencies Agent Framework",
5
5
  "main": "agent.mjs",
6
6
  "type": "module",
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 === "n" || key.name === "n") state.permission.resolve(false);
277
- else if (ch === "a" || key.name === "a") { agent.autoApprove = true; state.permission.resolve(true); pushLine(" [auto] Auto-approve ON.", C.warn); }
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 accept = await askPermission("distill-save", { title: c.title });
478
- if (accept) {
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++;