@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
@@ -0,0 +1,75 @@
1
+ import { quietlySaid } from './render/start-words.js';
2
+ /**
3
+ * How long a server started in the background is waited for, at most, while its process runs (PF3): its first pages are
4
+ * every conversation of the project, and a project with many takes a while. A process that ended is not waited for -
5
+ * a sandbox, a port refused - and the pages are written as files at once.
6
+ */
7
+ const WAIT_MS = 60_000;
8
+ const ASK_EVERY_MS = 250;
9
+ /**
10
+ * `start --detach` (`2026-10-02-a-page-not-a-file.md` PF3-PF5): the pages served, from a command that returns at once.
11
+ * A server already running for this project is used - its address stays the one the person has open; else one is
12
+ * started in the background and waited for; else, where none can start (a sandbox), the pages are written as files,
13
+ * as `--no-serve` writes them, and that is said with what to do.
14
+ */
15
+ export class DetachedStart {
16
+ #dependencies;
17
+ constructor(dependencies) {
18
+ this.#dependencies = dependencies;
19
+ }
20
+ async run(options) {
21
+ const { servers, probe, background, project, clock, sleep } = this.#dependencies;
22
+ const known = await servers.read(project);
23
+ if (known !== undefined && (await probe.answers(`${known.url}index.html`)))
24
+ return this.#show(known.url, options, true);
25
+ const pid = await background.start(backgroundArgs(options));
26
+ if (pid !== undefined) {
27
+ for (const deadline = clock() + WAIT_MS; clock() < deadline; await sleep(ASK_EVERY_MS)) {
28
+ const started = await servers.read(project);
29
+ if (started?.pid === pid && (await probe.answers(`${started.url}index.html`)))
30
+ return this.#show(started.url, options, false);
31
+ if (!background.running(pid))
32
+ break;
33
+ }
34
+ }
35
+ return this.#asFiles(options);
36
+ }
37
+ /** The page asked for on a running server: the session's report where it serves one, else every conversation (SW10). */
38
+ async #show(base, options, reused) {
39
+ const { probe, browser } = this.#dependencies;
40
+ const asked = options.session === undefined ? undefined : `${base}${options.session}.html`;
41
+ const found = asked === undefined || (await probe.answers(asked));
42
+ const url = asked !== undefined && found ? asked : `${base}index.html`;
43
+ const opened = options.open && (await browser.open(url));
44
+ // A conversation asked for and not served is said in every output, never passed off as its report (SW10).
45
+ const missing = found ? '' : 'That conversation was not found here, so all conversations are served.\n';
46
+ // Not opened, a served page is still there to open: said as served, never as a file written.
47
+ if (options.quiet === true)
48
+ return { outcome: 'written', output: opened ? quietlySaid(url, true, found) : `${missing}Serving: ${url}\n` };
49
+ return {
50
+ outcome: 'written',
51
+ output: missing +
52
+ `${reused ? 'agentwhy was already running for this project' : 'agentwhy runs in the background for this project'}: ${url}\n` +
53
+ 'It stops by itself 30 minutes after its last page is closed.\n',
54
+ };
55
+ }
56
+ /** PF4: no server came up - a sandbox, a refusal - so the pages are files, and what would serve them is said. */
57
+ async #asFiles(options) {
58
+ const written = await this.#dependencies.files.run({ ...options, serve: false });
59
+ return {
60
+ ...written,
61
+ output: 'agentwhy could not keep running in the background here, so the pages were written as files. To open them live, ' +
62
+ 'run this command again outside the sandbox, or type npx @agentwhy/cli in a terminal.\n' + written.output,
63
+ };
64
+ }
65
+ }
66
+ /** What the server in the background is started with: the same choices, served, opening nothing of its own. */
67
+ function backgroundArgs(options) {
68
+ return [
69
+ 'start', '--serve', '--no-open', '--since', options.since.asked,
70
+ ...(options.out === undefined ? [] : ['--out', options.out]),
71
+ ...(options.policyPath === undefined ? [] : ['--policy', options.policyPath]),
72
+ ...(options.settingsPath === undefined ? [] : ['--settings', options.settingsPath]),
73
+ ...(options.session === undefined ? [] : ['--session', options.session]),
74
+ ];
75
+ }
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { dataTable } from '../../render/ui/data-table.js';
2
4
  import { closeButton, pill } from '../../render/ui/button.js';
3
5
  import { CLOSES, popup, popupFoot } from '../../render/ui/popup.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 } from '../../render/report-copy.js';
3
5
  import { BRAND_MARK } from '../../render/ui/brand-mark.js';
@@ -82,14 +84,18 @@ function nothing(done, links) {
82
84
  : '<div class="ob-good"><span class="ob-good-mark" aria-hidden="true">✓</span><span>' +
83
85
  '<span class="ob-good-title">' + inLanguages((t) => t('ob.done.good')) + '</span>' +
84
86
  '<span class="ob-good-text">' + inLanguages((t) => t('ob.done.good.text', { when: when(t, done.asked) })) + '</span></span></div>') +
85
- '<div class="ob-done-go">' + pill({ label: inLanguages((t) => t('ob.done.open')), tone: 'mint', size: 'lg', href: links.conversations }) + '</div>';
87
+ openAgentwhy(links);
86
88
  }
87
- /** W1a: no AI chats yet - set up before there is anything to show, and Settings the one page there is. */
89
+ /** W1a: no AI chats yet - set up before there is anything to show, and then Conversations, where they will be listed. */
88
90
  function empty(links) {
89
91
  return '<h2 class="ob-h2 ob-done-title" id="ob-done-title"><span data-ob-if="on">' + inLanguages((t) => t('ob.done.protected')) + '</span>' +
90
92
  '<span data-ob-if="off" hidden>' + inLanguages((t) => t('ob.done.empty.title')) + '</span></h2>' +
91
93
  '<p class="ob-done-text">' + inLanguages((t) => t('ob.done.empty.text')) + '</p>' +
92
- '<div class="ob-done-go">' + pill({ label: inLanguages((t) => t('ob.done.settings')), tone: 'mint', size: 'lg', href: links.settings }) + '</div>';
94
+ openAgentwhy(links);
95
+ }
96
+ /** W19, W1a: Done's own way on, to Conversations, whether or not there are chats in it yet. */
97
+ function openAgentwhy(links) {
98
+ return '<div class="ob-done-go">' + pill({ label: inLanguages((t) => t('ob.done.open')), tone: 'mint', size: 'lg', href: links.conversations }) + '</div>';
93
99
  }
94
100
  /** "From now on, you'll be told right away." - or, where alerts end up off, how to turn them on (W18). */
95
101
  function told() {
@@ -24,11 +24,13 @@ export async function finishOnboarding(dependencies, choices) {
24
24
  for (const change of changes)
25
25
  results.push(await written(dependencies, change, choices.scope, telling));
26
26
  const recorded = await dependencies.store.add(dependencies.clock());
27
- // W20a: read from the files as Finish left them - the writes above go through `CodexMirror`, which writes Codex's hook
28
- // with Claude Code's `refuse` - and said only where Claude Code's block runs, so a project that blocks nothing is not
29
- // told Codex isn't blocked.
27
+ // W20a: read from the files as Finish left them - the writes above go through `CodexMirror`, which writes agentwhy's
28
+ // Codex check with Claude Code's `refuse` - and said only where Claude Code's block runs, so a project that blocks
29
+ // nothing is not told Codex isn't blocked. `on` only where the check is verified (AO3): a write that did not verify
30
+ // reads as `off`, whose line says Settings can make it.
30
31
  const after = (await dependencies.settingsNow()).hooks;
31
- const codex = after !== undefined && after.refuse !== false ? after.codex : undefined;
32
+ const state = after !== undefined && after.refuse !== false ? after.codex : undefined;
33
+ const codex = state === 'stale' ? 'off' : state;
32
34
  return { outcome: 'finished', results, recorded, ...(codex === undefined ? {} : { codex }) };
33
35
  }
34
36
  async function written(dependencies, change, scope, telling) {
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { inLanguages } from '../../render/report-copy.js';
2
4
  /**
3
5
  * The intro (`.ai/specs/2026-09-24-onboarding.md` W4-W6): the problem told without a technical word, in about twelve
@@ -43,7 +43,6 @@ export class OnboardingRenderer {
43
43
  });
44
44
  }
45
45
  #page(view, index) {
46
- const anyConversation = index.entries.length > 0;
47
46
  // which-project V7: in the home directory there is no project to set up - only the step that picks one.
48
47
  const project = view.project.kind === 'none' ? undefined : view.project.name;
49
48
  const state = {
@@ -62,7 +61,7 @@ export class OnboardingRenderer {
62
61
  '<div class="ob-body">' + introScreen() + welcomeScreen() +
63
62
  projectStep(view, { now: index.now, timeZone: index.timeZone ?? 'UTC' }) +
64
63
  (project === undefined ? '' : stepScreens(view, project) + doneScreen(view.done, this.#links)) +
65
- noScript(this.#links, anyConversation, project !== undefined) + '</div>' +
64
+ noScript(this.#links, project !== undefined) + '</div>' +
66
65
  (view.files.canAdd && project !== undefined ? addFilePopup(ADD_WINDOW) : '') +
67
66
  '</div>';
68
67
  }
@@ -84,13 +83,14 @@ function header() {
84
83
  /**
85
84
  * W27: without a script nothing here can be written, so the page says where the same choices are made - where there is a
86
85
  * project to make them for. In the home directory the project step already leads to its chats (which-project V7).
86
+ * Conversations is written for a project with no chats too (W1a, amended 2026-10-01), so both links are always there.
87
87
  */
88
- function noScript(links, anyConversation, aProject) {
88
+ function noScript(links, aProject) {
89
89
  if (!aProject)
90
90
  return '';
91
91
  return '<div class="ob-nojs"><p>' + inLanguages((t) => t('ob.nojs')) + '</p><p class="ob-nojs-links">' +
92
92
  pill({ label: inLanguages((t) => t('ob.nojs.settings')), tone: 'primary', size: 'lg', href: links.settings }) +
93
- (anyConversation ? pill({ label: inLanguages((t) => t('ob.nojs.conversations')), tone: 'outline', size: 'lg', href: links.conversations }) : '') +
93
+ pill({ label: inLanguages((t) => t('ob.nojs.conversations')), tone: 'outline', size: 'lg', href: links.conversations }) +
94
94
  '</p></div>';
95
95
  }
96
96
  /** What the script says, in every language: it cannot hold three languages in one text node. */
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { INTRO_MS } from './intro.js';
2
4
  import { PICK_GROUP } from './project-step.js';
3
5
  /**
@@ -91,7 +91,7 @@ function projectOf(index, read) {
91
91
  if (index.entries.length > 0)
92
92
  return { kind: 'found', name, ...place, conversations: index.entries.length, entryPoints: index.entryPoints ?? {}, read };
93
93
  const above = index.projects?.above;
94
- const holder = above === undefined ? undefined : index.projects?.rows.find((row) => row.id === above.id && row.exists);
94
+ const holder = above === undefined ? undefined : index.projects?.rows.find((row) => row.id === above.id && row.folder !== 'gone');
95
95
  if (above !== undefined && holder !== undefined)
96
96
  return { kind: 'inside', name, ...place, above: holder, within: above.within };
97
97
  return { kind: 'empty', name, ...place };
@@ -7,7 +7,7 @@ import { tag } from '../../render/ui/tag.js';
7
7
  /** The radio group the step's list chooses in (`.ai/specs/2026-09-27-which-project.md` V11). */
8
8
  export const PICK_GROUP = 'ob-project';
9
9
  /** The kinds V4 names, in the order the step says them; an unknown kind is counted last and not named. */
10
- const NAMED = ['editor', 'terminal', 'script'];
10
+ const NAMED = ['editor', 'terminal', 'desktop', 'script'];
11
11
  /** A small folder, drawn: the guidelines have no emoji, not even in a button (§9.3). */
12
12
  const FOLDER_SVG = '<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true">' +
13
13
  '<path d="M3 6.5A1.5 1.5 0 0 1 4.5 5h4.2l2 2.2h8.8A1.5 1.5 0 0 1 21 8.7v9.8a1.5 1.5 0 0 1-1.5 1.5h-15A1.5 1.5 0 0 1 3 18.5z"/></svg>';
@@ -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, textButton, trashButton } 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 { inLanguages } from '../../render/report-copy.js';
2
4
  import { pill } from '../../render/ui/button.js';
3
5
  /**
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { updateNotice } from '../render/ui/update-notice.js';
2
4
  /**
3
5
  * What a page `start` serves draws over its content, from the model it is written with: the update notice, where the
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { join, relative } from 'node:path';
2
4
  import { SETTINGS_FILES } from '../../../adapter/claude-code/contract/settings.js';
3
5
  import { projectDirectoryName } from '../../../adapter/claude-code/contract/projects.js';
@@ -15,12 +17,13 @@ export async function indexProjects(listing, sources) {
15
17
  const listed = temporary === undefined ? projects : projects.filter((project) => !temporary(project.path));
16
18
  const finished = await sources.onboarding?.doneFor(listed.map((project) => projectDirectoryName(project.path)));
17
19
  const rows = await Promise.all(listed.map(async (project) => {
18
- const setUp = project.exists ? await setUpIn(project, sources.files, finished) : undefined;
20
+ // V10b: a folder not looked at is not read for its settings either.
21
+ const setUp = project.folder === 'there' ? await setUpIn(project, sources.files, finished) : undefined;
19
22
  return {
20
23
  id: project.id,
21
24
  place: homeRelative(project.path, sources.home),
22
25
  name: projectName(project.path),
23
- exists: project.exists,
26
+ folder: project.folder,
24
27
  conversations: project.conversations,
25
28
  newest: project.newest,
26
29
  ...(setUp === undefined ? {} : { setUp }),
@@ -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 { INDEX_CONTENT_SECURITY_POLICY_META } from '../../render/html-head.js';
3
5
  import { inLanguages, LANGS, translator } from '../../render/report-copy.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, LANGS, translator } 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 { escapeHtml as e } from '../../render/html-report-components.js';
2
4
  import { inLanguages, labelAttributes } from '../../render/report-copy.js';
3
5
  import { BUTTON_STYLE, 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 { isAbsolute, relative, sep } from 'node:path';
2
4
  /** Where systems keep what is thrown away: `/tmp`, and on macOS the per-person folders `TMPDIR` points into. */
3
5
  const SYSTEM_TEMPORARY = ['/tmp', '/private/tmp', '/var/tmp', '/private/var/tmp', '/var/folders', '/private/var/folders'];
@@ -1,9 +1,22 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { paint } from '../../../shared/colour.js';
2
4
  import { printable } from '../../../shared/printable.js';
3
5
  import { TAGLINE, terminalLogo } from '../../../shared/terminal-logo.js';
4
6
  const INDENT = ' ';
5
7
  /** The mark a heading carries, as the report draws it (§4.1). It stands beside the word, never instead of it. */
6
8
  const HEADING = '▍';
9
+ /**
10
+ * What `start --quiet` says (`2026-10-02-said-where-the-person-is.md` SW11): where the page is and whether it opened,
11
+ * nothing a report found. Its reader is the agent that ran it because the person said yes to a report, as for `report
12
+ * --quiet` (`the-agent-tells-you` R19). A conversation asked for and not found is said, never passed off as its report.
13
+ */
14
+ export function quietlySaid(place, opened, found) {
15
+ const where = printable(place);
16
+ if (!found)
17
+ return `That conversation was not found here, so all conversations were ${opened ? 'opened' : 'written'}: ${where}\n`;
18
+ return opened ? `Opened: ${where}\n` : `Written: ${where}\n`;
19
+ }
7
20
  /**
8
21
  * Everything `start` says about one run: the wordmark, then what the range needs, then where the page is. The
9
22
  * sentences are the ones this command has always printed - the heading and the indent are what group them, so a
@@ -59,7 +72,8 @@ function page(said, view) {
59
72
  `${dim('Serving the page at', view)} ${bold(said.url, view)}`,
60
73
  dim('A mark made on it is recorded at once. It stops after 30 minutes with no request, or with Ctrl+C.', view),
61
74
  ]),
62
- ...(said.welcome === true ? [`Opened the welcome page, to set agentwhy up for ${said.project ?? 'this project'}.`] : []),
75
+ ...(said.welcome === 'opened' ? [`Opened the welcome page, to set agentwhy up for ${said.project ?? 'this project'}.`] : []),
76
+ ...(said.welcome === 'served' ? [`That address opens the welcome page, to set agentwhy up for ${said.project ?? 'this project'}.`] : []),
63
77
  ...(said.servedAsFile === true
64
78
  ? [warn('The page could not be served, so it was opened as a file: marks made on it are copied as commands.', view)]
65
79
  : []),
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { dirname, join } from 'node:path';
2
4
  import { FileAccessError } from '../../ports/file-access-error.js';
3
5
  /**
@@ -1,3 +1,5 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { ALERT_THRESHOLDS } from '../../watch/agent-alert.js';
2
4
  import { CLEAN_MODES, NOTICE_CHANNELS, NOTICE_LANGS, SAID_AS } from '../../watch/notice-choices.js';
3
5
  import { isMarkResult, MARK_RESULTS } from '../../../ports/mark-store.js';
@@ -257,7 +259,8 @@ function settingsChange(fields) {
257
259
  }
258
260
  if (Object.keys(rules).length === 0)
259
261
  return 'An uninstall names the files it takes agentwhy out of.';
260
- return { change: 'uninstall', rules };
262
+ // AO17: the window's unticked choice - out of the person's own Codex files too. Anything but true is unticked.
263
+ return { change: 'uninstall', rules, ...(fields.codex === true ? { codex: true } : {}) };
261
264
  }
262
265
  // The same rule, in the other file. `where` is the destination and is required here: a move with no file named
263
266
  // is not a move. A hook is moved by a `hooks` change instead, which names the file it is to run from.
@@ -112,6 +112,16 @@ async function uninstall(setup, change) {
112
112
  return { outcome: out.outcome, output: said.filter(Boolean).join('\n') };
113
113
  written ||= out.outcome === 'written';
114
114
  }
115
+ // AO17, ticked: agentwhy's check and its approvals out of the person's own Codex files too, after the project's part.
116
+ // `hooks: []` is the empty set, not silence: this run is Codex's alone, and the files the window named were already
117
+ // done above. Naming the two hooks here would take them out of the local file as well, which no tick asked for.
118
+ if (change.codex === true) {
119
+ const out = await setup.run({ protect: [], remove: true, yes: true, codex: true, hooks: [] });
120
+ said.push(out.output.trim());
121
+ if (out.outcome !== 'written' && out.outcome !== 'unchanged')
122
+ return { outcome: out.outcome, output: said.filter(Boolean).join('\n') };
123
+ written ||= out.outcome === 'written';
124
+ }
115
125
  return { outcome: written ? 'written' : 'unchanged', output: said.filter(Boolean).join('\n') };
116
126
  }
117
127
  /**
@@ -1,7 +1,10 @@
1
+ // Copyright 2026 Nessprim Karol Kozer
2
+ // SPDX-License-Identifier: Apache-2.0
1
3
  import { dirname, join, relative, resolve } from 'node:path';
2
- import { sessionKey } from '../../core/session-catalogue.js';
4
+ import { noStoreAnywhere, sessionKey } from '../../core/session-catalogue.js';
3
5
  import { splitBySince } from '../../core/session-filter.js';
4
6
  import { recogniseAll } from '../../core/session-titles.js';
7
+ import { isDirectory } from '../../ports/directory-reader.js';
5
8
  import { FileAccessError } from '../../ports/file-access-error.js';
6
9
  import { contentVersion } from '../../shared/content-version.js';
7
10
  import { fileStamp } from '../../shared/file-stamp.js';
@@ -21,6 +24,7 @@ import { indexProjects } from './projects/index-projects.js';
21
24
  import { NOT_A_PROJECT } from '../../setup/project-setup.js';
22
25
  import { notAProject } from '../../setup/not-a-project.js';
23
26
  import { inTemporarySpace } from './projects/temporary-space.js';
27
+ import { inChatFolder } from '../../adapter/codex/contract/chat-folders.js';
24
28
  import { behind } from '../../setup/behind.js';
25
29
  import { modeChange, onlyTakesOut, settingsChangeToSetup } from './serve/settings-setup.js';
26
30
  import { DEFAULT_THRESHOLD } from '../watch/agent-alert.js';
@@ -29,13 +33,15 @@ import { actionsAfterMarks, markLines, marksInRange, standingMarks } from '../ch
29
33
  import { finishOnboarding } from './onboarding/finish-onboarding.js';
30
34
  import { WHO_STEP } from './onboarding/onboarding-script.js';
31
35
  import { repositoryAbove } from './repository.js';
32
- import { pageStopped, startSaid } from './render/start-words.js';
36
+ import { pageStopped, quietlySaid, startSaid } from './render/start-words.js';
33
37
  import { PROJECT_HOOKS } from '../../adapter/codex/contract/hooks.js';
38
+ import { codexCheckState } from '../../adapter/codex/settings/codex-check.js';
34
39
  import { codexRefuseIn } from '../../adapter/codex/settings/codex-hooks.js';
35
40
  import { SETTINGS_FILES } from '../../adapter/claude-code/contract/settings.js';
36
41
  import { rulesReadByHooks } from '../../adapter/claude-code/settings/hook-entries.js';
37
42
  import { fileRulesIn, isDenied } from '../../adapter/claude-code/settings/deny-entries.js';
38
43
  import { fileRulePathOf, toPattern } from '../../adapter/claude-code/policy/deny-rules.js';
44
+ import { nothingSavedHere } from '../nothing-saved-here.js';
39
45
  const DAY = 86_400_000;
40
46
  /**
41
47
  * The patterns the page lists: the ones the policy read, and then any rule the project's own settings files hold
@@ -91,12 +97,19 @@ export class SessionStart {
91
97
  // which-project V16: a run the onboarding's project step switched to opens that project's onboarding, whatever W23
92
98
  // says - the person is in the middle of a setup, and the step chose this project for it.
93
99
  const arrivedFrom = this.#dependencies.arrivedFrom;
94
- const opensWelcome = welcome !== undefined && (arrivedFrom === 'step' || (options.open && welcome.opens));
100
+ // AW1 (`the-address-opens-the-welcome`): an address asked for with `--serve` is opened by a person - the one an AI app
101
+ // sends - so it names the page a browser this run opened would show. Not in an empty project (AW2): every empty
102
+ // listing seen there came from a place that cannot see the person's conversations, whose address nobody can open.
103
+ const anyConversation = listing.found && listing.sessions.length > 0;
104
+ const opensWelcome = welcome !== undefined && (arrivedFrom === 'step' || ((options.open || (options.serve === true && anyConversation)) && welcome.opens));
95
105
  if ((!listing.found || listing.sessions.length === 0) && !opensWelcome) {
96
106
  return {
97
107
  outcome: 'no-sessions',
98
108
  // Every place looked in, one per AI: "looked in the wrong place" is only answerable where each is named.
99
- output: noSessions(listing.searched.map((place) => place.directory).join(' and ') || listing.directory, projectName(workingDirectory), served && options.open, welcome?.setUp === true, notHere),
109
+ // R28, amended 2026-10-01: at a terminal only - a run that would open a page keeps W1a's words, naming no command.
110
+ output: noStoreAnywhere(listing) && notHere === undefined && !(served && options.open)
111
+ ? nothingSavedHere('Claude Code or Codex', listing.searched.map((place) => place.directory).join(' and '), 'use Claude Code or Codex here, then run agentwhy start again - or run agentwhy init now, to be protected in Claude Code before there is history to check.')
112
+ : noSessions(listing.searched.map((place) => place.directory).join(' and ') || listing.directory, projectName(workingDirectory), served && options.open, welcome?.setUp === true, notHere),
100
113
  };
101
114
  }
102
115
  // Resolved against the working directory given, never against whatever the process happens to be standing in.
@@ -125,6 +138,9 @@ export class SessionStart {
125
138
  // F55: a conversation a person once asked to check is read by every run after, as if it were in range - the button is
126
139
  // pressed once, not every morning. A list that cannot be read leaves the run its range, as it always had.
127
140
  const asked = new Set(this.#dependencies.checked === undefined ? [] : (await this.#dependencies.checked.read()).ids);
141
+ // SW10: the conversation a run was asked to open is written however long ago it was active. Not recorded as checked.
142
+ if (options.session !== undefined)
143
+ asked.add(options.session);
128
144
  // M5: each report is drawn with the marks that still hold for its session. A shared page carries none (R37), and a
129
145
  // record that cannot be read leaves every file to do rather than guessing which were done (invariant 4).
130
146
  const standingAtStart = options.share ? new Map() : standingMarks((await this.#dependencies.marks.read()).records);
@@ -160,14 +176,15 @@ export class SessionStart {
160
176
  const { inRange, outOfRange: older } = splitBySince(sessions, options.since.since);
161
177
  const generatedIds = new Set([...inRange, ...older.filter((session) => asked.has(sessionKey(session)))].map((session) => sessionKey(session)));
162
178
  // A title is user content, and a shared page is one that leaves this machine: it is not even read. A session that
163
- // has grown may have a new last prompt, so its title is read again with it. A Codex conversation is named in a file
164
- // of Codex's own, seconds after it starts (`what-codex-wrote` §2.9, X28a) - found by the maintainer, a row drawn in
165
- // those seconds stayed untitled once its rollout stopped changing - so a Codex row with no title asks again; the
166
- // names are a lookup in a list read again only where it changed.
179
+ // has grown may have a new last prompt, so its title is read again with it. Both desktop apps name a conversation
180
+ // in a file of their own, seconds after it starts (`what-codex-wrote` §2.9, X28a; `claude-desktop-conversations`
181
+ // CD5) - found by the maintainer, a row drawn in those seconds stayed untitled once its transcript stopped
182
+ // changing - so a row with no title asks again. That ask is a lookup in the app's own names, read again only
183
+ // where they changed; the adapter keeps an unchanged transcript's tail, so it is not read again (CD5).
167
184
  if (!options.share) {
168
185
  const stale = sessions.filter((session) => {
169
186
  const known = titles.get(sessionKey(session));
170
- return known?.modifiedAt !== session.modifiedAt || (session.provider === 'codex' && known.title === undefined);
187
+ return known?.modifiedAt !== session.modifiedAt || known.title === undefined;
171
188
  });
172
189
  const read_ = await recogniseAll(this.#dependencies.titles, stale);
173
190
  stale.forEach((session, at) => titles.set(sessionKey(session), { modifiedAt: session.modifiedAt, ...read_[at] }));
@@ -365,7 +382,9 @@ export class SessionStart {
365
382
  /*
366
383
  * R75: the conversations and the marks as they are now, drawn into every page. A page read while a refresh runs is
367
384
  * answered by that refresh; a write is not, because the refresh it would join began before the write - it waits,
368
- * then runs again. A listing that finds nothing now (a directory moved, a disk gone) leaves the pages as they were.
385
+ * then runs again. A listing that finds nothing now (a directory moved, a disk gone) leaves the conversations as
386
+ * they were, and the pages are still drawn again: Settings shows what the files hold (R60), in a project with no
387
+ * conversations yet too (W1a).
369
388
  */
370
389
  let refreshing;
371
390
  const refresh = async (when) => {
@@ -375,16 +394,16 @@ export class SessionStart {
375
394
  await refreshing;
376
395
  const running = (async () => {
377
396
  const now_ = await catalogue.list(workingDirectory);
378
- if (!now_.found || now_.sessions.length === 0)
379
- return undefined;
380
- // A policy that cannot be read now leaves the one the pages were drawn under, rather than rules nobody chose.
381
- const again = await choosePolicy(options, policyFiles, this.#dependencies.tell).catch(() => undefined);
382
- if (again !== undefined && !('errors' in again))
383
- policyNow = again.policy;
384
- const refusal = await sync(now_.sessions, standingMarks((await this.#dependencies.marks.read()).records));
385
- if (refusal !== undefined)
386
- return refusal;
387
- reportsServed();
397
+ if (now_.found && now_.sessions.length > 0) {
398
+ // A policy that cannot be read now leaves the one the pages were drawn under, rather than rules nobody chose.
399
+ const again = await choosePolicy(options, policyFiles, this.#dependencies.tell).catch(() => undefined);
400
+ if (again !== undefined && !('errors' in again))
401
+ policyNow = again.policy;
402
+ const refusal = await sync(now_.sessions, standingMarks((await this.#dependencies.marks.read()).records));
403
+ if (refusal !== undefined)
404
+ return refusal;
405
+ reportsServed();
406
+ }
388
407
  await writePages((await build()).index);
389
408
  return undefined;
390
409
  })();
@@ -428,10 +447,14 @@ export class SessionStart {
428
447
  if (chosen !== undefined)
429
448
  return switchToFolder(chosen, from);
430
449
  const project = listed?.projects.find((one) => one.id === id);
431
- if (project === undefined || !project.exists || this.#notAProject(project.path) !== undefined)
450
+ if (project === undefined || project.folder === 'gone' || this.#notAProject(project.path) !== undefined)
432
451
  return { failed: 'That project is not one this page listed.' };
433
452
  if (relative(project.path, workingDirectory) === '')
434
453
  return { failed: 'That project is the one shown already.' };
454
+ // which-project V10b: a folder the system guards was not looked into while listing; it is now, because the person
455
+ // picked it - so the system asks them, if at all, about something they did.
456
+ if (project.folder === 'not-looked' && !(await isDirectory(this.#dependencies.directories, project.path)))
457
+ return { failed: 'This folder isn’t there anymore.' };
435
458
  return switchToFolder(project.path, from);
436
459
  };
437
460
  // V12: a folder chosen in the computer's window - shown at once where it is a project or near none, and where it lies
@@ -520,16 +543,23 @@ export class SessionStart {
520
543
  },
521
544
  })
522
545
  : undefined;
546
+ // PF2: the address and its token, where a later `--detach` looks for a server to reuse. Never a shared page's.
547
+ if (serving !== undefined && !options.share)
548
+ await this.#dependencies.pageServer?.write(serving.urlOf(''));
523
549
  // W23: the onboarding is opened in place of the index where it is to be; the index otherwise, as it always was. A run
524
550
  // a page switched to opens it at *Who*: the project was chosen on the way here (which-project V16).
525
- const openAt = serving === undefined ? indexPath
526
- : opensWelcome && onboarding !== undefined ? serving.urlOf(onboarding.file) + (arrivedFrom === undefined ? '' : `#${WHO_STEP}`)
527
- : serving.url;
551
+ // SW10: a conversation asked for by name is opened in its place - the person said yes to that report, not to a setup.
552
+ const wanted = options.session === undefined ? undefined : reports.get(options.session);
553
+ const sessionFile = wanted !== undefined && wanted.report.kind === 'generated' ? wanted.file : undefined;
554
+ const openAt = sessionFile !== undefined ? (serving === undefined ? join(out, sessionFile) : serving.urlOf(sessionFile))
555
+ : serving === undefined ? indexPath
556
+ : opensWelcome && onboarding !== undefined ? serving.urlOf(onboarding.file) + (arrivedFrom === undefined ? '' : `#${WHO_STEP}`)
557
+ : serving.url;
528
558
  // V15: a run a page switched to is shown in that page's tab, so it opens none of its own.
529
559
  const handedOver = this.#dependencies.handedOver;
530
560
  const opened = options.open && handedOver === undefined ? await this.#dependencies.browser.open(openAt) : true;
531
561
  const view = this.#dependencies.terminal ?? { colour: false, decorated: false };
532
- const said = startSaid({
562
+ const said = options.quiet === true ? quietlySaid(openAt, opened && options.open, options.session === undefined || sessionFile !== undefined) : startSaid({
533
563
  ...counted(entries, outOfRange.length, indexPath, first.index, opened),
534
564
  // which-project V3: the folder the run was for, said where the terminal says what it read.
535
565
  ...(options.share ? {} : { project: projectName(workingDirectory) }),
@@ -540,7 +570,8 @@ export class SessionStart {
540
570
  ? { servedAsFile: true }
541
571
  : {}),
542
572
  ...(serving === undefined ? {} : { url: openAt }),
543
- ...(serving !== undefined && opensWelcome ? { welcome: true } : {}),
573
+ // AW3: "Opened" only where a browser or a page's tab shows it; an address handed on is said as one.
574
+ ...(serving !== undefined && opensWelcome ? { welcome: options.open || handedOver !== undefined ? 'opened' : 'served' } : {}),
544
575
  // which-project V3: a switch says which project is shown now, and where it is.
545
576
  ...(handedOver === undefined || options.share ? {} : { nowShowing: this.#dependencies.home === undefined ? projectName(workingDirectory) : `${projectName(workingDirectory)} (${homeRelative(workingDirectory, this.#dependencies.home)})` }),
546
577
  }, view);
@@ -550,6 +581,7 @@ export class SessionStart {
550
581
  printer?.write(said);
551
582
  handedOver?.(openAt);
552
583
  await serving.closed;
584
+ await this.#dependencies.pageServer?.remove();
553
585
  // V14: a run a page moved away from ends quietly - the run it moved to has already said what it shows.
554
586
  if (switchedAway)
555
587
  return { outcome: 'written', output: '' };
@@ -690,7 +722,8 @@ export class SessionStart {
690
722
  return {};
691
723
  const listing = await projects.list();
692
724
  const noProject = (path) => this.#notAProject(path) !== undefined;
693
- const temporary = (path) => inTemporarySpace(path, this.#dependencies.temporaryDirectory, home);
725
+ // V10b: a folder the ChatGPT app made for a chat with no project is a quick try too, and counted with them.
726
+ const temporary = (path) => inTemporarySpace(path, this.#dependencies.temporaryDirectory, home) || inChatFolder(path, home);
694
727
  // V20: a folder with no conversations of its own, inside a project that has them - where they are.
695
728
  const near = nearestProject(workingDirectory, listing, noProject);
696
729
  const above = near.kind === 'above' ? { id: near.project.id, within: relative(near.project.path, workingDirectory) } : undefined;
@@ -788,9 +821,16 @@ export class SessionStart {
788
821
  // U2: the version is this run's own, read once; the hooks are read from the files as they are now.
789
822
  this.#serving ??= this.#dependencies.invocation?.version() ?? Promise.resolve(undefined);
790
823
  const late = behind({ ...(local === undefined ? {} : { local }), ...(readable === undefined ? {} : { shared: readable }) }, await this.#serving);
791
- // CK8: Codex's hook file, read as the settings are; its state is drawn only where the project uses Codex (CK6).
824
+ // AO3: Codex's state is read, never assumed - `on` only where agentwhy's check in the person's own Codex files is
825
+ // verified as approved; `stale` where a check is written somewhere (the person's files unverified, or the old
826
+ // project-level copy) and Codex may still ask; `off` where the project uses Codex (CK6) and nothing is written.
792
827
  const codexHooks = await this.#settingsFile(join(workingDirectory, PROJECT_HOOKS.directory, PROJECT_HOOKS.file));
793
- const codex = typeof codexHooks === 'object' && codexRefuseIn(codexHooks) !== undefined ? 'on' : codexListed || codexHooks !== undefined ? 'off' : undefined;
828
+ const check = this.#dependencies.home === undefined ? 'absent' : await codexCheckState(this.#dependencies.policyFiles, this.#dependencies.home);
829
+ const legacy = typeof codexHooks === 'object' && codexRefuseIn(codexHooks) !== undefined;
830
+ const codex = check === 'on' ? 'on'
831
+ : check === 'stale' || legacy ? 'stale'
832
+ : codexListed || codexHooks !== undefined || (await this.#dependencies.codexOnThisComputer?.()) === true ? 'off'
833
+ : undefined;
794
834
  return {
795
835
  hooks: {
796
836
  watch: installed('watch'),
@@ -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 { textButton } from '../../render/ui/button.js';
@@ -78,7 +80,8 @@ function lockedSwitch(key, turnOn) {
78
80
  (turnOn === undefined ? '' : textButton(inLanguages((t) => t('set.need.go')), turnOn, 'set-tip-go')) +
79
81
  '</div></div>';
80
82
  }
81
- function stateLine(state) {
83
+ /** A switch's state in words, under its row: shared with General's system notifications (SW13). */
84
+ export function stateLine(state) {
82
85
  return '<div class="set-state' + (state.on ? ' set-state-on' : '') + '"><span class="set-dot" aria-hidden="true"></span>' +
83
86
  inLanguages((t) => t(state.key)) + '</div>';
84
87
  }