@agentwhy/cli 0.0.0-dev
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 +202 -0
- package/NOTICE +2 -0
- package/README.md +181 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/adapter/claude-code/contract/assumptions.js +208 -0
- package/dist/adapter/claude-code/contract/denials.js +15 -0
- package/dist/adapter/claude-code/contract/entry-points.js +15 -0
- package/dist/adapter/claude-code/contract/fields.js +73 -0
- package/dist/adapter/claude-code/contract/hooks.js +96 -0
- package/dist/adapter/claude-code/contract/identifiers.js +15 -0
- package/dist/adapter/claude-code/contract/layout.js +29 -0
- package/dist/adapter/claude-code/contract/line-types.js +26 -0
- package/dist/adapter/claude-code/contract/projects.js +23 -0
- package/dist/adapter/claude-code/contract/recognition.js +14 -0
- package/dist/adapter/claude-code/contract/session-title.js +20 -0
- package/dist/adapter/claude-code/contract/settings.js +27 -0
- package/dist/adapter/claude-code/contract/task-notifications.js +33 -0
- package/dist/adapter/claude-code/contract/tools.js +37 -0
- package/dist/adapter/claude-code/contract/version.js +51 -0
- package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +129 -0
- package/dist/adapter/claude-code/discovery/claude-code-recognition.js +46 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +100 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +61 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +28 -0
- package/dist/adapter/claude-code/discovery/discovered-session.js +1 -0
- package/dist/adapter/claude-code/discovery/session-entries.js +41 -0
- package/dist/adapter/claude-code/discovery/transcript-tail.js +81 -0
- package/dist/adapter/claude-code/events/claude-code-session-source.js +197 -0
- package/dist/adapter/claude-code/events/delegation-index.js +15 -0
- package/dist/adapter/claude-code/events/transcript-scan.js +179 -0
- package/dist/adapter/claude-code/hooks/hook-output.js +42 -0
- package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +23 -0
- package/dist/adapter/claude-code/hooks/stop-input.js +25 -0
- package/dist/adapter/claude-code/hooks/subagent-stop-input.js +26 -0
- package/dist/adapter/claude-code/policy/deny-rules.js +46 -0
- package/dist/adapter/claude-code/probe/claude-code-probe.js +86 -0
- package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +11 -0
- package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +19 -0
- package/dist/adapter/claude-code/probe/collectors/line-collector.js +1 -0
- package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +29 -0
- package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +51 -0
- package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +31 -0
- package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +28 -0
- package/dist/adapter/claude-code/probe/doctor-report.js +1 -0
- package/dist/adapter/claude-code/probe/meta-tally.js +42 -0
- package/dist/adapter/claude-code/probe/session-probe.js +1 -0
- package/dist/adapter/claude-code/probe/session-tally.js +41 -0
- package/dist/adapter/claude-code/probe/transcript-line.js +30 -0
- package/dist/adapter/claude-code/probe/transcript-tally.js +74 -0
- package/dist/adapter/claude-code/settings/deny-entries.js +65 -0
- package/dist/adapter/claude-code/settings/hook-entries.js +303 -0
- package/dist/adapter/codex/contract/actions.js +69 -0
- package/dist/adapter/codex/contract/assumptions.js +55 -0
- package/dist/adapter/codex/contract/capabilities.js +24 -0
- package/dist/adapter/codex/contract/delegations.js +30 -0
- package/dist/adapter/codex/contract/deliveries.js +25 -0
- package/dist/adapter/codex/contract/envelope.js +33 -0
- package/dist/adapter/codex/contract/hook-refusals.js +14 -0
- package/dist/adapter/codex/contract/hooks.js +46 -0
- package/dist/adapter/codex/contract/messages.js +47 -0
- package/dist/adapter/codex/contract/permissions.js +27 -0
- package/dist/adapter/codex/contract/reviews.js +13 -0
- package/dist/adapter/codex/contract/session.js +31 -0
- package/dist/adapter/codex/contract/turns.js +23 -0
- package/dist/adapter/codex/contract/version.js +15 -0
- package/dist/adapter/codex/discovery/codex-conversations.js +44 -0
- package/dist/adapter/codex/discovery/codex-project-catalogue.js +49 -0
- package/dist/adapter/codex/discovery/codex-recognition.js +20 -0
- package/dist/adapter/codex/discovery/codex-session-catalogue.js +28 -0
- package/dist/adapter/codex/discovery/codex-session-discovery.js +115 -0
- package/dist/adapter/codex/discovery/codex-session-index.js +21 -0
- package/dist/adapter/codex/discovery/codex-session-titles.js +135 -0
- package/dist/adapter/codex/discovery/session-header.js +86 -0
- package/dist/adapter/codex/discovery/session-owners.js +11 -0
- package/dist/adapter/codex/discovery/session-roots.js +49 -0
- package/dist/adapter/codex/events/action-items.js +138 -0
- package/dist/adapter/codex/events/capability-records.js +26 -0
- package/dist/adapter/codex/events/codex-session-source.js +121 -0
- package/dist/adapter/codex/events/hook-refusals.js +27 -0
- package/dist/adapter/codex/events/rollout-scan.js +614 -0
- package/dist/adapter/codex/events/turn-permissions.js +60 -0
- package/dist/adapter/codex/hooks/pre-tool-use-input.js +24 -0
- package/dist/adapter/codex/hooks/stop-refusals.js +57 -0
- package/dist/adapter/codex/probe/codex-doctor-report.js +1 -0
- package/dist/adapter/codex/probe/codex-probe.js +268 -0
- package/dist/adapter/codex/settings/codex-hooks.js +83 -0
- package/dist/cli/cli-command.js +1 -0
- package/dist/cli/command-router.js +42 -0
- package/dist/cli/commands/check-cli-command.js +128 -0
- package/dist/cli/commands/codex-stop-cli-command.js +27 -0
- package/dist/cli/commands/doctor-arguments.js +32 -0
- package/dist/cli/commands/doctor-cli-command.js +31 -0
- package/dist/cli/commands/doctor-usage.js +14 -0
- package/dist/cli/commands/init-cli-command.js +123 -0
- package/dist/cli/commands/menu-cli-command.js +58 -0
- package/dist/cli/commands/notify-cli-command.js +158 -0
- package/dist/cli/commands/refuse-cli-command.js +79 -0
- package/dist/cli/commands/report-arguments.js +62 -0
- package/dist/cli/commands/report-cli-command.js +36 -0
- package/dist/cli/commands/report-usage.js +41 -0
- package/dist/cli/commands/sessions-cli-command.js +184 -0
- package/dist/cli/commands/start-cli-command.js +83 -0
- package/dist/cli/commands/watch-cli-command.js +142 -0
- package/dist/cli/exit-codes.js +13 -0
- package/dist/cli/project-switches.js +46 -0
- package/dist/cli.js +80 -0
- package/dist/composition-root.js +411 -0
- package/dist/core/access/command-line.js +454 -0
- package/dist/core/access/listing.js +58 -0
- package/dist/core/access/path-shape.js +59 -0
- package/dist/core/access/path-tokens.js +37 -0
- package/dist/core/access/protected-access.js +310 -0
- package/dist/core/access/protected-values.js +102 -0
- package/dist/core/access/recorded-effect.js +67 -0
- package/dist/core/access/returns.js +173 -0
- package/dist/core/access/search-output.js +34 -0
- package/dist/core/access/search-reach.js +268 -0
- package/dist/core/access/uses.js +143 -0
- package/dist/core/access/words-before.js +52 -0
- package/dist/core/agent.js +1 -0
- package/dist/core/capability.js +11 -0
- package/dist/core/combined-project-catalogue.js +36 -0
- package/dist/core/combined-session-catalogue.js +25 -0
- package/dist/core/completeness.js +10 -0
- package/dist/core/context.js +1 -0
- package/dist/core/correlation/correlate.js +329 -0
- package/dist/core/delegation.js +1 -0
- package/dist/core/detector/prompt-signals.js +78 -0
- package/dist/core/entry-point.js +1 -0
- package/dist/core/event.js +1 -0
- package/dist/core/evidence.js +9 -0
- package/dist/core/late-message.js +32 -0
- package/dist/core/message.js +1 -0
- package/dist/core/nearest-project.js +28 -0
- package/dist/core/policy/default-policy.js +32 -0
- package/dist/core/policy/glob.js +56 -0
- package/dist/core/policy/parse-policy.js +70 -0
- package/dist/core/policy/policy.js +20 -0
- package/dist/core/policy/resolve-policy.js +16 -0
- package/dist/core/project-catalogue.js +1 -0
- package/dist/core/project-root.js +133 -0
- package/dist/core/provider-titles.js +10 -0
- package/dist/core/redaction/entropy.js +46 -0
- package/dist/core/redaction/redacted.js +4 -0
- package/dist/core/redaction/redactor.js +339 -0
- package/dist/core/redaction/secret-patterns.js +22 -0
- package/dist/core/redaction/value-shapes.js +81 -0
- package/dist/core/redaction/value-trace.js +110 -0
- package/dist/core/review.js +1 -0
- package/dist/core/session-catalogue.js +19 -0
- package/dist/core/session-filter.js +42 -0
- package/dist/core/session-format.js +27 -0
- package/dist/core/session-model.js +1 -0
- package/dist/core/session-reader.js +22 -0
- package/dist/core/session-records.js +1 -0
- package/dist/core/session-source.js +1 -0
- package/dist/core/session-titles.js +21 -0
- package/dist/core/turn.js +1 -0
- package/dist/doctor/codex-doctor.js +14 -0
- package/dist/doctor/doctor-use-case.js +1 -0
- package/dist/doctor/format-selecting-doctor.js +28 -0
- package/dist/doctor/render/attention/attention-rule.js +1 -0
- package/dist/doctor/render/attention/doctor-attention-rules.js +28 -0
- package/dist/doctor/render/attention/rules/damaged-meta-files-rule.js +8 -0
- package/dist/doctor/render/attention/rules/damaged-transcripts-rule.js +9 -0
- package/dist/doctor/render/attention/rules/incomplete-subagent-pairs-rule.js +6 -0
- package/dist/doctor/render/attention/rules/missing-main-transcript-rule.js +5 -0
- package/dist/doctor/render/attention/rules/missing-spilled-results-rule.js +6 -0
- package/dist/doctor/render/attention/rules/multiple-working-directories-rule.js +15 -0
- package/dist/doctor/render/attention/rules/unknown-denial-kinds-rule.js +5 -0
- package/dist/doctor/render/attention/rules/unknown-line-types-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unrecognised-entries-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unusable-directories-rule.js +12 -0
- package/dist/doctor/render/attention/transcript-scopes.js +6 -0
- package/dist/doctor/render/codex-text-doctor-renderer.js +97 -0
- package/dist/doctor/render/doctor-renderers.js +1 -0
- package/dist/doctor/render/json-doctor-renderer.js +7 -0
- package/dist/doctor/render/output-format.js +1 -0
- package/dist/doctor/render/text-doctor-renderer.js +124 -0
- package/dist/doctor/session-doctor.js +16 -0
- package/dist/infrastructure/clack-asker.js +24 -0
- package/dist/infrastructure/clack-chooser.js +40 -0
- package/dist/infrastructure/clack-multi-chooser.js +31 -0
- package/dist/infrastructure/file-alert-store.js +120 -0
- package/dist/infrastructure/file-checked-store.js +55 -0
- package/dist/infrastructure/file-mark-store.js +67 -0
- package/dist/infrastructure/file-onboarding-store.js +93 -0
- package/dist/infrastructure/folder-window.js +26 -0
- package/dist/infrastructure/node-agentwhy-invocation.js +99 -0
- package/dist/infrastructure/node-browser.js +40 -0
- package/dist/infrastructure/node-file-system.js +111 -0
- package/dist/infrastructure/node-local-server.js +115 -0
- package/dist/infrastructure/node-text-input.js +26 -0
- package/dist/infrastructure/os-notifier.js +21 -0
- package/dist/infrastructure/osascript-folder-chooser.js +53 -0
- package/dist/infrastructure/powershell-folder-chooser.js +78 -0
- package/dist/infrastructure/real-directory.js +18 -0
- package/dist/infrastructure/stream-printer.js +11 -0
- package/dist/infrastructure/terminal-banner.js +19 -0
- package/dist/ports/agentwhy-invocation.js +1 -0
- package/dist/ports/alert-store.js +1 -0
- package/dist/ports/asker.js +1 -0
- package/dist/ports/browser.js +1 -0
- package/dist/ports/checked-store.js +1 -0
- package/dist/ports/chooser.js +1 -0
- package/dist/ports/directory-reader.js +1 -0
- package/dist/ports/file-access-error.js +11 -0
- package/dist/ports/file-reader.js +1 -0
- package/dist/ports/file-tail-reader.js +1 -0
- package/dist/ports/file-writer.js +1 -0
- package/dist/ports/folder-chooser.js +1 -0
- package/dist/ports/local-server.js +1 -0
- package/dist/ports/mark-store.js +10 -0
- package/dist/ports/multi-chooser.js +1 -0
- package/dist/ports/notifier.js +1 -0
- package/dist/ports/onboarding-store.js +1 -0
- package/dist/ports/printer.js +1 -0
- package/dist/ports/text-input.js +1 -0
- package/dist/refuse/command-reach.js +135 -0
- package/dist/refuse/command-refusal.js +88 -0
- package/dist/refuse/render/codex-stop-words.js +21 -0
- package/dist/refuse/render/refusal-words.js +34 -0
- package/dist/report/build-report.js +1037 -0
- package/dist/report/check/actions-digest.js +73 -0
- package/dist/report/check/check-lines.js +83 -0
- package/dist/report/check/mark-request.js +90 -0
- package/dist/report/check/marks.js +84 -0
- package/dist/report/check/render/text-digest-renderer.js +332 -0
- package/dist/report/check/session-actions.js +90 -0
- package/dist/report/check/session-check.js +91 -0
- package/dist/report/choose-policy.js +61 -0
- package/dist/report/flow-reads.js +18 -0
- package/dist/report/private-files/tell-lists.js +84 -0
- package/dist/report/project-rules.js +33 -0
- package/dist/report/render/home-relative.js +18 -0
- package/dist/report/render/html-head.js +26 -0
- package/dist/report/render/html-report-components.js +48 -0
- package/dist/report/render/html-report-styles.js +500 -0
- package/dist/report/render/html-sidebar.js +42 -0
- package/dist/report/render/path-tail.js +14 -0
- package/dist/report/render/report-copy.js +1177 -0
- package/dist/report/render/report-page/advanced-view.js +167 -0
- package/dist/report/render/report-page/file-story.js +114 -0
- package/dist/report/render/report-page/files-view.js +381 -0
- package/dist/report/render/report-page/files.js +77 -0
- package/dist/report/render/report-page/fix-wizard-script.js +269 -0
- package/dist/report/render/report-page/fix-wizard.js +310 -0
- package/dist/report/render/report-page/helpers-view.js +383 -0
- package/dist/report/render/report-page/helpers.js +51 -0
- package/dist/report/render/report-page/item-names.js +63 -0
- package/dist/report/render/report-page/protect-patterns.js +13 -0
- package/dist/report/render/report-page/providers.js +208 -0
- package/dist/report/render/report-page/record-view.js +367 -0
- package/dist/report/render/report-page/report-page-renderer.js +129 -0
- package/dist/report/render/report-page/report-views.js +31 -0
- package/dist/report/render/report-page/story-window.js +356 -0
- package/dist/report/render/report-page/times.js +25 -0
- package/dist/report/render/report-page/to-do-view.js +299 -0
- package/dist/report/render/report-page/to-do.js +152 -0
- package/dist/report/render/report-page.js +1 -0
- package/dist/report/render/session-view.js +250 -0
- package/dist/report/render/text-report-renderer.js +981 -0
- package/dist/report/render/ui/add-file-popup.js +122 -0
- package/dist/report/render/ui/app-sidebar.js +78 -0
- package/dist/report/render/ui/ask-panel.js +80 -0
- package/dist/report/render/ui/avatar.js +19 -0
- package/dist/report/render/ui/backdrop.js +29 -0
- package/dist/report/render/ui/brand-mark.js +4 -0
- package/dist/report/render/ui/button.js +67 -0
- package/dist/report/render/ui/callout.js +20 -0
- package/dist/report/render/ui/chat-example.js +17 -0
- package/dist/report/render/ui/checklist.js +50 -0
- package/dist/report/render/ui/confirm-dialog.js +34 -0
- package/dist/report/render/ui/data-table.js +49 -0
- package/dist/report/render/ui/drawer.js +25 -0
- package/dist/report/render/ui/file-chip.js +37 -0
- package/dist/report/render/ui/fold-line.js +22 -0
- package/dist/report/render/ui/guide-card.js +25 -0
- package/dist/report/render/ui/hero.js +37 -0
- package/dist/report/render/ui/labelled-select.js +10 -0
- package/dist/report/render/ui/live-script.js +222 -0
- package/dist/report/render/ui/local-date.js +58 -0
- package/dist/report/render/ui/mode-icon.js +6 -0
- package/dist/report/render/ui/page-shell.js +101 -0
- package/dist/report/render/ui/pill-tabs.js +47 -0
- package/dist/report/render/ui/popup.js +77 -0
- package/dist/report/render/ui/progress.js +35 -0
- package/dist/report/render/ui/project-list.js +284 -0
- package/dist/report/render/ui/scope-choice.js +19 -0
- package/dist/report/render/ui/stats.js +21 -0
- package/dist/report/render/ui/status-icon.js +30 -0
- package/dist/report/render/ui/status-look.js +21 -0
- package/dist/report/render/ui/switch.js +11 -0
- package/dist/report/render/ui/tag.js +19 -0
- package/dist/report/render/ui/task-list.js +56 -0
- package/dist/report/render/ui/tokens.js +46 -0
- package/dist/report/render/ui/update-notice.js +146 -0
- package/dist/report/render/ui/words/app-words.js +163 -0
- package/dist/report/render/ui/words/conversations-words.js +457 -0
- package/dist/report/render/ui/words/month-words.js +59 -0
- package/dist/report/render/ui/words/onboarding-words.js +446 -0
- package/dist/report/render/ui/words/projects-words.js +217 -0
- package/dist/report/render/ui/words/report-words.js +2099 -0
- package/dist/report/render/ui/words/settings-words.js +611 -0
- package/dist/report/render/ui/words/to-fix-words.js +287 -0
- package/dist/report/report-model.js +2 -0
- package/dist/report/report-use-case.js +1 -0
- package/dist/report/rule-names.js +20 -0
- package/dist/report/session-report.js +163 -0
- package/dist/report/start/app-nav.js +37 -0
- package/dist/report/start/conversations/calendar-view.js +104 -0
- package/dist/report/start/conversations/conversation-columns.js +187 -0
- package/dist/report/start/conversations/conversations-renderer.js +54 -0
- package/dist/report/start/conversations/empty-week.js +120 -0
- package/dist/report/start/conversations/files-window.js +166 -0
- package/dist/report/start/conversations/period-section.js +280 -0
- package/dist/report/start/conversations/periods-script.js +440 -0
- package/dist/report/start/conversations/periods.js +110 -0
- package/dist/report/start/conversations/week-view.js +30 -0
- package/dist/report/start/conversations/weeks.js +17 -0
- package/dist/report/start/month/month-renderer.js +50 -0
- package/dist/report/start/month/month-view.js +135 -0
- package/dist/report/start/month/months.js +17 -0
- package/dist/report/start/onboarding/done.js +171 -0
- package/dist/report/start/onboarding/finish-onboarding.js +93 -0
- package/dist/report/start/onboarding/intro.js +100 -0
- package/dist/report/start/onboarding/onboarding-changes.js +40 -0
- package/dist/report/start/onboarding/onboarding-renderer.js +195 -0
- package/dist/report/start/onboarding/onboarding-script.js +400 -0
- package/dist/report/start/onboarding/onboarding-view.js +98 -0
- package/dist/report/start/onboarding/project-step.js +210 -0
- package/dist/report/start/onboarding/steps.js +239 -0
- package/dist/report/start/onboarding/welcome.js +66 -0
- package/dist/report/start/page-notice.js +10 -0
- package/dist/report/start/projects/index-projects.js +43 -0
- package/dist/report/start/projects/moved-page.js +82 -0
- package/dist/report/start/projects/project-switch.js +138 -0
- package/dist/report/start/projects/projects-window.js +67 -0
- package/dist/report/start/projects/temporary-space.js +18 -0
- package/dist/report/start/render/air-datepicker-vendor.js +27 -0
- package/dist/report/start/render/session-status.js +41 -0
- package/dist/report/start/render/start-words.js +84 -0
- package/dist/report/start/repository.js +28 -0
- package/dist/report/start/serve/index-handler.js +372 -0
- package/dist/report/start/serve/onboarding-request.js +50 -0
- package/dist/report/start/serve/settings-request.js +1 -0
- package/dist/report/start/serve/settings-setup.js +193 -0
- package/dist/report/start/session-index.js +1 -0
- package/dist/report/start/session-start.js +974 -0
- package/dist/report/start/settings/alerts-tab.js +104 -0
- package/dist/report/start/settings/files-tab.js +169 -0
- package/dist/report/start/settings/general-tab.js +71 -0
- package/dist/report/start/settings/settings-renderer.js +254 -0
- package/dist/report/start/settings/settings-script.js +161 -0
- package/dist/report/start/settings/settings-view.js +240 -0
- package/dist/report/start/settings/settings-windows.js +334 -0
- package/dist/report/start/settings-files.js +43 -0
- package/dist/report/start/to-fix/done-list.js +45 -0
- package/dist/report/start/to-fix/file-window.js +110 -0
- package/dist/report/start/to-fix/to-fix-list.js +70 -0
- package/dist/report/start/to-fix/to-fix-renderer.js +237 -0
- package/dist/report/start/to-fix/to-fix-script.js +94 -0
- package/dist/report/start/to-fix/to-fix-view.js +124 -0
- package/dist/report/watch/agent-alert.js +72 -0
- package/dist/report/watch/notice-choices.js +35 -0
- package/dist/report/watch/notice-settings.js +65 -0
- package/dist/report/watch/preferences.js +100 -0
- package/dist/report/watch/render/instruction-words.js +54 -0
- package/dist/report/watch/render/notice-words.js +182 -0
- package/dist/report/watch/subagent-watch.js +345 -0
- package/dist/report/watch/watch-notice.js +1 -0
- package/dist/setup/behind.js +18 -0
- package/dist/setup/codex-mirror.js +171 -0
- package/dist/setup/not-a-project.js +19 -0
- package/dist/setup/project-setup.js +644 -0
- package/dist/setup/protected-patterns.js +30 -0
- package/dist/shared/colour.js +23 -0
- package/dist/shared/compare.js +4 -0
- package/dist/shared/content-version.js +13 -0
- package/dist/shared/counter.js +11 -0
- package/dist/shared/file-stamp.js +4 -0
- package/dist/shared/json.js +13 -0
- package/dist/shared/label.js +13 -0
- package/dist/shared/package-name.js +6 -0
- package/dist/shared/plain-invocation.js +44 -0
- package/dist/shared/plain-version.js +21 -0
- package/dist/shared/printable.js +25 -0
- package/dist/shared/renderer.js +1 -0
- package/dist/shared/sentence.js +20 -0
- package/dist/shared/terminal-logo.js +41 -0
- package/dist/shared/text-table.js +15 -0
- package/dist/shared/wrap.js +29 -0
- package/package.json +70 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime permissions a `turn_context` records for its turn (§2.5; §2.8): recorded settings, never agentwhy's policy
|
|
3
|
+
* and never a verdict on a path (X21, X22). Values outside these lists are kept out of the model and said as unknown.
|
|
4
|
+
*/
|
|
5
|
+
export const PERMISSIONS = {
|
|
6
|
+
approval: 'approval_policy',
|
|
7
|
+
approver: 'approvals_reviewer',
|
|
8
|
+
sandbox: 'sandbox_policy',
|
|
9
|
+
sandboxType: 'type',
|
|
10
|
+
network: 'network_access',
|
|
11
|
+
fileSystem: 'file_system_sandbox_policy',
|
|
12
|
+
entries: 'entries',
|
|
13
|
+
access: 'access',
|
|
14
|
+
target: 'path',
|
|
15
|
+
targetType: 'type',
|
|
16
|
+
targetPath: 'path',
|
|
17
|
+
};
|
|
18
|
+
/** `approvals_reviewer`: `user` 128 and `auto_review` 341 turns (§2.5); `user` in 7 of 7 (§2.8). */
|
|
19
|
+
export const APPROVERS = { user: 'person', auto_review: 'reviewer' };
|
|
20
|
+
/** `approval_policy` string values: `on-request` 304 and `never` 145 turns (§2.5). A `granular` object (20) is not read. */
|
|
21
|
+
export const APPROVAL_POLICIES = ['on-request', 'never'];
|
|
22
|
+
/** `sandbox_policy.type`: `workspace-write` and `read-only` (§2.8). */
|
|
23
|
+
export const SANDBOX_TYPES = ['workspace-write', 'read-only'];
|
|
24
|
+
/** `entries[].access`: `read` 2,731 and `write` 1,112 (§2.5). The entries only allow; not one denies. */
|
|
25
|
+
export const SCOPE_ACCESS = ['read', 'write'];
|
|
26
|
+
/** `entries[].path.type`: `path` 2,436 and `special` 1,407 (§2.5). A special target names no path this reads. */
|
|
27
|
+
export const SCOPE_TARGETS = { path: 'path', special: 'special' };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex's own reviewer (X19, X20; §2.4): a child thread whose `source.subagent.other` is `guardian`, started by Codex and
|
|
3
|
+
* never by the agent. Its turns name the reviewed turn by `root_turn_id` (§2.7: 52 references, all present); its
|
|
4
|
+
* `task_complete.last_agent_message` is a JSON verdict (141 of 141) whose `outcome` was `allow` every time.
|
|
5
|
+
*/
|
|
6
|
+
export const REVIEWER = { other: 'other', guardian: 'guardian' };
|
|
7
|
+
export const VERDICT = {
|
|
8
|
+
outcome: 'outcome',
|
|
9
|
+
risk: 'risk_level',
|
|
10
|
+
rationale: 'rationale',
|
|
11
|
+
/** The one outcome measured. Any other is unrecognised, never a refusal (X20, XB1). */
|
|
12
|
+
allowed: 'allow',
|
|
13
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** First-line discovery (§2.1, §2.7). Measured 2026-09-29; action and message semantics have separate gates. */
|
|
2
|
+
export const SESSION = {
|
|
3
|
+
lineType: 'type',
|
|
4
|
+
metadataType: 'session_meta',
|
|
5
|
+
payload: 'payload',
|
|
6
|
+
id: 'id',
|
|
7
|
+
parentId: 'parent_thread_id',
|
|
8
|
+
workingDirectory: 'cwd',
|
|
9
|
+
historyMode: 'history_mode',
|
|
10
|
+
/** The build that wrote the file: `0.154.0-alpha.6.2`, `0.155.0-alpha.16.3`, `0.155.0-alpha.9` (§2.7), `0.157.0` (§2.8). */
|
|
11
|
+
version: 'cli_version',
|
|
12
|
+
/** A string for a person's session (`exec` in §2.8); an object `{subagent: …}` for a thread another started (§2.4). */
|
|
13
|
+
source: 'source',
|
|
14
|
+
filePrefix: 'rollout-',
|
|
15
|
+
fileSuffix: '.jsonl',
|
|
16
|
+
};
|
|
17
|
+
/** Where Codex keeps its rollouts, under the home directory (X1, §2.1). `$CODEX_HOME` is not measured (XB6). */
|
|
18
|
+
export const SESSIONS_ROOT = ['.codex', 'sessions'];
|
|
19
|
+
export const HISTORY_MODES = ['paginated', 'legacy'];
|
|
20
|
+
/**
|
|
21
|
+
* Codex's own list of thread names, under the home directory: one JSON object a line, `{"id", "thread_name",
|
|
22
|
+
* "updated_at"}`, appended as a name is given (a later line names the same id again). Measured 2026-09-30 on 55 lines:
|
|
23
|
+
* every root thread started from the VS Code extension, the desktop app and the terminal app (0.154.0-alpha.6.2 to
|
|
24
|
+
* 0.159.2) had a line, and no thread whose `source` is `exec` did (20); the name was never the person's first message nor its start, so it
|
|
25
|
+
* is a title Codex wrote, as Claude Code's `ai-title` is. The `id` is the rollout's thread id.
|
|
26
|
+
*/
|
|
27
|
+
export const THREAD_NAMES = {
|
|
28
|
+
file: ['.codex', 'session_index.jsonl'],
|
|
29
|
+
id: 'id',
|
|
30
|
+
name: 'thread_name',
|
|
31
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A turn and its boundaries (§2.2): `turn_context` opens one per turn with its `turn_id`; `task_started`,
|
|
3
|
+
* `task_complete` and `turn_aborted` name it by the same id. Every item and response record carries it (§2.2, §2.8).
|
|
4
|
+
*/
|
|
5
|
+
export const TURN_CONTEXT = {
|
|
6
|
+
turnId: 'turn_id',
|
|
7
|
+
workingDirectory: 'cwd',
|
|
8
|
+
};
|
|
9
|
+
export const TURN_EVENTS = {
|
|
10
|
+
started: 'task_started',
|
|
11
|
+
complete: 'task_complete',
|
|
12
|
+
aborted: 'turn_aborted',
|
|
13
|
+
turnId: 'turn_id',
|
|
14
|
+
/** On a reviewer's turns: the turn of the agent it reviewed (§2.4, §2.7). */
|
|
15
|
+
rootTurnId: 'root_turn_id',
|
|
16
|
+
/** On `task_complete`: a copy of the turn's final answer, or a reviewer's verdict (§2.4, §2.8). */
|
|
17
|
+
lastMessage: 'last_agent_message',
|
|
18
|
+
};
|
|
19
|
+
/** Where a response record carries its turn (§2.2): 1 record of all measured lacked it. */
|
|
20
|
+
export const RESPONSE_TURN = {
|
|
21
|
+
metadata: 'internal_chat_message_metadata_passthrough',
|
|
22
|
+
turnId: 'turn_id',
|
|
23
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1 (2026-09-29): first-line recognition and identity only, X1-X5, measured on 70 frozen files (spec §2.7).
|
|
3
|
+
* v2 (2026-09-29): the records a report reads - turns and their permissions, action items, messages, the cell's output
|
|
4
|
+
* handed to the model, delegations and reviewers - from §2.2-§2.6 and the controlled terminal sessions of §2.8, and the
|
|
5
|
+
* capability matrix they permit (`capabilities.ts`). Joins that depend on the probe's corrected session index (§2.4)
|
|
6
|
+
* stay provisional: they are made by id and leave a relation unresolved where they break; XB4 is open. Every other
|
|
7
|
+
* build and mode is read with its capabilities unmeasured.
|
|
8
|
+
*/
|
|
9
|
+
export const CONTRACT_VERSION = 2;
|
|
10
|
+
export const VERIFIED_AGAINST = {
|
|
11
|
+
date: '2026-09-29',
|
|
12
|
+
codex: ['0.154.0-alpha.6.2', '0.155.0-alpha.16.3', '0.155.0-alpha.9', '0.157.0'],
|
|
13
|
+
files: 77,
|
|
14
|
+
scope: 'session-records',
|
|
15
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
/**
|
|
3
|
+
* The conversations of one listing (X5): every root, with the files whose parents lead to it, and every file whose
|
|
4
|
+
* relation did not resolve, which is listed on its own rather than hidden as a thread of nothing. A resolved descendant
|
|
5
|
+
* is never a row: lists show the root only. `keep` chooses the rows by their first file, before any file is looked at:
|
|
6
|
+
* only the files of the rows kept are.
|
|
7
|
+
*/
|
|
8
|
+
export async function conversationsIn(listing, directories, keep = () => true) {
|
|
9
|
+
// The files resolved to each root, gathered in one pass: thousands of rollouts are walked once, not once per row.
|
|
10
|
+
const resolvedTo = new Map();
|
|
11
|
+
listing.sources.forEach((source, at) => {
|
|
12
|
+
if (source.relation.kind !== 'resolved')
|
|
13
|
+
return;
|
|
14
|
+
const members = resolvedTo.get(source.relation.root);
|
|
15
|
+
if (members === undefined)
|
|
16
|
+
resolvedTo.set(source.relation.root, [at]);
|
|
17
|
+
else
|
|
18
|
+
members.push(at);
|
|
19
|
+
});
|
|
20
|
+
const rows = listing.sources.flatMap((source, index) => {
|
|
21
|
+
const relation = source.relation;
|
|
22
|
+
if ((relation.kind === 'resolved' && relation.root !== index) || !keep(source))
|
|
23
|
+
return [];
|
|
24
|
+
return [{ source, index, members: relation.kind === 'resolved' ? (resolvedTo.get(index) ?? []) : [index] }];
|
|
25
|
+
});
|
|
26
|
+
return Promise.all(rows.map(async ({ source, index, members }) => {
|
|
27
|
+
const modified = await Promise.all(members.map((at) => modifiedAt(directories, listing.sources[at]?.path ?? '')));
|
|
28
|
+
return {
|
|
29
|
+
source,
|
|
30
|
+
modifiedAt: Math.max(...modified),
|
|
31
|
+
delegations: members.filter((at) => at !== index && listing.sources[at]?.header.origin.kind === 'spawned').length,
|
|
32
|
+
};
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
async function modifiedAt(directories, path) {
|
|
36
|
+
try {
|
|
37
|
+
return await directories.modifiedAt(path);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (!(error instanceof FileAccessError))
|
|
41
|
+
throw error;
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
import { conversationsIn } from './codex-conversations.js';
|
|
3
|
+
/**
|
|
4
|
+
* The projects Codex keeps conversations for (`which-project.md` V9; X4): one per recorded `cwd` of a conversation's
|
|
5
|
+
* first line, named by `projectId` and never decoded from it. A conversation that records no folder is counted, never
|
|
6
|
+
* guessed. Titles and ways in are not read: their sources are not measured for Codex.
|
|
7
|
+
*/
|
|
8
|
+
export class CodexProjectCatalogue {
|
|
9
|
+
#dependencies;
|
|
10
|
+
constructor(dependencies) {
|
|
11
|
+
this.#dependencies = dependencies;
|
|
12
|
+
}
|
|
13
|
+
async list() {
|
|
14
|
+
const { index, directories, sessionsRoot, projectId } = this.#dependencies;
|
|
15
|
+
const conversations = await conversationsIn(await index.list(sessionsRoot), directories);
|
|
16
|
+
const byFolder = new Map();
|
|
17
|
+
let unreadable = 0;
|
|
18
|
+
for (const conversation of conversations) {
|
|
19
|
+
const folder = conversation.source.header.project;
|
|
20
|
+
if (folder === undefined) {
|
|
21
|
+
unreadable += 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const known = byFolder.get(folder);
|
|
25
|
+
byFolder.set(folder, {
|
|
26
|
+
conversations: (known?.conversations ?? 0) + 1,
|
|
27
|
+
newest: Math.max(known?.newest ?? 0, conversation.modifiedAt),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const projects = await Promise.all([...byFolder].map(async ([path, folder]) => ({
|
|
31
|
+
id: projectId(path),
|
|
32
|
+
path,
|
|
33
|
+
exists: await this.#isDirectory(path),
|
|
34
|
+
conversations: folder.conversations,
|
|
35
|
+
newest: { modifiedAt: folder.newest },
|
|
36
|
+
})));
|
|
37
|
+
return { projects: projects.sort((a, b) => b.newest.modifiedAt - a.newest.modifiedAt), unreadable };
|
|
38
|
+
}
|
|
39
|
+
async #isDirectory(path) {
|
|
40
|
+
try {
|
|
41
|
+
return (await this.#dependencies.directories.kindOf(path)) === 'directory';
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (!(error instanceof FileAccessError))
|
|
45
|
+
throw error;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
/**
|
|
3
|
+
* Whether an input is Codex's (X2): a file whose first line is a Codex `session_meta`, or a folder holding at least one
|
|
4
|
+
* such rollout. Only first lines are read, and a folder's only until the first rollout recognised.
|
|
5
|
+
*/
|
|
6
|
+
export async function recogniseCodex(discovery, directories, input) {
|
|
7
|
+
let kind;
|
|
8
|
+
try {
|
|
9
|
+
kind = await directories.kindOf(input);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error instanceof FileAccessError)
|
|
13
|
+
return 'unavailable';
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
if (kind === 'directory')
|
|
17
|
+
return (await discovery.holdsSession(input)) ? 'recognised' : 'unknown';
|
|
18
|
+
const read = await discovery.readHeader(input);
|
|
19
|
+
return read.kind === 'recognised' ? 'recognised' : read.kind === 'unknown' ? 'unknown' : 'unavailable';
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { conversationsIn } from './codex-conversations.js';
|
|
2
|
+
/**
|
|
3
|
+
* A project's Codex conversations (X1, X4, X5): each conversation whose first line records the project's folder as its
|
|
4
|
+
* `cwd`, exactly - Codex records the folder itself, where Claude Code's store encodes it. First lines only are read.
|
|
5
|
+
*/
|
|
6
|
+
export class CodexSessionCatalogue {
|
|
7
|
+
#dependencies;
|
|
8
|
+
constructor(dependencies) {
|
|
9
|
+
this.#dependencies = dependencies;
|
|
10
|
+
}
|
|
11
|
+
async list(workingDirectory) {
|
|
12
|
+
const { index, directories, sessionsRoot } = this.#dependencies;
|
|
13
|
+
const listing = await index.list(sessionsRoot);
|
|
14
|
+
// The root itself missing or unreadable is "not looked at", never "no conversations".
|
|
15
|
+
const found = !listing.gaps.some((gap) => gap.path === sessionsRoot);
|
|
16
|
+
// Chosen by the first line before any file is looked at: only this project's conversations cost a look.
|
|
17
|
+
const sessions = (await conversationsIn(listing, directories, (root) => root.header.project === workingDirectory))
|
|
18
|
+
.map((conversation) => ({
|
|
19
|
+
id: conversation.source.header.id,
|
|
20
|
+
path: conversation.source.path,
|
|
21
|
+
modifiedAt: conversation.modifiedAt,
|
|
22
|
+
delegations: conversation.delegations,
|
|
23
|
+
provider: 'codex',
|
|
24
|
+
}))
|
|
25
|
+
.sort((a, b) => b.modifiedAt - a.modifiedAt);
|
|
26
|
+
return { directory: sessionsRoot, found, searched: [{ provider: 'codex', directory: sessionsRoot, found }], sessions };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
2
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
3
|
+
import { SESSION } from '../contract/session.js';
|
|
4
|
+
import { readSessionHeader } from './session-header.js';
|
|
5
|
+
import { AMBIGUOUS, sessionOwners } from './session-owners.js';
|
|
6
|
+
import { sessionRoots } from './session-roots.js';
|
|
7
|
+
/** Header-only discovery. The caller supplies the supported sessions root; no environment or SQLite access. */
|
|
8
|
+
export class CodexSessionDiscovery {
|
|
9
|
+
#directories;
|
|
10
|
+
#files;
|
|
11
|
+
constructor(dependencies) {
|
|
12
|
+
this.#directories = dependencies.directories;
|
|
13
|
+
this.#files = dependencies.files;
|
|
14
|
+
}
|
|
15
|
+
/** Explicit file recognition ignores its name and never reads beyond its first line. */
|
|
16
|
+
readHeader(path) {
|
|
17
|
+
return readSessionHeader(this.#files, path);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The tree of the conversation `input` names (X5). It is searched for in the supported sessions root when `input` is
|
|
21
|
+
* inside it, and otherwise in `input`'s own folder only: a copy of a session and the original it was copied from hold
|
|
22
|
+
* one id, and neither would join anything (X3). A listing of that same folder already taken - by the catalogue that
|
|
23
|
+
* listed this conversation - is used instead of walking it again.
|
|
24
|
+
*/
|
|
25
|
+
async tree(input, sessionsRoot, known) {
|
|
26
|
+
const path = resolve(input);
|
|
27
|
+
const head = await this.readHeader(path);
|
|
28
|
+
if (head.kind !== 'recognised')
|
|
29
|
+
return head;
|
|
30
|
+
const inside = !relative(resolve(sessionsRoot), path).startsWith('..');
|
|
31
|
+
const directory = inside ? resolve(sessionsRoot) : dirname(path);
|
|
32
|
+
const listing = known !== undefined && resolve(known.directory) === directory ? known.listing : await this.list(directory);
|
|
33
|
+
const byPath = new Map([[path, head.header]]);
|
|
34
|
+
for (const source of listing.sources)
|
|
35
|
+
if (!byPath.has(resolve(source.path)))
|
|
36
|
+
byPath.set(resolve(source.path), source.header);
|
|
37
|
+
const paths = [...byPath.keys()].sort((a, b) => a.localeCompare(b));
|
|
38
|
+
const headers = paths.map((each) => byPath.get(each));
|
|
39
|
+
const owners = sessionOwners(headers.map((header) => (header.id === '' ? undefined : header.id)));
|
|
40
|
+
const parentOf = (index) => {
|
|
41
|
+
const parent = headers[index]?.parent;
|
|
42
|
+
const owner = parent?.kind === 'parent' ? owners.get(parent.id) : undefined;
|
|
43
|
+
return typeof owner === 'number' ? owner : undefined;
|
|
44
|
+
};
|
|
45
|
+
const root = paths.indexOf(path);
|
|
46
|
+
// A member is a file whose chain of unique parents reaches the root; a cycle or a break ends the walk.
|
|
47
|
+
const reaches = (start) => {
|
|
48
|
+
const seen = new Set();
|
|
49
|
+
for (let at = parentOf(start); at !== undefined && !seen.has(at); at = parentOf(at)) {
|
|
50
|
+
if (at === root)
|
|
51
|
+
return true;
|
|
52
|
+
seen.add(at);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
};
|
|
56
|
+
const order = [root, ...paths.map((_, index) => index).filter((index) => index !== root && reaches(index))];
|
|
57
|
+
const position = new Map(order.map((index, at) => [index, at]));
|
|
58
|
+
const members = order.map((index) => {
|
|
59
|
+
const parent = index === root ? undefined : position.get(parentOf(index) ?? -1);
|
|
60
|
+
return { path: paths[index], header: headers[index], ...(parent === undefined ? {} : { parent }) };
|
|
61
|
+
});
|
|
62
|
+
return { kind: 'tree', members, rootShared: owners.get(head.header.id) === AMBIGUOUS, gaps: listing.gaps };
|
|
63
|
+
}
|
|
64
|
+
async list(directory) {
|
|
65
|
+
const sources = [];
|
|
66
|
+
const gaps = [];
|
|
67
|
+
for await (const { path, read } of this.#rollouts(directory, gaps)) {
|
|
68
|
+
if (read.kind === 'recognised')
|
|
69
|
+
sources.push({ path, header: read.header });
|
|
70
|
+
else
|
|
71
|
+
gaps.push({ path, reason: read.kind === 'unavailable' ? read.reason : 'unknown-format' });
|
|
72
|
+
}
|
|
73
|
+
// Path order is presentation only. Ownership and root resolution are independent of traversal order.
|
|
74
|
+
sources.sort((a, b) => a.path.localeCompare(b.path));
|
|
75
|
+
const relations = sessionRoots(sources.map((source) => source.header));
|
|
76
|
+
return {
|
|
77
|
+
sources: sources.map((source, index) => ({ ...source, relation: relations[index] })),
|
|
78
|
+
gaps,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Whether a folder holds at least one Codex session: the walk `list` makes, ended at the first header recognised (X2). */
|
|
82
|
+
async holdsSession(directory) {
|
|
83
|
+
for await (const { read } of this.#rollouts(directory, []))
|
|
84
|
+
if (read.kind === 'recognised')
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
/** Every rollout under a folder with its first line read, in walk order; a folder that cannot be listed is a gap. */
|
|
89
|
+
async *#rollouts(directory, gaps) {
|
|
90
|
+
const pending = [directory];
|
|
91
|
+
while (pending.length > 0) {
|
|
92
|
+
const current = pending.pop();
|
|
93
|
+
if (current === undefined)
|
|
94
|
+
break;
|
|
95
|
+
let entries;
|
|
96
|
+
try {
|
|
97
|
+
entries = await this.#directories.list(current);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
if (!(error instanceof FileAccessError))
|
|
101
|
+
throw error;
|
|
102
|
+
gaps.push({ path: current, reason: error.failure });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const path = join(current, entry.name);
|
|
107
|
+
if (entry.kind === 'directory')
|
|
108
|
+
pending.push(path);
|
|
109
|
+
else if (entry.kind === 'file' && entry.name.startsWith(SESSION.filePrefix) && entry.name.endsWith(SESSION.fileSuffix)) {
|
|
110
|
+
yield { path, read: await this.readHeader(path) };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last listing of the sessions root, taken by a catalogue and reused by the reader for the conversations it listed
|
|
3
|
+
* (the review of 2026-09-29, `.ai/plans/2026-09-29-what-codex-wrote.md`): the root's headers are read once per listing,
|
|
4
|
+
* not once per conversation. It is only ever the latest - every listing replaces it - so a thread started since is in
|
|
5
|
+
* the next one, and nothing is kept past it.
|
|
6
|
+
*/
|
|
7
|
+
export class CodexSessionIndex {
|
|
8
|
+
#discovery;
|
|
9
|
+
#latest;
|
|
10
|
+
constructor(discovery) {
|
|
11
|
+
this.#discovery = discovery;
|
|
12
|
+
}
|
|
13
|
+
async list(directory) {
|
|
14
|
+
const listing = await this.#discovery.list(directory);
|
|
15
|
+
this.#latest = { directory, listing };
|
|
16
|
+
return listing;
|
|
17
|
+
}
|
|
18
|
+
latest() {
|
|
19
|
+
return this.#latest;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
import { isJsonObject, parseJsonObject } from '../../../shared/json.js';
|
|
3
|
+
import { oneLine } from '../../../shared/printable.js';
|
|
4
|
+
import { ENVELOPE, LINE_TYPES } from '../contract/envelope.js';
|
|
5
|
+
import { MESSAGE, RESPONSE_ITEMS, ROLES, TEXT_BLOCKS } from '../contract/messages.js';
|
|
6
|
+
import { SESSION, THREAD_NAMES } from '../contract/session.js';
|
|
7
|
+
import { RESPONSE_TURN } from '../contract/turns.js';
|
|
8
|
+
/**
|
|
9
|
+
* What a person recognises a Codex conversation by: the name Codex gave its thread and shows in its own list, from
|
|
10
|
+
* `session_index.jsonl`, past the redactor. The file is read once, for the whole list; its last line for an id is the
|
|
11
|
+
* name the thread has now, since a name given again is appended. Where `codex exec` wrote no name, its first prompt
|
|
12
|
+
* follows a separate environment-context message in measured builds. That prompt can identify the conversation too.
|
|
13
|
+
*/
|
|
14
|
+
export class CodexSessionTitles {
|
|
15
|
+
#dependencies;
|
|
16
|
+
#names;
|
|
17
|
+
#readAt;
|
|
18
|
+
constructor(dependencies) {
|
|
19
|
+
this.#dependencies = dependencies;
|
|
20
|
+
}
|
|
21
|
+
async recognise(session) {
|
|
22
|
+
const indexed = (await this.#current()).get(session.id);
|
|
23
|
+
if (indexed !== undefined)
|
|
24
|
+
return { title: this.#dependencies.redactor.scan(indexed) };
|
|
25
|
+
const prompt = await this.#execPrompt(session);
|
|
26
|
+
if (prompt === undefined)
|
|
27
|
+
return {};
|
|
28
|
+
// Scan the entire prompt before shortening it: a cut secret might no longer match the redactor's pattern.
|
|
29
|
+
const safe = this.#dependencies.redactor.scan(prompt);
|
|
30
|
+
return { title: this.#dependencies.redactor.scan(Array.from(oneLine(safe)).slice(0, 160).join('')) };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Measured on 21 local `exec` roots (0.155.0-alpha.16.3, 0.157.0, 0.159.2): the first user message is injected
|
|
34
|
+
* context containing `<environment_context>`; the second, in the same turn, is one `input_text` prompt at line 9.
|
|
35
|
+
* Stop near the start. An unfamiliar shape has no fallback title instead of guessing from a later message.
|
|
36
|
+
*/
|
|
37
|
+
async #execPrompt(session) {
|
|
38
|
+
let lineNumber = 0;
|
|
39
|
+
let contextTurn;
|
|
40
|
+
try {
|
|
41
|
+
for await (const raw of this.#dependencies.files.readLines(session.path)) {
|
|
42
|
+
if (++lineNumber > 32)
|
|
43
|
+
break;
|
|
44
|
+
const line = parseJsonObject(raw);
|
|
45
|
+
if (!isJsonObject(line))
|
|
46
|
+
return undefined;
|
|
47
|
+
const payload = line[ENVELOPE.payload];
|
|
48
|
+
if (!isJsonObject(payload))
|
|
49
|
+
return undefined;
|
|
50
|
+
if (lineNumber === 1) {
|
|
51
|
+
if (line[ENVELOPE.type] !== LINE_TYPES.sessionMeta || payload[SESSION.id] !== session.id ||
|
|
52
|
+
payload[SESSION.source] !== 'exec' ||
|
|
53
|
+
!['0.155.0-alpha.16.3', '0.157.0', '0.159.2'].includes(String(payload[SESSION.version])))
|
|
54
|
+
return undefined;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (line[ENVELOPE.type] !== LINE_TYPES.responseItem || payload[ENVELOPE.payloadType] !== RESPONSE_ITEMS.message ||
|
|
58
|
+
payload[MESSAGE.role] !== ROLES.user)
|
|
59
|
+
continue;
|
|
60
|
+
const metadata = payload[RESPONSE_TURN.metadata];
|
|
61
|
+
const turn = isJsonObject(metadata) ? metadata[RESPONSE_TURN.turnId] : undefined;
|
|
62
|
+
const blocks = payload[MESSAGE.content];
|
|
63
|
+
if (typeof turn !== 'string' || !Array.isArray(blocks))
|
|
64
|
+
return undefined;
|
|
65
|
+
if (contextTurn === undefined) {
|
|
66
|
+
if (!blocks.some((block) => {
|
|
67
|
+
if (!isJsonObject(block) || block[MESSAGE.blockType] !== TEXT_BLOCKS.given)
|
|
68
|
+
return false;
|
|
69
|
+
const text = block[MESSAGE.blockText];
|
|
70
|
+
return typeof text === 'string' && text.includes('<environment_context>');
|
|
71
|
+
}))
|
|
72
|
+
return undefined;
|
|
73
|
+
contextTurn = turn;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (turn !== contextTurn || blocks.length !== 1 || !isJsonObject(blocks[0]) ||
|
|
77
|
+
blocks[0][MESSAGE.blockType] !== TEXT_BLOCKS.given)
|
|
78
|
+
return undefined;
|
|
79
|
+
const prompt = blocks[0][MESSAGE.blockText];
|
|
80
|
+
if (typeof prompt !== 'string')
|
|
81
|
+
return undefined;
|
|
82
|
+
const title = oneLine(prompt);
|
|
83
|
+
return title === '' ? undefined : title;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (!(error instanceof FileAccessError))
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The names as the file holds them now. Codex names a thread a few seconds after it starts (§2.9: 14 s in the
|
|
94
|
+
* maintainer's run), and a served page lists a conversation started since as it is drawn again - so the file is read
|
|
95
|
+
* again whenever it changed, and once for every list where it did not. Found by the maintainer: read once per process,
|
|
96
|
+
* a conversation started a moment before `start` kept "A conversation with no title" on every later drawing.
|
|
97
|
+
*/
|
|
98
|
+
async #current() {
|
|
99
|
+
let at;
|
|
100
|
+
try {
|
|
101
|
+
at = await this.#dependencies.directories.modifiedAt(this.#dependencies.path);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (!(error instanceof FileAccessError))
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
if (this.#names === undefined || at !== this.#readAt) {
|
|
108
|
+
this.#readAt = at;
|
|
109
|
+
this.#names = this.#read();
|
|
110
|
+
}
|
|
111
|
+
return this.#names;
|
|
112
|
+
}
|
|
113
|
+
async #read() {
|
|
114
|
+
const names = new Map();
|
|
115
|
+
try {
|
|
116
|
+
for await (const line of this.#dependencies.files.readLines(this.#dependencies.path)) {
|
|
117
|
+
const record = parseJsonObject(line);
|
|
118
|
+
if (!isJsonObject(record))
|
|
119
|
+
continue;
|
|
120
|
+
const id = record[THREAD_NAMES.id];
|
|
121
|
+
const name = record[THREAD_NAMES.name];
|
|
122
|
+
if (typeof id !== 'string' || typeof name !== 'string')
|
|
123
|
+
continue;
|
|
124
|
+
const shown = oneLine(name);
|
|
125
|
+
if (shown !== '')
|
|
126
|
+
names.set(id, shown);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (!(error instanceof FileAccessError))
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
return names;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
import { isJsonObject, parseJsonObject } from '../../../shared/json.js';
|
|
3
|
+
import { THREAD_SPAWN } from '../contract/delegations.js';
|
|
4
|
+
import { REVIEWER } from '../contract/reviews.js';
|
|
5
|
+
import { HISTORY_MODES, SESSION } from '../contract/session.js';
|
|
6
|
+
/** X2: the first physical line decides recognition. A later metadata line cannot rescue an unknown file. */
|
|
7
|
+
export async function readSessionHeader(files, path) {
|
|
8
|
+
try {
|
|
9
|
+
for await (const text of files.readLines(path)) {
|
|
10
|
+
const header = headerIn(text);
|
|
11
|
+
return header === undefined ? { kind: 'unknown' } : { kind: 'recognised', header };
|
|
12
|
+
}
|
|
13
|
+
return { kind: 'unknown' };
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error instanceof FileAccessError)
|
|
17
|
+
return { kind: 'unavailable', reason: error.failure };
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function headerIn(text) {
|
|
22
|
+
const line = parseJsonObject(text);
|
|
23
|
+
if (line?.[SESSION.lineType] !== SESSION.metadataType)
|
|
24
|
+
return undefined;
|
|
25
|
+
const payload = line[SESSION.payload];
|
|
26
|
+
if (!isJsonObject(payload))
|
|
27
|
+
return undefined;
|
|
28
|
+
const id = payload[SESSION.id];
|
|
29
|
+
if (typeof id !== 'string')
|
|
30
|
+
return undefined;
|
|
31
|
+
const issues = [];
|
|
32
|
+
if (id === '')
|
|
33
|
+
issues.push('empty-session-id');
|
|
34
|
+
const directory = payload[SESSION.workingDirectory];
|
|
35
|
+
const project = typeof directory === 'string' && directory !== '' ? directory : undefined;
|
|
36
|
+
if (project === undefined)
|
|
37
|
+
issues.push('project-missing');
|
|
38
|
+
const parentId = payload[SESSION.parentId];
|
|
39
|
+
const parent = !(SESSION.parentId in payload)
|
|
40
|
+
? { kind: 'root' }
|
|
41
|
+
: typeof parentId === 'string' && parentId !== ''
|
|
42
|
+
? { kind: 'parent', id: parentId }
|
|
43
|
+
: { kind: 'unknown' };
|
|
44
|
+
if (parent.kind === 'unknown')
|
|
45
|
+
issues.push('parent-invalid');
|
|
46
|
+
const historyMode = HISTORY_MODES.find((mode) => payload[SESSION.historyMode] === mode) ?? 'unknown';
|
|
47
|
+
if (historyMode === 'unknown')
|
|
48
|
+
issues.push('history-unrecognised');
|
|
49
|
+
const version = payload[SESSION.version];
|
|
50
|
+
return {
|
|
51
|
+
id,
|
|
52
|
+
parent,
|
|
53
|
+
...(project === undefined ? {} : { project }),
|
|
54
|
+
historyMode,
|
|
55
|
+
...(typeof version === 'string' ? { version } : {}),
|
|
56
|
+
origin: originOf(payload[SESSION.source]),
|
|
57
|
+
issues,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function originOf(source) {
|
|
61
|
+
if (typeof source === 'string')
|
|
62
|
+
return { kind: 'person' };
|
|
63
|
+
const subagent = isJsonObject(source) ? source[THREAD_SPAWN.subagent] : undefined;
|
|
64
|
+
if (!isJsonObject(subagent))
|
|
65
|
+
return { kind: 'unknown' };
|
|
66
|
+
if (subagent[REVIEWER.other] === REVIEWER.guardian)
|
|
67
|
+
return { kind: 'reviewer' };
|
|
68
|
+
const spawn = subagent[THREAD_SPAWN.threadSpawn];
|
|
69
|
+
return isJsonObject(spawn) ? { kind: 'spawned', ...spawnFacts(spawn) } : { kind: 'unknown' };
|
|
70
|
+
}
|
|
71
|
+
function spawnFacts(spawn) {
|
|
72
|
+
const text = (key) => {
|
|
73
|
+
const value = spawn[key];
|
|
74
|
+
return typeof value === 'string' && value !== '' ? value : undefined;
|
|
75
|
+
};
|
|
76
|
+
const depth = spawn[THREAD_SPAWN.depth];
|
|
77
|
+
const spawnedBy = text(THREAD_SPAWN.parentId);
|
|
78
|
+
const agentPath = text(THREAD_SPAWN.agentPath);
|
|
79
|
+
const role = text(THREAD_SPAWN.role);
|
|
80
|
+
return {
|
|
81
|
+
...(spawnedBy === undefined ? {} : { spawnedBy }),
|
|
82
|
+
...(typeof depth === 'number' && Number.isInteger(depth) && depth >= 0 ? { depth } : {}),
|
|
83
|
+
...(agentPath === undefined ? {} : { agentPath }),
|
|
84
|
+
...(role === undefined ? {} : { role }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** An id owned by multiple files must resolve to none, regardless of traversal order (X3). */
|
|
2
|
+
export const AMBIGUOUS = Symbol('held by more than one file');
|
|
3
|
+
/** Used by runtime discovery and the reconnaissance probe, so they cannot choose different owners. */
|
|
4
|
+
export function sessionOwners(ids) {
|
|
5
|
+
const owners = new Map();
|
|
6
|
+
ids.forEach((id, index) => {
|
|
7
|
+
if (id !== undefined)
|
|
8
|
+
owners.set(id, owners.has(id) ? AMBIGUOUS : index);
|
|
9
|
+
});
|
|
10
|
+
return owners;
|
|
11
|
+
}
|