koneck 2.25.56 → 2.25.58

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/glyphs.d.ts CHANGED
@@ -22,6 +22,8 @@ export interface GlyphSet {
22
22
  fail: string;
23
23
  /** Truncation marker. Three ASCII dots are wider but always legible. */
24
24
  ellipsis: string;
25
+ /** Marks a search field. */
26
+ search: string;
25
27
  /** Tab hints. */
26
28
  left: string;
27
29
  right: string;
@@ -1 +1 @@
1
- {"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../src/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,QAAQ;IACvB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,yFAAyF;IACzF,KAAK,EAAE,OAAO,CAAC;CAChB;AA2BD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,QAAQ,GAAE,MAAyB,EACnC,QAAQ,GAAE,MAAM,GAAG,SAAwC,EAC3D,IAAI,GAAE,MAAM,GAAG,SAA+B,GAC7C,QAAQ,CAKV;AAED,eAAO,MAAM,CAAC,EAAE,QAAuB,CAAC"}
1
+ {"version":3,"file":"glyphs.d.ts","sourceRoot":"","sources":["../src/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,QAAQ;IACvB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,yFAAyF;IACzF,KAAK,EAAE,OAAO,CAAC;CAChB;AA6BD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,QAAQ,GAAE,MAAyB,EACnC,QAAQ,GAAE,MAAM,GAAG,SAAwC,EAC3D,IAAI,GAAE,MAAM,GAAG,SAA+B,GAC7C,QAAQ,CAKV;AAED,eAAO,MAAM,CAAC,EAAE,QAAuB,CAAC"}
package/dist/glyphs.js CHANGED
@@ -18,6 +18,7 @@ const UNICODE = {
18
18
  ok: '✓',
19
19
  fail: '✖',
20
20
  ellipsis: '…',
21
+ search: '⌕',
21
22
  left: '←',
22
23
  right: '→',
23
24
  spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
@@ -30,6 +31,7 @@ const ASCII = {
30
31
  ok: '+',
31
32
  fail: 'x',
32
33
  ellipsis: '...',
34
+ search: '/',
33
35
  left: '<',
34
36
  right: '>',
35
37
  spinner: ['|', '/', '-', '\\'],
@@ -1 +1 @@
1
- {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../src/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAqBH,MAAM,OAAO,GAAa;IACxB,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,GAAG;IACV,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,GAAG;IACb,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IAC3D,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,MAAM,KAAK,GAAa;IACtB,iGAAiG;IACjG,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,GAAG;IACV,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,KAAK;IACf,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9B,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,WAAmB,OAAO,CAAC,QAAQ,EACnC,WAA+B,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAC3D,OAA2B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAE9C,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,MAAM,CAAC,GAAa,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../src/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAuBH,MAAM,OAAO,GAAa;IACxB,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,GAAG;IACV,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,GAAG;IACb,MAAM,EAAE,GAAG;IACX,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;IAC3D,KAAK,EAAE,KAAK;CACb,CAAC;AAEF,MAAM,KAAK,GAAa;IACtB,iGAAiG;IACjG,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,GAAG;IACV,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,GAAG;IACX,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9B,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,WAAmB,OAAO,CAAC,QAAQ,EACnC,WAA+B,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,EAC3D,OAA2B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;IAE9C,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IAC3C,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,MAAM,CAAC,GAAa,UAAU,EAAE,CAAC"}
@@ -143,6 +143,34 @@ export declare function pasteTarget(open: {
143
143
  picker?: boolean;
144
144
  ask?: boolean;
145
145
  }): PasteTarget;
146
+ /** Bytes as a person reads them, for a session listing. */
147
+ export declare function humanBytes(bytes: number): string;
148
+ /**
149
+ * What to call a session in a list.
150
+ *
151
+ * A name if one was given, otherwise the first thing asked of it — which is what people remember
152
+ * a piece of work by. The id is the last resort, because a timestamp identifies nothing.
153
+ */
154
+ export declare function sessionTitle(meta: {
155
+ name?: string;
156
+ task?: string;
157
+ id: string;
158
+ }): string;
159
+ /**
160
+ * The line at the top right of a picker.
161
+ *
162
+ * Says where you are, not merely how many rows exist — and says when a filter is hiding some,
163
+ * which "9 matches" cannot. A list that silently narrowed as you typed was indistinguishable
164
+ * from one that had mysteriously changed.
165
+ */
166
+ export declare function pickerCountText(selected: number, shown: number, total: number): string;
167
+ /**
168
+ * How much of a row belongs to the label.
169
+ *
170
+ * Sized from the rows actually on screen rather than fixed: a session title is what identifies
171
+ * its row, and the constant 36 that was here cut most of them in half.
172
+ */
173
+ export declare function pickerLabelWidth(labels: readonly string[], descs: readonly string[], width: number): number;
146
174
  export declare function runInkChatMode(config: AgentConfig): Promise<void>;
147
175
  export {};
148
176
  //# sourceMappingURL=ink-chat.d.ts.map
@@ -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":"AAgDA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAS5D,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,YAAY,CAAC;AAoE7D,6FAA6F;AAC7F,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM7C;AA0BD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+BpE;AAED,qFAAqF;AACrF,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAiB,GAAG,MAAM,CAM9E;AA6CD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGnE;AAuBD;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE3E,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA2GzE,CAAC;AAEF,0CAA0C;AAC1C,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAItD;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAMpD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpF;AA0DD,UAAU,UAAU;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE;AAUtG,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAK7E;AAeD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,EAAE,CAWlF;AAGD,iGAAiG;AACjG,UAAU,SAAS;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CAepE;AAQD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAMjF;AAGD;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7D,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAChC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACtD,GAAG,WAAW,CAMd;AAGD,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAKvF;AAGD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,EAAE,MAAM,GACZ,MAAM,CAIR;AA8jGD,wBAAsB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAQvE"}
package/dist/ink-chat.js CHANGED
@@ -36,6 +36,7 @@ import { AskQueue } from './ask-queue.js';
36
36
  import { planSummary } from './plan.js';
37
37
  import { logoFor } from './logo.js';
38
38
  import { describeActivity } from './activity.js';
39
+ import { redactSecrets, containsSecret } from './redact.js';
39
40
  import { renderTrajectory } from './trajectory.js';
40
41
  import { describeSandbox, resolveSandbox } from './sandbox.js';
41
42
  const CYAN = '#54D7E7';
@@ -571,6 +572,53 @@ export function pasteTarget(open) {
571
572
  return 'drop';
572
573
  return 'composer';
573
574
  }
575
+ /** Bytes as a person reads them, for a session listing. */
576
+ export function humanBytes(bytes) {
577
+ if (bytes < 1024)
578
+ return `${bytes}B`;
579
+ if (bytes < 1024 * 1024)
580
+ return `${(bytes / 1024).toFixed(1)}KB`;
581
+ return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
582
+ }
583
+ /**
584
+ * What to call a session in a list.
585
+ *
586
+ * A name if one was given, otherwise the first thing asked of it — which is what people remember
587
+ * a piece of work by. The id is the last resort, because a timestamp identifies nothing.
588
+ */
589
+ export function sessionTitle(meta) {
590
+ if (meta.name)
591
+ return meta.name;
592
+ const task = redactSecrets(meta.task ?? '').replace(/\s+/g, ' ').trim();
593
+ if (!task || task === '(chat session)')
594
+ return meta.id;
595
+ return task.length > 64 ? task.slice(0, 63) + '…' : task;
596
+ }
597
+ /**
598
+ * The line at the top right of a picker.
599
+ *
600
+ * Says where you are, not merely how many rows exist — and says when a filter is hiding some,
601
+ * which "9 matches" cannot. A list that silently narrowed as you typed was indistinguishable
602
+ * from one that had mysteriously changed.
603
+ */
604
+ export function pickerCountText(selected, shown, total) {
605
+ if (shown === 0)
606
+ return 'no matches · esc to close';
607
+ return `${selected + 1} of ${shown}` +
608
+ (shown < total ? ` (filtered from ${total})` : '') +
609
+ ' · esc to close';
610
+ }
611
+ /**
612
+ * How much of a row belongs to the label.
613
+ *
614
+ * Sized from the rows actually on screen rather than fixed: a session title is what identifies
615
+ * its row, and the constant 36 that was here cut most of them in half.
616
+ */
617
+ export function pickerLabelWidth(labels, descs, width) {
618
+ const longestDesc = Math.max(0, ...descs.map(d => d.length));
619
+ const longestLabel = Math.max(18, ...labels.map(l => l.length));
620
+ return Math.max(18, Math.min(longestLabel, width - longestDesc - 4));
621
+ }
574
622
  const MODES = MODE_SPECS.map(m => m.name);
575
623
  const HELP_TEXT = `
576
624
  Type / on an empty line to browse every command; keep typing to filter.
@@ -1242,8 +1290,12 @@ function App({ config: initialConfig }) {
1242
1290
  async function doSave() {
1243
1291
  const s = await activeSession.p.catch(() => null);
1244
1292
  const firstUser = s?.messages.find((m) => m.role === 'user');
1293
+ // Masked before it is written. A key pasted into the composer became the first user message,
1294
+ // which is where this title comes from — so it was persisted to disk and then printed in the
1295
+ // resume picker in full.
1245
1296
  const task = firstUser && typeof firstUser.content === 'string'
1246
- ? firstUser.content.slice(0, 120) : '(chat session)';
1297
+ ? redactSecrets(firstUser.content).slice(0, 120)
1298
+ : '(chat session)';
1247
1299
  // Both of these come from the live session rather than from React state: a save fired at the
1248
1300
  // end of a turn runs inside a closure that captured state as it was when the turn began, so
1249
1301
  // reading `stats` here recorded turn counts one behind and, after a resume, the wrong id.
@@ -1253,8 +1305,14 @@ function App({ config: initialConfig }) {
1253
1305
  // runs every turn would otherwise wipe a name the moment the agent replied.
1254
1306
  const existing = await loadSession(cfg.cwd, id).then(r => r.meta).catch(() => null);
1255
1307
  const livePlan = s?.plan?.steps.length ? { steps: s.plan.steps } : undefined;
1308
+ // Captured when saving, because a branch cannot be recovered from the file later — by then the
1309
+ // checkout has moved on.
1310
+ const branch = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: cfg.cwd, reject: false })
1311
+ .then(r => r.stdout.trim())
1312
+ .catch(() => '');
1256
1313
  return saveSession(cfg.cwd, id, {
1257
1314
  ...(livePlan ? { plan: livePlan } : {}),
1315
+ ...(branch && branch !== 'HEAD' ? { branch } : {}),
1258
1316
  ...(existing?.name ? { name: existing.name } : {}),
1259
1317
  ...(existing?.archived ? { archived: existing.archived } : {}),
1260
1318
  ...(existing?.forkedFrom ? { forkedFrom: existing.forkedFrom } : {}),
@@ -1950,13 +2008,21 @@ function App({ config: initialConfig }) {
1950
2008
  return;
1951
2009
  }
1952
2010
  if (pick === '') {
2011
+ // The title comes first because that is what tells two sessions apart; the id is a
2012
+ // timestamp and says only when. Everything under it is context: how long ago, which
2013
+ // branch, how much work. Masked on display as well as on write, since the files already
2014
+ // on disk were written before that existed and one of them holds a key.
1953
2015
  openPicker('resume', saved.slice(0, 20).map((x, i) => ({
1954
2016
  value: x.id,
1955
- label: x.name ?? x.id,
1956
- // Ordered by last write, so the first entry is the one you were last in — said out
1957
- // loud rather than left to be inferred from a timestamp buried in the id.
1958
- desc: `${i === 0 ? 'latest · ' : ''}${relativeAge(x.updatedAt)} ` +
1959
- `${x.turns} turns ${x.totalTokens.toLocaleString()} tok ${x.task.slice(0, 36)}`,
2017
+ label: sessionTitle(x),
2018
+ desc: [
2019
+ i === 0 ? 'latest' : relativeAge(x.updatedAt),
2020
+ ...(i === 0 ? [relativeAge(x.updatedAt)] : []),
2021
+ ...(x.branch ? [x.branch] : []),
2022
+ `${x.turns} turn${x.turns === 1 ? '' : 's'}`,
2023
+ ...(x.totalTokens > 0 ? [`${humanTokens(x.totalTokens)} tok`] : []),
2024
+ ...(x.sizeBytes ? [humanBytes(x.sizeBytes)] : []),
2025
+ ].join(' · '),
1960
2026
  })));
1961
2027
  return;
1962
2028
  }
@@ -2783,6 +2849,13 @@ function App({ config: initialConfig }) {
2783
2849
  setPasteCount(0);
2784
2850
  // Paths that really exist are shortened to their name for the transcript only. The model
2785
2851
  // still receives the full path, which it needs in order to open the file.
2852
+ // Said out loud rather than quietly sent. A key in a prompt reaches the provider, the
2853
+ // session file and the scrollback, and the only useful moment to mention it is now.
2854
+ if (containsSecret(task)) {
2855
+ addSystem('That looks like an API key or token. It has been sent as typed — treat it as exposed ' +
2856
+ 'and rotate it. Keys belong in /key or an environment variable, where they are not ' +
2857
+ 'written into the session log.');
2858
+ }
2786
2859
  const mentions = findMentions(task, pathKind);
2787
2860
  const forDisplay = pasteForDisplay(mentions.length ? shortenMentions(task, mentions) : task);
2788
2861
  addRow({ role: 'user', text: forDisplay });
@@ -3270,19 +3343,25 @@ function App({ config: initialConfig }) {
3270
3343
  // screen a list of short model names in a full-width box is mostly empty box.
3271
3344
  // The header is a title on the left and a count on the right, on one line. Estimating the
3272
3345
  // right-hand side at a constant made "Reference a file or directory" wrap onto two rows.
3273
- const countText = `${list.length} match${list.length === 1 ? '' : 'es'} · esc to close`;
3346
+ const countText = pickerCountText(pickIndex, list.length, picker.items.length);
3274
3347
  const headWidth = title.length + countText.length + 3;
3275
3348
  const rowWidth = Math.max(headWidth, ...shown.map(it => 38 + (it.current ? 10 : 0) + it.desc.length));
3276
3349
  const boxWidth = Math.max(40, Math.min(barWidth, rowWidth + 4));
3277
3350
  const width = boxWidth - 4;
3351
+ // Room for the longest label the list actually holds, rather than a constant. A session
3352
+ // title is what identifies the row, and 36 cells cut most of them in half.
3353
+ const labelWidth = pickerLabelWidth(shown.map(it => it.label), shown.map(it => it.desc), width);
3278
3354
  let lastGroup;
3279
- return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: CYAN, paddingX: 1, marginTop: 1, width: boxWidth, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: CYAN, bold: true, children: title }), _jsx(Text, { color: DIM, children: countText })] }), list.length === 0 && _jsxs(Text, { color: MUTED, children: ["No match for \"", pickQuery, "\""] }), shown.map((item, i) => {
3355
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: CYAN, paddingX: 1, marginTop: 1, width: boxWidth, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: CYAN, bold: true, children: title }), _jsx(Text, { color: DIM, children: countText })] }), _jsxs(Box, { children: [_jsxs(Text, { color: DIM, children: [G.search, " "] }), pickQuery === ''
3356
+ ? _jsx(Text, { color: DIM, children: "type to search" })
3357
+ : _jsx(Text, { color: INK, children: pickQuery }), _jsx(Text, { color: CYAN, children: pickQuery === '' ? '' : '▌' })] }), list.length === 0 && _jsxs(Text, { color: MUTED, children: ["Nothing matches \"", pickQuery, "\""] }), shown.map((item, i) => {
3280
3358
  const absolute = Math.max(0, start) + i;
3281
3359
  const selected = absolute === pickIndex;
3282
3360
  const header = item.group && item.group !== lastGroup ? item.group : null;
3283
3361
  lastGroup = item.group;
3284
3362
  const label = item.label.length > 34 ? item.label.slice(0, 31) + '...' : item.label;
3285
- return (_jsxs(Box, { flexDirection: "column", children: [header && _jsx(Text, { color: DIM, children: header }), _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${fitCells(label, 36)}${item.current ? '(current) ' : ''}${item.desc}`, width) })] }, item.value + absolute));
3363
+ return (_jsxs(Box, { flexDirection: "column", children: [header && _jsx(Text, { color: DIM, children: header }), _jsx(Text, { backgroundColor: selected ? CYAN : undefined, color: selected ? '#10222A' : INK, bold: selected, children: fitCells(`${selected ? G.caret + ' ' : ' '}${fitCells(label, labelWidth)}` +
3364
+ `${item.current ? '(current) ' : ''}${item.desc}`, width) })] }, item.value + absolute));
3286
3365
  }), list.length > PICKER_ROWS && (_jsxs(Text, { color: DIM, children: ["\u2191\u2193 navigate \u00B7 enter select \u00B7 showing ", Math.max(0, start) + 1, "\u2013", Math.max(0, start) + shown.length, " of ", list.length] }))] }));
3287
3366
  })(), 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 && (() => {
3288
3367
  let detail = '';