@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,3 +1,4 @@
|
|
|
1
|
+
import { sumRefusedByOthers } from '../refusals.js';
|
|
1
2
|
export const ALERT_THRESHOLDS = ['value', 'reached', 'refused'];
|
|
2
3
|
/**
|
|
3
4
|
* What is said when nobody has said otherwise: only a value written from a protected file. The other two are worth
|
|
@@ -32,6 +33,7 @@ export function alertOf(report, agentIndex) {
|
|
|
32
33
|
const wroteItself = node.wroteValue === true || node.wroteOnward === true || usedItself;
|
|
33
34
|
const reached = reachedFromReturn > 0 || node.filesReached > 0 || reachedBelow;
|
|
34
35
|
const refusedAttempts = subtree.reduce((sum, agent) => sum + agent.refusedAttempts, 0);
|
|
36
|
+
const byOthers = sumRefusedByOthers(subtree.map((agent) => agent.refusedByOthers));
|
|
35
37
|
const filesReached = own.length > 0 ? reachedFromReturn : reachedBelow ? undefined : node.filesReached;
|
|
36
38
|
const level = wroteOrReturnedValue || wroteItself ? 'value' : reached ? 'reached' : refusedAttempts > 0 ? 'refused' : 'nothing';
|
|
37
39
|
return {
|
|
@@ -40,6 +42,7 @@ export function alertOf(report, agentIndex) {
|
|
|
40
42
|
...(node.type === undefined ? {} : { type: node.type }),
|
|
41
43
|
...(filesReached === undefined ? {} : { filesReached }),
|
|
42
44
|
refusedAttempts,
|
|
45
|
+
...(byOthers === undefined ? {} : { refusedByOthers: byOthers }),
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
/** Whether a level is strong enough to be said, under the threshold asked for. */
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright 2026 Nessprim Karol Kozer
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { SETTINGS_FILES } from '../../adapter/claude-code/contract/settings.js';
|
|
5
|
+
import { refuseProjectAbove, refuseRulesOf } from '../../adapter/claude-code/settings/refuse-project.js';
|
|
6
|
+
import { codexReaderOf } from '../../adapter/codex/hooks/reader.js';
|
|
7
|
+
import { codexConversationOf } from '../../adapter/codex/hooks/codex-conversation.js';
|
|
8
|
+
import { codexStopFolder, parseCodexStopInput } from '../../adapter/codex/hooks/stop-input.js';
|
|
9
|
+
import { sessionKey } from '../../core/session-catalogue.js';
|
|
10
|
+
import { FileAccessError } from '../../ports/file-access-error.js';
|
|
11
|
+
import { CODEX_REQUESTS } from './render/codex-request-words.js';
|
|
12
|
+
/**
|
|
13
|
+
* Codex's end of a turn, for `watch` (`2026-10-02-codex-says-it-too.md` CX1-CX7): its `Stop` read by Codex's names,
|
|
14
|
+
* who reads it from its rollout, the commands agentwhy stopped in the turn, Codex's requests, a Codex session's key, and
|
|
15
|
+
* the project's rules - the ones Codex's `refuse` reads.
|
|
16
|
+
*/
|
|
17
|
+
export function codexTurnFormat(files, refusals, threadIndexPath, home) {
|
|
18
|
+
return {
|
|
19
|
+
readStop: parseCodexStopInput,
|
|
20
|
+
readerOf: (turn) => codexReaderOf(files, turn.transcriptPath),
|
|
21
|
+
stoppedIn: (text) => refusals.find(text),
|
|
22
|
+
requests: CODEX_REQUESTS,
|
|
23
|
+
sessionKey: (id) => sessionKey({ id, provider: 'codex' }),
|
|
24
|
+
settingsFor: (text) => rulesOfProject(files, codexStopFolder(text), home),
|
|
25
|
+
// CX8: one conversation across the session ids the Codex apps give it (CXB5).
|
|
26
|
+
conversationOf: (turn) => codexConversationOf(files, threadIndexPath, turn.sessionId),
|
|
27
|
+
// CX9 with AO5: watched where a project above the turn's folder runs `refuse` - as `refuse --codex` finds it.
|
|
28
|
+
watchedHere: async (text) => {
|
|
29
|
+
const folder = codexStopFolder(text);
|
|
30
|
+
return folder !== undefined && (await refuseProjectAbove(files, folder, home)) !== undefined;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* CX5 with AO5, AO6: the project is found as `refuse --codex` finds it - the nearest folder short of home whose Claude
|
|
36
|
+
* Code settings run `refuse` - and its rules are the settings file that project's `refuse` command names. Found by
|
|
37
|
+
* review: guessing the local file first read no rules where a project keeps them in the shared one, and so said a chat
|
|
38
|
+
* was clean over a file `refuse` protects. Where it names none, the local file, then the shared one, as before. A
|
|
39
|
+
* policy file is not a settings path, so it reads as none.
|
|
40
|
+
*/
|
|
41
|
+
async function rulesOfProject(files, folder, home) {
|
|
42
|
+
if (folder === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
const found = await refuseProjectAbove(files, folder, home);
|
|
45
|
+
if (found === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
const rules = refuseRulesOf(found);
|
|
48
|
+
if (rules.policyPath !== undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
if (rules.settingsPath !== undefined)
|
|
51
|
+
return rules.settingsPath;
|
|
52
|
+
for (const name of [SETTINGS_FILES.local, SETTINGS_FILES.shared]) {
|
|
53
|
+
const settings = join(found.project, SETTINGS_FILES.directory, name);
|
|
54
|
+
if (await readable(files, settings))
|
|
55
|
+
return settings;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
async function readable(files, path) {
|
|
60
|
+
try {
|
|
61
|
+
await files.readText(path);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (error instanceof FileAccessError)
|
|
66
|
+
return false;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright 2026 Nessprim Karol Kozer
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
/**
|
|
2
4
|
* The choices a notice can be given, in a module of their own so that both the code that says a notice and the code
|
|
3
5
|
* that remembers what a person chose can name them without depending on each other.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright 2026 Nessprim Karol Kozer
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
1
3
|
import { isJsonObject, parseJsonObject } from '../../shared/json.js';
|
|
2
4
|
import { ALERT_THRESHOLDS } from './agent-alert.js';
|
|
3
5
|
import { CLEAN_MODES, NOTICE_CHANNELS, NOTICE_LANGS, SAID_AS, } from './notice-choices.js';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The stopped limit says "now": told to never read the file another way, the agent refused to try again later in the
|
|
3
|
+
* same conversation after the person had switched the file to Track (seen by the maintainer, 2026-10-02). The hook is
|
|
4
|
+
* the gate, not the agent's memory of this sentence.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* What Codex's agent is handed (`2026-10-02-codex-says-it-too.md` CX2, CX3, amended 2026-10-02). Codex shows it whole, in
|
|
8
|
+
* a bubble above the reply (CXB1, CKB14), and the maintainer found the first wording - written to the agent, with the
|
|
9
|
+
* report's command in it - too long and not understandable for the person who reads it first. So each is written to the
|
|
10
|
+
* person: what happened and what to do, in one or two sentences of the glossary's words, in the language chosen for
|
|
11
|
+
* agentwhy; then, in brackets and marked as the AI's ("For the AI:", chosen by the maintainer so a person does not read it
|
|
12
|
+
* as said to them), the one thing the agent is asked - one short sentence back - with the limit that keeps
|
|
13
|
+
* the file safe: never show a key, never read a stopped file another way. No command, no report offered: the report is
|
|
14
|
+
* one step away in Conversations, and a command in that bubble is a line a person cannot read.
|
|
15
|
+
*/
|
|
16
|
+
export const CODEX_REQUESTS = {
|
|
17
|
+
stopped: (paths, lang) => STOPPED[lang](listed(paths, lang), paths.length === 1),
|
|
18
|
+
keys: (files, _command, lang) => KEYS[lang](listed(files.map(String), lang), files.length <= 1),
|
|
19
|
+
template: (files, _command, lang) => TEMPLATE[lang](listed(files.map(String), lang), files.length <= 1),
|
|
20
|
+
data: (files, _command, lang) => DATA[lang](listed(files.map(String), lang), files.length <= 1),
|
|
21
|
+
told: (files, _command, lang, foldsTurn) => TOLD[lang](listed(files.map(String), lang), files.length <= 1, foldsTurn === true),
|
|
22
|
+
quiet: (lang, foldsTurn) => QUIET[lang](foldsTurn === true),
|
|
23
|
+
};
|
|
24
|
+
const STOPPED = {
|
|
25
|
+
en: (files, one) => `**agentwhy** stopped your AI from reading the private ${one ? 'file' : 'files'} ${files}. ` +
|
|
26
|
+
`(For the AI: reply in one short sentence, and don't try to read ${one ? 'it' : 'them'} another way now.)`,
|
|
27
|
+
pl: (files, one) => `**agentwhy** nie pozwolił AI odczytać ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files}. ` +
|
|
28
|
+
`(Dla AI: odpowiedz jednym krótkim zdaniem i nie próbuj teraz odczytać ${one ? 'go' : 'ich'} inaczej.)`,
|
|
29
|
+
de: (files, one) => `**agentwhy** hat deine KI daran gehindert, ${one ? 'die private Datei' : 'die privaten Dateien'} ${files} zu lesen. ` +
|
|
30
|
+
`(Für die KI: antworte in einem kurzen Satz und versuch jetzt nicht, sie anders zu lesen.)`,
|
|
31
|
+
};
|
|
32
|
+
const KEYS = {
|
|
33
|
+
en: (files, one) => `**agentwhy**: a key from the private ${one ? 'file' : 'files'} ${files} is now in this chat. Make a new key ` +
|
|
34
|
+
`today where it was created; deleting the chat won't undo it. (For the AI: reply in one short sentence; don't show the key.)`,
|
|
35
|
+
pl: (files, one) => `**agentwhy**: klucz z ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files} jest teraz w tej rozmowie. ` +
|
|
36
|
+
`Zrób dziś nowy klucz tam, gdzie go utworzono; usunięcie rozmowy tego nie cofnie. (Dla AI: odpowiedz jednym krótkim ` +
|
|
37
|
+
`zdaniem; nie pokazuj klucza.)`,
|
|
38
|
+
de: (files, one) => `**agentwhy**: Ein Schlüssel aus ${one ? 'der privaten Datei' : 'den privaten Dateien'} ${files} ist jetzt ` +
|
|
39
|
+
`in diesem Chat. Erstelle heute dort einen neuen, wo er erstellt wurde; den Chat zu löschen hilft nicht. (Für die KI: ` +
|
|
40
|
+
`antworte in einem kurzen Satz; zeig den Schlüssel nicht.)`,
|
|
41
|
+
};
|
|
42
|
+
/** A template's value may be a placeholder: only a real key is to be made new (S6, `template`). */
|
|
43
|
+
const TEMPLATE = {
|
|
44
|
+
en: (files, one) => `**agentwhy**: a value from the template ${one ? 'file' : 'files'} ${files} is now in this chat. If it's a ` +
|
|
45
|
+
`real key, make a new one where it was created. (For the AI: reply in one short sentence; don't show the value.)`,
|
|
46
|
+
pl: (files, one) => `**agentwhy**: wartość z ${one ? 'pliku-szablonu' : 'plików-szablonów'} ${files} jest teraz w tej rozmowie. ` +
|
|
47
|
+
`Jeśli to prawdziwy klucz, zrób nowy tam, gdzie go utworzono. (Dla AI: odpowiedz jednym krótkim zdaniem; ` +
|
|
48
|
+
`nie pokazuj tej wartości.)`,
|
|
49
|
+
de: (files, one) => `**agentwhy**: Ein Wert aus ${one ? 'der Vorlagendatei' : 'den Vorlagendateien'} ${files} ist jetzt in ` +
|
|
50
|
+
`diesem Chat. Ist es ein echter Schlüssel, erstelle dort einen neuen, wo er erstellt wurde. (Für die KI: antworte in ` +
|
|
51
|
+
`einem kurzen Satz; zeig den Wert nicht.)`,
|
|
52
|
+
};
|
|
53
|
+
/** Data cannot be made new, so nothing is asked to be changed (S6, `data`): what cannot be taken back, and where to look. */
|
|
54
|
+
const DATA = {
|
|
55
|
+
en: (files, one) => `**agentwhy**: private data from the private ${one ? 'file' : 'files'} ${files} is now in this chat. It ` +
|
|
56
|
+
`can't be taken back; agentwhy's report shows how to limit the damage. (For the AI: reply in one short sentence; don't repeat the data.)`,
|
|
57
|
+
pl: (files, one) => `**agentwhy**: prywatne dane z ${one ? 'prywatnego pliku' : 'prywatnych plików'} ${files} są teraz w tej ` +
|
|
58
|
+
`rozmowie. Nie da się tego cofnąć; raport agentwhy pokazuje, jak ograniczyć szkody. (Dla AI: odpowiedz jednym krótkim ` +
|
|
59
|
+
`zdaniem; nie powtarzaj tych danych.)`,
|
|
60
|
+
de: (files, one) => `**agentwhy**: Private Daten aus ${one ? 'der privaten Datei' : 'den privaten Dateien'} ${files} sind jetzt ` +
|
|
61
|
+
`in diesem Chat. Das lässt sich nicht zurücknehmen; der Bericht von agentwhy zeigt, wie du den Schaden begrenzt. (Für ` +
|
|
62
|
+
`die KI: antworte in einem kurzen Satz; wiederhole die Daten nicht.)`,
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Where the app folds the turn away (`foldsTurn`, the desktop app, CXB5's display note), told and quiet ask the
|
|
66
|
+
* answer back - decided by the maintainer, 2026-10-02, after a control run: with a block, the whole Apache-licence
|
|
67
|
+
* answer sat behind "Worked for ...", and the visible reply was one line about private files. The full answer, not a
|
|
68
|
+
brief one: the maintainer tried "repeat your answer" the same day and found the visible repeat more condensed than
|
|
69
|
+
the original the fold hides. A told file is one the
|
|
70
|
+
* person lets their AI read, so the repeat carries nothing that may not be here; keys, data and a stopped command
|
|
71
|
+
* keep their limits - what their fold hides is what must not be repeated. VS Code's Codex folds nothing, so there
|
|
72
|
+
* the one short sentence stays and nothing is said twice.
|
|
73
|
+
*/
|
|
74
|
+
const TOLD = {
|
|
75
|
+
en: (files, one, folds) => `**agentwhy**: your AI read the private ${one ? 'file' : 'files'} ${files}, which you let it read. ` +
|
|
76
|
+
`Nothing to do. (For the AI: ${folds ? 'repeat your full answer for the user, then that nothing needs doing' : 'reply in one short sentence'}.)`,
|
|
77
|
+
pl: (files, one, folds) => `**agentwhy**: AI przeczytało ${one ? 'prywatny plik' : 'prywatne pliki'} ${files}, na ${one ? 'który' : 'które'} ` +
|
|
78
|
+
`pozwoliłeś. Nic nie trzeba robić. (Dla AI: ${folds ? 'powtórz całą swoją odpowiedź dla użytkownika i dodaj, że nic nie trzeba robić' : 'odpowiedz jednym krótkim zdaniem'}.)`,
|
|
79
|
+
de: (files, one, folds) => `**agentwhy**: Deine KI hat ${one ? 'die private Datei' : 'die privaten Dateien'} ${files} gelesen, die du ` +
|
|
80
|
+
`ihr erlaubt hast. Nichts zu tun. (Für die KI: ${folds ? 'wiederhole deine ganze Antwort für den Nutzer und ergänze, dass nichts zu tun ist' : 'antworte in einem kurzen Satz'}.)`,
|
|
81
|
+
};
|
|
82
|
+
const QUIET = {
|
|
83
|
+
en: (folds) => '**agentwhy** is keeping watch over your private files: none has been opened so far. ' +
|
|
84
|
+
`(For the AI: ${folds ? 'repeat your full answer for the user, then this in one sentence' : 'reply in one short sentence'}.)`,
|
|
85
|
+
pl: (folds) => '**agentwhy** pilnuje twoich prywatnych plików: jak dotąd żaden nie został otwarty. ' +
|
|
86
|
+
`(Dla AI: ${folds ? 'powtórz całą swoją odpowiedź dla użytkownika i dodaj to jednym zdaniem' : 'odpowiedz jednym krótkim zdaniem'}.)`,
|
|
87
|
+
de: (folds) => '**agentwhy** passt auf deine privaten Dateien auf: Bisher wurde keine geöffnet. ' +
|
|
88
|
+
`(Für die KI: ${folds ? 'wiederhole deine ganze Antwort für den Nutzer und ergänze dies in einem Satz' : 'antworte in einem kurzen Satz'}.)`,
|
|
89
|
+
};
|
|
90
|
+
/** At most three files by name, then how many more: a request a person reads is not a list. */
|
|
91
|
+
function listed(paths, lang) {
|
|
92
|
+
const named = [...new Set(paths)].slice(0, 3);
|
|
93
|
+
const rest = new Set(paths).size - named.length;
|
|
94
|
+
const and = AND[lang];
|
|
95
|
+
if (rest > 0)
|
|
96
|
+
named.push(MORE[lang](rest));
|
|
97
|
+
if (named.length <= 1)
|
|
98
|
+
return named[0] ?? '';
|
|
99
|
+
return `${named.slice(0, -1).join(', ')} ${and} ${named.at(-1)}`;
|
|
100
|
+
}
|
|
101
|
+
const AND = { en: 'and', pl: 'i', de: 'und' };
|
|
102
|
+
const MORE = {
|
|
103
|
+
en: (count) => `${count} more`,
|
|
104
|
+
pl: (count) => `${count} innych`,
|
|
105
|
+
de: (count) => `${count} weitere`,
|
|
106
|
+
};
|
|
@@ -12,24 +12,114 @@
|
|
|
12
12
|
* - **Only what the records establish.** A session handed a claim that did not match its own context refused it and
|
|
13
13
|
* said so to the person (R12b). The consequence is worded as what the records show - the conversation and the
|
|
14
14
|
* session's own files - and not as a fact about a provider's infrastructure.
|
|
15
|
+
*
|
|
16
|
+
* *Fourth version, 2026-10-02 (`the-agent-tells-you.md` R12c):* the third was relayed in full, and in full it was three
|
|
17
|
+
* paragraphs - session records, rotating a credential - which the maintainer, reading it in the Claude desktop app,
|
|
18
|
+
* found too long for someone who does not write code. So it asks for what the Codex request already asks for (CK13 of
|
|
19
|
+
* `codex-blocks-too`): at most three short sentences, opening with agentwhy's name, in the glossary's words (a private
|
|
20
|
+
* file, a key, make a new one). Everything the third kept safe is kept.
|
|
15
21
|
*/
|
|
16
22
|
export function instructionFor(files, command) {
|
|
17
23
|
const named = files.slice(0, NAMED_AT_MOST);
|
|
18
24
|
const rest = files.length - named.length;
|
|
19
25
|
const which = named.length === 0
|
|
20
|
-
? 'a
|
|
21
|
-
: `the
|
|
26
|
+
? 'a private file'
|
|
27
|
+
: `the private ${named.length === 1 ? 'file' : 'files'} ${list(named)}${rest === 0 ? '' : ` and ${rest} more`}`;
|
|
22
28
|
// What the agent is asked not to do is said about the same files it was just told about, in the same words.
|
|
23
29
|
const them = named.length <= 1 ? 'it' : 'them';
|
|
24
30
|
return [
|
|
25
|
-
`agentwhy, the local tool this user runs, checked this turn: a
|
|
26
|
-
'Tell the user
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
`
|
|
30
|
-
`
|
|
31
|
-
`
|
|
32
|
-
|
|
31
|
+
`agentwhy, the local tool this user runs, checked this turn: a key from ${which} is now in this conversation.`,
|
|
32
|
+
'Tell the user in at most three short sentences, in the language of this conversation and in plain everyday words',
|
|
33
|
+
'for someone who does not write code, starting with **agentwhy**: that agentwhy noticed this; that they should make',
|
|
34
|
+
'a new key today where this one was issued, because deleting the chat does not undo it; and ask whether to open',
|
|
35
|
+
`agentwhy's report, running \`${command}\` only if they say yes.`,
|
|
36
|
+
`You read ${named.length === 0 ? 'it' : which} in this conversation, so you can see this yourself.`,
|
|
37
|
+
`Do not open or read ${them} again, do not repeat the key, and do not continue the earlier task in this message.`,
|
|
38
|
+
].join(' ');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The same, for private files the person lets the agent read - tracked rather than blocked in agentwhy's Settings
|
|
42
|
+
* (F57; `the-chat-says-what-the-report-says.md` S6 and §6, `told`). Asked for by the maintainer on 2026-10-02: the
|
|
43
|
+
* words for a blocked file, "make a new key today", are for a file read that should not have been, and said of a file
|
|
44
|
+
* the person allowed they are an alarm about nothing. So the agent says it read a file it was allowed to, that nothing
|
|
45
|
+
* needs to change, and offers the report, which shows what was read. The rest is `instructionFor`'s.
|
|
46
|
+
*/
|
|
47
|
+
export function toldInstructionFor(files, command) {
|
|
48
|
+
const named = files.slice(0, NAMED_AT_MOST);
|
|
49
|
+
const rest = files.length - named.length;
|
|
50
|
+
const which = named.length === 0
|
|
51
|
+
? 'a private file'
|
|
52
|
+
: `the private ${named.length === 1 ? 'file' : 'files'} ${list(named)}${rest === 0 ? '' : ` and ${rest} more`}`;
|
|
53
|
+
const them = named.length <= 1 ? 'it' : 'them';
|
|
54
|
+
return [
|
|
55
|
+
`agentwhy, the local tool this user runs, checked this turn: you read ${which}, which this user lets you read -`,
|
|
56
|
+
`they set ${them} to be tracked, not blocked, in agentwhy's settings.`,
|
|
57
|
+
'Tell the user in at most two short sentences, in the language of this conversation and in plain everyday words for',
|
|
58
|
+
'someone who does not write code, starting with **agentwhy**: that agentwhy noticed you read a private file they',
|
|
59
|
+
'allowed, so nothing needs to change; and ask whether to open agentwhy\'s report, which shows what was read, running',
|
|
60
|
+
`\`${command}\` only if they say yes.`,
|
|
61
|
+
`Do not open or read ${them} again, do not repeat anything from ${them}, and do not continue the earlier task in this message.`,
|
|
62
|
+
].join(' ');
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The same, for private data - a file of the conversation's that agentwhy recognises no key in (`the-chat-says-what-the-
|
|
66
|
+
* report-says.md` S1, S6 and §6, `data`). A key can be replaced and data cannot, so "make a new key" said of a customer
|
|
67
|
+
* list is advice that does nothing - the agent contradicted it in the session that found this (§1). The agent says what
|
|
68
|
+
* cannot be taken back and offers the report, which shows how to limit the damage and protect the file.
|
|
69
|
+
*/
|
|
70
|
+
export function dataInstructionFor(files, command) {
|
|
71
|
+
const { which, them } = named(files);
|
|
72
|
+
return [
|
|
73
|
+
`agentwhy, the local tool this user runs, checked this turn: private data from ${which} is now in this conversation,`,
|
|
74
|
+
'and agentwhy recognises no key in it.',
|
|
75
|
+
'Tell the user in at most three short sentences, in the language of this conversation and in plain everyday words',
|
|
76
|
+
'for someone who does not write code, starting with **agentwhy**: that agentwhy noticed this; that it cannot be taken',
|
|
77
|
+
'back, and agentwhy\'s report shows how to limit the damage and protect the file; and ask whether to open it, running',
|
|
78
|
+
`\`${command}\` only if they say yes.`,
|
|
79
|
+
`You read ${which} in this conversation, so you can see this yourself.`,
|
|
80
|
+
`Do not open or read ${them} again, do not repeat anything from ${them}, and do not continue the earlier task in this message.`,
|
|
81
|
+
].join(' ');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The same, for a file named as a template - `.env.example` (S6, `template`): its value may be a placeholder or a real
|
|
85
|
+
* key, and the agent read it, so it can say which it looks like - without repeating it.
|
|
86
|
+
*/
|
|
87
|
+
export function templateInstructionFor(files, command) {
|
|
88
|
+
const { which, them } = named(files);
|
|
89
|
+
return [
|
|
90
|
+
`agentwhy, the local tool this user runs, checked this turn: a value from ${which}, named as a template, is now in`,
|
|
91
|
+
'this conversation. Tell the user in at most three short sentences, in the language of this conversation and in plain',
|
|
92
|
+
'everyday words for someone who does not write code, starting with **agentwhy**: that agentwhy noticed this; whether',
|
|
93
|
+
'the value looks like a real key or a placeholder, and that a real one should be made new where it was issued; and ask',
|
|
94
|
+
`whether to open agentwhy's report, running \`${command}\` only if they say yes.`,
|
|
95
|
+
`You read ${which} in this conversation, so you can see this yourself.`,
|
|
96
|
+
`Do not open or read ${them} again, do not repeat the value, and do not continue the earlier task in this message.`,
|
|
97
|
+
].join(' ');
|
|
98
|
+
}
|
|
99
|
+
/** The files an instruction names - three at most, then how many more - and the pronoun that refers back to them. */
|
|
100
|
+
function named(files) {
|
|
101
|
+
const shown = files.slice(0, NAMED_AT_MOST);
|
|
102
|
+
const rest = files.length - shown.length;
|
|
103
|
+
return {
|
|
104
|
+
which: shown.length === 0
|
|
105
|
+
? 'a private file'
|
|
106
|
+
: `the private ${shown.length === 1 ? 'file' : 'files'} ${list(shown)}${rest === 0 ? '' : ` and ${rest} more`}`,
|
|
107
|
+
them: shown.length <= 1 ? 'it' : 'them',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The first quiet turn of a session, said by the agent (`the-agent-tells-you.md` R5, amended 2026-10-02): where the line
|
|
112
|
+
* is folded away or easily passed, the one sentence that says agentwhy is keeping watch. agentwhy's claim, passed on as
|
|
113
|
+
* agentwhy's, so the agent is not asked to vouch for files it cannot know are private; nothing to offer, since there
|
|
114
|
+
* is nothing to see in a report.
|
|
115
|
+
*/
|
|
116
|
+
export function cleanInstruction() {
|
|
117
|
+
return [
|
|
118
|
+
'agentwhy, the local tool this user runs, checked this conversation so far: none of the private files it watches',
|
|
119
|
+
'has been opened. Tell the user in one short sentence, in the language of this conversation and in plain everyday',
|
|
120
|
+
'words for someone who does not write code, starting with **agentwhy**: that agentwhy is keeping watch over their',
|
|
121
|
+
'private files and none has been opened so far. Do not ask anything, and do not continue the earlier task in this',
|
|
122
|
+
'message.',
|
|
33
123
|
].join(' ');
|
|
34
124
|
}
|
|
35
125
|
/**
|
|
@@ -39,10 +129,14 @@ export function instructionFor(files, command) {
|
|
|
39
129
|
const NAMED_AT_MOST = 3;
|
|
40
130
|
/**
|
|
41
131
|
* The command the agent offers to run: this session by its id, and nothing of what the report holds (R18, R19), with
|
|
42
|
-
* agentwhy invoked the way the hook invokes it.
|
|
132
|
+
* agentwhy invoked the way the hook invokes it. *Changed 2026-10-02 (`2026-10-02-said-where-the-person-is.md` SW10):*
|
|
133
|
+
* `start` writing every conversation of the project, opening this one's report among them, so its "All conversations"
|
|
134
|
+
* leads back to them - `report --open` opened a page alone, with nowhere to go back to. *Changed again the same day
|
|
135
|
+
* (`2026-10-02-a-page-not-a-file.md` PF5):* served from the background (`--detach`), so the page is live - marks, Settings,
|
|
136
|
+
* new conversations - and the agent's call still returns at once; a server already running for the project is reused.
|
|
43
137
|
*/
|
|
44
138
|
export function reportCommand(sessionId, invocation) {
|
|
45
|
-
return `${invocation}
|
|
139
|
+
return `${invocation} start --detach --session ${sessionId} --quiet`;
|
|
46
140
|
}
|
|
47
141
|
function list(files) {
|
|
48
142
|
const [one, two, three] = files;
|
|
@@ -52,3 +146,11 @@ function list(files) {
|
|
|
52
146
|
return `${one} and ${two}`;
|
|
53
147
|
return String(one);
|
|
54
148
|
}
|
|
149
|
+
/** Claude Code's: the language is the conversation's, which the agent answers in whatever it is asked in. */
|
|
150
|
+
export const CLAUDE_CODE_REQUESTS = {
|
|
151
|
+
keys: (files, command) => instructionFor(files, command),
|
|
152
|
+
template: (files, command) => templateInstructionFor(files, command),
|
|
153
|
+
data: (files, command) => dataInstructionFor(files, command),
|
|
154
|
+
told: (files, command) => toldInstructionFor(files, command),
|
|
155
|
+
quiet: () => cleanInstruction(),
|
|
156
|
+
};
|
|
@@ -17,6 +17,8 @@ const NOTE = 'NOTE:';
|
|
|
17
17
|
* both read; `tests/report/watch/render/notice-words.test.ts` holds each one to it.
|
|
18
18
|
*/
|
|
19
19
|
export const NOTICE_BUDGET = 150;
|
|
20
|
+
/** How a refusal by a rule is said, in one agent's notice and in the line that counts several. */
|
|
21
|
+
const RULE_REFUSED = 'a rule refused';
|
|
20
22
|
/**
|
|
21
23
|
* Who is speaking, for the one channel with no title of its own. A terminal notification and a system notification
|
|
22
24
|
* are drawn with `NOTICE_TITLE` above them; the line in the conversation arrives under a frame Claude Code writes
|
|
@@ -33,6 +35,17 @@ const NOT_CHECKED = {
|
|
|
33
35
|
session: "Finished agent not checked: the session's records could not be read.",
|
|
34
36
|
'agent-not-found': "Finished agent not checked: it is not in the session's records yet.",
|
|
35
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* A helper that kept no record (`when-an-agent-finishes.md` R4a), in the person's language and the glossary's words -
|
|
40
|
+
* in the Claude desktop app it is often the only notice that reaches them. Said once a session, and it says so: a
|
|
41
|
+
* person who hears it once should not take a quiet later turn for a later helper that was checked. No `Details:`, since
|
|
42
|
+
* the report holds nothing about it.
|
|
43
|
+
*/
|
|
44
|
+
const NO_RECORD_WORDS = {
|
|
45
|
+
en: "A helper finished without leaving a record, so I can't check what it did. I say this once per chat.",
|
|
46
|
+
pl: 'Zakończył się pomocnik, który nie zostawił zapisu, więc nie sprawdzę, co robił. Mówię to raz na rozmowę.',
|
|
47
|
+
de: 'Ein Helfer ist fertig und hat nichts aufgezeichnet, daher kann ich nicht prüfen, was er tat. Ich sage das einmal pro Chat.',
|
|
48
|
+
};
|
|
36
49
|
/**
|
|
37
50
|
* One notice in words, or no words for nothing to say (`specs/2026-09-16-when-an-agent-finishes.md` R5-R7, R15). It
|
|
38
51
|
* reads counts and enumerations only - the agent's type, how many files, how many attempts - so no path, value,
|
|
@@ -46,6 +59,12 @@ export class NoticeWordsRenderer {
|
|
|
46
59
|
return '';
|
|
47
60
|
case 'not-checked':
|
|
48
61
|
return `${NOT_CHECKED[notice.reason]} ${DETAILS}`;
|
|
62
|
+
case 'no-record':
|
|
63
|
+
return NO_RECORD_WORDS[notice.lang];
|
|
64
|
+
case 'told':
|
|
65
|
+
return TOLD_WORDS[notice.lang];
|
|
66
|
+
case 'read':
|
|
67
|
+
return READ_WORDS[notice.lang][notice.what];
|
|
49
68
|
case 'alert':
|
|
50
69
|
return `${sentenceOf(notice.alert)} ${DETAILS}`;
|
|
51
70
|
case 'turn':
|
|
@@ -58,6 +77,38 @@ export class NoticeWordsRenderer {
|
|
|
58
77
|
}
|
|
59
78
|
}
|
|
60
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* A key from a private file the person lets their AI read (`the-chat-says-what-the-report-says.md` §5, the `told` row):
|
|
82
|
+
* the words proposed there, unchanged. Not `ROTATE` - the person allowed the read in Settings, and nothing needs to
|
|
83
|
+
* change.
|
|
84
|
+
*/
|
|
85
|
+
/**
|
|
86
|
+
* A private file's text in the conversation, by what it held (`the-chat-says-what-the-report-says.md` S8, §5): agentwhy's
|
|
87
|
+
* voice, as the clean line's; what to do before `Details`, inside the budget; no path and no service (S9). A template
|
|
88
|
+
* asks to check, since its value may be a placeholder; data cannot be changed, so it asks to see what to do.
|
|
89
|
+
*/
|
|
90
|
+
const READ_WORDS = {
|
|
91
|
+
en: {
|
|
92
|
+
keys: 'I keep watch over your private files. A key from one of them is now in this conversation — change it. Details: agentwhy report --open',
|
|
93
|
+
template: 'I keep watch over your private files. A key from one of them is now in this conversation — check it. Details: agentwhy report --open',
|
|
94
|
+
data: 'I keep watch over your private files. Private data from one of them is now in this conversation — see what to do: agentwhy report --open',
|
|
95
|
+
},
|
|
96
|
+
pl: {
|
|
97
|
+
keys: 'Pilnuję twoich prywatnych plików. Klucz z jednego z nich jest teraz w tej rozmowie — zmień go. Szczegóły: agentwhy report --open',
|
|
98
|
+
template: 'Pilnuję twoich prywatnych plików. Klucz z jednego z nich jest teraz w tej rozmowie — sprawdź go. Szczegóły: agentwhy report --open',
|
|
99
|
+
data: 'Pilnuję twoich prywatnych plików. Prywatne dane z jednego z nich są teraz w tej rozmowie — zobacz, co zrobić: agentwhy report --open',
|
|
100
|
+
},
|
|
101
|
+
de: {
|
|
102
|
+
keys: 'Ich passe auf deine privaten Dateien auf. Ein Schlüssel daraus ist jetzt in dieser Unterhaltung — ändere ihn. Details: agentwhy report --open',
|
|
103
|
+
template: 'Ich passe auf deine privaten Dateien auf. Ein Schlüssel daraus ist jetzt in dieser Unterhaltung — prüfe ihn. Details: agentwhy report --open',
|
|
104
|
+
data: 'Ich passe auf deine privaten Dateien auf. Private Daten daraus sind jetzt in dieser Unterhaltung. Was tun: agentwhy report --open',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const TOLD_WORDS = {
|
|
108
|
+
en: 'I keep watch over your private files. Your AI read one you let it read. Nothing to do. Details: agentwhy report --open',
|
|
109
|
+
pl: 'Pilnuję twoich prywatnych plików. AI przeczytało jeden, na który mu pozwoliłeś. Nic nie trzeba robić. Szczegóły: agentwhy report --open',
|
|
110
|
+
de: 'Ich passe auf deine privaten Dateien auf. Deine KI hat eine gelesen, die du ihr erlaubt hast. Nichts zu tun. Details: agentwhy report --open',
|
|
111
|
+
};
|
|
61
112
|
/**
|
|
62
113
|
* A turn that found nothing (R4-R6), in the language the person reads (R29). Three lines, and the difference between
|
|
63
114
|
* them is the whole point:
|
|
@@ -75,7 +126,14 @@ export class NoticeWordsRenderer {
|
|
|
75
126
|
*/
|
|
76
127
|
function cleanWords(first, counts, lang) {
|
|
77
128
|
const words = CLEAN_WORDS[lang];
|
|
78
|
-
const
|
|
129
|
+
const told = counts.told ?? 0;
|
|
130
|
+
const data = counts.data ?? 0;
|
|
131
|
+
// The gravest first: a key that should not be here, then private data, then a private file opened, then one the
|
|
132
|
+
// person lets their AI read.
|
|
133
|
+
const earlier = counts.values > 0 ? words.valueEarlier(counts.values)
|
|
134
|
+
: data > 0 ? words.dataEarlier(data)
|
|
135
|
+
: counts.reached > 0 ? words.reachedEarlier(counts.reached)
|
|
136
|
+
: told > 0 ? words.toldEarlier(told) : undefined;
|
|
79
137
|
if (earlier !== undefined)
|
|
80
138
|
return `${words.nothingNew} ${earlier} ${words.details}: agentwhy report --open`;
|
|
81
139
|
return first ? words.first : words.turn;
|
|
@@ -87,6 +145,8 @@ const CLEAN_WORDS = {
|
|
|
87
145
|
nothingNew: 'Nothing new now.',
|
|
88
146
|
valueEarlier: (count) => `Earlier in this chat your AI read ${count === 1 ? 'a key' : `${count} keys`} from a private file.`,
|
|
89
147
|
reachedEarlier: (count) => `Earlier in this chat your AI opened ${count === 1 ? 'a private file' : `${count} private files`}.`,
|
|
148
|
+
toldEarlier: (count) => `Earlier in this chat your AI read ${count === 1 ? 'a private file' : `${count} private files`} you let it read.`,
|
|
149
|
+
dataEarlier: (count) => `Earlier in this chat your AI read private data from ${count === 1 ? 'a private file' : `${count} private files`}.`,
|
|
90
150
|
details: 'Details',
|
|
91
151
|
},
|
|
92
152
|
pl: {
|
|
@@ -95,6 +155,8 @@ const CLEAN_WORDS = {
|
|
|
95
155
|
nothingNew: 'Teraz nic nowego.',
|
|
96
156
|
valueEarlier: (count) => `Wcześniej w tej rozmowie AI przeczytało ${count === 1 ? 'klucz' : `${count} ${polish(count, 'klucze', 'kluczy')}`} z prywatnego pliku.`,
|
|
97
157
|
reachedEarlier: (count) => `Wcześniej w tej rozmowie AI otworzyło ${count === 1 ? 'prywatny plik' : `${count} ${polish(count, 'prywatne pliki', 'prywatnych plików')}`}.`,
|
|
158
|
+
toldEarlier: (count) => `Wcześniej w tej rozmowie AI przeczytało ${count === 1 ? 'prywatny plik' : `${count} ${polish(count, 'prywatne pliki', 'prywatnych plików')}`}, na ${count === 1 ? 'który' : 'które'} mu pozwoliłeś.`,
|
|
159
|
+
dataEarlier: (count) => `Wcześniej w tej rozmowie AI przeczytało prywatne dane z ${count === 1 ? 'prywatnego pliku' : `${count} prywatnych plików`}.`,
|
|
98
160
|
details: 'Szczegóły',
|
|
99
161
|
},
|
|
100
162
|
de: {
|
|
@@ -103,6 +165,8 @@ const CLEAN_WORDS = {
|
|
|
103
165
|
nothingNew: 'Jetzt nichts Neues.',
|
|
104
166
|
valueEarlier: (count) => `Vorhin hat deine KI ${count === 1 ? 'einen Schlüssel' : `${count} Schlüssel`} aus einer privaten Datei gelesen.`,
|
|
105
167
|
reachedEarlier: (count) => `Vorhin hat deine KI ${count === 1 ? 'eine private Datei' : `${count} private Dateien`} geöffnet.`,
|
|
168
|
+
toldEarlier: (count) => `Vorhin hat deine KI ${count === 1 ? 'eine private Datei' : `${count} private Dateien`} gelesen, die du ihr erlaubt hast.`,
|
|
169
|
+
dataEarlier: (count) => `Vorhin hat deine KI private Daten aus ${count === 1 ? 'einer privaten Datei' : `${count} privaten Dateien`} gelesen.`,
|
|
106
170
|
details: 'Details',
|
|
107
171
|
},
|
|
108
172
|
};
|
|
@@ -128,10 +192,13 @@ function sentenceOf(alert) {
|
|
|
128
192
|
* story: where a file was reached or a value written, those words come first and the refusals go unmentioned -
|
|
129
193
|
* they are what a notification cuts first, and the report has them either way.
|
|
130
194
|
* The rule is the subject, not the agent: "a rule refused" reads the same for a delegated agent and for the
|
|
131
|
-
* conversation itself, where "this conversation was refused" would not.
|
|
195
|
+
* conversation itself, where "this conversation was refused" would not. A rule is named only where a rule refused
|
|
196
|
+
* them all (`who-stopped-it` WS5): what auto mode or the person stopped is said as stopped, by nobody's rule.
|
|
132
197
|
*/
|
|
133
198
|
if (alert.level === 'refused') {
|
|
134
|
-
return
|
|
199
|
+
return alert.refusedByOthers === undefined
|
|
200
|
+
? `${NOTE} ${RULE_REFUSED} ${plural(alert.refusedAttempts, 'attempt', 'attempts')} at protected files; nothing was reached.`
|
|
201
|
+
: `${NOTE} ${plural(alert.refusedAttempts, 'attempt', 'attempts')} at protected files ${alert.refusedAttempts === 1 ? 'was' : 'were'} stopped; nothing was reached.`;
|
|
135
202
|
}
|
|
136
203
|
if (alert.own === true) {
|
|
137
204
|
return alert.level === 'value'
|
|
@@ -166,7 +233,10 @@ export function turnWords(remembered) {
|
|
|
166
233
|
const reached = checked.filter((alert) => alert.level === 'value' || alert.level === 'reached');
|
|
167
234
|
const values = reached.filter((alert) => alert.level === 'value').length;
|
|
168
235
|
const head = reached.length === 0
|
|
169
|
-
|
|
236
|
+
// A rule is credited only where a rule refused every one of them (WS5): the record says where it did not.
|
|
237
|
+
? checked.every((alert) => alert.notByRule !== true)
|
|
238
|
+
? `${NOTE} ${RULE_REFUSED} attempts by ${plural(checked.length, 'agent', 'agents')}; nothing was reached`
|
|
239
|
+
: `${NOTE} attempts by ${plural(checked.length, 'agent', 'agents')} at protected files were stopped; nothing was reached`
|
|
170
240
|
: values === 0
|
|
171
241
|
? `${CHECK} ${plural(reached.length, 'agent', 'agents')} reached protected files; no value found in their messages`
|
|
172
242
|
: values === reached.length
|