@agentwhy/cli 0.0.0-dev → 0.2.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.
Files changed (273) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +57 -5
  3. package/dist/adapter/claude-code/contract/assumptions.js +16 -4
  4. package/dist/adapter/claude-code/contract/denials.js +16 -1
  5. package/dist/adapter/claude-code/contract/desktop-sessions.js +28 -0
  6. package/dist/adapter/claude-code/contract/entry-points.js +6 -1
  7. package/dist/adapter/claude-code/contract/fields.js +2 -0
  8. package/dist/adapter/claude-code/contract/hooks.js +8 -0
  9. package/dist/adapter/claude-code/contract/identifiers.js +2 -0
  10. package/dist/adapter/claude-code/contract/layout.js +2 -0
  11. package/dist/adapter/claude-code/contract/line-types.js +2 -0
  12. package/dist/adapter/claude-code/contract/projects.js +2 -0
  13. package/dist/adapter/claude-code/contract/recognition.js +2 -0
  14. package/dist/adapter/claude-code/contract/settings.js +2 -0
  15. package/dist/adapter/claude-code/contract/task-notifications.js +2 -0
  16. package/dist/adapter/claude-code/contract/version.js +14 -1
  17. package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +12 -12
  18. package/dist/adapter/claude-code/discovery/claude-code-recognition.js +2 -0
  19. package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +28 -7
  20. package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +2 -0
  21. package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +30 -6
  22. package/dist/adapter/claude-code/discovery/claude-desktop-titles.js +102 -0
  23. package/dist/adapter/claude-code/discovery/session-entries.js +2 -0
  24. package/dist/adapter/claude-code/events/delegation-index.js +2 -0
  25. package/dist/adapter/claude-code/events/transcript-scan.js +6 -5
  26. package/dist/adapter/claude-code/hooks/hook-output.js +2 -0
  27. package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +2 -0
  28. package/dist/adapter/claude-code/hooks/stop-input.js +2 -0
  29. package/dist/adapter/claude-code/hooks/subagent-stop-input.js +4 -0
  30. package/dist/adapter/claude-code/probe/claude-code-probe.js +2 -0
  31. package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +2 -0
  32. package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +2 -0
  33. package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +2 -0
  34. package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +2 -0
  35. package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +2 -0
  36. package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +2 -0
  37. package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +2 -0
  38. package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +2 -0
  39. package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +2 -0
  40. package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +2 -0
  41. package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +2 -0
  42. package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +2 -0
  43. package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +2 -0
  44. package/dist/adapter/claude-code/probe/meta-tally.js +2 -0
  45. package/dist/adapter/claude-code/probe/transcript-line.js +2 -0
  46. package/dist/adapter/claude-code/probe/transcript-tally.js +2 -0
  47. package/dist/adapter/claude-code/settings/deny-entries.js +2 -0
  48. package/dist/adapter/claude-code/settings/hook-entries.js +9 -0
  49. package/dist/adapter/claude-code/settings/refuse-project.js +99 -0
  50. package/dist/adapter/codex/contract/actions.js +2 -0
  51. package/dist/adapter/codex/contract/assumptions.js +2 -0
  52. package/dist/adapter/codex/contract/chat-folders.js +19 -0
  53. package/dist/adapter/codex/contract/delegations.js +2 -0
  54. package/dist/adapter/codex/contract/deliveries.js +2 -0
  55. package/dist/adapter/codex/contract/envelope.js +2 -0
  56. package/dist/adapter/codex/contract/hook-refusals.js +2 -0
  57. package/dist/adapter/codex/contract/hooks.js +51 -1
  58. package/dist/adapter/codex/contract/messages.js +2 -0
  59. package/dist/adapter/codex/contract/permissions.js +2 -0
  60. package/dist/adapter/codex/contract/reviews.js +2 -0
  61. package/dist/adapter/codex/contract/session.js +29 -1
  62. package/dist/adapter/codex/contract/turns.js +2 -0
  63. package/dist/adapter/codex/contract/version.js +2 -0
  64. package/dist/adapter/codex/discovery/codex-on-this-computer.js +13 -0
  65. package/dist/adapter/codex/discovery/codex-project-catalogue.js +2 -12
  66. package/dist/adapter/codex/discovery/codex-session-catalogue.js +2 -1
  67. package/dist/adapter/codex/discovery/codex-session-discovery.js +2 -0
  68. package/dist/adapter/codex/discovery/session-owners.js +2 -0
  69. package/dist/adapter/codex/events/action-items.js +2 -0
  70. package/dist/adapter/codex/events/codex-session-source.js +2 -0
  71. package/dist/adapter/codex/events/hook-refusals.js +2 -0
  72. package/dist/adapter/codex/events/rollout-scan.js +1 -1
  73. package/dist/adapter/codex/hooks/codex-conversation.js +54 -0
  74. package/dist/adapter/codex/hooks/pre-tool-use-input.js +2 -0
  75. package/dist/adapter/codex/hooks/reader.js +38 -0
  76. package/dist/adapter/codex/hooks/stop-input.js +32 -0
  77. package/dist/adapter/codex/hooks/stop-refusals.js +3 -6
  78. package/dist/adapter/codex/probe/codex-probe.js +2 -0
  79. package/dist/adapter/codex/settings/codex-check.js +38 -0
  80. package/dist/adapter/codex/settings/codex-hooks.js +168 -1
  81. package/dist/adapter/codex/settings/hook-approval.js +374 -0
  82. package/dist/cli/commands/check-cli-command.js +2 -0
  83. package/dist/cli/commands/codex-stop-cli-command.js +15 -12
  84. package/dist/cli/commands/doctor-arguments.js +2 -0
  85. package/dist/cli/commands/doctor-usage.js +2 -0
  86. package/dist/cli/commands/init-cli-command.js +10 -5
  87. package/dist/cli/commands/notify-cli-command.js +3 -1
  88. package/dist/cli/commands/refuse-cli-command.js +9 -4
  89. package/dist/cli/commands/report-arguments.js +2 -0
  90. package/dist/cli/commands/report-usage.js +2 -0
  91. package/dist/cli/commands/sessions-cli-command.js +9 -1
  92. package/dist/cli/commands/start-cli-command.js +29 -2
  93. package/dist/cli/commands/watch-cli-command.js +4 -2
  94. package/dist/cli/exit-codes.js +2 -0
  95. package/dist/cli.js +10 -0
  96. package/dist/composition-root.js +80 -15
  97. package/dist/core/access/command-line.js +48 -2
  98. package/dist/core/access/listing.js +2 -0
  99. package/dist/core/access/path-tokens.js +2 -0
  100. package/dist/core/access/protected-access.js +36 -3
  101. package/dist/core/access/protected-values.js +17 -0
  102. package/dist/core/access/search-output.js +2 -0
  103. package/dist/core/access/search-reach.js +2 -0
  104. package/dist/core/combined-project-catalogue.js +7 -1
  105. package/dist/core/correlation/correlate.js +13 -1
  106. package/dist/core/detector/prompt-signals.js +2 -0
  107. package/dist/core/guarded-places.js +42 -0
  108. package/dist/core/nearest-project.js +5 -2
  109. package/dist/core/policy/glob.js +2 -0
  110. package/dist/core/policy/parse-policy.js +2 -0
  111. package/dist/core/policy/policy.js +14 -2
  112. package/dist/core/policy/resolve-policy.js +2 -0
  113. package/dist/core/redaction/entropy.js +2 -0
  114. package/dist/core/redaction/redactor.js +2 -0
  115. package/dist/core/redaction/value-shapes.js +2 -0
  116. package/dist/core/redaction/value-trace.js +2 -0
  117. package/dist/core/session-catalogue.js +7 -0
  118. package/dist/core/session-filter.js +2 -0
  119. package/dist/infrastructure/clack-asker.js +2 -0
  120. package/dist/infrastructure/clack-chooser.js +2 -0
  121. package/dist/infrastructure/clack-multi-chooser.js +2 -0
  122. package/dist/infrastructure/file-alert-store.js +10 -3
  123. package/dist/infrastructure/file-checked-store.js +2 -0
  124. package/dist/infrastructure/file-mark-store.js +2 -0
  125. package/dist/infrastructure/file-onboarding-store.js +2 -0
  126. package/dist/infrastructure/file-page-servers.js +51 -0
  127. package/dist/infrastructure/folder-window.js +2 -0
  128. package/dist/infrastructure/node-agentwhy-invocation.js +2 -0
  129. package/dist/infrastructure/node-background-run.js +45 -0
  130. package/dist/infrastructure/node-browser.js +2 -0
  131. package/dist/infrastructure/node-file-system.js +42 -1
  132. package/dist/infrastructure/node-local-server.js +2 -0
  133. package/dist/infrastructure/node-page-probe.js +40 -0
  134. package/dist/infrastructure/os-notifier.js +2 -0
  135. package/dist/infrastructure/real-directory.js +2 -0
  136. package/dist/infrastructure/shell-command-trial.js +60 -0
  137. package/dist/infrastructure/terminal-banner.js +2 -0
  138. package/dist/ports/command-trial.js +1 -0
  139. package/dist/ports/directory-reader.js +14 -1
  140. package/dist/ports/file-replacer.js +1 -0
  141. package/dist/ports/mark-store.js +2 -0
  142. package/dist/ports/page-server.js +1 -0
  143. package/dist/refuse/command-reach.js +2 -0
  144. package/dist/refuse/command-refusal.js +19 -30
  145. package/dist/refuse/render/refusal-words.js +3 -1
  146. package/dist/report/build-report.js +59 -11
  147. package/dist/report/check/actions-digest.js +3 -0
  148. package/dist/report/check/check-lines.js +2 -1
  149. package/dist/report/check/render/text-digest-renderer.js +31 -9
  150. package/dist/report/check/session-actions.js +4 -6
  151. package/dist/report/check/session-check.js +8 -0
  152. package/dist/report/choose-policy.js +7 -1
  153. package/dist/report/nothing-saved-here.js +16 -0
  154. package/dist/report/private-files/tell-lists.js +2 -0
  155. package/dist/report/project-rules.js +2 -0
  156. package/dist/report/refusals.js +15 -0
  157. package/dist/report/render/home-relative.js +2 -0
  158. package/dist/report/render/html-head.js +2 -0
  159. package/dist/report/render/html-report-styles.js +2 -0
  160. package/dist/report/render/html-sidebar.js +2 -0
  161. package/dist/report/render/path-tail.js +2 -0
  162. package/dist/report/render/report-copy.js +2 -0
  163. package/dist/report/render/report-page/advanced-view.js +2 -0
  164. package/dist/report/render/report-page/files-view.js +2 -0
  165. package/dist/report/render/report-page/files.js +2 -0
  166. package/dist/report/render/report-page/fix-wizard-script.js +2 -0
  167. package/dist/report/render/report-page/helpers-view.js +2 -0
  168. package/dist/report/render/report-page/item-names.js +2 -0
  169. package/dist/report/render/report-page/protect-patterns.js +2 -0
  170. package/dist/report/render/report-page/report-views.js +2 -0
  171. package/dist/report/render/report-page/to-do-view.js +3 -2
  172. package/dist/report/render/text-report-renderer.js +2 -0
  173. package/dist/report/render/ui/add-file-popup.js +2 -0
  174. package/dist/report/render/ui/app-sidebar.js +2 -0
  175. package/dist/report/render/ui/ask-panel.js +2 -0
  176. package/dist/report/render/ui/avatar.js +2 -0
  177. package/dist/report/render/ui/backdrop.js +2 -0
  178. package/dist/report/render/ui/brand-mark.js +2 -0
  179. package/dist/report/render/ui/checklist.js +2 -0
  180. package/dist/report/render/ui/confirm-dialog.js +2 -0
  181. package/dist/report/render/ui/drawer.js +2 -0
  182. package/dist/report/render/ui/file-chip.js +2 -0
  183. package/dist/report/render/ui/fold-line.js +2 -0
  184. package/dist/report/render/ui/guide-card.js +2 -0
  185. package/dist/report/render/ui/live-script.js +2 -0
  186. package/dist/report/render/ui/page-shell.js +2 -0
  187. package/dist/report/render/ui/popup.js +2 -0
  188. package/dist/report/render/ui/progress.js +2 -0
  189. package/dist/report/render/ui/project-list.js +9 -9
  190. package/dist/report/render/ui/stats.js +2 -0
  191. package/dist/report/render/ui/status-icon.js +2 -0
  192. package/dist/report/render/ui/status-look.js +2 -0
  193. package/dist/report/render/ui/task-list.js +2 -0
  194. package/dist/report/render/ui/tokens.js +2 -0
  195. package/dist/report/render/ui/update-notice.js +2 -0
  196. package/dist/report/render/ui/words/app-words.js +3 -3
  197. package/dist/report/render/ui/words/onboarding-words.js +9 -9
  198. package/dist/report/render/ui/words/projects-words.js +3 -3
  199. package/dist/report/render/ui/words/report-words.js +11 -11
  200. package/dist/report/render/ui/words/settings-words.js +30 -6
  201. package/dist/report/rule-names.js +2 -0
  202. package/dist/report/session-report.js +2 -0
  203. package/dist/report/start/conversations/calendar-view.js +2 -0
  204. package/dist/report/start/conversations/conversation-columns.js +2 -0
  205. package/dist/report/start/conversations/empty-week.js +2 -0
  206. package/dist/report/start/conversations/files-window.js +2 -0
  207. package/dist/report/start/conversations/period-section.js +2 -0
  208. package/dist/report/start/conversations/periods-script.js +2 -0
  209. package/dist/report/start/conversations/periods.js +2 -0
  210. package/dist/report/start/conversations/week-view.js +2 -0
  211. package/dist/report/start/conversations/weeks.js +2 -0
  212. package/dist/report/start/detached-start.js +75 -0
  213. package/dist/report/start/month/month-view.js +2 -0
  214. package/dist/report/start/onboarding/done.js +9 -3
  215. package/dist/report/start/onboarding/finish-onboarding.js +6 -4
  216. package/dist/report/start/onboarding/intro.js +2 -0
  217. package/dist/report/start/onboarding/onboarding-renderer.js +4 -4
  218. package/dist/report/start/onboarding/onboarding-script.js +2 -0
  219. package/dist/report/start/onboarding/onboarding-view.js +1 -1
  220. package/dist/report/start/onboarding/project-step.js +1 -1
  221. package/dist/report/start/onboarding/steps.js +2 -0
  222. package/dist/report/start/onboarding/welcome.js +2 -0
  223. package/dist/report/start/page-notice.js +2 -0
  224. package/dist/report/start/projects/index-projects.js +5 -2
  225. package/dist/report/start/projects/moved-page.js +2 -0
  226. package/dist/report/start/projects/project-switch.js +2 -0
  227. package/dist/report/start/projects/projects-window.js +2 -0
  228. package/dist/report/start/projects/temporary-space.js +2 -0
  229. package/dist/report/start/render/start-words.js +15 -1
  230. package/dist/report/start/repository.js +2 -0
  231. package/dist/report/start/serve/index-handler.js +4 -1
  232. package/dist/report/start/serve/settings-setup.js +10 -0
  233. package/dist/report/start/session-start.js +69 -29
  234. package/dist/report/start/settings/alerts-tab.js +4 -1
  235. package/dist/report/start/settings/files-tab.js +18 -12
  236. package/dist/report/start/settings/general-tab.js +33 -1
  237. package/dist/report/start/settings/settings-renderer.js +2 -0
  238. package/dist/report/start/settings/settings-script.js +8 -1
  239. package/dist/report/start/settings/settings-view.js +29 -2
  240. package/dist/report/start/settings/settings-windows.js +16 -5
  241. package/dist/report/start/settings-files.js +2 -0
  242. package/dist/report/start/to-fix/done-list.js +2 -0
  243. package/dist/report/start/to-fix/file-window.js +2 -0
  244. package/dist/report/start/to-fix/to-fix-list.js +2 -0
  245. package/dist/report/start/to-fix/to-fix-renderer.js +2 -0
  246. package/dist/report/start/to-fix/to-fix-script.js +2 -0
  247. package/dist/report/watch/agent-alert.js +3 -0
  248. package/dist/report/watch/codex-turn-format.js +69 -0
  249. package/dist/report/watch/notice-choices.js +2 -0
  250. package/dist/report/watch/notice-settings.js +2 -0
  251. package/dist/report/watch/preferences.js +2 -0
  252. package/dist/report/watch/render/codex-request-words.js +106 -0
  253. package/dist/report/watch/render/instruction-words.js +114 -12
  254. package/dist/report/watch/render/notice-words.js +74 -4
  255. package/dist/report/watch/subagent-watch.js +270 -45
  256. package/dist/setup/behind.js +2 -0
  257. package/dist/setup/codex-mirror.js +311 -104
  258. package/dist/setup/not-a-project.js +2 -0
  259. package/dist/setup/project-setup.js +2 -0
  260. package/dist/shared/compare.js +2 -0
  261. package/dist/shared/content-version.js +2 -0
  262. package/dist/shared/counter.js +2 -0
  263. package/dist/shared/file-stamp.js +2 -0
  264. package/dist/shared/label.js +2 -0
  265. package/dist/shared/package-name.js +2 -0
  266. package/dist/shared/plain-invocation.js +2 -0
  267. package/dist/shared/printable.js +2 -0
  268. package/dist/shared/sentence.js +2 -0
  269. package/dist/shared/terminal-logo.js +2 -0
  270. package/dist/shared/text-table.js +2 -0
  271. package/dist/shared/wrap.js +2 -0
  272. package/package.json +2 -1
  273. package/dist/refuse/render/codex-stop-words.js +0 -21
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages, labelAttributes } from '../../render/report-copy.js';
3
5
  import { pill, trashButton } from '../../render/ui/button.js';
@@ -91,13 +93,17 @@ function ruleRow(row, at, view) {
91
93
  const action = row.remove !== undefined && canWrite ? remove(removeWindowId(at))
92
94
  : row.untell !== undefined ? remove(untellWindowId(at))
93
95
  : '<span class="set-none" aria-hidden="true">—</span>';
94
- const icon = !row.watched ? '' : row.kept !== undefined ? OPEN_LOCK_SVG : MODE_SVG[row.mode];
95
- const iconClass = !row.watched ? '' : ' set-icon-' + (row.kept !== undefined ? 'gap' : row.mode);
96
+ const covered = row.covered;
97
+ const gapped = row.kept !== undefined || covered !== undefined;
98
+ const icon = !row.watched ? '' : gapped ? OPEN_LOCK_SVG : MODE_SVG[row.mode];
99
+ const iconClass = !row.watched ? '' : ' set-icon-' + (gapped ? 'gap' : row.mode);
96
100
  return '<li class="set-rule' + (row.watched ? '' : ' set-rule-off') + (watchable ? ' set-rule-watchable' : '') + '"' + (watchable ? ' data-set-row' : '') + '>' +
97
101
  '<span class="set-icon' + iconClass + '" aria-hidden="true">' + icon + '</span>' +
98
102
  '<span class="set-rule-text"><span class="set-rule-name">' + label + '</span>' +
99
103
  '<span class="chip set-rule-chip" title="' + e(pattern) + '">' + e(patternShort(pattern)) + '</span>' +
100
- (row.kept === undefined ? '' : '<span class="set-rule-gap">' + inLanguages((t) => t('set.rule.gap.' + row.kept)) + '</span>') + '</span>' +
104
+ (row.kept === undefined ? '' : '<span class="set-rule-gap">' + inLanguages((t) => t('set.rule.gap.' + row.kept)) + '</span>') +
105
+ // SW19: tracked here, and still refused by Claude Code's own rule for a wider pattern - said on the row, like a half block.
106
+ (covered === undefined ? '' : '<span class="set-rule-gap">' + inLanguages((t) => t('set.rule.covered', { pattern: e(patternShort(covered)) })) + '</span>') + '</span>' +
101
107
  '<span class="set-rule-mode">' + mode + '</span>' +
102
108
  '<span class="set-rule-src">' + tag(inLanguages((t) => t('set.src.short.' + row.source)), SOURCE_TONE[row.source]) + '</span>' +
103
109
  '<span class="set-rule-act">' + action + '</span>' +
@@ -149,21 +155,21 @@ function finishCard(view) {
149
155
  });
150
156
  }
151
157
  /**
152
- * `codex-blocks-too` CK8: in a project that uses Codex, whether the files blocked here are kept from Codex too. Where
153
- * agentwhy's hook is written, a grey line says it holds once approved in Codex, which nothing here can see (CKB9). Where
154
- * it is not and Claude Code's block runs, a coral line with the one write that adds it; where Claude Code's does not
155
- * run either, Finish blocking above writes both, and this says nothing.
158
+ * `codex-approves-its-own-hook` AO3, AO10: in a project that uses Codex, whether the files blocked here are kept from
159
+ * Codex too. Verified (`on`), a grey line states the fact: from the first message, in the terminal and in VS Code,
160
+ * nothing to confirm in Codex. Written but not verified (`stale`), a coral line says Codex may still ask, with the one
161
+ * write that makes it automatic. Not written (`off`) and Claude Code's block running, a coral line with the same
162
+ * write; where Claude Code's block does not run either, Finish blocking above writes both, and this says nothing.
156
163
  */
157
164
  function codexCard(view) {
158
- if (!view.known || view.codex === undefined)
165
+ if (!view.known || view.codex === undefined || !view.stop.on)
159
166
  return '';
160
167
  if (view.codex === 'on')
161
168
  return callout({ tone: 'grey', body: inLanguages((t) => t('set.codex.on')) });
162
- if (!view.stop.on)
163
- return '';
169
+ const stale = view.codex === 'stale';
164
170
  return callout({
165
171
  tone: 'coral',
166
- body: inLanguages((t) => t('set.codex.off')),
167
- ...(view.canWrite ? { action: pill({ label: inLanguages((t) => t('set.codex.go')), tone: 'primary', size: 'task', href: '#' + CODEX_WINDOW, attributes: opener(CODEX_WINDOW) }) } : {}),
172
+ body: inLanguages((t) => t(stale ? 'set.codex.stale' : 'set.codex.off')),
173
+ ...(view.canWrite ? { action: pill({ label: inLanguages((t) => t(stale ? 'set.codex.fix' : 'set.codex.go')), tone: 'primary', size: 'task', href: '#' + CODEX_WINDOW, attributes: opener(CODEX_WINDOW) }) } : {}),
168
174
  });
169
175
  }
@@ -1,10 +1,15 @@
1
- import { inLanguages } from '../../render/report-copy.js';
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { escapeHtml as e } from '../../render/html-report-components.js';
4
+ import { inLanguages, labelAttributes } from '../../render/report-copy.js';
2
5
  import { avatar } from '../../render/ui/avatar.js';
3
6
  import { BRAND_MARK } from '../../render/ui/brand-mark.js';
4
7
  import { pill, TRASH_SVG } from '../../render/ui/button.js';
5
8
  import { callout } from '../../render/ui/callout.js';
6
9
  import { opener } from '../../render/ui/popup.js';
10
+ import { toggleSwitch } from '../../render/ui/switch.js';
7
11
  import { tag } from '../../render/ui/tag.js';
12
+ import { stateLine } from './alerts-tab.js';
8
13
  import { scopeWindowId, UNINSTALL_WINDOW } from './settings-windows.js';
9
14
  /**
10
15
  * General (`for-people-who-build-with-ai.md` F56, asked for 2026-09-24; redrawn 2026-09-25): who what agentwhy saved in
@@ -25,9 +30,36 @@ export function generalTab(view, welcome) {
25
30
  // which-project V5: which AI agentwhy sees, in the words the project step and the list of projects say it.
26
31
  '<p class="set-quiet set-general-note"><span class="set-info" aria-hidden="true">i</span><span>' + inLanguages((t) => t('proj.which')) + '</span></p>' +
27
32
  '</section>' +
33
+ systemCard(view) +
28
34
  setupCard(welcome) +
29
35
  uninstallCard(view);
30
36
  }
37
+ /**
38
+ * Whether agentwhy also shows what it says in the corner of the screen (`2026-10-02-said-where-the-person-is.md` SW13).
39
+ * Asked for by the maintainer on 2026-10-02, who found the notifications tiresome and had only a command to turn them
40
+ * off. Written at once, as Alerts' optional rows are (R26a), keeping every other channel in force; on a page opened as a
41
+ * file, the script offers the command to copy instead.
42
+ */
43
+ function systemCard(view) {
44
+ const on = view.system.on;
45
+ const others = view.system.channels.filter((channel) => channel !== 'os');
46
+ const next = on ? (others.length === 0 ? ['chat'] : others) : [...others, 'os'];
47
+ return '<section class="set-box set-system"><div class="set-head"><h2 class="set-h2">' + inLanguages((t) => t('set.system.title')) + '</h2>' +
48
+ '<p class="set-lead">' + inLanguages((t) => t('set.system.lead')) + '</p></div>' +
49
+ // Not `set-msg-opt`: that row hangs from Alerts' first on a line, and this one hangs from nothing.
50
+ '<article class="set-msg' + (on ? ' set-msg-on' : '') + '"><div class="set-card-body">' +
51
+ '<div class="set-card-name"><h3 class="set-msg-name">' + inLanguages((t) => t('set.system.name')) + '</h3></div>' +
52
+ '<p class="set-msg-why">' + inLanguages((t) => t('set.system.when')) + '</p>' +
53
+ // As Alerts' optional rows: where the hooks are unknown, so is whether anything is said at all - the switch alone.
54
+ (view.known ? stateLine({ key: on ? 'set.state.on' : 'set.state.off', on }) : '') +
55
+ '<p class="set-say" data-set-say hidden></p></div>' +
56
+ toggleSwitch({
57
+ on,
58
+ disabled: !view.noticesWritable,
59
+ attributes: labelAttributes((t) => t('set.system.name')) + ' data-set-notice="' + e(JSON.stringify({ notify: next })) + '"',
60
+ }) +
61
+ '</article></section>';
62
+ }
31
63
  /**
32
64
  * W25a: the onboarding again, where this run serves it - drawn as Uninstall is (a mark, one sentence, the button), and
33
65
  * above it: going through the setup again comes before taking it all out. Nothing is written until its Finish (W10).
@@ -115,6 +115,8 @@ const SETTINGS_STYLE = String.raw `
115
115
  .set-cannot{margin:-8px 0 22px}
116
116
  .set-quiet{font-size:14px;line-height:1.5;color:var(--text-3)}
117
117
  .set-soft{color:var(--text-2)}
118
+ .set-tick{display:flex;gap:10px;align-items:flex-start;font-size:14px;color:var(--text-2);cursor:pointer}
119
+ .set-tick input{margin-top:3px;accent-color:var(--coral)}
118
120
  .set-head{margin-bottom:16px}
119
121
  .co+.set-head{margin-top:30px}
120
122
  .set-h2{margin:0;font-size:19px;font-weight:650;line-height:1.3}
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  /**
2
4
  * What only Settings does (`.ai/plans/2026-09-23-settings-redesign.md`, step 3). The kit opens and closes the windows
3
5
  * and switches the tabs; this posts what a confirm button carries, and reads the page again once the server has
@@ -43,7 +45,7 @@ export const SETTINGS_SCRIPT = String.raw `
43
45
  if (body.change === 'protect') return 'agentwhy init --protect ' + quote(body.pattern) + ' --refuse' + shared(body.where);
44
46
  if (body.change === 'adopt') return 'agentwhy init' + body.patterns.map((pattern) => ' --protect ' + quote(pattern)).join('') + ' --refuse' + shared(body.where);
45
47
  if (body.change === 'unprotect') return 'agentwhy init --remove --unprotect ' + quote(body.pattern) + shared(body.where);
46
- if (body.scope === 'project') return 'agentwhy notify' + (body.on ? ' --on ' + body.on : '') + (body.clean ? ' --clean ' + body.clean : '');
48
+ if (body.scope === 'project') return 'agentwhy notify' + (body.on ? ' --on ' + body.on : '') + (body.clean ? ' --clean ' + body.clean : '') + (body.notify ? ' --notify ' + body.notify.join(',') : '');
47
49
  return '';
48
50
  };
49
51
 
@@ -101,6 +103,11 @@ export const SETTINGS_SCRIPT = String.raw `
101
103
  const picked = document.querySelector('input[name="' + confirm.dataset.setWho + '"]:checked');
102
104
  body.where = picked ? picked.value : 'local';
103
105
  }
106
+ // AO17: the window's unticked choice - out of the person's own Codex files too, only when ticked.
107
+ if (confirm.dataset.setTick) {
108
+ const tick = document.querySelector('input[name="' + confirm.dataset.setTick + '"]');
109
+ if (tick && tick.checked) body.codex = true;
110
+ }
104
111
  change(body, confirm, confirm.getAttribute('data-set-command-' + (body.where || 'local')));
105
112
  return;
106
113
  }
@@ -1,7 +1,10 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { DEFAULT_POLICY } from '../../../core/policy/default-policy.js';
4
+ import { matchesGlob } from '../../../core/policy/glob.js';
2
5
  import { SETTINGS_FILES } from '../../../adapter/claude-code/contract/settings.js';
3
6
  import { DEFAULT_THRESHOLD } from '../../watch/agent-alert.js';
4
- import { DEFAULT_CLEAN } from '../../watch/notice-choices.js';
7
+ import { DEFAULT_CHANNELS, DEFAULT_CLEAN } from '../../watch/notice-choices.js';
5
8
  import { RULE_NAMES } from '../../rule-names.js';
6
9
  /**
7
10
  * What the Settings page shows, decided from what the files hold (`for-people-who-build-with-ai.md` §3 C; the plan's
@@ -32,7 +35,9 @@ export function settingsView(settings) {
32
35
  const read = known ? readPatterns(settings, reads) : undefined;
33
36
  const told = toldIn(settings);
34
37
  const canTell = canWrite && settings.told !== undefined && settings.told.local !== 'unreadable' && settings.told.shared !== 'unreadable';
35
- const rows = read === undefined ? policyRows(settings) : fileRows(settings, reads, read, told, canTell).map((row) => keptOf(row, settings, hooks));
38
+ const rows = read === undefined
39
+ ? policyRows(settings)
40
+ : fileRows(settings, reads, read, told, canTell).map((row) => coveredOf(keptOf(row, settings, hooks), settings, told));
36
41
  const unread = read === undefined ? [] : unreadRules(settings, read);
37
42
  const addTo = reads === 'local' || reads === 'shared' ? reads : alerts.who ?? stop.who ?? 'local';
38
43
  // A built-in pattern on a told list is read and told, never blocked again by an add (F57).
@@ -45,6 +50,7 @@ export function settingsView(settings) {
45
50
  stopped: { on: on === 'refused', locked: known && !alerts.on },
46
51
  fine: { on: clean !== 'off', locked: known && !alerts.on },
47
52
  noticesWritable: notices !== undefined && !notices.unusable,
53
+ system: { on: (notices?.notify ?? DEFAULT_CHANNELS).includes('os'), channels: notices?.notify ?? DEFAULT_CHANNELS },
48
54
  stop,
49
55
  scope,
50
56
  moves: { local: movesInto(settings, hooks, 'local'), shared: movesInto(settings, hooks, 'shared') },
@@ -189,6 +195,27 @@ function keptOf(row, settings, hooks) {
189
195
  const whole = ruled.length === row.patterns.length && (searched === undefined || row.patterns.every((pattern) => searched.has(pattern)));
190
196
  return whole ? row : { ...row, kept: 'open' };
191
197
  }
198
+ /**
199
+ * SW19, found by the maintainer on 2026-10-02: `demo.env` was switched to Track, and Claude Code still refused every
200
+ * read - its own deny rule for every `.env` file covers that name, and deny rules know no exceptions. The row carries
201
+ * the shortest written Block rule that still matches the row's file, by the name a command or a path would use. Only
202
+ * a row tracked by a file's name is asked - a folder or a hand-written pattern names no one file to try - and only
203
+ * rules a settings file actually holds count, because only those are rules Claude Code applies itself.
204
+ */
205
+ function coveredOf(row, settings, told) {
206
+ if (row.mode !== 'tell' || !row.watched)
207
+ return row;
208
+ const names = row.patterns.flatMap((pattern) => {
209
+ const name = /^\*\*\/([^*?/]+)$/.exec(pattern)?.[1];
210
+ return name === undefined ? [] : [name];
211
+ });
212
+ if (names.length === 0)
213
+ return row;
214
+ const written = FILES.flatMap((file) => settings.held?.[file] ?? []).filter((pattern) => !told.has(pattern));
215
+ const covering = written.filter((pattern) => names.some((name) => matchesGlob(name, pattern) || matchesGlob(`a/${name}`, pattern)));
216
+ const covered = covering.sort((one, two) => one.length - two.length || (one < two ? -1 : 1))[0];
217
+ return covered === undefined ? row : { ...row, covered };
218
+ }
192
219
  function denied(settings, pattern) {
193
220
  return FILES.some((file) => settings.held?.[file]?.includes(pattern) ?? false);
194
221
  }
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages } from '../../render/report-copy.js';
3
5
  import { addFilePopup } from '../../render/ui/add-file-popup.js';
@@ -115,18 +117,25 @@ function uninstallWindow(view) {
115
117
  return '';
116
118
  const command = files.map((file) => 'agentwhy init --remove --watch --refuse' +
117
119
  (view.uninstall[file] ?? []).map((rule) => ' --unprotect ' + quoted(rule)).join('') + shared(file)).join(' && ');
120
+ // AO17: one project's uninstall leaves agentwhy's check in the person's own Codex settings for the other projects
121
+ // (AOD4) - said here, with the unticked choice that takes it out of Codex too.
122
+ const codex = view.codex === undefined ? '' :
123
+ '<label class="set-tick"><input type="checkbox" name="set-uninstall-codex">' +
124
+ '<span>' + inLanguages((t) => t('set.confirm.uninstall.codex')) + '</span></label>';
118
125
  return confirmDialog({
119
126
  id: UNINSTALL_WINDOW,
120
127
  glyph: '',
121
128
  title: inLanguages((t) => t('set.confirm.uninstall')),
122
129
  subject: '',
123
130
  sentence: inLanguages((t) => t('set.confirm.uninstall.text') + (files.includes('shared') ? ' ' + t('set.confirm.everyone') : '')),
124
- option: '',
131
+ option: codex,
125
132
  note: say(),
126
133
  cancel: inLanguages((t) => t('app.cancel')),
127
134
  confirm: inLanguages((t) => t('set.confirm.uninstall.go')),
128
135
  tone: 'primary',
129
- confirmAttributes: write({ change: 'uninstall', rules: view.uninstall }) + commandAttribute('local', command),
136
+ confirmAttributes: write({ change: 'uninstall', rules: view.uninstall }) +
137
+ (view.codex === undefined ? '' : ' data-set-tick="set-uninstall-codex"') +
138
+ commandAttribute('local', command),
130
139
  });
131
140
  }
132
141
  /** The hooks a file runs once the moved ones are in it: `init` with hook flags takes out any it is not given (R4b). */
@@ -167,11 +176,13 @@ function finishWindow(view) {
167
176
  });
168
177
  }
169
178
  /**
170
- * CK5, CK7: Codex's hook, written to match the block Claude Code runs - the page's confirmation is the consent, as for
171
- * every block - and the honesty line under it. Opened as a file, it hands over `agentwhy init --codex`.
179
+ * `codex-approves-its-own-hook` AO8, AO10: agentwhy's Codex check, written to the person's own files and approved by
180
+ * agentwhy itself - the page's confirmation is the consent, as for every block - and the honesty line under it. Opened
181
+ * from `off` and from `stale` alike: the same write installs and heals. Opened as a file, it hands over
182
+ * `agentwhy init --codex`.
172
183
  */
173
184
  function codexWindow(view) {
174
- if (view.codex !== 'off' || !view.stop.on)
185
+ if ((view.codex !== 'off' && view.codex !== 'stale') || !view.stop.on)
175
186
  return '';
176
187
  return confirmDialog({
177
188
  id: CODEX_WINDOW,
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { fileRulesIn } from '../../adapter/claude-code/settings/deny-entries.js';
2
4
  import { missingHookEntries } from '../../adapter/claude-code/settings/hook-entries.js';
3
5
  /**
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages } from '../../render/report-copy.js';
3
5
  import { textButton } from '../../render/ui/button.js';
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages, labelAttributes } from '../../render/report-copy.js';
3
5
  import { closeButton, pill } from '../../render/ui/button.js';
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages, labelAttributes } from '../../render/report-copy.js';
3
5
  import { pill } from '../../render/ui/button.js';
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { matchesGlob } from '../../../core/policy/glob.js';
2
4
  import { idInKey } from '../../../core/session-catalogue.js';
3
5
  import { escapeHtml as e } from '../../render/html-report-components.js';
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  /**
2
4
  * What only To fix does (`.ai/plans/2026-09-23-to-fix-redesign.md`, step 3). The kit opens and closes the windows and
3
5
  * switches the tabs, and the Fix it wizard writes a mark; this posts an undo, and reads the page again once the server
@@ -1,3 +1,4 @@
1
+ import { sumRefusedByOthers } from '../refusals.js';
1
2
  export const ALERT_THRESHOLDS = ['value', 'reached', 'refused'];
2
3
  /**
3
4
  * What is said when nobody has said otherwise: only a value written from a protected file. The other two are worth
@@ -32,6 +33,7 @@ export function alertOf(report, agentIndex) {
32
33
  const wroteItself = node.wroteValue === true || node.wroteOnward === true || usedItself;
33
34
  const reached = reachedFromReturn > 0 || node.filesReached > 0 || reachedBelow;
34
35
  const refusedAttempts = subtree.reduce((sum, agent) => sum + agent.refusedAttempts, 0);
36
+ const byOthers = sumRefusedByOthers(subtree.map((agent) => agent.refusedByOthers));
35
37
  const filesReached = own.length > 0 ? reachedFromReturn : reachedBelow ? undefined : node.filesReached;
36
38
  const level = wroteOrReturnedValue || wroteItself ? 'value' : reached ? 'reached' : refusedAttempts > 0 ? 'refused' : 'nothing';
37
39
  return {
@@ -40,6 +42,7 @@ export function alertOf(report, agentIndex) {
40
42
  ...(node.type === undefined ? {} : { type: node.type }),
41
43
  ...(filesReached === undefined ? {} : { filesReached }),
42
44
  refusedAttempts,
45
+ ...(byOthers === undefined ? {} : { refusedByOthers: byOthers }),
43
46
  };
44
47
  }
45
48
  /** Whether a level is strong enough to be said, under the threshold asked for. */
@@ -0,0 +1,69 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { join } from 'node:path';
4
+ import { SETTINGS_FILES } from '../../adapter/claude-code/contract/settings.js';
5
+ import { refuseProjectAbove, refuseRulesOf } from '../../adapter/claude-code/settings/refuse-project.js';
6
+ import { codexReaderOf } from '../../adapter/codex/hooks/reader.js';
7
+ import { codexConversationOf } from '../../adapter/codex/hooks/codex-conversation.js';
8
+ import { codexStopFolder, parseCodexStopInput } from '../../adapter/codex/hooks/stop-input.js';
9
+ import { sessionKey } from '../../core/session-catalogue.js';
10
+ import { FileAccessError } from '../../ports/file-access-error.js';
11
+ import { CODEX_REQUESTS } from './render/codex-request-words.js';
12
+ /**
13
+ * Codex's end of a turn, for `watch` (`2026-10-02-codex-says-it-too.md` CX1-CX7): its `Stop` read by Codex's names,
14
+ * who reads it from its rollout, the commands agentwhy stopped in the turn, Codex's requests, a Codex session's key, and
15
+ * the project's rules - the ones Codex's `refuse` reads.
16
+ */
17
+ export function codexTurnFormat(files, refusals, threadIndexPath, home) {
18
+ return {
19
+ readStop: parseCodexStopInput,
20
+ readerOf: (turn) => codexReaderOf(files, turn.transcriptPath),
21
+ stoppedIn: (text) => refusals.find(text),
22
+ requests: CODEX_REQUESTS,
23
+ sessionKey: (id) => sessionKey({ id, provider: 'codex' }),
24
+ settingsFor: (text) => rulesOfProject(files, codexStopFolder(text), home),
25
+ // CX8: one conversation across the session ids the Codex apps give it (CXB5).
26
+ conversationOf: (turn) => codexConversationOf(files, threadIndexPath, turn.sessionId),
27
+ // CX9 with AO5: watched where a project above the turn's folder runs `refuse` - as `refuse --codex` finds it.
28
+ watchedHere: async (text) => {
29
+ const folder = codexStopFolder(text);
30
+ return folder !== undefined && (await refuseProjectAbove(files, folder, home)) !== undefined;
31
+ },
32
+ };
33
+ }
34
+ /**
35
+ * CX5 with AO5, AO6: the project is found as `refuse --codex` finds it - the nearest folder short of home whose Claude
36
+ * Code settings run `refuse` - and its rules are the settings file that project's `refuse` command names. Found by
37
+ * review: guessing the local file first read no rules where a project keeps them in the shared one, and so said a chat
38
+ * was clean over a file `refuse` protects. Where it names none, the local file, then the shared one, as before. A
39
+ * policy file is not a settings path, so it reads as none.
40
+ */
41
+ async function rulesOfProject(files, folder, home) {
42
+ if (folder === undefined)
43
+ return undefined;
44
+ const found = await refuseProjectAbove(files, folder, home);
45
+ if (found === undefined)
46
+ return undefined;
47
+ const rules = refuseRulesOf(found);
48
+ if (rules.policyPath !== undefined)
49
+ return undefined;
50
+ if (rules.settingsPath !== undefined)
51
+ return rules.settingsPath;
52
+ for (const name of [SETTINGS_FILES.local, SETTINGS_FILES.shared]) {
53
+ const settings = join(found.project, SETTINGS_FILES.directory, name);
54
+ if (await readable(files, settings))
55
+ return settings;
56
+ }
57
+ return undefined;
58
+ }
59
+ async function readable(files, path) {
60
+ try {
61
+ await files.readText(path);
62
+ return true;
63
+ }
64
+ catch (error) {
65
+ if (error instanceof FileAccessError)
66
+ return false;
67
+ throw error;
68
+ }
69
+ }
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  /**
2
4
  * The choices a notice can be given, in a module of their own so that both the code that says a notice and the code
3
5
  * that remembers what a person chose can name them without depending on each other.
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { dirname } from 'node:path';
2
4
  import { longestMatch, NO_PREFERENCES, preferencesFor, readPreferences, withProjectChoices, writtenPreferences, } from './preferences.js';
3
5
  /**
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { isJsonObject, parseJsonObject } from '../../shared/json.js';
2
4
  import { ALERT_THRESHOLDS } from './agent-alert.js';
3
5
  import { CLEAN_MODES, NOTICE_CHANNELS, NOTICE_LANGS, SAID_AS, } from './notice-choices.js';
@@ -0,0 +1,106 @@
1
+ /*
2
+ * The stopped limit says "now": told to never read the file another way, the agent refused to try again later in the
3
+ * same conversation after the person had switched the file to Track (seen by the maintainer, 2026-10-02). The hook is
4
+ * the gate, not the agent's memory of this sentence.
5
+ */
6
+ /**
7
+ * What Codex's agent is handed (`2026-10-02-codex-says-it-too.md` CX2, CX3, amended 2026-10-02). Codex shows it whole, in
8
+ * a bubble above the reply (CXB1, CKB14), and the maintainer found the first wording - written to the agent, with the
9
+ * report's command in it - too long and not understandable for the person who reads it first. So each is written to the
10
+ * person: what happened and what to do, in one or two sentences of the glossary's words, in the language chosen for
11
+ * agentwhy; then, in brackets and marked as the AI's ("For the AI:", chosen by the maintainer so a person does not read it
12
+ * as said to them), the one thing the agent is asked - one short sentence back - with the limit that keeps
13
+ * the file safe: never show a key, never read a stopped file another way. No command, no report offered: the report is
14
+ * one step away in Conversations, and a command in that bubble is a line a person cannot read.
15
+ */
16
+ export const CODEX_REQUESTS = {
17
+ stopped: (paths, lang) => STOPPED[lang](listed(paths, lang), paths.length === 1),
18
+ keys: (files, _command, lang) => KEYS[lang](listed(files.map(String), lang), files.length <= 1),
19
+ template: (files, _command, lang) => TEMPLATE[lang](listed(files.map(String), lang), files.length <= 1),
20
+ data: (files, _command, lang) => DATA[lang](listed(files.map(String), lang), files.length <= 1),
21
+ told: (files, _command, lang, foldsTurn) => TOLD[lang](listed(files.map(String), lang), files.length <= 1, foldsTurn === true),
22
+ quiet: (lang, foldsTurn) => QUIET[lang](foldsTurn === true),
23
+ };
24
+ const STOPPED = {
25
+ en: (files, one) => `**agentwhy** stopped your AI from reading the private ${one ? 'file' : 'files'} ${files}. ` +
26
+ `(For the AI: reply in one short sentence, and don't try to read ${one ? 'it' : 'them'} another way now.)`,
27
+ pl: (files, one) => `**agentwhy** nie pozwolił AI odczytać ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files}. ` +
28
+ `(Dla AI: odpowiedz jednym krótkim zdaniem i nie próbuj teraz odczytać ${one ? 'go' : 'ich'} inaczej.)`,
29
+ de: (files, one) => `**agentwhy** hat deine KI daran gehindert, ${one ? 'die private Datei' : 'die privaten Dateien'} ${files} zu lesen. ` +
30
+ `(Für die KI: antworte in einem kurzen Satz und versuch jetzt nicht, sie anders zu lesen.)`,
31
+ };
32
+ const KEYS = {
33
+ en: (files, one) => `**agentwhy**: a key from the private ${one ? 'file' : 'files'} ${files} is now in this chat. Make a new key ` +
34
+ `today where it was created; deleting the chat won't undo it. (For the AI: reply in one short sentence; don't show the key.)`,
35
+ pl: (files, one) => `**agentwhy**: klucz z ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files} jest teraz w tej rozmowie. ` +
36
+ `Zrób dziś nowy klucz tam, gdzie go utworzono; usunięcie rozmowy tego nie cofnie. (Dla AI: odpowiedz jednym krótkim ` +
37
+ `zdaniem; nie pokazuj klucza.)`,
38
+ de: (files, one) => `**agentwhy**: Ein Schlüssel aus ${one ? 'der privaten Datei' : 'den privaten Dateien'} ${files} ist jetzt ` +
39
+ `in diesem Chat. Erstelle heute dort einen neuen, wo er erstellt wurde; den Chat zu löschen hilft nicht. (Für die KI: ` +
40
+ `antworte in einem kurzen Satz; zeig den Schlüssel nicht.)`,
41
+ };
42
+ /** A template's value may be a placeholder: only a real key is to be made new (S6, `template`). */
43
+ const TEMPLATE = {
44
+ en: (files, one) => `**agentwhy**: a value from the template ${one ? 'file' : 'files'} ${files} is now in this chat. If it's a ` +
45
+ `real key, make a new one where it was created. (For the AI: reply in one short sentence; don't show the value.)`,
46
+ pl: (files, one) => `**agentwhy**: wartość z ${one ? 'pliku-szablonu' : 'plików-szablonów'} ${files} jest teraz w tej rozmowie. ` +
47
+ `Jeśli to prawdziwy klucz, zrób nowy tam, gdzie go utworzono. (Dla AI: odpowiedz jednym krótkim zdaniem; ` +
48
+ `nie pokazuj tej wartości.)`,
49
+ de: (files, one) => `**agentwhy**: Ein Wert aus ${one ? 'der Vorlagendatei' : 'den Vorlagendateien'} ${files} ist jetzt in ` +
50
+ `diesem Chat. Ist es ein echter Schlüssel, erstelle dort einen neuen, wo er erstellt wurde. (Für die KI: antworte in ` +
51
+ `einem kurzen Satz; zeig den Wert nicht.)`,
52
+ };
53
+ /** Data cannot be made new, so nothing is asked to be changed (S6, `data`): what cannot be taken back, and where to look. */
54
+ const DATA = {
55
+ en: (files, one) => `**agentwhy**: private data from the private ${one ? 'file' : 'files'} ${files} is now in this chat. It ` +
56
+ `can't be taken back; agentwhy's report shows how to limit the damage. (For the AI: reply in one short sentence; don't repeat the data.)`,
57
+ pl: (files, one) => `**agentwhy**: prywatne dane z ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files} są teraz w tej ` +
58
+ `rozmowie. Nie da się tego cofnąć; raport agentwhy pokazuje, jak ograniczyć szkody. (Dla AI: odpowiedz jednym krótkim ` +
59
+ `zdaniem; nie powtarzaj tych danych.)`,
60
+ de: (files, one) => `**agentwhy**: Private Daten aus ${one ? 'der privaten Datei' : 'den privaten Dateien'} ${files} sind jetzt ` +
61
+ `in diesem Chat. Das lässt sich nicht zurücknehmen; der Bericht von agentwhy zeigt, wie du den Schaden begrenzt. (Für ` +
62
+ `die KI: antworte in einem kurzen Satz; wiederhole die Daten nicht.)`,
63
+ };
64
+ /**
65
+ * Where the app folds the turn away (`foldsTurn`, the desktop app, CXB5's display note), told and quiet ask the
66
+ * answer back - decided by the maintainer, 2026-10-02, after a control run: with a block, the whole Apache-licence
67
+ * answer sat behind "Worked for ...", and the visible reply was one line about private files. The full answer, not a
68
+ brief one: the maintainer tried "repeat your answer" the same day and found the visible repeat more condensed than
69
+ the original the fold hides. A told file is one the
70
+ * person lets their AI read, so the repeat carries nothing that may not be here; keys, data and a stopped command
71
+ * keep their limits - what their fold hides is what must not be repeated. VS Code's Codex folds nothing, so there
72
+ * the one short sentence stays and nothing is said twice.
73
+ */
74
+ const TOLD = {
75
+ en: (files, one, folds) => `**agentwhy**: your AI read the private ${one ? 'file' : 'files'} ${files}, which you let it read. ` +
76
+ `Nothing to do. (For the AI: ${folds ? 'repeat your full answer for the user, then that nothing needs doing' : 'reply in one short sentence'}.)`,
77
+ pl: (files, one, folds) => `**agentwhy**: AI przeczytało ${one ? 'prywatny plik' : 'prywatne pliki'} ${files}, na ${one ? 'który' : 'które'} ` +
78
+ `pozwoliłeś. Nic nie trzeba robić. (Dla AI: ${folds ? 'powtórz całą swoją odpowiedź dla użytkownika i dodaj, że nic nie trzeba robić' : 'odpowiedz jednym krótkim zdaniem'}.)`,
79
+ de: (files, one, folds) => `**agentwhy**: Deine KI hat ${one ? 'die private Datei' : 'die privaten Dateien'} ${files} gelesen, die du ` +
80
+ `ihr erlaubt hast. Nichts zu tun. (Für die KI: ${folds ? 'wiederhole deine ganze Antwort für den Nutzer und ergänze, dass nichts zu tun ist' : 'antworte in einem kurzen Satz'}.)`,
81
+ };
82
+ const QUIET = {
83
+ en: (folds) => '**agentwhy** is keeping watch over your private files: none has been opened so far. ' +
84
+ `(For the AI: ${folds ? 'repeat your full answer for the user, then this in one sentence' : 'reply in one short sentence'}.)`,
85
+ pl: (folds) => '**agentwhy** pilnuje twoich prywatnych plików: jak dotąd żaden nie został otwarty. ' +
86
+ `(Dla AI: ${folds ? 'powtórz całą swoją odpowiedź dla użytkownika i dodaj to jednym zdaniem' : 'odpowiedz jednym krótkim zdaniem'}.)`,
87
+ de: (folds) => '**agentwhy** passt auf deine privaten Dateien auf: Bisher wurde keine geöffnet. ' +
88
+ `(Für die KI: ${folds ? 'wiederhole deine ganze Antwort für den Nutzer und ergänze dies in einem Satz' : 'antworte in einem kurzen Satz'}.)`,
89
+ };
90
+ /** At most three files by name, then how many more: a request a person reads is not a list. */
91
+ function listed(paths, lang) {
92
+ const named = [...new Set(paths)].slice(0, 3);
93
+ const rest = new Set(paths).size - named.length;
94
+ const and = AND[lang];
95
+ if (rest > 0)
96
+ named.push(MORE[lang](rest));
97
+ if (named.length <= 1)
98
+ return named[0] ?? '';
99
+ return `${named.slice(0, -1).join(', ')} ${and} ${named.at(-1)}`;
100
+ }
101
+ const AND = { en: 'and', pl: 'i', de: 'und' };
102
+ const MORE = {
103
+ en: (count) => `${count} more`,
104
+ pl: (count) => `${count} innych`,
105
+ de: (count) => `${count} weitere`,
106
+ };