cli-jaw 2.2.15 → 2.2.17
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/README.md +23 -2
- package/dist/bin/cli-jaw.js +39 -0
- package/dist/bin/cli-jaw.js.map +1 -1
- package/dist/bin/commands/_shared/dashboard-client.js +42 -0
- package/dist/bin/commands/_shared/dashboard-client.js.map +1 -0
- package/dist/bin/commands/_shared/search-format.js +40 -0
- package/dist/bin/commands/_shared/search-format.js.map +1 -0
- package/dist/bin/commands/chat-search.js +5 -3
- package/dist/bin/commands/chat-search.js.map +1 -1
- package/dist/bin/commands/dashboard-chat.js +14 -31
- package/dist/bin/commands/dashboard-chat.js.map +1 -1
- package/dist/bin/commands/dashboard-memory.js +12 -49
- package/dist/bin/commands/dashboard-memory.js.map +1 -1
- package/dist/bin/commands/doctor.js +56 -11
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +31 -1
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/memory.js +5 -1
- package/dist/bin/commands/memory.js.map +1 -1
- package/dist/bin/commands/provider.js +23 -0
- package/dist/bin/commands/provider.js.map +1 -1
- package/dist/bin/commands/slack.js +57 -3
- package/dist/bin/commands/slack.js.map +1 -1
- package/dist/bin/commands/tui/input-handler.js +1 -1
- package/dist/bin/commands/tui/input-handler.js.map +1 -1
- package/dist/bin/commands/tui/slash-command-runner.js +4 -5
- package/dist/bin/commands/tui/slash-command-runner.js.map +1 -1
- package/dist/bin/postinstall.js +16 -18
- package/dist/bin/postinstall.js.map +1 -1
- package/dist/lib/media-kind.js +28 -0
- package/dist/lib/media-kind.js.map +1 -0
- package/dist/lib/upload.js +27 -10
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +35 -51
- package/dist/server.js.map +1 -1
- package/dist/src/agent/args.js +34 -0
- package/dist/src/agent/args.js.map +1 -1
- package/dist/src/agent/codex-app-client.js +1016 -135
- package/dist/src/agent/codex-app-client.js.map +1 -1
- package/dist/src/agent/codex-app-events.js +72 -1
- package/dist/src/agent/codex-app-events.js.map +1 -1
- package/dist/src/agent/codex-host-pool.js +617 -0
- package/dist/src/agent/codex-host-pool.js.map +1 -0
- package/dist/src/agent/jwc-runtime.js +12 -4
- package/dist/src/agent/jwc-runtime.js.map +1 -1
- package/dist/src/agent/kiro-auth.js +10 -4
- package/dist/src/agent/kiro-auth.js.map +1 -1
- package/dist/src/agent/kiro-runtime.js +45 -8
- package/dist/src/agent/kiro-runtime.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +75 -52
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/memory-flush-controller.js +241 -33
- package/dist/src/agent/memory-flush-controller.js.map +1 -1
- package/dist/src/agent/prompt-context.js +4 -0
- package/dist/src/agent/prompt-context.js.map +1 -1
- package/dist/src/agent/runtime-pool.js +64 -34
- package/dist/src/agent/runtime-pool.js.map +1 -1
- package/dist/src/agent/session-persistence.js +71 -15
- package/dist/src/agent/session-persistence.js.map +1 -1
- package/dist/src/agent/spawn/queue.js +319 -162
- package/dist/src/agent/spawn/queue.js.map +1 -1
- package/dist/src/agent/spawn.js +581 -268
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/bgtask/types.js +1 -1
- package/dist/src/bgtask/types.js.map +1 -1
- package/dist/src/browser/adaptive-fetch/safety.js +25 -0
- package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
- package/dist/src/browser/connection.js +2 -8
- package/dist/src/browser/connection.js.map +1 -1
- package/dist/src/cli/capability-probe-worker.js +172 -0
- package/dist/src/cli/capability-probe-worker.js.map +1 -0
- package/dist/src/cli/capability-probe.js +83 -0
- package/dist/src/cli/capability-probe.js.map +1 -0
- package/dist/src/cli/claude-models.js +2 -2
- package/dist/src/cli/claude-models.js.map +1 -1
- package/dist/src/cli/cli-status-worker.js +296 -0
- package/dist/src/cli/cli-status-worker.js.map +1 -0
- package/dist/src/cli/cli-status.js +81 -0
- package/dist/src/cli/cli-status.js.map +1 -0
- package/dist/src/cli/command-context.js +17 -5
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/compact.js +56 -19
- package/dist/src/cli/compact.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +6 -4
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers.js +7 -2
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/opencodex-models.js +5 -32
- package/dist/src/cli/opencodex-models.js.map +1 -1
- package/dist/src/cli/opencodex-runtime.js +82 -0
- package/dist/src/cli/opencodex-runtime.js.map +1 -0
- package/dist/src/cli/readiness.js +35 -18
- package/dist/src/cli/readiness.js.map +1 -1
- package/dist/src/cli/registry-live.js +8 -2
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/cli/registry.js +1 -1
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/browser-open-default.js +6 -4
- package/dist/src/core/browser-open-default.js.map +1 -1
- package/dist/src/core/browser-open.js +11 -15
- package/dist/src/core/browser-open.js.map +1 -1
- package/dist/src/core/bus.js +7 -1
- package/dist/src/core/bus.js.map +1 -1
- package/dist/src/core/chat-sessions.js +121 -12
- package/dist/src/core/chat-sessions.js.map +1 -1
- package/dist/src/core/cli-detect.js +148 -4
- package/dist/src/core/cli-detect.js.map +1 -1
- package/dist/src/core/codex-config.js +21 -0
- package/dist/src/core/codex-config.js.map +1 -1
- package/dist/src/core/compact.js +73 -21
- package/dist/src/core/compact.js.map +1 -1
- package/dist/src/core/config.js +449 -20
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/db.js +225 -0
- package/dist/src/core/db.js.map +1 -1
- package/dist/src/core/event-bus.js +49 -4
- package/dist/src/core/event-bus.js.map +1 -1
- package/dist/src/core/event-scope.js +100 -0
- package/dist/src/core/event-scope.js.map +1 -0
- package/dist/src/core/install-integrity.js +176 -0
- package/dist/src/core/install-integrity.js.map +1 -0
- package/dist/src/core/main-session.js +64 -21
- package/dist/src/core/main-session.js.map +1 -1
- package/dist/src/core/message-summary.js +12 -2
- package/dist/src/core/message-summary.js.map +1 -1
- package/dist/src/core/platform-kind.js +139 -0
- package/dist/src/core/platform-kind.js.map +1 -0
- package/dist/src/core/rate-limit.js +156 -0
- package/dist/src/core/rate-limit.js.map +1 -0
- package/dist/src/core/runtime-settings.js +165 -17
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/session-context.js +7 -0
- package/dist/src/core/session-context.js.map +1 -0
- package/dist/src/core/session-ops.js +53 -9
- package/dist/src/core/session-ops.js.map +1 -1
- package/dist/src/core/settings-merge.js +105 -3
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/core/settings-watch.js +29 -5
- package/dist/src/core/settings-watch.js.map +1 -1
- package/dist/src/core/widget-watcher.js +15 -2
- package/dist/src/core/widget-watcher.js.map +1 -1
- package/dist/src/discord/bot.js +5 -2
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +4 -4
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/goal/heartbeat.js +1 -1
- package/dist/src/goal/heartbeat.js.map +1 -1
- package/dist/src/goal/runtime.js +1 -1
- package/dist/src/goal/runtime.js.map +1 -1
- package/dist/src/goal-run/controller.js +2 -2
- package/dist/src/goal-run/controller.js.map +1 -1
- package/dist/src/manager/internal-fetch.js +15 -0
- package/dist/src/manager/internal-fetch.js.map +1 -0
- package/dist/src/manager/logs.js +2 -1
- package/dist/src/manager/logs.js.map +1 -1
- package/dist/src/manager/memory/chat-federation.js +104 -11
- package/dist/src/manager/memory/chat-federation.js.map +1 -1
- package/dist/src/manager/memory/federation.js +100 -0
- package/dist/src/manager/memory/federation.js.map +1 -1
- package/dist/src/manager/metadata.js +4 -1
- package/dist/src/manager/metadata.js.map +1 -1
- package/dist/src/manager/notes/assets.js +1 -1
- package/dist/src/manager/notes/assets.js.map +1 -1
- package/dist/src/manager/notes/frontmatter.js +5 -49
- package/dist/src/manager/notes/frontmatter.js.map +1 -1
- package/dist/src/manager/notes/link-resolver.js +2 -2
- package/dist/src/manager/notes/link-resolver.js.map +1 -1
- package/dist/src/manager/notes/remote-assets.js +1 -1
- package/dist/src/manager/notes/remote-assets.js.map +1 -1
- package/dist/src/manager/notes/routes.js +2 -2
- package/dist/src/manager/notes/routes.js.map +1 -1
- package/dist/src/manager/notes/store.js +2 -2
- package/dist/src/manager/notes/store.js.map +1 -1
- package/dist/src/manager/notes/trash.js +1 -1
- package/dist/src/manager/notes/trash.js.map +1 -1
- package/dist/src/manager/notes/vault-index.js +2 -2
- package/dist/src/manager/notes/vault-index.js.map +1 -1
- package/dist/src/manager/notes/wiki-links.js +1 -1
- package/dist/src/manager/notes/wiki-links.js.map +1 -1
- package/dist/src/manager/notes/wiki-routes.js +84 -0
- package/dist/src/manager/notes/wiki-routes.js.map +1 -0
- package/dist/src/manager/proxy.js +1 -0
- package/dist/src/manager/proxy.js.map +1 -1
- package/dist/src/manager/routes/dashboard-memory.js +24 -1
- package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
- package/dist/src/manager/routes/runtime-monitor.js +1 -1
- package/dist/src/manager/routes/runtime-monitor.js.map +1 -1
- package/dist/src/manager/scan.js +5 -3
- package/dist/src/manager/scan.js.map +1 -1
- package/dist/src/manager/server.js +20 -6
- package/dist/src/manager/server.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +4 -3
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/manager/worker-events.js +2 -1
- package/dist/src/manager/worker-events.js.map +1 -1
- package/dist/src/memory/heartbeat.js +6 -5
- package/dist/src/memory/heartbeat.js.map +1 -1
- package/dist/src/memory/indexing.js +162 -45
- package/dist/src/memory/indexing.js.map +1 -1
- package/dist/src/messaging/channel-validate.js +14 -1
- package/dist/src/messaging/channel-validate.js.map +1 -1
- package/dist/src/messaging/redact.js +2 -1
- package/dist/src/messaging/redact.js.map +1 -1
- package/dist/src/messaging/session-key.js +21 -2
- package/dist/src/messaging/session-key.js.map +1 -1
- package/dist/src/notes/constants.js.map +1 -0
- package/dist/src/notes/frontmatter.js +59 -0
- package/dist/src/notes/frontmatter.js.map +1 -0
- package/dist/src/notes/path-guards.js.map +1 -0
- package/dist/src/notes/search.js.map +1 -0
- package/dist/src/orchestrator/attestation.js +1 -1
- package/dist/src/orchestrator/attestation.js.map +1 -1
- package/dist/src/orchestrator/gateway.js +135 -31
- package/dist/src/orchestrator/gateway.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +53 -21
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/orchestrator/scope.js +36 -2
- package/dist/src/orchestrator/scope.js.map +1 -1
- package/dist/src/orchestrator/session-lanes.js +87 -0
- package/dist/src/orchestrator/session-lanes.js.map +1 -0
- package/dist/src/orchestrator/session-work.js +25 -0
- package/dist/src/orchestrator/session-work.js.map +1 -0
- package/dist/src/orchestrator/state-machine.js +1 -5
- package/dist/src/orchestrator/state-machine.js.map +1 -1
- package/dist/src/orchestrator/worker-registry.js +20 -10
- package/dist/src/orchestrator/worker-registry.js.map +1 -1
- package/dist/src/prompt/builder.js +40 -2
- package/dist/src/prompt/builder.js.map +1 -1
- package/dist/src/prompt/templates/a1-system.md +8 -1
- package/dist/src/routes/agent-control.js +44 -8
- package/dist/src/routes/agent-control.js.map +1 -1
- package/dist/src/routes/chat-sessions.js +34 -6
- package/dist/src/routes/chat-sessions.js.map +1 -1
- package/dist/src/routes/command.js +63 -8
- package/dist/src/routes/command.js.map +1 -1
- package/dist/src/routes/events.js +11 -2
- package/dist/src/routes/events.js.map +1 -1
- package/dist/src/routes/link-preview.js +1 -27
- package/dist/src/routes/link-preview.js.map +1 -1
- package/dist/src/routes/messages.js +6 -5
- package/dist/src/routes/messages.js.map +1 -1
- package/dist/src/routes/messaging.js +45 -1
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/orchestrate.js +53 -33
- package/dist/src/routes/orchestrate.js.map +1 -1
- package/dist/src/routes/search.js +84 -0
- package/dist/src/routes/search.js.map +1 -0
- package/dist/src/routes/session-request.js +29 -0
- package/dist/src/routes/session-request.js.map +1 -0
- package/dist/src/routes/settings.js +110 -3
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/routes/static.js +30 -8
- package/dist/src/routes/static.js.map +1 -1
- package/dist/src/routes/system.js +1 -1
- package/dist/src/routes/system.js.map +1 -1
- package/dist/src/routes/wiki.js +122 -0
- package/dist/src/routes/wiki.js.map +1 -0
- package/dist/src/search/contract.js +2 -0
- package/dist/src/search/contract.js.map +1 -0
- package/dist/src/search/coordinator.js +153 -0
- package/dist/src/search/coordinator.js.map +1 -0
- package/dist/src/search/provider.js +40 -0
- package/dist/src/search/provider.js.map +1 -0
- package/dist/src/search/providers/chat.js +115 -0
- package/dist/src/search/providers/chat.js.map +1 -0
- package/dist/src/search/providers/memory.js +53 -0
- package/dist/src/search/providers/memory.js.map +1 -0
- package/dist/src/search/providers/wiki.js +80 -0
- package/dist/src/search/providers/wiki.js.map +1 -0
- package/dist/src/slack/api.js +10 -1
- package/dist/src/slack/api.js.map +1 -1
- package/dist/src/slack/attachment-recovery.js +42 -0
- package/dist/src/slack/attachment-recovery.js.map +1 -0
- package/dist/src/slack/bot.js +120 -41
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/commands.js +24 -8
- package/dist/src/slack/commands.js.map +1 -1
- package/dist/src/slack/events.js +12 -1
- package/dist/src/slack/events.js.map +1 -1
- package/dist/src/slack/history.js +108 -0
- package/dist/src/slack/history.js.map +1 -0
- package/dist/src/slack/inbound-file.js +242 -0
- package/dist/src/slack/inbound-file.js.map +1 -0
- package/dist/src/slack/inbound-url.js +39 -0
- package/dist/src/slack/inbound-url.js.map +1 -0
- package/dist/src/slack/ingress.js +140 -0
- package/dist/src/slack/ingress.js.map +1 -0
- package/dist/src/slack/manifest.js +3 -0
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/socket.js +1 -1
- package/dist/src/slack/socket.js.map +1 -1
- package/dist/src/slack/thread-tracker.js +86 -0
- package/dist/src/slack/thread-tracker.js.map +1 -0
- package/dist/src/telegram/bot.js +21 -11
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/wiki/config.js +193 -0
- package/dist/src/wiki/config.js.map +1 -0
- package/dist/src/wiki/entities.js +61 -0
- package/dist/src/wiki/entities.js.map +1 -0
- package/dist/src/wiki/ontology.js +72 -0
- package/dist/src/wiki/ontology.js.map +1 -0
- package/dist/src/wiki/prompt.js +172 -0
- package/dist/src/wiki/prompt.js.map +1 -0
- package/dist/src/wiki/safe-traversal.js +198 -0
- package/dist/src/wiki/safe-traversal.js.map +1 -0
- package/dist/src/wiki/scaffold.js +77 -0
- package/dist/src/wiki/scaffold.js.map +1 -0
- package/package.json +6 -5
- package/public/css/session-hub.css +266 -0
- package/public/dist/assets/AdvancedExport-DYGcCAUq.js +1 -0
- package/public/dist/assets/Agent-CNNQ7KkJ.js +1 -0
- package/public/dist/assets/{DesignPanel-BGMPhZSx.js → DesignPanel-DcL369nB.js} +1 -1
- package/public/dist/assets/{DocPanel-Dz-J_l5M.js → DocPanel-K8A2pm_1.js} +1 -1
- package/public/dist/assets/{FolderPanel-DbPhzzvi.js → FolderPanel-CV52EzwG.js} +1 -1
- package/public/dist/assets/{MilkdownWysiwygEditor-Em7iC6y9.js → MilkdownWysiwygEditor-BKOCDI4Z.js} +1 -1
- package/public/dist/assets/api-B25zNNFJ.js +1 -0
- package/public/dist/assets/{app-DX4e6isX.css → app-DjgGeXhJ.css} +1 -1
- package/public/dist/assets/app-Dsgp4xpU.js +55 -0
- package/public/dist/assets/bgtask-badge-C6h5C-o4.js +1 -0
- package/public/dist/assets/{bgtask-badge-qMkKOZx0.js → bgtask-badge-CpavV0Jc.js} +4 -4
- package/public/dist/assets/{employees-BnobWM_2.js → employees-BJyEgim7.js} +4 -4
- package/public/dist/assets/idb-cache-B5PX63p7.js +1 -0
- package/public/dist/assets/{idb-cache-BzzftUET.js → idb-cache-lOntwU3S.js} +1 -1
- package/public/dist/assets/{iframe-renderer-C5vRbbl9.js → iframe-renderer-B8k3AvTv.js} +12 -12
- package/public/dist/assets/iframe-renderer-BO_GgCTR.js +1 -0
- package/public/dist/assets/manager-LkGs6qEJ.css +1 -0
- package/public/dist/assets/manager-kXYQyMhR.js +13 -0
- package/public/dist/assets/{memory-DF4kPwwC.js → memory-D2RbBVO-.js} +11 -11
- package/public/dist/assets/memory-amsvg4Ey.js +1 -0
- package/public/dist/assets/message-history-BGHZxvgV.js +1 -0
- package/public/dist/assets/message-history-Bw2E_b8L.js +1 -0
- package/public/dist/assets/pi-settings-5HVg1sXJ.js +1 -0
- package/public/dist/assets/render-BkGcpH7z.js +157 -0
- package/public/dist/assets/settings-B1FPORO2.js +56 -0
- package/public/dist/assets/settings-CMN-zN4Q.js +1 -0
- package/public/dist/assets/settings-core-B_OjGYVN.js +1 -0
- package/public/dist/assets/settings-core-CxRwqx86.js +144 -0
- package/public/dist/assets/sidebar-DW78DANx.js +49 -0
- package/public/dist/assets/skills-Dz-9Yrxr.js +1 -0
- package/public/dist/assets/{skills-IGquen_j.js → skills-lWi0ZFiV.js} +6 -6
- package/public/dist/assets/{slash-commands-ZRDOL99E.js → slash-commands-DYOI6nKj.js} +1 -1
- package/public/dist/assets/slash-commands-xJFCdTTM.js +1 -0
- package/public/dist/assets/{trace-drawer-D8To8fYt.js → trace-drawer-DBX9xzmt.js} +1 -1
- package/public/dist/assets/{ui-Hx_mNoDf.js → ui-Bx7eqnXt.js} +1 -1
- package/public/dist/assets/ui-JOcrYeG-.js +1 -0
- package/public/dist/assets/{wiki-link-suggestions-DZQc78gj.js → wiki-link-suggestions-Dp6kBQVu.js} +1 -1
- package/public/dist/index.html +4 -4
- package/public/dist/manager/index.html +2 -2
- package/public/index.html +3 -2
- package/public/js/event-channel.ts +25 -0
- package/public/js/features/channel-onboarding-flow.ts +16 -1
- package/public/js/features/channel-onboarding.ts +20 -2
- package/public/js/features/chat-messages.ts +3 -1
- package/public/js/features/chat-search.ts +2 -1
- package/public/js/features/chat.ts +56 -18
- package/public/js/features/elicitation.ts +15 -0
- package/public/js/features/idb-cache.ts +34 -0
- package/public/js/features/message-history.ts +2 -1
- package/public/js/features/session-hub.ts +334 -0
- package/public/js/features/settings-cli-status-render.ts +1 -1
- package/public/js/features/settings-cli-status.ts +25 -11
- package/public/js/features/settings-core.ts +72 -3
- package/public/js/features/settings-types.ts +32 -0
- package/public/js/features/ui-status.ts +2 -1
- package/public/js/main.ts +20 -0
- package/public/js/render/compose-block.ts +7 -0
- package/public/js/ws.ts +22 -2
- package/public/locales/en.json +46 -4
- package/public/locales/ja.json +46 -4
- package/public/locales/ko.json +46 -4
- package/public/locales/zh.json +46 -4
- package/public/manager/src/App.tsx +1 -1
- package/public/manager/src/InstancePreview.tsx +5 -1
- package/public/manager/src/api.ts +2 -1
- package/public/manager/src/components/InstanceGroups.tsx +10 -0
- package/public/manager/src/components/InstanceListContent.tsx +24 -1
- package/public/manager/src/components/InstanceRow.tsx +33 -3
- package/public/manager/src/components/InstanceSessionList.tsx +132 -0
- package/public/manager/src/hooks/useActiveSessionDisclosure.ts +45 -0
- package/public/manager/src/lib/resilient-fetch.ts +93 -0
- package/public/manager/src/lib/session-store.ts +216 -0
- package/public/manager/src/manager-components.css +97 -0
- package/public/manager/src/settings/pages/AdvancedExport.tsx +12 -2
- package/public/manager/src/settings/pages/Agent.tsx +113 -1
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +1 -1
- package/scripts/bundle-sidecar.sh +50 -2
- package/scripts/burst-scenario.mjs +72 -0
- package/scripts/check-redaction-sinks.mjs +1 -0
- package/scripts/check-strict-baseline.mjs +1 -1
- package/scripts/ensure-native-modules.cjs +50 -3
- package/scripts/install-risk-gate.mjs +8 -0
- package/scripts/install-wsl.sh +27 -2
- package/scripts/install.ps1 +121 -0
- package/scripts/install.sh +28 -2
- package/scripts/postinstall-guard.cjs +44 -2
- package/scripts/release-gates.mjs +12 -0
- package/dist/src/manager/notes/constants.js.map +0 -1
- package/dist/src/manager/notes/path-guards.js.map +0 -1
- package/dist/src/manager/notes/search.js.map +0 -1
- package/public/dist/assets/AdvancedExport-DsDpqXoH.js +0 -1
- package/public/dist/assets/Agent-BjbZVZ9p.js +0 -1
- package/public/dist/assets/api-C9dOxby7.js +0 -1
- package/public/dist/assets/app-BzD_ZLvt.js +0 -55
- package/public/dist/assets/bgtask-badge-Dqbp-Y4e.js +0 -1
- package/public/dist/assets/idb-cache-ZH6lOebk.js +0 -1
- package/public/dist/assets/iframe-renderer-Df4KIVyx.js +0 -1
- package/public/dist/assets/manager-C4eNDNFt.css +0 -1
- package/public/dist/assets/manager-DaN201mN.js +0 -13
- package/public/dist/assets/memory-D-bULTHl.js +0 -1
- package/public/dist/assets/message-history-C4WOisGx.js +0 -1
- package/public/dist/assets/message-history-ZBoza6ew.js +0 -1
- package/public/dist/assets/pi-settings-CBUGDAuT.js +0 -1
- package/public/dist/assets/render-DYX6GRQ0.js +0 -122
- package/public/dist/assets/settings-DbJpGoaX.js +0 -55
- package/public/dist/assets/settings-SreihUje.js +0 -1
- package/public/dist/assets/settings-core-B1Y5Nb4Z.js +0 -1
- package/public/dist/assets/settings-core-DobZWIZ7.js +0 -140
- package/public/dist/assets/sidebar-Cp22g0Gv.js +0 -49
- package/public/dist/assets/skills-DwPIP08e.js +0 -1
- package/public/dist/assets/slash-commands-BBVfwcnW.js +0 -1
- package/public/dist/assets/ui-CAicpUZc.js +0 -1
- /package/dist/src/{manager/notes → notes}/constants.js +0 -0
- /package/dist/src/{manager/notes → notes}/path-guards.js +0 -0
- /package/dist/src/{manager/notes → notes}/search.js +0 -0
- /package/public/dist/assets/{pi-settings-Dh5lgb87.js → pi-settings-DEuKgvIj.js} +0 -0
package/dist/src/core/config.js
CHANGED
|
@@ -3,10 +3,12 @@ import os from 'os';
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
-
import { DEFAULT_CLI, buildDefaultPerCli } from '../cli/registry.js';
|
|
6
|
+
import { DEFAULT_CLI, CLI_KEYS, buildDefaultPerCli } from '../cli/registry.js';
|
|
7
7
|
import { pickFirstReadyCli } from '../cli/readiness.js';
|
|
8
8
|
import { migrateLegacyClaudeValue } from '../cli/claude-models.js';
|
|
9
9
|
import { resolveHomePath } from './path-expand.js';
|
|
10
|
+
import { SETUP_STATE_FILE } from './install-integrity.js';
|
|
11
|
+
import { sanitizeSettingsInput, } from './settings-merge.js';
|
|
10
12
|
export { detectAllCli, detectCli, getClaudeExecHelperCandidates, getClaudeIHelperCandidates } from './cli-detection.js';
|
|
11
13
|
// ─── Version (single source of truth: package.json) ──
|
|
12
14
|
import { dirname } from 'path';
|
|
@@ -164,8 +166,23 @@ export function runMigration(projectDir) {
|
|
|
164
166
|
fs.writeFileSync(MIGRATION_MARKER, JSON.stringify({ migratedAt: new Date().toISOString() }));
|
|
165
167
|
}
|
|
166
168
|
// ─── Settings ────────────────────────────────────────
|
|
169
|
+
export const SETTINGS_SCHEMA_VERSION = 3;
|
|
170
|
+
export const RUNTIME_DEFAULT_MIGRATION_ID = 'codex-app-default-v2';
|
|
171
|
+
export const MULTI_SESSION_DEFAULT_MIGRATION_ID = 'multi-session-default-v3';
|
|
172
|
+
// What multiSession meant before this flip. A document written by a schema that predates
|
|
173
|
+
// the new defaults must resolve absent keys to these, not to the new ones — otherwise the
|
|
174
|
+
// upgrade turns sessions on for someone who never asked (110 §4b-1).
|
|
175
|
+
export const LEGACY_MULTI_SESSION_BASELINE = {
|
|
176
|
+
enabled: false,
|
|
177
|
+
maxConcurrent: 1,
|
|
178
|
+
midRunPolicy: 'steer',
|
|
179
|
+
channels: { telegram: false, discord: false, slack: true },
|
|
180
|
+
};
|
|
167
181
|
function createDefaultSettings() {
|
|
168
182
|
return {
|
|
183
|
+
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
184
|
+
runtimeDefaultMigration: null,
|
|
185
|
+
multiSessionDefaultMigration: null,
|
|
169
186
|
port: '', // persisted by server on startup; CLI commands use as fallback
|
|
170
187
|
cli: DEFAULT_CLI,
|
|
171
188
|
fallbackOrder: [],
|
|
@@ -226,12 +243,35 @@ function createDefaultSettings() {
|
|
|
226
243
|
// Slack bots typically live in shared team channels, where
|
|
227
244
|
// answering every message is antisocial. DMs bypass this gate.
|
|
228
245
|
mentionOnly: true,
|
|
246
|
+
// But a thread the bot participates in keeps flowing without
|
|
247
|
+
// re-mention (true = strict, mention required even in threads).
|
|
248
|
+
threadRequireMention: false,
|
|
229
249
|
replyInThread: true,
|
|
250
|
+
inboundDownloadConcurrency: 6,
|
|
230
251
|
},
|
|
231
252
|
messaging: {
|
|
232
253
|
latestSeen: { telegram: null, discord: null, slack: null },
|
|
233
254
|
lastActive: { telegram: null, discord: null, slack: null },
|
|
234
255
|
},
|
|
256
|
+
multiSession: {
|
|
257
|
+
// Sessions are on by default as of schema v3. An existing install does not
|
|
258
|
+
// get this silently: the merge substitutes the legacy baseline for its
|
|
259
|
+
// cohort and asks (110 §4b).
|
|
260
|
+
enabled: true,
|
|
261
|
+
// Two, not more: the observable unit is that a second tab does not wait for
|
|
262
|
+
// the first. Beyond that is the user's CPU and token budget to spend.
|
|
263
|
+
maxConcurrent: 2,
|
|
264
|
+
midRunPolicy: 'steer',
|
|
265
|
+
channels: { telegram: false, discord: false, slack: true },
|
|
266
|
+
},
|
|
267
|
+
// Opt-in Markdown vault (devlog 040). Default OFF and nothing is created on
|
|
268
|
+
// disk until it is explicitly enabled, so a user who never turns it on sees no
|
|
269
|
+
// new directory and no change in behaviour.
|
|
270
|
+
wiki: {
|
|
271
|
+
enabled: false,
|
|
272
|
+
root: '~/jaw-wiki',
|
|
273
|
+
promptDigest: false,
|
|
274
|
+
},
|
|
235
275
|
memory: {
|
|
236
276
|
enabled: true,
|
|
237
277
|
flushEvery: 10,
|
|
@@ -242,10 +282,16 @@ function createDefaultSettings() {
|
|
|
242
282
|
autoReflectAfterFlush: false,
|
|
243
283
|
flushMessageWindow: 0,
|
|
244
284
|
},
|
|
285
|
+
search: { engine: 'like' },
|
|
245
286
|
trace: {
|
|
246
287
|
retentionDays: 7,
|
|
247
288
|
maxRows: 50000,
|
|
248
289
|
},
|
|
290
|
+
runtime: {
|
|
291
|
+
codexApp: {
|
|
292
|
+
multiplex: false,
|
|
293
|
+
},
|
|
294
|
+
},
|
|
249
295
|
code: {
|
|
250
296
|
maxConcurrentSessions: 4,
|
|
251
297
|
idleReapMs: 30_000,
|
|
@@ -298,6 +344,74 @@ function createDefaultSettings() {
|
|
|
298
344
|
};
|
|
299
345
|
}
|
|
300
346
|
export const DEFAULT_SETTINGS = createDefaultSettings();
|
|
347
|
+
/**
|
|
348
|
+
* The keys a document must carry to legitimately claim the current schema.
|
|
349
|
+
*
|
|
350
|
+
* `cli-jaw init` writes a settings file, so a new install is not the ENOENT case the
|
|
351
|
+
* cohort rules were written around. Left unstamped, its document reads as v1 and the
|
|
352
|
+
* person who just installed is offered a migration away from a state they never had.
|
|
353
|
+
* Stamping the version alone is not enough either — the loader holds a document claiming
|
|
354
|
+
* this schema to what this schema writes — so the writer takes the whole set from here
|
|
355
|
+
* rather than assembling it from memory (110 §4b-3).
|
|
356
|
+
*/
|
|
357
|
+
export function freshInstallSchemaFields() {
|
|
358
|
+
// Same question as the loader asks, answered the same way: `init` running against a
|
|
359
|
+
// home that already has a database is re-initialising, not installing.
|
|
360
|
+
const defaults = settingsForHomeWithoutSettingsFile();
|
|
361
|
+
return {
|
|
362
|
+
settingsSchemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
363
|
+
multiSession: defaults.multiSession,
|
|
364
|
+
// Null only for a home with no history — there is nothing to migrate from and
|
|
365
|
+
// nothing to ask about. An established home carries the pending marker instead.
|
|
366
|
+
multiSessionDefaultMigration: defaults.multiSessionDefaultMigration,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
// Things a home only has once someone has set it up or used it. A settings file can be
|
|
370
|
+
// deleted, lost to a failed write, or restored from a backup that omitted it — none of
|
|
371
|
+
// which makes the install new. These do not come back on their own.
|
|
372
|
+
//
|
|
373
|
+
// The list errs toward "established" on purpose. Calling a new home established costs a
|
|
374
|
+
// question the user did not need to answer; calling an established home new turns a
|
|
375
|
+
// feature on for someone who was never asked. Those are not the same mistake. That is
|
|
376
|
+
// also why the postinstall-created directories stay in the list even though they can
|
|
377
|
+
// precede a first boot.
|
|
378
|
+
const ESTABLISHED_HOME_ARTIFACTS = [
|
|
379
|
+
DB_PATH,
|
|
380
|
+
MIGRATION_MARKER,
|
|
381
|
+
PROMPTS_DIR,
|
|
382
|
+
UPLOADS_DIR,
|
|
383
|
+
// `jaw init` leaves all three, and an init-only home whose settings file was then
|
|
384
|
+
// lost is exactly the case that must not read as new.
|
|
385
|
+
join(JAW_HOME, SETUP_STATE_FILE),
|
|
386
|
+
HEARTBEAT_JOBS_PATH,
|
|
387
|
+
SKILLS_DIR,
|
|
388
|
+
WIDGETS_DIR,
|
|
389
|
+
];
|
|
390
|
+
/**
|
|
391
|
+
* An absent settings file is not proof of a new install, and the difference decides
|
|
392
|
+
* whether sessions start on. Getting it wrong turns a feature on for someone who was
|
|
393
|
+
* never asked, which is the one outcome the whole migration exists to prevent — so the
|
|
394
|
+
* question is answered from the home directory rather than from one file in it.
|
|
395
|
+
*/
|
|
396
|
+
export function isEstablishedHome() {
|
|
397
|
+
return ESTABLISHED_HOME_ARTIFACTS.some(path => fs.existsSync(path));
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* What a home with no readable settings should start with. A home that has been used
|
|
401
|
+
* before gets the previous meaning plus a pending marker, so it is asked like any other
|
|
402
|
+
* upgrade; only a home with no history at all gets the new defaults.
|
|
403
|
+
*/
|
|
404
|
+
export function settingsForHomeWithoutSettingsFile() {
|
|
405
|
+
const next = createDefaultSettings();
|
|
406
|
+
if (!isEstablishedHome())
|
|
407
|
+
return next;
|
|
408
|
+
next.multiSession = { ...next.multiSession, ...LEGACY_MULTI_SESSION_BASELINE };
|
|
409
|
+
next.multiSessionDefaultMigration = {
|
|
410
|
+
id: MULTI_SESSION_DEFAULT_MIGRATION_ID,
|
|
411
|
+
state: 'pending',
|
|
412
|
+
};
|
|
413
|
+
return next;
|
|
414
|
+
}
|
|
301
415
|
export function normalizeModelForCli(cli, model) {
|
|
302
416
|
if (typeof model !== 'string')
|
|
303
417
|
return model;
|
|
@@ -336,7 +450,7 @@ function normalizeActiveOverrides(activeOverrides = {}, perCli = {}) {
|
|
|
336
450
|
return next;
|
|
337
451
|
}
|
|
338
452
|
/** @internal — exported for unit testing */
|
|
339
|
-
export function migrateSettings(s) {
|
|
453
|
+
export function migrateSettings(s, sourceVersion = readSettingsSchemaVersion(s)) {
|
|
340
454
|
if (s["planning"]) {
|
|
341
455
|
if (s["planning"].cli && s["planning"].cli !== s["cli"])
|
|
342
456
|
s["cli"] = s["planning"].cli;
|
|
@@ -352,6 +466,51 @@ export function migrateSettings(s) {
|
|
|
352
466
|
}
|
|
353
467
|
delete s["planning"];
|
|
354
468
|
}
|
|
469
|
+
if (sourceVersion === 1) {
|
|
470
|
+
const fromCli = CLI_KEYS.includes(s["cli"]) ? s["cli"] : 'claude';
|
|
471
|
+
s["cli"] = fromCli;
|
|
472
|
+
s["settingsSchemaVersion"] = SETTINGS_SCHEMA_VERSION;
|
|
473
|
+
s["runtimeDefaultMigration"] = {
|
|
474
|
+
id: RUNTIME_DEFAULT_MIGRATION_ID,
|
|
475
|
+
state: fromCli === 'codex-app' ? 'already-codex-app' : 'pending',
|
|
476
|
+
fromCli,
|
|
477
|
+
toCli: 'codex-app',
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
validateRuntimeDefaultMigration(s["runtimeDefaultMigration"]);
|
|
482
|
+
}
|
|
483
|
+
// The session-default flip is its own migration with its own marker. It is NOT
|
|
484
|
+
// folded into the runtime one: the two flips can be rolled back at different times
|
|
485
|
+
// for different reasons, and a shared marker would make that impossible to express.
|
|
486
|
+
// A v1 document crosses both boundaries and so gets both markers in this one pass.
|
|
487
|
+
if (sourceVersion < SETTINGS_SCHEMA_VERSION) {
|
|
488
|
+
s["settingsSchemaVersion"] = SETTINGS_SCHEMA_VERSION;
|
|
489
|
+
s["multiSessionDefaultMigration"] = {
|
|
490
|
+
id: MULTI_SESSION_DEFAULT_MIGRATION_ID,
|
|
491
|
+
// Someone who already turned sessions on made this decision before we asked.
|
|
492
|
+
// Offering it again would be noise, and accepting for them would change a
|
|
493
|
+
// concurrency they chose.
|
|
494
|
+
state: s["multiSession"]?.enabled === true ? 'already-enabled' : 'pending',
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
validateMultiSessionDefaultMigration(s["multiSessionDefaultMigration"]);
|
|
499
|
+
}
|
|
500
|
+
// A pending marker means the user has not answered yet, so sessions being on
|
|
501
|
+
// contradicts it. The dedicated accept route moves both together, but it is not the
|
|
502
|
+
// only way in: the settings watcher merges an external file write, and a generic
|
|
503
|
+
// settings patch can set `multiSession.enabled` directly. Both strip the marker as
|
|
504
|
+
// server-owned and then leave this pair inconsistent — enabled, and still pending.
|
|
505
|
+
//
|
|
506
|
+
// Resolved by treating an enable while pending AS the answer rather than by refusing
|
|
507
|
+
// it. Someone who reaches in and turns it on has decided; what must not survive is a
|
|
508
|
+
// marker that still claims to be waiting, because the next boot would ask again and
|
|
509
|
+
// the record would no longer mean anything.
|
|
510
|
+
const sessionMigration = s["multiSessionDefaultMigration"];
|
|
511
|
+
if (sessionMigration?.state === 'pending' && s["multiSession"]?.enabled === true) {
|
|
512
|
+
s["multiSessionDefaultMigration"] = { ...sessionMigration, state: 'accepted' };
|
|
513
|
+
}
|
|
355
514
|
// Claude model alias migration
|
|
356
515
|
s["perCli"] = normalizePerCliModels(s["perCli"] || {});
|
|
357
516
|
s["activeOverrides"] = normalizeActiveOverrides(s["activeOverrides"] || {}, s["perCli"] || {});
|
|
@@ -394,8 +553,14 @@ export function migrateSettings(s) {
|
|
|
394
553
|
allowBots: false,
|
|
395
554
|
mentionOnly: true,
|
|
396
555
|
replyInThread: true,
|
|
556
|
+
inboundDownloadConcurrency: 6,
|
|
397
557
|
};
|
|
398
558
|
}
|
|
559
|
+
if (!Number.isInteger(s["slack"].inboundDownloadConcurrency)
|
|
560
|
+
|| s["slack"].inboundDownloadConcurrency < 1
|
|
561
|
+
|| s["slack"].inboundDownloadConcurrency > 32) {
|
|
562
|
+
s["slack"].inboundDownloadConcurrency = 6;
|
|
563
|
+
}
|
|
399
564
|
if (!s["messaging"]) {
|
|
400
565
|
s["messaging"] = {
|
|
401
566
|
latestSeen: { telegram: null, discord: null, slack: null },
|
|
@@ -412,6 +577,44 @@ export function migrateSettings(s) {
|
|
|
412
577
|
s["messaging"].lastActive.slack = null;
|
|
413
578
|
}
|
|
414
579
|
}
|
|
580
|
+
if (!s["multiSession"]) {
|
|
581
|
+
s["multiSession"] = createDefaultSettings().multiSession;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// Deliberately the OLD defaults, not createDefaultSettings(). This function takes
|
|
585
|
+
// an already-merged object and cannot tell which cohort it came from, so handing
|
|
586
|
+
// out the new defaults here would enable sessions for a legacy document that
|
|
587
|
+
// reached this line by some path other than the boot merge. The boot merge fills
|
|
588
|
+
// both keys from the cohort baseline, which makes these two lines unreachable on
|
|
589
|
+
// the normal path — they are the net under it, and a net set to the new defaults
|
|
590
|
+
// is not a net (110 §4b-2).
|
|
591
|
+
if (s["multiSession"].enabled === undefined) {
|
|
592
|
+
s["multiSession"].enabled = LEGACY_MULTI_SESSION_BASELINE.enabled;
|
|
593
|
+
}
|
|
594
|
+
if (s["multiSession"].maxConcurrent === undefined) {
|
|
595
|
+
s["multiSession"].maxConcurrent = LEGACY_MULTI_SESSION_BASELINE.maxConcurrent;
|
|
596
|
+
}
|
|
597
|
+
if (s["multiSession"].midRunPolicy === undefined)
|
|
598
|
+
s["multiSession"].midRunPolicy = 'steer';
|
|
599
|
+
if (s["multiSession"].channels === undefined) {
|
|
600
|
+
s["multiSession"].channels = { telegram: false, discord: false, slack: true };
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
if (s["multiSession"].channels.telegram === undefined)
|
|
604
|
+
s["multiSession"].channels.telegram = false;
|
|
605
|
+
if (s["multiSession"].channels.discord === undefined)
|
|
606
|
+
s["multiSession"].channels.discord = false;
|
|
607
|
+
if (s["multiSession"].channels.slack === undefined)
|
|
608
|
+
s["multiSession"].channels.slack = true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const maxConcurrent = Number(s["multiSession"].maxConcurrent);
|
|
612
|
+
s["multiSession"].maxConcurrent = Number.isInteger(maxConcurrent) && maxConcurrent > 0
|
|
613
|
+
? maxConcurrent
|
|
614
|
+
: 1;
|
|
615
|
+
if (!['steer', 'followup', 'collect', 'interrupt'].includes(s["multiSession"].midRunPolicy)) {
|
|
616
|
+
s["multiSession"].midRunPolicy = 'steer';
|
|
617
|
+
}
|
|
415
618
|
if (!s["jawCeo"]) {
|
|
416
619
|
s["jawCeo"] = { openaiApiKey: '' };
|
|
417
620
|
}
|
|
@@ -471,10 +674,145 @@ function applyEnvOverrides(s) {
|
|
|
471
674
|
}
|
|
472
675
|
/** Mutable settings object — shared across all modules via ESM live binding */
|
|
473
676
|
export let settings = createDefaultSettings();
|
|
677
|
+
let settingsPersistenceShape = 'absent';
|
|
678
|
+
function readSettingsSchemaVersion(raw) {
|
|
679
|
+
if (!("settingsSchemaVersion" in raw))
|
|
680
|
+
return 1;
|
|
681
|
+
const version = raw["settingsSchemaVersion"];
|
|
682
|
+
if (!Number.isInteger(version) || version < 1 || version > SETTINGS_SCHEMA_VERSION) {
|
|
683
|
+
throw new Error(`unsupported_settings_schema_version:${String(version)}`);
|
|
684
|
+
}
|
|
685
|
+
return version;
|
|
686
|
+
}
|
|
687
|
+
function validateRuntimeDefaultMigration(value) {
|
|
688
|
+
if (value === null || value === undefined)
|
|
689
|
+
return;
|
|
690
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
691
|
+
throw new Error('invalid_runtime_default_migration');
|
|
692
|
+
}
|
|
693
|
+
const migration = value;
|
|
694
|
+
const keys = Object.keys(migration).sort();
|
|
695
|
+
const expectedKeys = ['fromCli', 'id', 'state', 'toCli'];
|
|
696
|
+
const validState = ['pending', 'accepted', 'kept', 'already-codex-app'].includes(String(migration["state"]));
|
|
697
|
+
if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)
|
|
698
|
+
|| migration["id"] !== RUNTIME_DEFAULT_MIGRATION_ID
|
|
699
|
+
|| !validState
|
|
700
|
+
|| typeof migration["fromCli"] !== 'string'
|
|
701
|
+
|| migration["toCli"] !== 'codex-app') {
|
|
702
|
+
throw new Error('invalid_runtime_default_migration');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function validateMultiSessionDefaultMigration(value) {
|
|
706
|
+
if (value === null || value === undefined)
|
|
707
|
+
return;
|
|
708
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
709
|
+
throw new Error('invalid_multi_session_default_migration');
|
|
710
|
+
}
|
|
711
|
+
const migration = value;
|
|
712
|
+
const keys = Object.keys(migration).sort();
|
|
713
|
+
const validState = ['pending', 'accepted', 'kept', 'already-enabled'].includes(String(migration["state"]));
|
|
714
|
+
if (JSON.stringify(keys) !== JSON.stringify(['id', 'state'])
|
|
715
|
+
|| migration["id"] !== MULTI_SESSION_DEFAULT_MIGRATION_ID
|
|
716
|
+
|| !validState) {
|
|
717
|
+
throw new Error('invalid_multi_session_default_migration');
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function isPlainRecord(value) {
|
|
721
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
722
|
+
}
|
|
723
|
+
// ─── Clobber-protection latch (260806) ───────────────
|
|
724
|
+
// When settings.json existed but could not be read at boot (corrupt JSON, an
|
|
725
|
+
// unsupported/future schema version, a permission error), the in-memory state is
|
|
726
|
+
// defaults — and any later save (messaging.latestSeen, a UI write) would flush those
|
|
727
|
+
// defaults over the user's real file, tokens included. That actually happened: a v2
|
|
728
|
+
// packaged app read a v3 file and wiped a Slack config. While latched, saves commit
|
|
729
|
+
// to memory only and the file on disk is left exactly as it was.
|
|
730
|
+
let settingsPersistenceBlockedReason = null;
|
|
731
|
+
let settingsPersistenceBlockedWarned = false;
|
|
732
|
+
export function isSettingsPersistenceBlocked() {
|
|
733
|
+
return settingsPersistenceBlockedReason !== null;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* A v3 document was written by this schema, and this schema always writes both the
|
|
737
|
+
* multiSession block and its migration marker. If either is missing or malformed, the
|
|
738
|
+
* document is not what it claims to be — and the consequence of guessing is specific:
|
|
739
|
+
* an absent block would inherit the new defaults and start running sessions while the
|
|
740
|
+
* marker still said the user had not consented (110 §4b-3). Older documents are held to
|
|
741
|
+
* no such rule: their schema did not know these keys, so absent is normal there and the
|
|
742
|
+
* legacy baseline covers it.
|
|
743
|
+
*/
|
|
744
|
+
function assertCurrentSchemaSessionShape(raw) {
|
|
745
|
+
const block = raw["multiSession"];
|
|
746
|
+
if (!isPlainRecord(block))
|
|
747
|
+
throw new Error('invalid_multi_session_block');
|
|
748
|
+
if (typeof block["enabled"] !== 'boolean')
|
|
749
|
+
throw new Error('invalid_multi_session_enabled');
|
|
750
|
+
const maxConcurrent = block["maxConcurrent"];
|
|
751
|
+
if (!Number.isInteger(maxConcurrent) || maxConcurrent < 1) {
|
|
752
|
+
throw new Error('invalid_multi_session_max_concurrent');
|
|
753
|
+
}
|
|
754
|
+
if ('channels' in block && !isPlainRecord(block["channels"])) {
|
|
755
|
+
throw new Error('invalid_multi_session_channels');
|
|
756
|
+
}
|
|
757
|
+
if (!('multiSessionDefaultMigration' in raw)) {
|
|
758
|
+
throw new Error('missing_multi_session_default_migration');
|
|
759
|
+
}
|
|
760
|
+
validateMultiSessionDefaultMigration(raw["multiSessionDefaultMigration"]);
|
|
761
|
+
}
|
|
474
762
|
export function loadSettings() {
|
|
763
|
+
// A fresh load is a fresh verdict: a successful parse (or a genuinely absent
|
|
764
|
+
// file) below re-enables persistence; only the unreadable-existing-file catch
|
|
765
|
+
// re-arms the latch.
|
|
766
|
+
settingsPersistenceBlockedReason = null;
|
|
767
|
+
settingsPersistenceBlockedWarned = false;
|
|
475
768
|
try {
|
|
476
|
-
|
|
769
|
+
let raw = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
770
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
771
|
+
throw new Error('invalid_settings_document');
|
|
772
|
+
}
|
|
773
|
+
const sanitized = sanitizeSettingsInput(raw, 'boot');
|
|
774
|
+
raw = sanitized.value;
|
|
775
|
+
const nextShape = sanitized.persistenceShape;
|
|
776
|
+
if (sanitized.rejectedPaths.length > 0) {
|
|
777
|
+
console.warn(`[jaw:settings] ignored server-owned settings fields: ${sanitized.rejectedPaths.join(', ')}`);
|
|
778
|
+
}
|
|
779
|
+
if (sanitized.invalidPaths.length > 0) {
|
|
780
|
+
console.warn(`[jaw:settings] ignored invalid settings fields: ${sanitized.invalidPaths.join(', ')}`);
|
|
781
|
+
}
|
|
782
|
+
const sourceVersion = readSettingsSchemaVersion(raw);
|
|
783
|
+
// Dropping a malformed field and carrying on is right for a patch arriving over
|
|
784
|
+
// the API — the rest of the patch is still what the caller meant. It is wrong for
|
|
785
|
+
// a document claiming the current schema at boot: this schema wrote that block,
|
|
786
|
+
// so a malformed one means the file is not what it says, and silently replacing
|
|
787
|
+
// it with defaults is how a pending consent turns into a running feature.
|
|
788
|
+
if (sourceVersion >= 3 && sanitized.invalidPaths.some(path => path.startsWith('multiSession'))) {
|
|
789
|
+
throw new Error(`invalid_multi_session_block:${sanitized.invalidPaths.join(',')}`);
|
|
790
|
+
}
|
|
791
|
+
// A v1 document predates the codex-app default, so an absent or unknown
|
|
792
|
+
// cli is normalised to the historical fallback rather than inheriting
|
|
793
|
+
// the new default. A v2 document was written by this schema and must
|
|
794
|
+
// already name a known runtime; if it does not, the file is not
|
|
795
|
+
// trustworthy enough to silently supply one, so it closes the same way
|
|
796
|
+
// a corrupt or future-versioned document does.
|
|
797
|
+
if (sourceVersion >= 2 && !CLI_KEYS.includes(raw["cli"])) {
|
|
798
|
+
throw new Error(`invalid_settings_cli:${String(raw["cli"])}`);
|
|
799
|
+
}
|
|
800
|
+
// A document claiming the current schema must actually carry what this schema
|
|
801
|
+
// writes. Checked before the merge, because after it the absence is gone.
|
|
802
|
+
if (sourceVersion >= 3)
|
|
803
|
+
assertCurrentSchemaSessionShape(raw);
|
|
804
|
+
const legacyCli = sourceVersion === 1 && !CLI_KEYS.includes(raw["cli"])
|
|
805
|
+
? 'claude'
|
|
806
|
+
: raw["cli"];
|
|
807
|
+
const hadPlanning = !!raw.planning;
|
|
477
808
|
const defaults = createDefaultSettings();
|
|
809
|
+
// Everything below the current schema predates the session defaults, so absent
|
|
810
|
+
// keys resolve to what they used to mean rather than to the new defaults. The
|
|
811
|
+
// merge runs before migrateSettings, so this is the only place that still knows
|
|
812
|
+
// whether a key was in the document or is about to be invented (110 §4b-1).
|
|
813
|
+
const multiSessionBaseline = sourceVersion < SETTINGS_SCHEMA_VERSION
|
|
814
|
+
? LEGACY_MULTI_SESSION_BASELINE
|
|
815
|
+
: defaults.multiSession;
|
|
478
816
|
// Deep merge perCli so new CLI defaults (e.g. copilot) are preserved
|
|
479
817
|
const mergedPerCli = buildDefaultPerCli();
|
|
480
818
|
if (raw.perCli) {
|
|
@@ -485,12 +823,18 @@ export function loadSettings() {
|
|
|
485
823
|
const merged = migrateSettings({
|
|
486
824
|
...defaults,
|
|
487
825
|
...raw,
|
|
826
|
+
...(sourceVersion === 1 ? { cli: legacyCli } : {}),
|
|
488
827
|
perCli: mergedPerCli,
|
|
489
828
|
tui: { ...defaults.tui, ...(raw.tui || {}) },
|
|
490
829
|
telegram: { ...defaults.telegram, ...(raw.telegram || {}) },
|
|
491
830
|
discord: { ...defaults.discord, ...(raw.discord || {}) },
|
|
492
831
|
slack: { ...defaults.slack, ...(raw.slack || {}) },
|
|
493
832
|
memory: { ...defaults.memory, ...(raw.memory || {}) },
|
|
833
|
+
search: {
|
|
834
|
+
...defaults.search,
|
|
835
|
+
...(raw.search || {}),
|
|
836
|
+
engine: raw.search?.engine === 'fts5' ? 'fts5' : 'like',
|
|
837
|
+
},
|
|
494
838
|
trace: { ...defaults.trace, ...(raw.trace || {}) },
|
|
495
839
|
avatar: {
|
|
496
840
|
agent: { ...defaults.avatar.agent, ...(raw.avatar?.agent || {}) },
|
|
@@ -503,17 +847,39 @@ export function loadSettings() {
|
|
|
503
847
|
jawCeo: { ...defaults.jawCeo, ...(raw.jawCeo || {}) },
|
|
504
848
|
pi: { ...defaults.pi, ...(raw.pi || {}) },
|
|
505
849
|
network: { ...defaults.network, ...(raw.network || {}) },
|
|
850
|
+
runtime: {
|
|
851
|
+
...defaults.runtime,
|
|
852
|
+
...(raw.runtime || {}),
|
|
853
|
+
codexApp: {
|
|
854
|
+
...defaults.runtime.codexApp,
|
|
855
|
+
...(raw.runtime?.codexApp || {}),
|
|
856
|
+
},
|
|
857
|
+
},
|
|
506
858
|
code: { ...defaults.code, ...(raw.code || {}) },
|
|
507
|
-
|
|
859
|
+
multiSession: {
|
|
860
|
+
...multiSessionBaseline,
|
|
861
|
+
...(raw.multiSession || {}),
|
|
862
|
+
channels: { ...multiSessionBaseline.channels, ...(raw.multiSession?.channels || {}) },
|
|
863
|
+
},
|
|
864
|
+
wiki: { ...defaults.wiki, ...(raw.wiki || {}) },
|
|
865
|
+
}, sourceVersion);
|
|
508
866
|
// #64 safety: auto-correct stale workingDir (e.g. copied instance)
|
|
509
867
|
// but allow valid paths to persist (dynamic project targeting)
|
|
868
|
+
// Any document below the current schema was rewritten by the migration above, so
|
|
869
|
+
// it has to reach disk. Leaving it unsaved would keep memory at v3 while the file
|
|
870
|
+
// stayed older, and the next boot would run the migration again — including
|
|
871
|
+
// recreating a marker the user had already resolved.
|
|
872
|
+
let needsSave = sourceVersion < SETTINGS_SCHEMA_VERSION || hadPlanning;
|
|
510
873
|
if (typeof merged["workingDir"] === 'string' && merged["workingDir"] !== JAW_HOME && !fs.existsSync(merged["workingDir"])) {
|
|
511
874
|
console.warn(`[jaw:workingDir] stale path ${merged["workingDir"]}, resetting to JAW_HOME`);
|
|
512
875
|
merged["workingDir"] = JAW_HOME;
|
|
513
|
-
|
|
876
|
+
needsSave = true;
|
|
514
877
|
}
|
|
515
|
-
|
|
516
|
-
|
|
878
|
+
const candidate = { value: merged, shape: nextShape };
|
|
879
|
+
if (needsSave)
|
|
880
|
+
persistAndCommit(candidate);
|
|
881
|
+
else
|
|
882
|
+
commitCandidate(candidate);
|
|
517
883
|
// normalize projectDirs on load (reject corrupted/injected values)
|
|
518
884
|
merged["projectDirs"] = normalizeProjectDirs(merged["projectDirs"]);
|
|
519
885
|
// env overrides
|
|
@@ -531,22 +897,43 @@ export function loadSettings() {
|
|
|
531
897
|
}
|
|
532
898
|
catch { /* best-effort */ }
|
|
533
899
|
}
|
|
534
|
-
settings = merged;
|
|
535
900
|
return merged;
|
|
536
901
|
}
|
|
537
902
|
catch (error) {
|
|
538
|
-
const next = createDefaultSettings();
|
|
539
|
-
next.cli = pickFirstReadyCli();
|
|
540
|
-
applyEnvOverrides(next);
|
|
541
|
-
settings = next;
|
|
542
903
|
const err = error;
|
|
543
904
|
if (err?.code === 'ENOENT') {
|
|
544
|
-
|
|
905
|
+
// ENOENT means no settings file, not necessarily no install. A home that has
|
|
906
|
+
// a database or an uploads directory has been used, so it is treated as an
|
|
907
|
+
// upgrade and asked rather than switched on.
|
|
908
|
+
const next = settingsForHomeWithoutSettingsFile();
|
|
909
|
+
next.cli = pickFirstReadyCli();
|
|
910
|
+
applyEnvOverrides(next);
|
|
911
|
+
persistAndCommit({ value: next, shape: 'absent' });
|
|
545
912
|
return next;
|
|
546
913
|
}
|
|
914
|
+
const next = createDefaultSettings();
|
|
915
|
+
next.cli = 'claude';
|
|
916
|
+
// This branch stands in for a state we could not read — corrupt JSON, an
|
|
917
|
+
// unsupported version, a permission error. The new-install defaults are the wrong
|
|
918
|
+
// thing to borrow here: they would turn sessions on for someone whose real
|
|
919
|
+
// settings might have had them off, and who was never asked. Pinned to what the
|
|
920
|
+
// previous schema meant (110 §4c). The ENOENT branch above is genuinely new and
|
|
921
|
+
// keeps the new defaults.
|
|
922
|
+
next.multiSession = { ...next.multiSession, ...LEGACY_MULTI_SESSION_BASELINE };
|
|
923
|
+
applyEnvOverrides(next);
|
|
924
|
+
commitCandidate({ value: next, shape: 'absent' });
|
|
547
925
|
console.warn(`[jaw:settings] failed to load ${SETTINGS_PATH}: ${err?.message || String(error)}`);
|
|
548
926
|
if (fs.existsSync(SETTINGS_PATH)) {
|
|
549
|
-
|
|
927
|
+
// The file is real and we could not read it, so what is in memory now is
|
|
928
|
+
// NOT the user's settings. Latch persistence so no later save clobbers
|
|
929
|
+
// the file; the backup below is a copy, not a license to overwrite.
|
|
930
|
+
settingsPersistenceBlockedReason = err?.message || String(error);
|
|
931
|
+
let backupTimestamp = Date.now();
|
|
932
|
+
let backupPath = `${SETTINGS_PATH}.corrupt-${backupTimestamp}.bak`;
|
|
933
|
+
while (fs.existsSync(backupPath)) {
|
|
934
|
+
backupTimestamp += 1;
|
|
935
|
+
backupPath = `${SETTINGS_PATH}.corrupt-${backupTimestamp}.bak`;
|
|
936
|
+
}
|
|
550
937
|
try {
|
|
551
938
|
fs.copyFileSync(SETTINGS_PATH, backupPath);
|
|
552
939
|
console.warn(`[jaw:settings] backed up unreadable settings to ${backupPath}`);
|
|
@@ -561,10 +948,19 @@ export function loadSettings() {
|
|
|
561
948
|
// Self-write fingerprint for the settings watcher: external writers (a separate
|
|
562
949
|
// `cli-jaw project set` process) produce content that won't match this string.
|
|
563
950
|
let lastSavedSettingsRaw = null;
|
|
564
|
-
export function
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
951
|
+
export function serializeSettingsForSave(candidate) {
|
|
952
|
+
const value = structuredClone(candidate.value);
|
|
953
|
+
const runtime = value["runtime"];
|
|
954
|
+
if (candidate.shape === 'absent' && runtime?.codexApp?.multiplex === false) {
|
|
955
|
+
delete runtime.codexApp.multiplex;
|
|
956
|
+
if (Object.keys(runtime.codexApp).length === 0)
|
|
957
|
+
delete runtime.codexApp;
|
|
958
|
+
if (Object.keys(runtime).length === 0)
|
|
959
|
+
delete value["runtime"];
|
|
960
|
+
}
|
|
961
|
+
return JSON.stringify(value, null, 2);
|
|
962
|
+
}
|
|
963
|
+
function writeSettingsRaw(raw) {
|
|
568
964
|
// settings.json carries live channel tokens (xoxb-/xapp-/bot tokens), so
|
|
569
965
|
// it must never be group/other-readable. writeFileSync's mode applies
|
|
570
966
|
// only at creation — chmod covers the existing-file path.
|
|
@@ -576,12 +972,45 @@ export function saveSettings(s) {
|
|
|
576
972
|
catch { /* best-effort */ }
|
|
577
973
|
}
|
|
578
974
|
}
|
|
975
|
+
export function commitCandidate(candidate) {
|
|
976
|
+
settings = candidate.value;
|
|
977
|
+
settingsPersistenceShape = candidate.shape;
|
|
978
|
+
}
|
|
979
|
+
export function persistAndCommit(candidate, write = writeSettingsRaw) {
|
|
980
|
+
if (settingsPersistenceBlockedReason !== null) {
|
|
981
|
+
// Memory-only: the file on disk holds the user's real settings in a shape
|
|
982
|
+
// this binary could not read, and writing would destroy them.
|
|
983
|
+
if (!settingsPersistenceBlockedWarned) {
|
|
984
|
+
settingsPersistenceBlockedWarned = true;
|
|
985
|
+
console.warn(`[jaw:settings] persistence disabled: settings.json was unreadable at boot (${settingsPersistenceBlockedReason}); refusing to overwrite it`);
|
|
986
|
+
}
|
|
987
|
+
commitCandidate(candidate);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const raw = serializeSettingsForSave(candidate);
|
|
991
|
+
write(raw);
|
|
992
|
+
lastSavedSettingsRaw = raw;
|
|
993
|
+
commitCandidate(candidate);
|
|
994
|
+
}
|
|
995
|
+
/** Re-save the current settings pair for legacy callers that mutate settings in place. */
|
|
996
|
+
export function saveSettings(s) {
|
|
997
|
+
persistAndCommit({ value: s, shape: settingsPersistenceShape });
|
|
998
|
+
}
|
|
999
|
+
export function snapshotSettingsState() {
|
|
1000
|
+
return {
|
|
1001
|
+
value: structuredClone(settings),
|
|
1002
|
+
shape: settingsPersistenceShape,
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
export function getSettingsPersistenceShape() {
|
|
1006
|
+
return settingsPersistenceShape;
|
|
1007
|
+
}
|
|
579
1008
|
export function getLastSavedSettingsRaw() {
|
|
580
1009
|
return lastSavedSettingsRaw;
|
|
581
1010
|
}
|
|
582
1011
|
/** Replace settings object (for API PUT /api/settings deep merge) */
|
|
583
|
-
export function replaceSettings(s) {
|
|
584
|
-
|
|
1012
|
+
export function replaceSettings(s, shape) {
|
|
1013
|
+
commitCandidate({ value: s, shape });
|
|
585
1014
|
}
|
|
586
1015
|
export function loadHeartbeatFile() {
|
|
587
1016
|
try {
|