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.
- package/dist/ink-chat.d.ts +2 -0
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +169 -20
- package/dist/ink-chat.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +37 -21
- package/dist/web/api.js.map +1 -1
- package/dist/web/events.d.ts +15 -0
- package/dist/web/events.d.ts.map +1 -1
- package/dist/web/events.js.map +1 -1
- package/dist/web/search.d.ts +12 -0
- package/dist/web/search.d.ts.map +1 -1
- package/dist/web/search.js +38 -0
- package/dist/web/search.js.map +1 -1
- package/dist/web/sessions.d.ts +61 -2
- package/dist/web/sessions.d.ts.map +1 -1
- package/dist/web/sessions.js +94 -4
- package/dist/web/sessions.js.map +1 -1
- package/dist/web/ui-client.d.ts.map +1 -1
- package/dist/web/ui-client.js +0 -0
- package/dist/web/ui-client.js.map +1 -1
- package/dist/web/ui-css.d.ts.map +1 -1
- package/dist/web/ui-css.js +22 -0
- package/dist/web/ui-css.js.map +1 -1
- package/dist/web/ui.d.ts.map +1 -1
- package/dist/web/ui.js +13 -1
- package/dist/web/ui.js.map +1 -1
- package/dist/workspace-trust.d.ts +2 -0
- package/dist/workspace-trust.d.ts.map +1 -1
- package/dist/workspace-trust.js +12 -0
- package/dist/workspace-trust.js.map +1 -1
- package/package.json +1 -1
package/dist/ink-chat.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/ink-chat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ink-chat.d.ts","sourceRoot":"","sources":["../src/ink-chat.tsx"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
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
|
-
|
|
775
|
-
|
|
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
|
|
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
|
|
2507
|
-
|
|
2508
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
4139
|
-
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${
|
|
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 && (() => {
|