@agentwhy/cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +57 -5
- package/dist/adapter/claude-code/contract/assumptions.js +16 -4
- package/dist/adapter/claude-code/contract/denials.js +16 -1
- package/dist/adapter/claude-code/contract/desktop-sessions.js +28 -0
- package/dist/adapter/claude-code/contract/entry-points.js +6 -1
- package/dist/adapter/claude-code/contract/fields.js +2 -0
- package/dist/adapter/claude-code/contract/hooks.js +8 -0
- package/dist/adapter/claude-code/contract/identifiers.js +2 -0
- package/dist/adapter/claude-code/contract/layout.js +2 -0
- package/dist/adapter/claude-code/contract/line-types.js +2 -0
- package/dist/adapter/claude-code/contract/projects.js +2 -0
- package/dist/adapter/claude-code/contract/recognition.js +2 -0
- package/dist/adapter/claude-code/contract/settings.js +2 -0
- package/dist/adapter/claude-code/contract/task-notifications.js +2 -0
- package/dist/adapter/claude-code/contract/version.js +14 -1
- package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +12 -12
- package/dist/adapter/claude-code/discovery/claude-code-recognition.js +2 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +28 -7
- package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +2 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +30 -6
- package/dist/adapter/claude-code/discovery/claude-desktop-titles.js +102 -0
- package/dist/adapter/claude-code/discovery/session-entries.js +2 -0
- package/dist/adapter/claude-code/events/delegation-index.js +2 -0
- package/dist/adapter/claude-code/events/transcript-scan.js +6 -5
- package/dist/adapter/claude-code/hooks/hook-output.js +2 -0
- package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +2 -0
- package/dist/adapter/claude-code/hooks/stop-input.js +2 -0
- package/dist/adapter/claude-code/hooks/subagent-stop-input.js +4 -0
- package/dist/adapter/claude-code/probe/claude-code-probe.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +2 -0
- package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +2 -0
- package/dist/adapter/claude-code/probe/meta-tally.js +2 -0
- package/dist/adapter/claude-code/probe/transcript-line.js +2 -0
- package/dist/adapter/claude-code/probe/transcript-tally.js +2 -0
- package/dist/adapter/claude-code/settings/deny-entries.js +2 -0
- package/dist/adapter/claude-code/settings/hook-entries.js +9 -0
- package/dist/adapter/claude-code/settings/refuse-project.js +99 -0
- package/dist/adapter/codex/contract/actions.js +2 -0
- package/dist/adapter/codex/contract/assumptions.js +2 -0
- package/dist/adapter/codex/contract/chat-folders.js +19 -0
- package/dist/adapter/codex/contract/delegations.js +2 -0
- package/dist/adapter/codex/contract/deliveries.js +2 -0
- package/dist/adapter/codex/contract/envelope.js +2 -0
- package/dist/adapter/codex/contract/hook-refusals.js +2 -0
- package/dist/adapter/codex/contract/hooks.js +51 -1
- package/dist/adapter/codex/contract/messages.js +2 -0
- package/dist/adapter/codex/contract/permissions.js +2 -0
- package/dist/adapter/codex/contract/reviews.js +2 -0
- package/dist/adapter/codex/contract/session.js +29 -1
- package/dist/adapter/codex/contract/turns.js +2 -0
- package/dist/adapter/codex/contract/version.js +2 -0
- package/dist/adapter/codex/discovery/codex-on-this-computer.js +13 -0
- package/dist/adapter/codex/discovery/codex-project-catalogue.js +2 -12
- package/dist/adapter/codex/discovery/codex-session-catalogue.js +2 -1
- package/dist/adapter/codex/discovery/codex-session-discovery.js +2 -0
- package/dist/adapter/codex/discovery/session-owners.js +2 -0
- package/dist/adapter/codex/events/action-items.js +2 -0
- package/dist/adapter/codex/events/codex-session-source.js +2 -0
- package/dist/adapter/codex/events/hook-refusals.js +2 -0
- package/dist/adapter/codex/events/rollout-scan.js +1 -1
- package/dist/adapter/codex/hooks/codex-conversation.js +54 -0
- package/dist/adapter/codex/hooks/pre-tool-use-input.js +2 -0
- package/dist/adapter/codex/hooks/reader.js +38 -0
- package/dist/adapter/codex/hooks/stop-input.js +32 -0
- package/dist/adapter/codex/hooks/stop-refusals.js +3 -6
- package/dist/adapter/codex/probe/codex-probe.js +2 -0
- package/dist/adapter/codex/settings/codex-check.js +38 -0
- package/dist/adapter/codex/settings/codex-hooks.js +168 -1
- package/dist/adapter/codex/settings/hook-approval.js +374 -0
- package/dist/cli/command-router.js +8 -2
- package/dist/cli/commands/check-cli-command.js +2 -0
- package/dist/cli/commands/codex-stop-cli-command.js +15 -12
- package/dist/cli/commands/doctor-arguments.js +2 -0
- package/dist/cli/commands/doctor-usage.js +2 -0
- package/dist/cli/commands/init-cli-command.js +10 -5
- package/dist/cli/commands/notify-cli-command.js +3 -1
- package/dist/cli/commands/refuse-cli-command.js +9 -4
- package/dist/cli/commands/report-arguments.js +2 -0
- package/dist/cli/commands/report-usage.js +2 -0
- package/dist/cli/commands/sessions-cli-command.js +9 -1
- package/dist/cli/commands/start-cli-command.js +29 -2
- package/dist/cli/commands/watch-cli-command.js +4 -2
- package/dist/cli/exit-codes.js +2 -0
- package/dist/cli.js +12 -1
- package/dist/composition-root.js +101 -16
- package/dist/core/access/command-line.js +48 -2
- package/dist/core/access/listing.js +2 -0
- package/dist/core/access/path-tokens.js +2 -0
- package/dist/core/access/protected-access.js +36 -3
- package/dist/core/access/protected-values.js +17 -0
- package/dist/core/access/search-output.js +2 -0
- package/dist/core/access/search-reach.js +2 -0
- package/dist/core/combined-project-catalogue.js +7 -1
- package/dist/core/correlation/correlate.js +13 -1
- package/dist/core/detector/prompt-signals.js +2 -0
- package/dist/core/guarded-places.js +42 -0
- package/dist/core/nearest-project.js +5 -2
- package/dist/core/policy/glob.js +2 -0
- package/dist/core/policy/parse-policy.js +2 -0
- package/dist/core/policy/policy.js +14 -2
- package/dist/core/policy/resolve-policy.js +2 -0
- package/dist/core/redaction/entropy.js +2 -0
- package/dist/core/redaction/redactor.js +2 -0
- package/dist/core/redaction/value-shapes.js +2 -0
- package/dist/core/redaction/value-trace.js +2 -0
- package/dist/core/session-catalogue.js +7 -0
- package/dist/core/session-filter.js +2 -0
- package/dist/infrastructure/clack-asker.js +2 -0
- package/dist/infrastructure/clack-chooser.js +2 -0
- package/dist/infrastructure/clack-multi-chooser.js +2 -0
- package/dist/infrastructure/file-alert-store.js +10 -3
- package/dist/infrastructure/file-checked-store.js +2 -0
- package/dist/infrastructure/file-mark-store.js +2 -0
- package/dist/infrastructure/file-onboarding-store.js +2 -0
- package/dist/infrastructure/file-page-servers.js +51 -0
- package/dist/infrastructure/folder-window.js +2 -0
- package/dist/infrastructure/node-agentwhy-invocation.js +2 -0
- package/dist/infrastructure/node-background-run.js +45 -0
- package/dist/infrastructure/node-browser.js +2 -0
- package/dist/infrastructure/node-file-system.js +42 -1
- package/dist/infrastructure/node-local-server.js +2 -0
- package/dist/infrastructure/node-page-probe.js +40 -0
- package/dist/infrastructure/os-notifier.js +2 -0
- package/dist/infrastructure/real-directory.js +2 -0
- package/dist/infrastructure/shell-command-trial.js +60 -0
- package/dist/infrastructure/terminal-banner.js +2 -0
- package/dist/ports/command-trial.js +1 -0
- package/dist/ports/directory-reader.js +14 -1
- package/dist/ports/file-replacer.js +1 -0
- package/dist/ports/mark-store.js +2 -0
- package/dist/ports/page-server.js +1 -0
- package/dist/refuse/command-reach.js +2 -0
- package/dist/refuse/command-refusal.js +19 -30
- package/dist/refuse/render/refusal-words.js +3 -1
- package/dist/report/build-report.js +59 -11
- package/dist/report/check/actions-digest.js +3 -0
- package/dist/report/check/check-lines.js +2 -1
- package/dist/report/check/render/text-digest-renderer.js +31 -9
- package/dist/report/check/session-actions.js +4 -6
- package/dist/report/check/session-check.js +8 -0
- package/dist/report/choose-policy.js +7 -1
- package/dist/report/nothing-saved-here.js +16 -0
- package/dist/report/private-files/tell-lists.js +2 -0
- package/dist/report/project-rules.js +2 -0
- package/dist/report/refusals.js +15 -0
- package/dist/report/render/home-relative.js +2 -0
- package/dist/report/render/html-head.js +2 -0
- package/dist/report/render/html-report-styles.js +2 -0
- package/dist/report/render/html-sidebar.js +2 -0
- package/dist/report/render/path-tail.js +2 -0
- package/dist/report/render/report-copy.js +2 -0
- package/dist/report/render/report-page/advanced-view.js +2 -0
- package/dist/report/render/report-page/files-view.js +2 -0
- package/dist/report/render/report-page/files.js +2 -0
- package/dist/report/render/report-page/fix-wizard-script.js +2 -0
- package/dist/report/render/report-page/helpers-view.js +2 -0
- package/dist/report/render/report-page/item-names.js +2 -0
- package/dist/report/render/report-page/protect-patterns.js +2 -0
- package/dist/report/render/report-page/report-views.js +2 -0
- package/dist/report/render/report-page/to-do-view.js +3 -2
- package/dist/report/render/text-report-renderer.js +2 -0
- package/dist/report/render/ui/add-file-popup.js +2 -0
- package/dist/report/render/ui/app-sidebar.js +2 -0
- package/dist/report/render/ui/ask-panel.js +2 -0
- package/dist/report/render/ui/avatar.js +2 -0
- package/dist/report/render/ui/backdrop.js +2 -0
- package/dist/report/render/ui/brand-mark.js +2 -0
- package/dist/report/render/ui/checklist.js +2 -0
- package/dist/report/render/ui/confirm-dialog.js +2 -0
- package/dist/report/render/ui/drawer.js +2 -0
- package/dist/report/render/ui/file-chip.js +2 -0
- package/dist/report/render/ui/fold-line.js +2 -0
- package/dist/report/render/ui/guide-card.js +2 -0
- package/dist/report/render/ui/live-script.js +2 -0
- package/dist/report/render/ui/page-shell.js +2 -0
- package/dist/report/render/ui/popup.js +2 -0
- package/dist/report/render/ui/progress.js +2 -0
- package/dist/report/render/ui/project-list.js +9 -9
- package/dist/report/render/ui/stats.js +2 -0
- package/dist/report/render/ui/status-icon.js +2 -0
- package/dist/report/render/ui/status-look.js +2 -0
- package/dist/report/render/ui/task-list.js +2 -0
- package/dist/report/render/ui/tokens.js +2 -0
- package/dist/report/render/ui/update-notice.js +2 -0
- package/dist/report/render/ui/words/app-words.js +3 -3
- package/dist/report/render/ui/words/onboarding-words.js +9 -9
- package/dist/report/render/ui/words/projects-words.js +3 -3
- package/dist/report/render/ui/words/report-words.js +11 -11
- package/dist/report/render/ui/words/settings-words.js +30 -6
- package/dist/report/rule-names.js +2 -0
- package/dist/report/session-report.js +2 -0
- package/dist/report/start/conversations/calendar-view.js +2 -0
- package/dist/report/start/conversations/conversation-columns.js +2 -0
- package/dist/report/start/conversations/empty-week.js +2 -0
- package/dist/report/start/conversations/files-window.js +2 -0
- package/dist/report/start/conversations/period-section.js +2 -0
- package/dist/report/start/conversations/periods-script.js +2 -0
- package/dist/report/start/conversations/periods.js +2 -0
- package/dist/report/start/conversations/week-view.js +2 -0
- package/dist/report/start/conversations/weeks.js +2 -0
- package/dist/report/start/detached-start.js +94 -0
- package/dist/report/start/month/month-view.js +2 -0
- package/dist/report/start/onboarding/done.js +9 -3
- package/dist/report/start/onboarding/finish-onboarding.js +6 -4
- package/dist/report/start/onboarding/intro.js +2 -0
- package/dist/report/start/onboarding/onboarding-renderer.js +4 -4
- package/dist/report/start/onboarding/onboarding-script.js +2 -0
- package/dist/report/start/onboarding/onboarding-view.js +1 -1
- package/dist/report/start/onboarding/project-step.js +1 -1
- package/dist/report/start/onboarding/steps.js +2 -0
- package/dist/report/start/onboarding/welcome-decision.js +44 -0
- package/dist/report/start/onboarding/welcome.js +2 -0
- package/dist/report/start/page-notice.js +2 -0
- package/dist/report/start/projects/index-projects.js +5 -2
- package/dist/report/start/projects/moved-page.js +2 -0
- package/dist/report/start/projects/project-switch.js +2 -0
- package/dist/report/start/projects/projects-window.js +2 -0
- package/dist/report/start/projects/temporary-space.js +2 -0
- package/dist/report/start/render/start-words.js +21 -2
- package/dist/report/start/repository.js +2 -0
- package/dist/report/start/serve/index-handler.js +4 -1
- package/dist/report/start/serve/settings-setup.js +10 -0
- package/dist/report/start/session-start.js +80 -55
- package/dist/report/start/settings/alerts-tab.js +4 -1
- package/dist/report/start/settings/files-tab.js +18 -12
- package/dist/report/start/settings/general-tab.js +33 -1
- package/dist/report/start/settings/settings-renderer.js +2 -0
- package/dist/report/start/settings/settings-script.js +8 -1
- package/dist/report/start/settings/settings-view.js +29 -2
- package/dist/report/start/settings/settings-windows.js +16 -5
- package/dist/report/start/settings-files.js +2 -0
- package/dist/report/start/to-fix/done-list.js +2 -0
- package/dist/report/start/to-fix/file-window.js +2 -0
- package/dist/report/start/to-fix/to-fix-list.js +2 -0
- package/dist/report/start/to-fix/to-fix-renderer.js +2 -0
- package/dist/report/start/to-fix/to-fix-script.js +2 -0
- package/dist/report/watch/agent-alert.js +3 -0
- package/dist/report/watch/codex-turn-format.js +69 -0
- package/dist/report/watch/notice-choices.js +2 -0
- package/dist/report/watch/notice-settings.js +2 -0
- package/dist/report/watch/preferences.js +2 -0
- package/dist/report/watch/render/codex-request-words.js +106 -0
- package/dist/report/watch/render/instruction-words.js +114 -12
- package/dist/report/watch/render/notice-words.js +74 -4
- package/dist/report/watch/subagent-watch.js +270 -45
- package/dist/setup/behind.js +2 -0
- package/dist/setup/codex-mirror.js +311 -104
- package/dist/setup/not-a-project.js +2 -0
- package/dist/setup/project-setup.js +2 -0
- package/dist/shared/compare.js +2 -0
- package/dist/shared/content-version.js +2 -0
- package/dist/shared/counter.js +2 -0
- package/dist/shared/file-stamp.js +2 -0
- package/dist/shared/label.js +2 -0
- package/dist/shared/package-name.js +2 -0
- package/dist/shared/plain-invocation.js +2 -0
- package/dist/shared/printable.js +2 -0
- package/dist/shared/sentence.js +2 -0
- package/dist/shared/terminal-logo.js +2 -0
- package/dist/shared/text-table.js +2 -0
- package/dist/shared/wrap.js +2 -0
- package/package.json +2 -1
- package/dist/refuse/render/codex-stop-words.js +0 -21
|
@@ -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';
|
|
@@ -16,26 +19,30 @@ import { indexHandler } from './serve/index-handler.js';
|
|
|
16
19
|
import { movedPage } from './projects/moved-page.js';
|
|
17
20
|
import { nearestProject } from '../../core/nearest-project.js';
|
|
18
21
|
import { translator } from '../render/report-copy.js';
|
|
19
|
-
import { hookComplete, readSettingsFile
|
|
22
|
+
import { hookComplete, readSettingsFile } from './settings-files.js';
|
|
20
23
|
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';
|
|
27
31
|
import { DEFAULT_CHANNELS, DEFAULT_CLEAN, DEFAULT_SAID_AS } from '../watch/notice-choices.js';
|
|
28
32
|
import { actionsAfterMarks, markLines, marksInRange, standingMarks } from '../check/marks.js';
|
|
29
33
|
import { finishOnboarding } from './onboarding/finish-onboarding.js';
|
|
34
|
+
import { welcomeIn } from './onboarding/welcome-decision.js';
|
|
30
35
|
import { WHO_STEP } from './onboarding/onboarding-script.js';
|
|
31
36
|
import { repositoryAbove } from './repository.js';
|
|
32
|
-
import { pageStopped, startSaid } from './render/start-words.js';
|
|
37
|
+
import { pageStopped, quietlySaid, startSaid } from './render/start-words.js';
|
|
33
38
|
import { PROJECT_HOOKS } from '../../adapter/codex/contract/hooks.js';
|
|
39
|
+
import { codexCheckState } from '../../adapter/codex/settings/codex-check.js';
|
|
34
40
|
import { codexRefuseIn } from '../../adapter/codex/settings/codex-hooks.js';
|
|
35
41
|
import { SETTINGS_FILES } from '../../adapter/claude-code/contract/settings.js';
|
|
36
42
|
import { rulesReadByHooks } from '../../adapter/claude-code/settings/hook-entries.js';
|
|
37
43
|
import { fileRulesIn, isDenied } from '../../adapter/claude-code/settings/deny-entries.js';
|
|
38
44
|
import { fileRulePathOf, toPattern } from '../../adapter/claude-code/policy/deny-rules.js';
|
|
45
|
+
import { nothingSavedHere } from '../nothing-saved-here.js';
|
|
39
46
|
const DAY = 86_400_000;
|
|
40
47
|
/**
|
|
41
48
|
* The patterns the page lists: the ones the policy read, and then any rule the project's own settings files hold
|
|
@@ -91,12 +98,19 @@ export class SessionStart {
|
|
|
91
98
|
// which-project V16: a run the onboarding's project step switched to opens that project's onboarding, whatever W23
|
|
92
99
|
// says - the person is in the middle of a setup, and the step chose this project for it.
|
|
93
100
|
const arrivedFrom = this.#dependencies.arrivedFrom;
|
|
94
|
-
|
|
101
|
+
// AW1 (`the-address-opens-the-welcome`): an address asked for with `--serve` is opened by a person - the one an AI app
|
|
102
|
+
// sends - so it names the page a browser this run opened would show. Not in an empty project (AW2): every empty
|
|
103
|
+
// listing seen there came from a place that cannot see the person's conversations, whose address nobody can open.
|
|
104
|
+
const anyConversation = listing.found && listing.sessions.length > 0;
|
|
105
|
+
const opensWelcome = welcome !== undefined && (arrivedFrom === 'step' || ((options.open || (options.serve === true && anyConversation)) && welcome.opens));
|
|
95
106
|
if ((!listing.found || listing.sessions.length === 0) && !opensWelcome) {
|
|
96
107
|
return {
|
|
97
108
|
outcome: 'no-sessions',
|
|
98
109
|
// Every place looked in, one per AI: "looked in the wrong place" is only answerable where each is named.
|
|
99
|
-
|
|
110
|
+
// R28, amended 2026-10-01: at a terminal only - a run that would open a page keeps W1a's words, naming no command.
|
|
111
|
+
output: noStoreAnywhere(listing) && notHere === undefined && !(served && options.open)
|
|
112
|
+
? 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.')
|
|
113
|
+
: noSessions(listing.searched.map((place) => place.directory).join(' and ') || listing.directory, projectName(workingDirectory), served && options.open, welcome?.setUp === true, notHere),
|
|
100
114
|
};
|
|
101
115
|
}
|
|
102
116
|
// Resolved against the working directory given, never against whatever the process happens to be standing in.
|
|
@@ -125,6 +139,9 @@ export class SessionStart {
|
|
|
125
139
|
// 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
140
|
// pressed once, not every morning. A list that cannot be read leaves the run its range, as it always had.
|
|
127
141
|
const asked = new Set(this.#dependencies.checked === undefined ? [] : (await this.#dependencies.checked.read()).ids);
|
|
142
|
+
// SW10: the conversation a run was asked to open is written however long ago it was active. Not recorded as checked.
|
|
143
|
+
if (options.session !== undefined)
|
|
144
|
+
asked.add(options.session);
|
|
128
145
|
// M5: each report is drawn with the marks that still hold for its session. A shared page carries none (R37), and a
|
|
129
146
|
// record that cannot be read leaves every file to do rather than guessing which were done (invariant 4).
|
|
130
147
|
const standingAtStart = options.share ? new Map() : standingMarks((await this.#dependencies.marks.read()).records);
|
|
@@ -160,14 +177,15 @@ export class SessionStart {
|
|
|
160
177
|
const { inRange, outOfRange: older } = splitBySince(sessions, options.since.since);
|
|
161
178
|
const generatedIds = new Set([...inRange, ...older.filter((session) => asked.has(sessionKey(session)))].map((session) => sessionKey(session)));
|
|
162
179
|
// 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.
|
|
164
|
-
// of
|
|
165
|
-
//
|
|
166
|
-
//
|
|
180
|
+
// has grown may have a new last prompt, so its title is read again with it. Both desktop apps name a conversation
|
|
181
|
+
// in a file of their own, seconds after it starts (`what-codex-wrote` §2.9, X28a; `claude-desktop-conversations`
|
|
182
|
+
// CD5) - found by the maintainer, a row drawn in those seconds stayed untitled once its transcript stopped
|
|
183
|
+
// changing - so a row with no title asks again. That ask is a lookup in the app's own names, read again only
|
|
184
|
+
// where they changed; the adapter keeps an unchanged transcript's tail, so it is not read again (CD5).
|
|
167
185
|
if (!options.share) {
|
|
168
186
|
const stale = sessions.filter((session) => {
|
|
169
187
|
const known = titles.get(sessionKey(session));
|
|
170
|
-
return known?.modifiedAt !== session.modifiedAt ||
|
|
188
|
+
return known?.modifiedAt !== session.modifiedAt || known.title === undefined;
|
|
171
189
|
});
|
|
172
190
|
const read_ = await recogniseAll(this.#dependencies.titles, stale);
|
|
173
191
|
stale.forEach((session, at) => titles.set(sessionKey(session), { modifiedAt: session.modifiedAt, ...read_[at] }));
|
|
@@ -365,7 +383,9 @@ export class SessionStart {
|
|
|
365
383
|
/*
|
|
366
384
|
* R75: the conversations and the marks as they are now, drawn into every page. A page read while a refresh runs is
|
|
367
385
|
* 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
|
|
386
|
+
* then runs again. A listing that finds nothing now (a directory moved, a disk gone) leaves the conversations as
|
|
387
|
+
* they were, and the pages are still drawn again: Settings shows what the files hold (R60), in a project with no
|
|
388
|
+
* conversations yet too (W1a).
|
|
369
389
|
*/
|
|
370
390
|
let refreshing;
|
|
371
391
|
const refresh = async (when) => {
|
|
@@ -375,16 +395,16 @@ export class SessionStart {
|
|
|
375
395
|
await refreshing;
|
|
376
396
|
const running = (async () => {
|
|
377
397
|
const now_ = await catalogue.list(workingDirectory);
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
398
|
+
if (now_.found && now_.sessions.length > 0) {
|
|
399
|
+
// A policy that cannot be read now leaves the one the pages were drawn under, rather than rules nobody chose.
|
|
400
|
+
const again = await choosePolicy(options, policyFiles, this.#dependencies.tell).catch(() => undefined);
|
|
401
|
+
if (again !== undefined && !('errors' in again))
|
|
402
|
+
policyNow = again.policy;
|
|
403
|
+
const refusal = await sync(now_.sessions, standingMarks((await this.#dependencies.marks.read()).records));
|
|
404
|
+
if (refusal !== undefined)
|
|
405
|
+
return refusal;
|
|
406
|
+
reportsServed();
|
|
407
|
+
}
|
|
388
408
|
await writePages((await build()).index);
|
|
389
409
|
return undefined;
|
|
390
410
|
})();
|
|
@@ -428,10 +448,14 @@ export class SessionStart {
|
|
|
428
448
|
if (chosen !== undefined)
|
|
429
449
|
return switchToFolder(chosen, from);
|
|
430
450
|
const project = listed?.projects.find((one) => one.id === id);
|
|
431
|
-
if (project === undefined ||
|
|
451
|
+
if (project === undefined || project.folder === 'gone' || this.#notAProject(project.path) !== undefined)
|
|
432
452
|
return { failed: 'That project is not one this page listed.' };
|
|
433
453
|
if (relative(project.path, workingDirectory) === '')
|
|
434
454
|
return { failed: 'That project is the one shown already.' };
|
|
455
|
+
// which-project V10b: a folder the system guards was not looked into while listing; it is now, because the person
|
|
456
|
+
// picked it - so the system asks them, if at all, about something they did.
|
|
457
|
+
if (project.folder === 'not-looked' && !(await isDirectory(this.#dependencies.directories, project.path)))
|
|
458
|
+
return { failed: 'This folder isn’t there anymore.' };
|
|
435
459
|
return switchToFolder(project.path, from);
|
|
436
460
|
};
|
|
437
461
|
// 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 +544,23 @@ export class SessionStart {
|
|
|
520
544
|
},
|
|
521
545
|
})
|
|
522
546
|
: undefined;
|
|
547
|
+
// PF2: the address and its token, where a later `--detach` looks for a server to reuse. Never a shared page's.
|
|
548
|
+
if (serving !== undefined && !options.share)
|
|
549
|
+
await this.#dependencies.pageServer?.write(serving.urlOf(''));
|
|
523
550
|
// 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
551
|
// a page switched to opens it at *Who*: the project was chosen on the way here (which-project V16).
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
552
|
+
// SW10: a conversation asked for by name is opened in its place - the person said yes to that report, not to a setup.
|
|
553
|
+
const wanted = options.session === undefined ? undefined : reports.get(options.session);
|
|
554
|
+
const sessionFile = wanted !== undefined && wanted.report.kind === 'generated' ? wanted.file : undefined;
|
|
555
|
+
const openAt = sessionFile !== undefined ? (serving === undefined ? join(out, sessionFile) : serving.urlOf(sessionFile))
|
|
556
|
+
: serving === undefined ? indexPath
|
|
557
|
+
: opensWelcome && onboarding !== undefined ? serving.urlOf(onboarding.file) + (arrivedFrom === undefined ? '' : `#${WHO_STEP}`)
|
|
558
|
+
: serving.url;
|
|
528
559
|
// V15: a run a page switched to is shown in that page's tab, so it opens none of its own.
|
|
529
560
|
const handedOver = this.#dependencies.handedOver;
|
|
530
561
|
const opened = options.open && handedOver === undefined ? await this.#dependencies.browser.open(openAt) : true;
|
|
531
562
|
const view = this.#dependencies.terminal ?? { colour: false, decorated: false };
|
|
532
|
-
const said = startSaid({
|
|
563
|
+
const said = options.quiet === true ? quietlySaid(openAt, opened && options.open, options.session === undefined || sessionFile !== undefined) : startSaid({
|
|
533
564
|
...counted(entries, outOfRange.length, indexPath, first.index, opened),
|
|
534
565
|
// which-project V3: the folder the run was for, said where the terminal says what it read.
|
|
535
566
|
...(options.share ? {} : { project: projectName(workingDirectory) }),
|
|
@@ -540,7 +571,8 @@ export class SessionStart {
|
|
|
540
571
|
? { servedAsFile: true }
|
|
541
572
|
: {}),
|
|
542
573
|
...(serving === undefined ? {} : { url: openAt }),
|
|
543
|
-
|
|
574
|
+
// AW3: "Opened" only where a browser or a page's tab shows it; an address handed on is said as one.
|
|
575
|
+
...(serving !== undefined && opensWelcome ? { welcome: options.open || handedOver !== undefined ? 'opened' : 'served' } : {}),
|
|
544
576
|
// which-project V3: a switch says which project is shown now, and where it is.
|
|
545
577
|
...(handedOver === undefined || options.share ? {} : { nowShowing: this.#dependencies.home === undefined ? projectName(workingDirectory) : `${projectName(workingDirectory)} (${homeRelative(workingDirectory, this.#dependencies.home)})` }),
|
|
546
578
|
}, view);
|
|
@@ -550,6 +582,7 @@ export class SessionStart {
|
|
|
550
582
|
printer?.write(said);
|
|
551
583
|
handedOver?.(openAt);
|
|
552
584
|
await serving.closed;
|
|
585
|
+
await this.#dependencies.pageServer?.remove();
|
|
553
586
|
// V14: a run a page moved away from ends quietly - the run it moved to has already said what it shows.
|
|
554
587
|
if (switchedAway)
|
|
555
588
|
return { outcome: 'written', output: '' };
|
|
@@ -652,33 +685,17 @@ export class SessionStart {
|
|
|
652
685
|
* alerts off later never brings the onboarding back.
|
|
653
686
|
*/
|
|
654
687
|
async #welcome(workingDirectory) {
|
|
655
|
-
const { onboarding, setup, notices } = this.#dependencies;
|
|
688
|
+
const { onboarding, setup, notices, policyFiles, home, realHome } = this.#dependencies;
|
|
689
|
+
// The onboarding writes through both (W15), so a run without them names no page it could not finish.
|
|
656
690
|
if (onboarding === undefined || setup === undefined || notices === undefined)
|
|
657
691
|
return undefined;
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
return { opens: true, intro, setUp: false, ...(intro ? {} : { atProject: true }) };
|
|
666
|
-
if (reading.here)
|
|
667
|
-
return { opens: false, intro, setUp: true };
|
|
668
|
-
if (reading.failed)
|
|
669
|
-
return { opens: false, intro, setUp: false };
|
|
670
|
-
// A local file that cannot be read leaves what runs unknown, and the onboarding closed (as `#settingsNow` does).
|
|
671
|
-
const local = await this.#settingsFile(join(workingDirectory, SETTINGS_FILES.directory, SETTINGS_FILES.local));
|
|
672
|
-
if (local === 'unreadable')
|
|
673
|
-
return { opens: false, intro, setUp: false };
|
|
674
|
-
const shared = await this.#settingsFile(join(workingDirectory, SETTINGS_FILES.directory, SETTINGS_FILES.shared));
|
|
675
|
-
// N6, widened 2026-09-28 by the maintainer (which-project V10): a project where one of agentwhy's hooks runs, or whose
|
|
676
|
-
// settings block files, was set up - before this screen existed, or by hand - and is recorded so, silently.
|
|
677
|
-
if (setUpBy([local, shared])) {
|
|
678
|
-
await onboarding.store.add((this.#dependencies.clock ?? (() => this.#dependencies.now))());
|
|
679
|
-
return { opens: false, intro, setUp: true };
|
|
680
|
-
}
|
|
681
|
-
return { opens: true, intro, setUp: false };
|
|
692
|
+
return welcomeIn(workingDirectory, {
|
|
693
|
+
store: onboarding.store,
|
|
694
|
+
files: policyFiles,
|
|
695
|
+
...(home === undefined ? {} : { home }),
|
|
696
|
+
...(realHome === undefined ? {} : { realHome }),
|
|
697
|
+
now: this.#dependencies.clock ?? (() => this.#dependencies.now),
|
|
698
|
+
});
|
|
682
699
|
}
|
|
683
700
|
/**
|
|
684
701
|
* The person's projects, read again with every refresh as the settings are, since a project set up in another tab is
|
|
@@ -690,7 +707,8 @@ export class SessionStart {
|
|
|
690
707
|
return {};
|
|
691
708
|
const listing = await projects.list();
|
|
692
709
|
const noProject = (path) => this.#notAProject(path) !== undefined;
|
|
693
|
-
|
|
710
|
+
// V10b: a folder the ChatGPT app made for a chat with no project is a quick try too, and counted with them.
|
|
711
|
+
const temporary = (path) => inTemporarySpace(path, this.#dependencies.temporaryDirectory, home) || inChatFolder(path, home);
|
|
694
712
|
// V20: a folder with no conversations of its own, inside a project that has them - where they are.
|
|
695
713
|
const near = nearestProject(workingDirectory, listing, noProject);
|
|
696
714
|
const above = near.kind === 'above' ? { id: near.project.id, within: relative(near.project.path, workingDirectory) } : undefined;
|
|
@@ -788,9 +806,16 @@ export class SessionStart {
|
|
|
788
806
|
// U2: the version is this run's own, read once; the hooks are read from the files as they are now.
|
|
789
807
|
this.#serving ??= this.#dependencies.invocation?.version() ?? Promise.resolve(undefined);
|
|
790
808
|
const late = behind({ ...(local === undefined ? {} : { local }), ...(readable === undefined ? {} : { shared: readable }) }, await this.#serving);
|
|
791
|
-
//
|
|
809
|
+
// AO3: Codex's state is read, never assumed - `on` only where agentwhy's check in the person's own Codex files is
|
|
810
|
+
// verified as approved; `stale` where a check is written somewhere (the person's files unverified, or the old
|
|
811
|
+
// project-level copy) and Codex may still ask; `off` where the project uses Codex (CK6) and nothing is written.
|
|
792
812
|
const codexHooks = await this.#settingsFile(join(workingDirectory, PROJECT_HOOKS.directory, PROJECT_HOOKS.file));
|
|
793
|
-
const
|
|
813
|
+
const check = this.#dependencies.home === undefined ? 'absent' : await codexCheckState(this.#dependencies.policyFiles, this.#dependencies.home);
|
|
814
|
+
const legacy = typeof codexHooks === 'object' && codexRefuseIn(codexHooks) !== undefined;
|
|
815
|
+
const codex = check === 'on' ? 'on'
|
|
816
|
+
: check === 'stale' || legacy ? 'stale'
|
|
817
|
+
: codexListed || codexHooks !== undefined || (await this.#dependencies.codexOnThisComputer?.()) === true ? 'off'
|
|
818
|
+
: undefined;
|
|
794
819
|
return {
|
|
795
820
|
hooks: {
|
|
796
821
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
95
|
-
const
|
|
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>') +
|
|
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-
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 }) +
|
|
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
|
-
*
|
|
171
|
-
* every block - and the honesty line under it. Opened
|
|
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 { 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
|