koneck 2.28.0 → 2.29.0

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.
@@ -2,6 +2,8 @@ import type { AllowRule } from './permissions.js';
2
2
  import type { AgentConfig } from './types.js';
3
3
  /** 92000 → "1m 32s", 7400 → "7.4s", 12 → "12ms". Sub-second work read as a broken "0.0s". */
4
4
  export declare function fmtElapsed(ms: number): string;
5
+ /** A picker label uses all of its allocated column, only ellipsising at the actual boundary. */
6
+ export declare function pickerLabel(text: string, width: number): string;
5
7
  /**
6
8
  * Wraps text while keeping the shape it was written in.
7
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"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"}
package/dist/ink-chat.js CHANGED
@@ -45,6 +45,7 @@ import { doctorChecks, doctorReport } from './doctor.js';
45
45
  import { runQualityGate } from './quality-gate.js';
46
46
  import { listAuditEvents } from './audit.js';
47
47
  import { initializePolicy, loadPolicy, POLICY_FILE } from './policy.js';
48
+ import { isWorkspaceTrusted, trustWorkspace, untrustWorkspace } from './workspace-trust.js';
48
49
  const CYAN = '#54D7E7';
49
50
  const GREEN = '#9BCB8F';
50
51
  const CRIMSON = '#E8878D';
@@ -130,6 +131,10 @@ function fitCells(text, width, ellipsis = false) {
130
131
  const head = sliceToWidth(text, Math.max(0, width - 3));
131
132
  return head.text + '.'.repeat(Math.min(3, width)) + ' '.repeat(Math.max(0, width - head.width - 3));
132
133
  }
134
+ /** A picker label uses all of its allocated column, only ellipsising at the actual boundary. */
135
+ export function pickerLabel(text, width) {
136
+ return fitCells(text, width, true);
137
+ }
133
138
  /**
134
139
  * Wraps text while keeping the shape it was written in.
135
140
  *
@@ -519,6 +524,9 @@ const COMMANDS = [
519
524
  { cmd: '/subtask', desc: 'Run a task in an isolated sub-agent' },
520
525
  { cmd: '/aside', desc: 'Ask a side question without interrupting the running task' },
521
526
  { cmd: '/clear', desc: 'Clear conversation history' },
527
+ { cmd: '/cancel', desc: 'Stop the current task and keep your composer text' },
528
+ { cmd: '/retry', desc: 'Run the last non-secret task again' },
529
+ { cmd: '/queue', desc: 'Inspect or clear prompts waiting behind the current task' },
522
530
  { cmd: '/new', desc: 'New session (alias of /clear)' },
523
531
  { cmd: '/reset', desc: 'Reset session (alias of /clear)' },
524
532
  { cmd: '/mode', desc: 'Set mode: auto, plan or code' },
@@ -537,9 +545,11 @@ const COMMANDS = [
537
545
  { cmd: '/fork', desc: 'Copy this session and continue on the copy' },
538
546
  { cmd: '/archive', desc: 'Keep a session but hide it from /resume' },
539
547
  { cmd: '/diff', desc: 'Show uncommitted git changes' },
548
+ { cmd: '/changes', desc: 'Summarize staged, unstaged, and untracked changes' },
540
549
  { cmd: '/quality', desc: 'Run the detected lint and test checks' },
541
550
  { cmd: '/audit', desc: 'Show recent redacted tool activity' },
542
551
  { cmd: '/policy', desc: 'Show or initialize workspace safety rules' },
552
+ { cmd: '/trust', desc: 'Show, persist, or revoke this workspace trust grant' },
543
553
  { cmd: '/verify', desc: 'Run the project test command' },
544
554
  { cmd: '/rewind', desc: 'Drop the last exchange from history' },
545
555
  { cmd: '/copy', desc: 'Copy the last response to the clipboard' },
@@ -771,8 +781,10 @@ export function pickerCountText(selected, shown, total) {
771
781
  * its row, and the constant 36 that was here cut most of them in half.
772
782
  */
773
783
  export function pickerLabelWidth(labels, descs, width) {
774
- const longestDesc = Math.max(0, ...descs.map(d => d.length));
775
- const longestLabel = Math.max(18, ...labels.map(l => l.length));
784
+ // Terminal cells, not JS code units: one wide glyph in a model name or session title used to
785
+ // make a supposedly exact row spill past the palette edge.
786
+ const longestDesc = Math.max(0, ...descs.map(displayWidth));
787
+ const longestLabel = Math.max(18, ...labels.map(displayWidth));
776
788
  return Math.max(18, Math.min(longestLabel, width - longestDesc - 4));
777
789
  }
778
790
  /**
@@ -787,7 +799,7 @@ export function pickerLabelWidth(labels, descs, width) {
787
799
  * a new one should work without being registered twice. The dangerous ones are few and named.
788
800
  */
789
801
  const COMMANDS_THAT_MUST_WAIT = new Set([
790
- '/clear', '/reset', '/new', // would wipe the history the turn is using
802
+ '/clear', '/reset', '/new', '/retry', // would wipe the history or begin a new turn
791
803
  '/model', '/provider', '/connect', '/key', '/endpoint', '/baseurl', // rebuild the session
792
804
  '/resume', '/fork', // replace it
793
805
  '/revert', // undo files the turn may still be writing
@@ -820,6 +832,9 @@ Slash commands:
820
832
  /status Provider, model, mode, tokens, cost, CWD
821
833
  /doctor Diagnose the active provider, workspace, and developer tools
822
834
  /clear /reset /new Clear conversation history
835
+ /cancel Stop the current task; keeps the composer text
836
+ /retry Run the last non-secret task again
837
+ /queue [clear|drop n] Inspect, clear, or drop one queued prompt
823
838
  /model [name] Show or switch model
824
839
  /provider [name] Show or switch provider
825
840
  /mode [auto|plan|code] Show or set mode
@@ -831,9 +846,11 @@ Slash commands:
831
846
  /cost Show session cost
832
847
  /memory Show .koneck/MEMORY.md
833
848
  /diff Show uncommitted git changes
849
+ /changes Summarize staged, unstaged, and untracked changes
834
850
  /quality Run the detected lint and test checks
835
851
  /audit [count] Show recent redacted tool activity
836
852
  /policy [init] Show or create workspace safety rules
853
+ /trust [always|forget] Show, persist, or revoke this workspace trust grant
837
854
  /verify Run project test command
838
855
  /agents How parallel sub-agents are configured
839
856
  /subtask <task> Run a task in an isolated sub-agent
@@ -846,6 +863,7 @@ Slash commands:
846
863
  Keyboard:
847
864
  Tab Toggle analytics panel
848
865
  Shift+Tab Cycle mode (auto → plan → code)
866
+ Up/Down Recall prior submitted prompts in the composer
849
867
  Ctrl+C Cancel task / exit if idle
850
868
  `.trim();
851
869
  /**
@@ -1176,6 +1194,8 @@ function App({ config: initialConfig, clearFrame }) {
1176
1194
  const draftHistoryRef = useRef([]);
1177
1195
  const historyIndexRef = useRef(null);
1178
1196
  const historyDraftRef = useRef('');
1197
+ /** Last agent task eligible for one-command recovery; never populated from secret-looking input. */
1198
+ const retryTaskRef = useRef(null);
1179
1199
  /** Caret offset into `draft`. Kept beside it so every edit can place the caret deliberately. */
1180
1200
  const [caret, setCaret] = useState(0);
1181
1201
  const [busy, setBusy] = useState(false);
@@ -1631,6 +1651,21 @@ function App({ config: initialConfig, clearFrame }) {
1631
1651
  useEffect(() => { void snapshotTree(cfg.cwd).then(t => { baseTreeRef.current = t; }); }, [cfg.cwd]);
1632
1652
  const addRow = (row) => setRows(prev => [...prev, row]);
1633
1653
  const addSystem = (text) => addRow({ role: 'system', text });
1654
+ /** Stops the in-flight turn without touching anything the developer is composing or queued. */
1655
+ function cancelCurrentTurn() {
1656
+ if (!busy || !abortRef.current)
1657
+ return false;
1658
+ abortRef.current.abort();
1659
+ // Anything still waiting on a permission answer is refused rather than left hanging: the
1660
+ // agents are being abandoned, and an unsettled promise keeps the pool alive after its work
1661
+ // has been cancelled.
1662
+ const stranded = askQueueRef.current.drain(false);
1663
+ if (stranded > 0) {
1664
+ setAsk(null);
1665
+ addSystem(`Cancelled with ${stranded} permission ${stranded === 1 ? 'request' : 'requests'} unanswered; refused.`);
1666
+ }
1667
+ return true;
1668
+ }
1634
1669
  /** Walk submitted composer entries without losing whatever was being typed now. */
1635
1670
  function recallDraft(direction) {
1636
1671
  const history = draftHistoryRef.current;
@@ -2266,6 +2301,54 @@ function App({ config: initialConfig, clearFrame }) {
2266
2301
  addSystem('Conversation cleared. The previous session is kept; /resume lists it.');
2267
2302
  return;
2268
2303
  }
2304
+ case '/cancel': {
2305
+ if (!cancelCurrentTurn())
2306
+ addSystem('No task is running.');
2307
+ return;
2308
+ }
2309
+ case '/retry': {
2310
+ const retry = retryTaskRef.current;
2311
+ if (!retry) {
2312
+ addSystem('Nothing safe to retry yet. Submit a non-secret task first.');
2313
+ return;
2314
+ }
2315
+ addSystem('Retrying the previous task…');
2316
+ // `/retry` itself is handled as a local command. Let its `finally` release the command
2317
+ // busy flag before starting the next agent turn, so the two cannot race each other.
2318
+ setTimeout(() => submitDraft(retry), 0);
2319
+ return;
2320
+ }
2321
+ case '/queue': {
2322
+ const requested = arg.trim();
2323
+ const drop = /^drop\s+(\d+)$/i.exec(requested);
2324
+ if (drop) {
2325
+ const index = Number(drop[1]) - 1;
2326
+ if (!Number.isInteger(index) || index < 0 || index >= queued.length) {
2327
+ addSystem(`There is no queued prompt #${drop[1]}. Run \`/queue\` to inspect the queue.`);
2328
+ return;
2329
+ }
2330
+ const [removed] = queued.slice(index, index + 1);
2331
+ setQueued(items => items.filter((_, itemIndex) => itemIndex !== index));
2332
+ addSystem(`Dropped queued prompt #${index + 1}: ${removed.replace(/\s+/g, ' ').slice(0, 180)}`);
2333
+ return;
2334
+ }
2335
+ if (/^(clear|drop)$/i.test(requested)) {
2336
+ const count = queued.length;
2337
+ setQueued([]);
2338
+ addSystem(count === 0
2339
+ ? 'There are no queued prompts to clear.'
2340
+ : `Cleared ${count} queued ${count === 1 ? 'prompt' : 'prompts'}. The current task keeps running.`);
2341
+ return;
2342
+ }
2343
+ if (queued.length === 0) {
2344
+ addSystem('No prompts are queued. Type while a task runs and press enter to queue it.');
2345
+ return;
2346
+ }
2347
+ addSystem(`**Queued prompts** — ${queued.length}\n\n` +
2348
+ queued.map((prompt, index) => `${index + 1}. ${prompt.replace(/\s+/g, ' ').slice(0, 240)}`).join('\n') +
2349
+ '\n\n`/queue drop 2` removes one prompt. `/queue clear` drops all of them; the current task keeps running.');
2350
+ return;
2351
+ }
2269
2352
  case '/model': {
2270
2353
  if (!arg) {
2271
2354
  const now = modelFor(cfg.provider);
@@ -2503,9 +2586,50 @@ function App({ config: initialConfig, clearFrame }) {
2503
2586
  }
2504
2587
  case '/diff': {
2505
2588
  try {
2506
- const result = await execa('git', ['diff'], { cwd: cfg.cwd, all: true, reject: false });
2507
- const out = result.all?.trim() ?? '';
2508
- addSystem(out || 'No uncommitted changes.');
2589
+ const [unstaged, staged] = await Promise.all([
2590
+ execa('git', ['diff', '--no-ext-diff'], { cwd: cfg.cwd, all: true, reject: false }),
2591
+ execa('git', ['diff', '--cached', '--no-ext-diff'], { cwd: cfg.cwd, all: true, reject: false }),
2592
+ ]);
2593
+ if (unstaged.exitCode !== 0 || staged.exitCode !== 0) {
2594
+ addSystem('Not a git repository.');
2595
+ return;
2596
+ }
2597
+ const sections = [
2598
+ (staged.all ?? '').trim() ? `**Staged**\n\n${(staged.all ?? '').trim()}` : '',
2599
+ (unstaged.all ?? '').trim() ? `**Unstaged**\n\n${(unstaged.all ?? '').trim()}` : '',
2600
+ ].filter(Boolean);
2601
+ addSystem(sections.length ? sections.join('\n\n') : 'No staged or unstaged changes. `/changes` also shows untracked files.');
2602
+ }
2603
+ catch {
2604
+ addSystem('Not a git repository.');
2605
+ }
2606
+ return;
2607
+ }
2608
+ case '/changes': {
2609
+ try {
2610
+ const [status, unstaged, staged] = await Promise.all([
2611
+ execa('git', ['status', '--short'], { cwd: cfg.cwd, all: true, reject: false }),
2612
+ execa('git', ['diff', '--stat', '--no-ext-diff'], { cwd: cfg.cwd, all: true, reject: false }),
2613
+ execa('git', ['diff', '--cached', '--stat', '--no-ext-diff'], { cwd: cfg.cwd, all: true, reject: false }),
2614
+ ]);
2615
+ const entries = (status.all ?? '').trim();
2616
+ if (status.exitCode !== 0 || unstaged.exitCode !== 0 || staged.exitCode !== 0) {
2617
+ addSystem('Not a git repository.');
2618
+ return;
2619
+ }
2620
+ if (!entries) {
2621
+ addSystem('Working tree is clean.');
2622
+ return;
2623
+ }
2624
+ const lines = ['**Working tree changes**', '', '```', entries, '```'];
2625
+ const stagedStat = (staged.all ?? '').trim();
2626
+ const unstagedStat = (unstaged.all ?? '').trim();
2627
+ if (stagedStat)
2628
+ lines.push('', '**Staged**', '```', stagedStat, '```');
2629
+ if (unstagedStat)
2630
+ lines.push('', '**Unstaged**', '```', unstagedStat, '```');
2631
+ lines.push('', 'Use `/diff` to inspect patches, `/quality` to verify, or `/revert` to undo KONECK checkpoints.');
2632
+ addSystem(lines.join('\n'));
2509
2633
  }
2510
2634
  catch {
2511
2635
  addSystem('Not a git repository.');
@@ -2581,6 +2705,28 @@ function App({ config: initialConfig, clearFrame }) {
2581
2705
  `Approval rules : ${policy.requireApprovalFor?.join(', ') || 'none'}`);
2582
2706
  return;
2583
2707
  }
2708
+ case '/trust': {
2709
+ const choice = arg.trim().toLowerCase();
2710
+ if (choice === 'always') {
2711
+ await trustWorkspace(cfg.cwd);
2712
+ addSystem(`This workspace is now trusted for future KONECK sessions:\n\`${cfg.cwd}\``);
2713
+ return;
2714
+ }
2715
+ if (choice === 'forget' || choice === 'revoke' || choice === 'remove') {
2716
+ const removed = await untrustWorkspace(cfg.cwd);
2717
+ addSystem(removed
2718
+ ? `Removed the persistent trust grant for \`${cfg.cwd}\`. This open session remains active; KONECK will ask again next time.`
2719
+ : 'This workspace did not have a persistent trust grant.');
2720
+ return;
2721
+ }
2722
+ const trusted = await isWorkspaceTrusted(cfg.cwd);
2723
+ addSystem(`**Workspace trust**\n\n` +
2724
+ `Path : \`${cfg.cwd}\`\n` +
2725
+ `Persistent : ${trusted ? 'yes' : 'no — this session may be a one-time grant'}\n\n` +
2726
+ '`/trust always` saves this grant for future sessions.\n' +
2727
+ '`/trust forget` removes a saved grant; this open session stays active.');
2728
+ return;
2729
+ }
2584
2730
  case '/verify': {
2585
2731
  const { detectProject } = await import('./project.js');
2586
2732
  const project = await detectProject(cfg.cwd);
@@ -3070,15 +3216,7 @@ function App({ config: initialConfig, clearFrame }) {
3070
3216
  // Esc abandons the turn in flight and hands the keyboard straight back. The composer is
3071
3217
  // deliberately left alone: cancelling should not cost whatever was typed while waiting.
3072
3218
  if (key.escape && busy && !picker && !keyPrompt && !updatePane && !usagePane) {
3073
- abortRef.current?.abort();
3074
- // Anything still waiting on a permission answer is refused rather than left hanging: the
3075
- // agents are being abandoned, and an unsettled promise keeps the pool alive after the work
3076
- // it was doing has been given up on.
3077
- const stranded = askQueueRef.current.drain(false);
3078
- if (stranded > 0) {
3079
- setAsk(null);
3080
- addSystem(`Cancelled with ${stranded} permission ${stranded === 1 ? 'request' : 'requests'} unanswered; refused.`);
3081
- }
3219
+ cancelCurrentTurn();
3082
3220
  return;
3083
3221
  }
3084
3222
  if (key.ctrl && input === 'c') {
@@ -3538,6 +3676,8 @@ function App({ config: initialConfig, clearFrame }) {
3538
3676
  return;
3539
3677
  const next = draft.slice(0, caret) + text + draft.slice(caret);
3540
3678
  const nextCaret = caret + text.length;
3679
+ // Once the recalled line is edited it is a new draft, not a history cursor position.
3680
+ historyIndexRef.current = null;
3541
3681
  setDraft(next);
3542
3682
  setCaret(nextCaret);
3543
3683
  // A lone "@" at a word boundary offers workspace paths. Checking the draft as it will be,
@@ -3581,6 +3721,13 @@ function App({ config: initialConfig, clearFrame }) {
3581
3721
  const task = expandPastes(shown, pastesRef.current).trim();
3582
3722
  if (!task)
3583
3723
  return;
3724
+ // A recalled secret is just as dangerous as a saved one. Secret-looking input is sent only
3725
+ // when the user explicitly submits it, and never retained for Up-arrow recall.
3726
+ if (!containsSecret(task)) {
3727
+ draftHistoryRef.current = appendDraftHistory(draftHistoryRef.current, shown);
3728
+ }
3729
+ historyIndexRef.current = null;
3730
+ historyDraftRef.current = '';
3584
3731
  setDraft('');
3585
3732
  setCaret(0);
3586
3733
  pastesRef.current = [];
@@ -3605,6 +3752,8 @@ function App({ config: initialConfig, clearFrame }) {
3605
3752
  void handleCommand(cmd, arg).finally(() => setBusy(false));
3606
3753
  return;
3607
3754
  }
3755
+ if (!containsSecret(task))
3756
+ retryTaskRef.current = task;
3608
3757
  setBusy(true);
3609
3758
  setAgentState('processing');
3610
3759
  const started = Date.now();
@@ -3828,7 +3977,7 @@ function App({ config: initialConfig, clearFrame }) {
3828
3977
  });
3829
3978
  return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 40), color: CYAN, title: `Plan ${done}/${steps.length}`, children: lines }) }, index));
3830
3979
  })() : row.role === 'error' ? (() => {
3831
- const lines = [row.text ?? '', '', 'Fix the above, then retry. /status shows the active provider and model.'];
3980
+ const lines = [row.text ?? '', '', 'Fix the above, then `/retry` to rerun the task. `/status` shows the active provider and model.'];
3832
3981
  return (_jsx(Box, { marginTop: 1, children: _jsx(Panel, { width: panelWidth(lines, barWidth, 40), color: CRIMSON, title: "[!] ERROR", children: lines }) }, index));
3833
3982
  })() : row.role === 'system' ? (() => {
3834
3983
  const lines = (row.text ?? '').split('\n');
@@ -4117,11 +4266,11 @@ function App({ config: initialConfig, clearFrame }) {
4117
4266
  // The header is a title on the left and a count on the right, on one line. Estimating the
4118
4267
  // right-hand side at a constant made "Reference a file or directory" wrap onto two rows.
4119
4268
  const countText = pickerCountText(pickIndex, list.length, picker.items.length);
4120
- const headWidth = title.length + countText.length + 3;
4269
+ const headWidth = displayWidth(title) + displayWidth(countText) + 3;
4121
4270
  // Measure the filtered list, not just its current scroll window. A palette whose right
4122
4271
  // edge breathes as the highlight crosses a long description feels broken even when the
4123
4272
  // selection itself is correct.
4124
- const rowWidth = Math.max(headWidth, ...list.map(it => 38 + (it.current ? 10 : 0) + it.desc.length + (it.group?.length ?? 0) + 3));
4273
+ const rowWidth = Math.max(headWidth, ...list.map(it => 38 + (it.current ? 10 : 0) + displayWidth(it.desc) + displayWidth(it.group ?? '') + 3));
4125
4274
  const boxWidth = Math.max(40, Math.min(barWidth, rowWidth + 4));
4126
4275
  const width = boxWidth - 4;
4127
4276
  // Room for the longest label the list actually holds, rather than a constant. A session
@@ -4135,8 +4284,8 @@ function App({ config: initialConfig, clearFrame }) {
4135
4284
  const selected = absolute === pickIndex;
4136
4285
  const header = item.group && item.group !== lastGroup ? item.group : null;
4137
4286
  lastGroup = item.group;
4138
- const label = item.label.length > 34 ? item.label.slice(0, 31) + '...' : item.label;
4139
- return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${fitCells(label, labelWidth)}` +
4287
+ const label = pickerLabel(item.label, labelWidth);
4288
+ return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${label}` +
4140
4289
  `${item.current ? '(current) ' : ''}${header ? `${header} · ` : ''}${item.desc}`, width) }) }, item.value + absolute));
4141
4290
  }), list.length > pickerRows && (_jsxs(Text, { color: DIM, children: ["\u2191\u2193 navigate \u00B7 pgup/pgdn jump \u00B7 enter select \u00B7 showing ", Math.max(0, start) + 1, "\u2013", Math.max(0, start) + shown.length, " of ", list.length] }))] }));
4142
4291
  })(), keyPrompt && (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: AMBER, paddingX: 1, marginTop: 1, width: barWidth, children: [_jsxs(Text, { color: AMBER, bold: true, children: ["API key for ", keyPrompt.provider] }), _jsx(Text, { color: MUTED, children: "Paste it and press enter. Held in memory for this session only; esc to cancel." }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: MUTED, children: [keyPrompt.env, ": "] }), _jsx(Text, { color: INK, children: '*'.repeat(Math.min(keyPrompt.value.length, 48)) }), _jsx(Text, { color: CYAN, children: "\u2588" })] })] })), ask && (() => {