@agentwhy/cli 0.2.1 → 0.3.1

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.
Files changed (38) hide show
  1. package/dist/adapter/codex/contract/assumptions.js +4 -4
  2. package/dist/adapter/codex/contract/deliveries.js +12 -0
  3. package/dist/adapter/codex/contract/messages.js +8 -1
  4. package/dist/adapter/codex/events/cell-commands.js +191 -0
  5. package/dist/adapter/codex/events/delivered-output.js +30 -0
  6. package/dist/adapter/codex/events/rollout-scan.js +99 -7
  7. package/dist/core/access/command-line.js +4 -1
  8. package/dist/core/access/listing.js +10 -3
  9. package/dist/core/access/protected-access.js +94 -1
  10. package/dist/core/access/recorded-effect.js +22 -1
  11. package/dist/report/build-report.js +125 -19
  12. package/dist/report/render/report-page/file-story.js +33 -2
  13. package/dist/report/render/report-page/files-view.js +176 -55
  14. package/dist/report/render/report-page/files.js +51 -8
  15. package/dist/report/render/report-page/fix-wizard-script.js +17 -7
  16. package/dist/report/render/report-page/fix-wizard.js +4 -1
  17. package/dist/report/render/report-page/helpers.js +2 -1
  18. package/dist/report/render/report-page/report-page-renderer.js +51 -12
  19. package/dist/report/render/report-page/story-window.js +33 -12
  20. package/dist/report/render/report-page/to-do-view.js +42 -10
  21. package/dist/report/render/ui/app-sidebar.js +31 -2
  22. package/dist/report/render/ui/confirm-dialog.js +69 -4
  23. package/dist/report/render/ui/fold-line.js +2 -0
  24. package/dist/report/render/ui/popup.js +1 -1
  25. package/dist/report/render/ui/project-list.js +49 -10
  26. package/dist/report/render/ui/words/app-words.js +6 -0
  27. package/dist/report/render/ui/words/conversations-words.js +12 -0
  28. package/dist/report/render/ui/words/projects-words.js +9 -0
  29. package/dist/report/render/ui/words/report-words.js +114 -12
  30. package/dist/report/render/ui/words/settings-words.js +12 -9
  31. package/dist/report/start/conversations/calendar-view.js +1 -1
  32. package/dist/report/start/conversations/conversation-columns.js +6 -4
  33. package/dist/report/start/conversations/period-section.js +33 -17
  34. package/dist/report/start/conversations/periods.js +7 -1
  35. package/dist/report/start/render/session-status.js +14 -9
  36. package/dist/report/start/session-start.js +6 -3
  37. package/dist/report/start/settings/alerts-tab.js +2 -1
  38. package/package.json +1 -1
@@ -21,9 +21,9 @@ export const SETTINGS_WORDS = {
21
21
  'set.card.optional': 'Optional',
22
22
  'set.card.read.when': 'So you can change the keys the same day, before anyone uses them. You’ll see what to do.',
23
23
  'set.card.read.answer': 'Heads up: while working, I read the passwords for your live app. See what to do →',
24
- 'set.card.stopped.name': 'When my AI is stopped from opening a private file',
25
- 'set.card.stopped.when': 'So you know a block held: your AI tried to open a blocked file and was stopped in time. You’ll only see this for files you block.',
26
- 'set.card.stopped.answer': 'I tried to open your passwords file, but it’s blocked — nothing was read.',
24
+ 'set.card.stopped.name': 'When my AI opens a private file, or is stopped',
25
+ 'set.card.stopped.when': 'Everything but the keys: your AI opened a private file and nothing was read out of it, or a block stopped it in time. There will be more notes, and agentwhy writes them itself, in English.',
26
+ 'set.card.stopped.answer': 'CHECK: This conversation reached 1 protected file; no value found in its messages.',
27
27
  'set.card.fine.name': 'Once per conversation, even when all is fine',
28
28
  'set.card.fine.when': 'Peace of mind: a quick sign that we’re watching.',
29
29
  'set.card.fine.answer': 'All good — I didn’t open anything private.',
@@ -32,6 +32,7 @@ export const SETTINGS_WORDS = {
32
32
  'set.system.name': 'A notification in the corner of the screen',
33
33
  'set.system.when': 'It shows for a moment, whichever app your AI runs in. Without it, agentwhy speaks only in the conversation. On a Mac only.',
34
34
  'set.chat.ai': 'AI',
35
+ 'set.chat.line': '✦',
35
36
  'set.state.local': 'On — for just you',
36
37
  'set.state.shared': 'On — for everyone on this project',
37
38
  'set.state.on': 'On',
@@ -227,9 +228,9 @@ export const SETTINGS_WORDS = {
227
228
  'set.card.optional': 'Opcjonalne',
228
229
  'set.card.read.when': 'Żeby zmienić klucze tego samego dnia, zanim ktoś ich użyje. Zobaczysz, co zrobić.',
229
230
  'set.card.read.answer': 'Uwaga: w trakcie pracy przeczytałem hasła do twojej działającej aplikacji. Zobacz, co zrobić →',
230
- 'set.card.stopped.name': 'Gdy moje AI zostanie zatrzymane przed otwarciem prywatnego pliku',
231
- 'set.card.stopped.when': 'Wiesz wtedy, że blokada zadziałała: AI próbowało otworzyć zablokowany plik i zostało zatrzymane w porę. Zobaczysz to tylko przy plikach, które blokujesz.',
232
- 'set.card.stopped.answer': 'Próbowałem otworzyć twój plik z hasłami, ale jest zablokowany — nic nie zostało przeczytane.',
231
+ 'set.card.stopped.name': 'Gdy moje AI otworzy prywatny plik albo zostanie zatrzymane',
232
+ 'set.card.stopped.when': 'Wszystko poza kluczami: AI otworzyło prywatny plik i nic z niego nie przeczytało albo blokada zatrzymała je w porę. Wiadomości będzie więcej, a agentwhy pisze je sam, po angielsku.',
233
+ 'set.card.stopped.answer': 'CHECK: This conversation reached 1 protected file; no value found in its messages.',
233
234
  'set.card.fine.name': 'Raz na rozmowę, nawet gdy wszystko jest w porządku',
234
235
  'set.card.fine.when': 'Dla spokoju: krótki znak, że czuwamy.',
235
236
  'set.card.fine.answer': 'Wszystko w porządku — nie otwierałem niczego prywatnego.',
@@ -238,6 +239,7 @@ export const SETTINGS_WORDS = {
238
239
  'set.system.name': 'Powiadomienie w rogu ekranu',
239
240
  'set.system.when': 'Pojawia się na chwilę, w jakiejkolwiek aplikacji działa twoje AI. Bez niego agentwhy mówi tylko w rozmowie. Tylko na Macu.',
240
241
  'set.chat.ai': 'AI',
242
+ 'set.chat.line': '✦',
241
243
  'set.state.local': 'Włączone — tylko dla ciebie',
242
244
  'set.state.shared': 'Włączone — dla wszystkich w projekcie',
243
245
  'set.state.on': 'Włączone',
@@ -443,9 +445,9 @@ export const SETTINGS_WORDS = {
443
445
  'set.card.optional': 'Optional',
444
446
  'set.card.read.when': 'Damit du die Schlüssel noch am selben Tag ändern kannst, bevor jemand sie nutzt. Du siehst, was zu tun ist.',
445
447
  'set.card.read.answer': 'Achtung: Bei der Arbeit habe ich die Passwörter deiner Live-App gelesen. Sieh dir an, was zu tun ist →',
446
- 'set.card.stopped.name': 'Wenn meine KI davon abgehalten wird, eine private Datei zu öffnen',
447
- 'set.card.stopped.when': 'So weißt du, dass die Blockierung gehalten hat: Deine KI wollte eine blockierte Datei öffnen und wurde rechtzeitig gestoppt. Du siehst das nur bei Dateien, die du blockierst.',
448
- 'set.card.stopped.answer': 'Ich wollte deine Passwortdatei öffnen, aber sie ist blockiert — nichts wurde gelesen.',
448
+ 'set.card.stopped.name': 'Wenn meine KI eine private Datei öffnet oder gestoppt wird',
449
+ 'set.card.stopped.when': 'Alles außer den Schlüsseln: Deine KI hat eine private Datei geöffnet und nichts daraus gelesen, oder eine Blockierung hat sie rechtzeitig gestoppt. Es werden mehr Nachrichten, und agentwhy schreibt sie selbst, auf Englisch.',
450
+ 'set.card.stopped.answer': 'CHECK: This conversation reached 1 protected file; no value found in its messages.',
449
451
  'set.card.fine.name': 'Einmal pro Unterhaltung, auch wenn alles gut ist',
450
452
  'set.card.fine.when': 'Zur Beruhigung: ein kurzes Zeichen, dass wir aufpassen.',
451
453
  'set.card.fine.answer': 'Alles gut — ich habe nichts Privates geöffnet.',
@@ -454,6 +456,7 @@ export const SETTINGS_WORDS = {
454
456
  'set.system.name': 'Eine Benachrichtigung in der Bildschirmecke',
455
457
  'set.system.when': 'Sie erscheint kurz, egal in welcher App deine KI läuft. Ohne sie spricht agentwhy nur im Chat. Nur auf dem Mac.',
456
458
  'set.chat.ai': 'KI',
459
+ 'set.chat.line': '✦',
457
460
  'set.state.local': 'An — nur für dich',
458
461
  'set.state.shared': 'An — für alle in diesem Projekt',
459
462
  'set.state.on': 'An',
@@ -46,7 +46,7 @@ export function dayTile(day, label, parts = {}) {
46
46
  : day.toCheck > 0 ? inLanguages((t) => t('conv.day.toCheck', { n: day.toCheck }))
47
47
  : day.fixed > 0 ? inLanguages((t) => t('conv.day.fixed', { n: day.fixed }))
48
48
  // F17: a day whose unchecked conversations were all read, with gaps, says that, as their list's heading does.
49
- : day.unchecked > 0 ? inLanguages((t) => t((day.looks.unchecked ?? 0) === day.unchecked ? 'conv.day.partial' : 'conv.day.unchecked', { n: day.unchecked }))
49
+ : day.unchecked > 0 ? inLanguages((t) => t(day.partial === day.unchecked ? 'conv.day.partial' : 'conv.day.unchecked', { n: day.unchecked }))
50
50
  : inLanguages((t) => t('conv.day.good'));
51
51
  const count = tone === 'future' && ahead ? ' ' : tone === 'yet' ? '0' : empty ? '—' : String(day.conversations);
52
52
  const opens = parts.opens;
@@ -64,7 +64,7 @@ export function conversationRow(item, widen, include = false) {
64
64
  // X28: every row names the AI the conversation was with, in the badge that says where something came from (§9.2).
65
65
  '<span class="cw-ask">' + ask + '</span><span class="cw-ai">' + tag(e(PROVIDER_NAMES[entry.provider]), 'grey', 'badge') + '</span>' + tech,
66
66
  whatHappened(item, report),
67
- fixedCell(item.look),
67
+ fixedCell(item.look, item.partial),
68
68
  '<span class="cw-action">' + action + '</span>',
69
69
  ],
70
70
  // A read of files the person let it read is put before them too, with its bar, and leads to its report (F57a).
@@ -88,7 +88,7 @@ function whatHappened(item, report) {
88
88
  // A gap does not erase what the record established. Keep the uncertain look, and name its positive observation
89
89
  // separately; the title only says what the person asked, while this count comes from confirmed file evidence.
90
90
  const knownNames = item.look === 'unchecked' && item.entry.report.kind === 'generated'
91
- ? (item.entry.report.files ?? []).filter((file) => file.kind === 'named' || file.kind === 'result').length : 0;
91
+ ? (item.entry.report.files ?? []).filter((file) => file.kind === 'named' || file.kind === 'result' || file.kind === 'told').length : 0;
92
92
  const known = knownNames === 0 ? '' : '<span class="cw-known">' + inLanguages((t) => t('conv.did.partial.name', { n: knownNames })) + '</span>';
93
93
  const n = item.reached ?? 0;
94
94
  const all = report === undefined || n === 0 ? '' :
@@ -101,7 +101,7 @@ function whatHappened(item, report) {
101
101
  * the person chose Track for was never something to fix, and is not "nothing" either (the maintainer, 2026-09-25): it
102
102
  * says the mode, with Settings' eye and words, in the sand of its look (F57a).
103
103
  */
104
- function fixedCell(look) {
104
+ function fixedCell(look, partial) {
105
105
  const say = (key) => inLanguages((t) => t(key));
106
106
  if (look === 'fixed')
107
107
  return glyphIcon('✓', 'mint', say('conv.fixed.yes'));
@@ -109,7 +109,9 @@ function fixedCell(look) {
109
109
  return glyphIcon('✕', 'coral', say('conv.fixed.no'));
110
110
  if (look === 'allowed')
111
111
  return glyphIcon(MODE_SVG.tell, 'sand', say('set.mode.tell'));
112
- return '<span class="cw-fixed-plain">' + say(LOOKS[look].known ? 'conv.fixed.na' : 'conv.fixed.unknown') + '</span>';
112
+ // F17 is about this cell: a record with gaps is never told there is nothing to fix, whatever its badge says. The
113
+ // badge now names the file such a record did reach, and only this cell knows that the rest of it is missing.
114
+ return '<span class="cw-fixed-plain">' + say(LOOKS[look].known && !partial ? 'conv.fixed.na' : 'conv.fixed.unknown') + '</span>';
113
115
  }
114
116
  /** What the Check it button says as it works, in every language: one attribute cannot hold three. */
115
117
  function includeWords() {
@@ -15,6 +15,7 @@ import { LOOKS } from '../../render/ui/status-look.js';
15
15
  import { AIR_DATEPICKER_SCRIPT, AIR_DATEPICKER_STYLE } from '../render/air-datepicker-vendor.js';
16
16
  import { CALENDAR_VIEW_STYLE } from './calendar-view.js';
17
17
  import { CONVERSATION_COLUMNS_STYLE, CONVERSATION_ROW_SCRIPT, conversationRow, CONVERSATION_TABLE } from './conversation-columns.js';
18
+ import { unchecked } from './periods.js';
18
19
  import { PERIODS_SCRIPT } from './periods-script.js';
19
20
  /**
20
21
  * One week or one month (`for-people-who-build-with-ai.md` F8-F14): the answer first, then where to start, then the
@@ -33,8 +34,8 @@ export function periodSection(periods, at, index, spec) {
33
34
  const asks = fix.length > 0;
34
35
  // What this run did not read, or read with gaps, is neither to fix nor "nothing private": it is listed apart, with
35
36
  // the command that includes it, and never folded under the line that says there is nothing to fix (F17, O4).
36
- const unchecked = period.conversations.filter((item) => !LOOKS[item.look].known);
37
- const others = period.conversations.filter((item) => LOOKS[item.look].known && !LOOKS[item.look].attention);
37
+ const listedApart = period.conversations.filter(unchecked);
38
+ const others = period.conversations.filter((item) => !unchecked(item) && !LOOKS[item.look].attention);
38
39
  // F8, changed 2026-09-25: what was read and then fixed is still counted as read - it happened - and asks for nothing;
39
40
  // so is what the person let it read (F57a).
40
41
  const fixed = period.conversations.filter((item) => item.look === 'fixed').length;
@@ -44,9 +45,9 @@ export function periodSection(periods, at, index, spec) {
44
45
  const after = fixed > 0 && allowed > 0 ? 'fixedAllowed' : fixed > 0 ? 'fixed' : allowed > 0 ? 'allowed' : undefined;
45
46
  // F8, changed 2026-09-24: a period whose every conversation was checked and none read anything says so in mint - and,
46
47
  // since 2026-09-25, one whose every read was fixed.
47
- const clean = total > 0 && need.length === 0 && unchecked.length === 0;
48
+ const clean = total > 0 && need.length === 0 && listedApart.length === 0;
48
49
  // F17: conversations read with gaps alone - every Codex record has one - are named as that, not as ones never checked.
49
- const lead = uncheckedLead(unchecked, index.shared);
50
+ const lead = uncheckedLead(listedApart, index.shared);
50
51
  const partial = lead === 'conv.unchecked.partial';
51
52
  // Every period opens whole (F10, changed 2026-09-24); a day is chosen only by a tap.
52
53
  const open = '<section class="cw' + (at === 0 ? ' cw-current' : '') + '" id="' + spec.kind + '-' + period.ago + '" data-period-at="' + at + '"' +
@@ -62,14 +63,14 @@ export function periodSection(periods, at, index, spec) {
62
63
  eyebrow: spec.eyebrow,
63
64
  fact: inLanguages((t) => (total === 0 ? t(spec.zero) : t('conv.hero.fact', { n: total }))),
64
65
  action: inLanguages((t) => (need.length > 0 ? t('conv.hero.times', { n: read })
65
- : unchecked.length > 0 ? t(partial ? 'conv.hero.partial' : 'conv.hero.unchecked', { n: unchecked.length })
66
+ : listedApart.length > 0 ? t(partial ? 'conv.hero.partial' : 'conv.hero.unchecked', { n: listedApart.length })
66
67
  : read > 0 ? t('conv.hero.times', { n: read }) : t('conv.hero.none'))),
67
68
  // "The ones marked in coral" only where one is: a period with nothing to fix says so, and what is grey.
68
- lead: inLanguages((t) => t(asks ? 'conv.hero.lead' : unchecked.length > 0 ? 'conv.hero.lead.unchecked'
69
+ lead: inLanguages((t) => t(asks ? 'conv.hero.lead' : listedApart.length > 0 ? 'conv.hero.lead.unchecked'
69
70
  : after === undefined ? 'conv.hero.lead.clean' : 'conv.hero.lead.' + after)),
70
71
  ...(clean ? { calm: true } : {}),
71
72
  }) +
72
- (spec.clean === undefined ? '' : guideCard(guideOf(period, spec.clean, after, unchecked.length, index.widen, lead, spec.lists))) +
73
+ (spec.clean === undefined ? '' : guideCard(guideOf(period, spec.clean, after, listedApart.length, index.widen, lead, spec.lists))) +
73
74
  spec.view +
74
75
  (!spec.lists ? '' : dayChip(spec.whole)) +
75
76
  (!spec.lists || fix.length === 0 ? '' :
@@ -78,12 +79,7 @@ export function periodSection(periods, at, index, spec) {
78
79
  dataTable({ ...CONVERSATION_TABLE, rows: fix.map((item) => conversationRow(item, index.widen, !index.shared)) }) +
79
80
  '</section>') +
80
81
  (!spec.lists || info.length === 0 ? '' : forYourInfo(info, index)) +
81
- (!spec.lists || unchecked.length === 0 ? '' :
82
- '<section class="cw-need cw-unchecked" data-need><div class="cw-need-head"><span class="cw-need-dot" aria-hidden="true"></span>' +
83
- '<h2 class="cw-h2">' + inLanguages((t) => t(partial ? 'look.unchecked' : 'conv.unchecked')) + '</h2><span class="cw-need-count" data-need-count>' + unchecked.length + '</span></div>' +
84
- '<p class="cw-unchecked-lead">' + inLanguages((t) => t(lead, { command: '<code>' + e(index.widen) + '</code>' })) + '</p>' +
85
- dataTable({ ...CONVERSATION_TABLE, rows: unchecked.map((item) => conversationRow(item, index.widen, !index.shared)) }) +
86
- '</section>') +
82
+ (!spec.lists || listedApart.length === 0 ? '' : notFullyChecked(listedApart, partial, lead, index)) +
87
83
  (!spec.lists || others.length === 0 ? '' : theRest(others, need.length === 0, index)) +
88
84
  '</section>';
89
85
  }
@@ -101,6 +97,27 @@ function forYourInfo(info, index) {
101
97
  body: dataTable({ ...CONVERSATION_TABLE, rows: info.map((item) => conversationRow(item, index.widen, !index.shared)) }),
102
98
  }) + '</section>';
103
99
  }
100
+ /**
101
+ * "Couldn't check fully (n)" (the maintainer, 2026-10-05): what this run could not read, or read with gaps, in a fold
102
+ * of its own - a solid grey line with the look's ?, shut, and opened with **Show**. Every Codex record has a gap
103
+ * (`2026-09-27-what-codex-wrote.md` X23), so on a project worked in with Codex this list holds the whole week, asks
104
+ * for nothing, and was the longest thing on the page; the maintainer asked for it shut on arrival (2026-10-05), not
105
+ * only shuttable. Shut it still says all of it the page owes: the heading, how many, and in brief why - and the
106
+ * period's card above says the number too. The sentence of the reason and the command that would include them go
107
+ * inside, with the rows; the list is still apart from what is to fix and is never a group of the rest's table
108
+ * (F17, F13, O4). The script narrows it to the day chosen as it does the other lists (`data-need`), and the count it
109
+ * rewrites is the one in the line, which is in view whether the fold is open or not.
110
+ */
111
+ function notFullyChecked(unchecked, partial, lead, index) {
112
+ return '<section class="cw-need cw-unchecked" data-need>' + foldLine({
113
+ summary: '<strong>' + inLanguages((t) => t(partial ? 'look.unchecked' : 'conv.unchecked')) +
114
+ ' (<span data-need-count>' + unchecked.length + '</span>)</strong> <span class="fold-rest">' +
115
+ inLanguages((t) => t(lead + '.rest')) + '</span>',
116
+ mark: { glyph: LOOKS.unchecked.glyph, tone: 'grey' },
117
+ body: '<p class="cw-unchecked-lead">' + inLanguages((t) => t(lead, { command: '<code>' + e(index.widen) + '</code>' })) + '</p>' +
118
+ dataTable({ ...CONVERSATION_TABLE, rows: unchecked.map((item) => conversationRow(item, index.widen, !index.shared)) }),
119
+ }) + '</section>';
120
+ }
104
121
  /** The looks of the rest, in the order of F16's ladder: the groups of F13 and F14, and the order they are listed in. */
105
122
  const REST = ['fixed', 'name', 'stopped', 'none'];
106
123
  /** A group's table rows past this many wait behind "Show all" while the whole period is shown (F14). */
@@ -207,11 +224,11 @@ function guideOf(period, clean, after, unchecked, widen, lead, listed) {
207
224
  function uncheckedLead(unchecked, shared) {
208
225
  // F17: a record with gaps was read - what it cannot show is said, never that the run did not read it. Every Codex record
209
226
  // is one (`2026-09-27-what-codex-wrote.md` X23), so a list of them alone is common.
210
- if (unchecked.every((item) => item.look === 'unchecked'))
227
+ if (unchecked.every((item) => item.partial))
211
228
  return 'conv.unchecked.partial';
212
229
  if (!shared && unchecked.every((item) => item.look === 'outside'))
213
230
  return 'conv.unchecked.check';
214
- return unchecked.some((item) => item.look === 'unchecked') ? 'conv.unchecked.mixed' : 'conv.unchecked.lead';
231
+ return unchecked.some((item) => item.partial) ? 'conv.unchecked.mixed' : 'conv.unchecked.lead';
215
232
  }
216
233
  /**
217
234
  * A count the script may change - the day chosen narrows the rest of the period to that day - written with every
@@ -237,8 +254,7 @@ export const PERIOD_SECTION_STYLE = String.raw `
237
254
  .cw-need-head{display:flex;align-items:baseline;gap:10px;margin-bottom:12px}
238
255
  .cw-need-dot{width:10px;height:10px;border-radius:50%;background:var(--coral);align-self:center}
239
256
  .cw-need-count{font-size:15px;font-weight:650;color:var(--coral-text)}
240
- .cw-unchecked .cw-need-dot{background:var(--text-3)}.cw-unchecked .cw-need-count{color:var(--text-2)}
241
- .cw-unchecked-lead{margin:-4px 0 12px;font-size:14px;line-height:1.5;color:var(--text-2)}
257
+ .cw-unchecked-lead{margin:0 0 12px;font-size:14px;line-height:1.5;color:var(--text-2)}
242
258
  .cw-unchecked-lead code,.guide code{font-family:var(--mono);font-size:13px;color:var(--text);background:var(--white-07);border-radius:6px;padding:1px 6px}
243
259
  .cw-tools{display:none;gap:10px;flex-wrap:wrap;align-items:center;margin-bottom:16px}
244
260
  .js .cw-tools{display:flex}
@@ -41,8 +41,9 @@ export function period(ago, first, last, today, conversations) {
41
41
  // A read of files the person let the AI read is to check too: it is put before them, as a read is (F57a).
42
42
  toCheck: those.filter((item) => LOOKS[item.look].attention).length,
43
43
  fixed: those.filter((item) => item.look === 'fixed').length,
44
- unchecked: those.filter((item) => !LOOKS[item.look].known).length,
44
+ unchecked: those.filter(unchecked).length,
45
45
  looks,
46
+ partial: those.filter((item) => item.partial).length,
46
47
  future: number > today,
47
48
  today: number === today,
48
49
  };
@@ -108,5 +109,10 @@ function conversation(entry, local, today, done) {
108
109
  files: files.map((file) => file.path),
109
110
  read: files.filter((file) => file.kind === 'seen').length,
110
111
  ...(entry.report.kind === 'generated' && entry.report.reached !== undefined ? { reached: entry.report.reached } : {}),
112
+ partial: look === 'unchecked',
111
113
  };
112
114
  }
115
+ /** Not checked, or read with gaps: what is listed apart, and never counted as nothing to fix (F17, O4). */
116
+ export function unchecked(item) {
117
+ return !LOOKS[item.look].known || item.partial;
118
+ }
@@ -14,19 +14,24 @@ export function statusOf(entry) {
14
14
  const { tally, incomplete } = entry.report;
15
15
  if (tally.contentsSeen > 0)
16
16
  return 'seen';
17
- // F17, `2026-09-27-what-codex-wrote.md` X10: a record with gaps cannot say nothing needs doing, so short of a read it is
18
- // not known - its name seen stays in its report, which leads with the gap too. Found on a Codex record, which always has
19
- // one: a conversation that listed `.env` was folded under "nothing private to fix". `codex-blocks-too` CK12, decided by
20
- // the maintainer on 2026-09-30: an attempt agentwhy stopped is a fact in its own words, so where one was stopped and no
21
- // file was reached nor attempted to no known end, the row says Stopped; the gap stays in the report.
22
- if (incomplete)
23
- return tally.refusedAttempts > 0 && tally.filesReached === 0 && tally.unknownAttempts === 0 ? 'blocked' : 'unknown';
17
+ // `codex-blocks-too` CK12, amended 2026-10-05 by the maintainer: an attempt agentwhy stopped is a fact in its own
18
+ // words, and outranks a name seen - the agent found the file, was stopped from opening it, and the rule held. Never
19
+ // where an attempt has no known end: then what happened to it is not known.
20
+ if (tally.refusedAttempts > 0 && tally.unknownAttempts === 0)
21
+ return 'blocked';
22
+ // F17, X10: an attempt with no known end, on a record with gaps, leaves what the agent saw not known, whatever names
23
+ // it saw beside it - and so does a file's text a process printed that no agent is shown to have received (X14).
24
+ if (incomplete && (tally.unknownAttempts > 0 || (tally.printedUnseen ?? 0) > 0))
25
+ return 'unknown';
26
+ // A gap does not unmake what the record did establish: a name seen is said, as the report's headline says it
27
+ // (amended 2026-10-05 - every Codex record has a gap, and each reach of theirs short of a read was hidden behind it).
24
28
  if (tally.filesReached > 0 && tally.onlyThroughResult === tally.filesReached)
25
29
  return 'result';
26
30
  if (tally.filesReached > 0)
27
31
  return 'named';
28
- if (tally.refusedAttempts > 0)
29
- return 'blocked';
32
+ // F17: short of a reach, a record with gaps cannot say nothing needs doing.
33
+ if (incomplete)
34
+ return 'unknown';
30
35
  return 'clean';
31
36
  }
32
37
  /**
@@ -910,7 +910,7 @@ function entryOf(name, title, session, report) {
910
910
  * call named it, it only appeared in a result, or its outcome is not recorded. The same four the row's badge
911
911
  * climbs, so a row and the files under it never disagree.
912
912
  */
913
- function filesOf(actions) {
913
+ export function filesOf(actions) {
914
914
  const strongest = new Map();
915
915
  const add = (path, kind) => {
916
916
  if (!strongest.has(path))
@@ -922,10 +922,13 @@ function filesOf(actions) {
922
922
  add(route.path, 'named');
923
923
  for (const path of actions.onlyInResults)
924
924
  add(path, 'result');
925
- for (const path of actions.unknown)
926
- add(path, 'unknown');
925
+ // F57a: a file the record shows was read, and that the person chose Track for, is told whatever else was tried on it.
926
+ // `actionsOf` keeps a told path off every other list but `unknown`, so read after it, a search that matched nothing
927
+ // made a tracked file read through `head` an unknown one, and its row asked for a fix (found 2026-10-05).
927
928
  for (const path of actions.told ?? [])
928
929
  add(path, 'told');
930
+ for (const path of actions.unknown)
931
+ add(path, 'unknown');
929
932
  return [...strongest.values()];
930
933
  }
931
934
  /** A policy as one comparable value: what it protects, how, and its exceptions (F57). */
@@ -40,6 +40,7 @@ export function alertsTab(view) {
40
40
  row({
41
41
  tone: 'mint',
42
42
  key: 'stopped',
43
+ says: 'line',
43
44
  locked: view.stopped.locked,
44
45
  state: optional(view.stopped.on, view.stopped.locked),
45
46
  control: view.stopped.locked ? lockedSwitch('stopped', turnOn) : toggleSwitch({
@@ -100,7 +101,7 @@ function row(spec) {
100
101
  ? tag(inLanguages((t) => t('set.card.read.badge')), 'mint', 'badge')
101
102
  : tag(inLanguages((t) => t('set.card.optional')), 'grey', 'badge')) + '</div>' +
102
103
  '<p class="set-msg-why">' + word('when') + '</p>' +
103
- chatExample({ tone: spec.tone, ai: inLanguages((t) => t('set.chat.ai')), answer: word('answer') }) +
104
+ chatExample({ tone: spec.tone, ai: inLanguages((t) => t(spec.says === 'line' ? 'set.chat.line' : 'set.chat.ai')), answer: word('answer') }) +
104
105
  (spec.state === undefined ? '' : stateLine(spec.state)) +
105
106
  '<p class="set-say" data-set-say hidden></p>' +
106
107
  '</div>' + spec.control + '</article>';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentwhy/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Reconstructs from transcripts why an AI agent reached for protected data",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",