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
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { API_BASE, api, getAuthToken } from '../api.js';
|
|
2
|
+
import { escapeHtml } from '../render/html.js';
|
|
3
|
+
import { getLang, t } from './i18n.js';
|
|
4
|
+
|
|
5
|
+
export type SessionSource = 'local' | 'slack' | 'telegram' | 'discord';
|
|
6
|
+
|
|
7
|
+
export interface SessionListItem {
|
|
8
|
+
id: string;
|
|
9
|
+
seq: number;
|
|
10
|
+
label: string | null;
|
|
11
|
+
message_count: number;
|
|
12
|
+
source?: SessionSource;
|
|
13
|
+
remoteKey?: string | null;
|
|
14
|
+
lastActivityAt?: string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SessionListResponse {
|
|
18
|
+
sessions: SessionListItem[];
|
|
19
|
+
active: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type SessionViewMode = 'off' | 'hub' | 'session' | 'redirect';
|
|
23
|
+
|
|
24
|
+
interface SessionViewState {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
mode: SessionViewMode;
|
|
27
|
+
activeId: string | null;
|
|
28
|
+
viewed: SessionListItem | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const viewState: SessionViewState = {
|
|
32
|
+
enabled: false,
|
|
33
|
+
mode: 'off',
|
|
34
|
+
activeId: null,
|
|
35
|
+
viewed: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
let cancelRecordingOnReadonly: (() => void) | null = null;
|
|
39
|
+
let latestList: SessionListResponse | null = null;
|
|
40
|
+
|
|
41
|
+
const ALLOWED_READONLY_COMMAND = /^\s*(?:\/switch(?:\s|$)|\/sessions(?:\s|$)|\/fork(?:\s|$)|\/\d+(?:\s|$))/i;
|
|
42
|
+
|
|
43
|
+
function routePath(pathname: string): string {
|
|
44
|
+
const withoutProxy = API_BASE && pathname.startsWith(API_BASE)
|
|
45
|
+
? pathname.slice(API_BASE.length)
|
|
46
|
+
: pathname;
|
|
47
|
+
return withoutProxy || '/';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parsedSeq(pathname: string): number | null {
|
|
51
|
+
const match = routePath(pathname).match(/^\/(\d+)\/?$/);
|
|
52
|
+
return match ? Number(match[1]) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function navigationFieldsPresent(response: SessionListResponse): boolean {
|
|
56
|
+
return response.sessions.some(session => Object.prototype.hasOwnProperty.call(session, 'source'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function configureSessionView(response: SessionListResponse, pathname = window.location.pathname): SessionViewMode {
|
|
60
|
+
latestList = response;
|
|
61
|
+
if (!navigationFieldsPresent(response)) {
|
|
62
|
+
Object.assign(viewState, { enabled: false, mode: 'off', activeId: null, viewed: null });
|
|
63
|
+
return 'off';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const path = routePath(pathname);
|
|
67
|
+
if (path === '/' || path === '') {
|
|
68
|
+
Object.assign(viewState, { enabled: true, mode: 'hub', activeId: response.active, viewed: null });
|
|
69
|
+
return 'hub';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const seq = parsedSeq(pathname);
|
|
73
|
+
const viewed = seq === null ? null : response.sessions.find(session => session.seq === seq) || null;
|
|
74
|
+
if (!viewed) {
|
|
75
|
+
Object.assign(viewState, { enabled: true, mode: 'redirect', activeId: response.active, viewed: null });
|
|
76
|
+
return 'redirect';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Object.assign(viewState, { enabled: true, mode: 'session', activeId: response.active, viewed });
|
|
80
|
+
if (!canSendFromCurrentView()) cancelRecordingOnReadonly?.();
|
|
81
|
+
return 'session';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function canSendFromCurrentView(commandText = ''): boolean {
|
|
85
|
+
// Fail CLOSED on numeric routes before initialization. Write listeners are
|
|
86
|
+
// installed at module load (main.ts:120) but initializeSessionView() only
|
|
87
|
+
// runs later in bootstrap, so an early click on /:seq would otherwise pass
|
|
88
|
+
// the guard and write to the GLOBAL active session — the exact
|
|
89
|
+
// cross-session write this guard exists to prevent.
|
|
90
|
+
if (!viewState.enabled) {
|
|
91
|
+
if (parsedSeq(window.location.pathname) === null) return true;
|
|
92
|
+
return commandText ? ALLOWED_READONLY_COMMAND.test(commandText) : false;
|
|
93
|
+
}
|
|
94
|
+
if (commandText && ALLOWED_READONLY_COMMAND.test(commandText)) return true;
|
|
95
|
+
return viewState.mode === 'session'
|
|
96
|
+
&& viewState.viewed?.id === viewState.activeId
|
|
97
|
+
&& viewState.viewed.remoteKey === null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function currentSessionId(): string | null {
|
|
101
|
+
return viewState.enabled && viewState.mode === 'session' ? viewState.viewed?.id || null : null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Mirrors scopeForChatSession() on the server (src/orchestrator/scope.ts). A tab on the
|
|
105
|
+
// hub or on a pre-navigation build returns null, which means an unfiltered connection
|
|
106
|
+
// that receives everything — the behaviour before per-tab scoping existed.
|
|
107
|
+
//
|
|
108
|
+
// Remotely bound sessions run under their remote key, so a tab viewing one must
|
|
109
|
+
// subscribe to that key rather than a local scope it would never see events on.
|
|
110
|
+
export function currentEventScope(): string | null {
|
|
111
|
+
if (!viewState.enabled || viewState.mode !== 'session') return null;
|
|
112
|
+
const viewed = viewState.viewed;
|
|
113
|
+
if (!viewed) return null;
|
|
114
|
+
if (viewed.id === 'default') return 'default';
|
|
115
|
+
if (viewed.remoteKey) return viewed.remoteKey;
|
|
116
|
+
return `local:${viewed.id}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function withCurrentSessionQuery(path: string): string {
|
|
120
|
+
const sessionId = currentSessionId();
|
|
121
|
+
if (!sessionId) return path;
|
|
122
|
+
if (path.startsWith('?')) {
|
|
123
|
+
const params = new URLSearchParams(path.slice(1));
|
|
124
|
+
params.set('session', sessionId);
|
|
125
|
+
return `?${params.toString()}`;
|
|
126
|
+
}
|
|
127
|
+
const url = new URL(path, 'http://session.local');
|
|
128
|
+
url.searchParams.set('session', sessionId);
|
|
129
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Writes carry the session id, not a scope: the scope rule belongs to the server
|
|
133
|
+
// (src/orchestrator/scope.ts). A tab on the hub or a pre-navigation build sends
|
|
134
|
+
// nothing extra and the server keeps using the globally active session, which is
|
|
135
|
+
// exactly the behaviour before per-tab routing existed.
|
|
136
|
+
export function withCurrentSessionBody<T extends Record<string, unknown>>(body: T): T & { sessionId?: string } {
|
|
137
|
+
const sessionId = currentSessionId();
|
|
138
|
+
return sessionId ? { ...body, sessionId } : body;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function sessionHref(seq: number): string {
|
|
142
|
+
return `${API_BASE}/${seq}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function hubHref(): string {
|
|
146
|
+
return API_BASE || '/';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function displayLabel(session: SessionListItem): string {
|
|
150
|
+
if (session.seq === 0) return t('sessionHub.defaultSession');
|
|
151
|
+
return session.label?.trim() || t('sessionHub.sessionLabel', { seq: session.seq });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatLastActivity(value: string | null | undefined): string {
|
|
155
|
+
if (value == null) return t('sessionHub.noActivity');
|
|
156
|
+
const date = new Date(value);
|
|
157
|
+
if (Number.isNaN(date.getTime())) return t('sessionHub.noActivity');
|
|
158
|
+
return new Intl.DateTimeFormat(getLang(), { dateStyle: 'medium', timeStyle: 'short' }).format(date);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function sourceLabel(source: SessionSource | undefined): string {
|
|
162
|
+
return t(`sessionHub.source.${source || 'local'}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function renderSessionCard(session: SessionListItem, activeId: string): string {
|
|
166
|
+
const remoteBound = session.remoteKey != null;
|
|
167
|
+
const canDelete = session.id !== 'default' && !remoteBound;
|
|
168
|
+
const deleteReason = remoteBound ? t('sessionHub.deleteRemoteDisabled') : '';
|
|
169
|
+
return `<article class="session-card${session.id === activeId ? ' is-active' : ''}" data-session-id="${escapeHtml(session.id)}">
|
|
170
|
+
<a class="session-card-link" href="${escapeHtml(sessionHref(session.seq))}">
|
|
171
|
+
<div class="session-card-heading">
|
|
172
|
+
<span class="session-seq">/${session.seq}</span>
|
|
173
|
+
<span class="session-source">${escapeHtml(sourceLabel(session.source))}</span>
|
|
174
|
+
</div>
|
|
175
|
+
<h2>${escapeHtml(displayLabel(session))}</h2>
|
|
176
|
+
<dl class="session-card-meta">
|
|
177
|
+
<div><dt>${escapeHtml(t('sessionHub.messages'))}</dt><dd>${session.message_count}</dd></div>
|
|
178
|
+
<div><dt>${escapeHtml(t('sessionHub.lastActivity'))}</dt><dd>${escapeHtml(formatLastActivity(session.lastActivityAt))}</dd></div>
|
|
179
|
+
</dl>
|
|
180
|
+
</a>
|
|
181
|
+
${session.id === 'default' ? '' : (canDelete
|
|
182
|
+
// A disabled button is not focusable and its title is unreliable on
|
|
183
|
+
// touch and assistive tech, so the remote-bound reason is rendered as
|
|
184
|
+
// visible text and wired with aria-describedby instead.
|
|
185
|
+
? `<button type="button" class="session-delete" data-delete-session="${escapeHtml(session.id)}">${escapeHtml(t('sessionHub.delete'))}</button>`
|
|
186
|
+
: `<button type="button" class="session-delete" data-delete-session="${escapeHtml(session.id)}" aria-disabled="true" aria-describedby="delete-reason-${escapeHtml(session.id)}">${escapeHtml(t('sessionHub.delete'))}</button>
|
|
187
|
+
<p class="session-delete-reason" id="delete-reason-${escapeHtml(session.id)}">${escapeHtml(deleteReason)}</p>`)}
|
|
188
|
+
</article>`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function renderHubSkeleton(): void {
|
|
192
|
+
document.body.classList.add('session-hub-mode');
|
|
193
|
+
let hub = document.getElementById('sessionHub');
|
|
194
|
+
if (!hub) {
|
|
195
|
+
hub = document.createElement('main');
|
|
196
|
+
hub.id = 'sessionHub';
|
|
197
|
+
hub.className = 'session-hub';
|
|
198
|
+
document.body.prepend(hub);
|
|
199
|
+
}
|
|
200
|
+
hub.setAttribute('aria-busy', 'true');
|
|
201
|
+
hub.innerHTML = `<div class="session-hub-shell">
|
|
202
|
+
<header class="session-hub-header"><div><p class="session-hub-kicker">CLI-JAW</p><h1>${escapeHtml(t('sessionHub.title'))}</h1></div></header>
|
|
203
|
+
<div class="session-grid session-grid-loading" aria-hidden="true">
|
|
204
|
+
<div class="session-card-skeleton"><span></span><span></span><span></span></div>
|
|
205
|
+
<div class="session-card-skeleton"><span></span><span></span><span></span></div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function renderHub(response: SessionListResponse): void {
|
|
211
|
+
document.body.classList.add('session-hub-mode');
|
|
212
|
+
let hub = document.getElementById('sessionHub');
|
|
213
|
+
if (!hub) {
|
|
214
|
+
hub = document.createElement('main');
|
|
215
|
+
hub.id = 'sessionHub';
|
|
216
|
+
hub.className = 'session-hub';
|
|
217
|
+
document.body.prepend(hub);
|
|
218
|
+
}
|
|
219
|
+
hub.removeAttribute('aria-busy');
|
|
220
|
+
const onlyDefault = response.sessions.length === 1 && response.sessions[0]?.id === 'default';
|
|
221
|
+
hub.innerHTML = `<div class="session-hub-shell">
|
|
222
|
+
<header class="session-hub-header">
|
|
223
|
+
<div><p class="session-hub-kicker">CLI-JAW</p><h1>${escapeHtml(t('sessionHub.title'))}</h1></div>
|
|
224
|
+
<p>${escapeHtml(t('sessionHub.description'))}</p>
|
|
225
|
+
</header>
|
|
226
|
+
${onlyDefault ? `<section class="session-hub-empty" aria-labelledby="sessionHubEmptyTitle">
|
|
227
|
+
<h2 id="sessionHubEmptyTitle">${escapeHtml(t('sessionHub.empty.title'))}</h2>
|
|
228
|
+
<p>${escapeHtml(t('sessionHub.empty.description'))}</p>
|
|
229
|
+
<a href="${escapeHtml(sessionHref(0))}#settings">${escapeHtml(t('sessionHub.empty.action'))}</a>
|
|
230
|
+
</section>` : ''}
|
|
231
|
+
<section class="session-grid" aria-label="${escapeHtml(t('sessionHub.listAria'))}">
|
|
232
|
+
${response.sessions.map(session => renderSessionCard(session, response.active)).join('')}
|
|
233
|
+
</section>
|
|
234
|
+
<p class="session-hub-error" id="sessionHubError" role="status" aria-live="polite"></p>
|
|
235
|
+
</div>`;
|
|
236
|
+
if (!hub.dataset['eventsBound']) {
|
|
237
|
+
hub.addEventListener('click', handleHubClick);
|
|
238
|
+
hub.dataset['eventsBound'] = 'true';
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function handleHubClick(event: Event): Promise<void> {
|
|
243
|
+
const button = (event.target as HTMLElement | null)?.closest<HTMLButtonElement>('[data-delete-session]');
|
|
244
|
+
// aria-disabled keeps the control focusable so its reason can be announced,
|
|
245
|
+
// so activation has to be suppressed here rather than by the DOM.
|
|
246
|
+
if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return;
|
|
247
|
+
event.preventDefault();
|
|
248
|
+
const sessionId = button.dataset['deleteSession'];
|
|
249
|
+
if (!sessionId || !window.confirm(t('sessionHub.deleteConfirm'))) return;
|
|
250
|
+
button.disabled = true;
|
|
251
|
+
const token = await getAuthToken();
|
|
252
|
+
try {
|
|
253
|
+
const response = await fetch(`${API_BASE}/api/chat-sessions/${encodeURIComponent(sessionId)}`, {
|
|
254
|
+
method: 'DELETE',
|
|
255
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
258
|
+
const refreshed = await api<SessionListResponse>('/api/chat-sessions');
|
|
259
|
+
if (refreshed) {
|
|
260
|
+
latestList = refreshed;
|
|
261
|
+
renderHub(refreshed);
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
const error = document.getElementById('sessionHubError');
|
|
265
|
+
if (error) error.textContent = t('sessionHub.deleteFailed');
|
|
266
|
+
button.disabled = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function renderSessionViewChrome(): void {
|
|
271
|
+
const viewed = viewState.viewed;
|
|
272
|
+
if (!viewed) return;
|
|
273
|
+
document.title = `${displayLabel(viewed)} · CLI-JAW`;
|
|
274
|
+
const header = document.querySelector<HTMLElement>('.chat-header > span');
|
|
275
|
+
if (header) header.insertAdjacentHTML('beforeend', `<span class="session-view-label">/${viewed.seq} · ${escapeHtml(displayLabel(viewed))}</span>`);
|
|
276
|
+
if (!canSendFromCurrentView()) showReadOnlySwitchAffordance();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function showReadOnlySwitchAffordance(): void {
|
|
280
|
+
if (!viewState.enabled || !viewState.viewed) return;
|
|
281
|
+
let notice = document.getElementById('sessionReadonlyNotice');
|
|
282
|
+
if (!notice) {
|
|
283
|
+
notice = document.createElement('div');
|
|
284
|
+
notice.id = 'sessionReadonlyNotice';
|
|
285
|
+
notice.className = 'session-readonly-notice';
|
|
286
|
+
notice.setAttribute('role', 'status');
|
|
287
|
+
document.querySelector('.chat-input-area')?.before(notice);
|
|
288
|
+
}
|
|
289
|
+
const remote = viewState.viewed.remoteKey != null;
|
|
290
|
+
const command = remote ? '/sessions' : `/switch ${viewState.viewed.seq}`;
|
|
291
|
+
const actionLabel = remote
|
|
292
|
+
? t('sessionReadonly.openSessions')
|
|
293
|
+
: t('sessionReadonly.switch', { seq: viewState.viewed.seq });
|
|
294
|
+
notice.innerHTML = `<span>${escapeHtml(t(remote ? 'sessionReadonly.remote' : 'sessionReadonly.message'))}</span>
|
|
295
|
+
<button type="button">${escapeHtml(actionLabel)}</button>`;
|
|
296
|
+
notice.querySelector('button')?.addEventListener('click', () => {
|
|
297
|
+
const input = document.getElementById('chatInput') as HTMLTextAreaElement | null;
|
|
298
|
+
if (!input) return;
|
|
299
|
+
input.value = command;
|
|
300
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
301
|
+
input.dispatchEvent(new Event('cmd-execute', { bubbles: true }));
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function handleSessionListBroadcast(message: { deleted?: { id?: unknown; seq?: unknown } }): boolean {
|
|
306
|
+
if (!viewState.enabled || viewState.mode !== 'session' || !viewState.viewed) return false;
|
|
307
|
+
const deleted = message.deleted;
|
|
308
|
+
if (!deleted) return false;
|
|
309
|
+
if (deleted.id !== viewState.viewed.id && deleted.seq !== viewState.viewed.seq) return false;
|
|
310
|
+
window.location.assign(hubHref());
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function initializeSessionView(options: { cancelRecording?: () => void } = {}): Promise<SessionViewMode> {
|
|
315
|
+
cancelRecordingOnReadonly = options.cancelRecording || null;
|
|
316
|
+
const response = await api<SessionListResponse>('/api/chat-sessions');
|
|
317
|
+
if (!response) {
|
|
318
|
+
if (parsedSeq(window.location.pathname) !== null) {
|
|
319
|
+
Object.assign(viewState, { enabled: true, mode: 'redirect', activeId: null, viewed: null });
|
|
320
|
+
window.location.replace(hubHref());
|
|
321
|
+
return 'redirect';
|
|
322
|
+
}
|
|
323
|
+
return 'off';
|
|
324
|
+
}
|
|
325
|
+
const mode = configureSessionView(response);
|
|
326
|
+
if (mode === 'hub') {
|
|
327
|
+
renderHubSkeleton();
|
|
328
|
+
const refreshed = await api<SessionListResponse>('/api/chat-sessions');
|
|
329
|
+
renderHub(refreshed || response);
|
|
330
|
+
}
|
|
331
|
+
if (mode === 'session') renderSessionViewChrome();
|
|
332
|
+
if (mode === 'redirect') window.location.replace(hubHref());
|
|
333
|
+
return mode;
|
|
334
|
+
}
|
|
@@ -12,7 +12,7 @@ type QuotaSetupHint = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
export const QUOTA_HIDDEN_CLIS = new Set(['ai-e', 'codex-app']);
|
|
15
|
-
export const SIDEBAR_HIDDEN_CLIS = new Set(['ai-e', 'claude-e', 'jwc', 'pi', '
|
|
15
|
+
export const SIDEBAR_HIDDEN_CLIS = new Set(['ai-e', 'claude-e', 'jwc', 'pi', 'gemini']);
|
|
16
16
|
export const QUOTA_CUSTOM_MSG: Record<string, string> = {};
|
|
17
17
|
|
|
18
18
|
export const QUOTA_SETUP_HINTS: Record<string, QuotaSetupHint> = {
|
|
@@ -5,7 +5,7 @@ import { t } from './i18n.js';
|
|
|
5
5
|
import { state } from '../state.js';
|
|
6
6
|
import { ICONS } from '../icons.js';
|
|
7
7
|
import { providerIcon, providerLabel } from '../provider-icons.js';
|
|
8
|
-
import { resolveQuotaWindowDisplay, type QuotaEntry } from './settings-types.js';
|
|
8
|
+
import { describeCliProbe, resolveQuotaWindowDisplay, type CliStatusInfo, type QuotaEntry } from './settings-types.js';
|
|
9
9
|
import {
|
|
10
10
|
buildAccountParts,
|
|
11
11
|
normalizeQuotaWindowLabel,
|
|
@@ -184,7 +184,7 @@ export function setCliStatusInterval(value: string): void {
|
|
|
184
184
|
|
|
185
185
|
function scheduleEmbeddedQuotaRetry(
|
|
186
186
|
seq: number,
|
|
187
|
-
cliStatus: Record<string,
|
|
187
|
+
cliStatus: Record<string, CliStatusInfo>,
|
|
188
188
|
cachedQuota: Record<string, QuotaEntry> | null | undefined,
|
|
189
189
|
): void {
|
|
190
190
|
if (!isEmbeddedPreviewFrame()) return;
|
|
@@ -201,7 +201,7 @@ function scheduleEmbeddedQuotaRetry(
|
|
|
201
201
|
|
|
202
202
|
async function fetchAndRenderQuota(
|
|
203
203
|
seq: number,
|
|
204
|
-
cliStatus: Record<string,
|
|
204
|
+
cliStatus: Record<string, CliStatusInfo>,
|
|
205
205
|
cachedQuota: Record<string, QuotaEntry> | null | undefined,
|
|
206
206
|
): Promise<boolean> {
|
|
207
207
|
const quota = await api<Record<string, QuotaEntry>>('/api/quota');
|
|
@@ -219,7 +219,7 @@ async function fetchAndRenderQuota(
|
|
|
219
219
|
function scheduleQuotaFetch(
|
|
220
220
|
force: boolean,
|
|
221
221
|
seq: number,
|
|
222
|
-
cliStatus: Record<string,
|
|
222
|
+
cliStatus: Record<string, CliStatusInfo>,
|
|
223
223
|
cachedQuota: Record<string, QuotaEntry> | null | undefined,
|
|
224
224
|
): Promise<boolean> | void {
|
|
225
225
|
const run = () => { void fetchAndRenderQuota(seq, cliStatus, cachedQuota).catch(() => {}); };
|
|
@@ -243,7 +243,7 @@ export async function loadCliStatus(force = false): Promise<void> {
|
|
|
243
243
|
const interval = readCliStatusInterval();
|
|
244
244
|
if (!force && state.cliStatusCache && interval > 0 && (Date.now() - state.cliStatusTs) < interval * 1000) {
|
|
245
245
|
renderCliStatus({
|
|
246
|
-
cliStatus: (state.cliStatusCache as Record<string, unknown>)?.['cliStatus'] as Record<string,
|
|
246
|
+
cliStatus: (state.cliStatusCache as Record<string, unknown>)?.['cliStatus'] as Record<string, CliStatusInfo> | null,
|
|
247
247
|
quota: (state.cliStatusCache as Record<string, unknown>)?.['quota'] as Record<string, QuotaEntry> | null,
|
|
248
248
|
});
|
|
249
249
|
return;
|
|
@@ -252,7 +252,7 @@ export async function loadCliStatus(force = false): Promise<void> {
|
|
|
252
252
|
const cachedQuota = (state.cliStatusCache as Record<string, unknown> | null)?.['quota'] as Record<string, QuotaEntry> | null | undefined;
|
|
253
253
|
if (el && !cachedQuota) el.innerHTML = '<div style="color:var(--text-dim);font-size:11px">Loading...</div>';
|
|
254
254
|
|
|
255
|
-
const cliStatus = await api<Record<string,
|
|
255
|
+
const cliStatus = await api<Record<string, CliStatusInfo>>('/api/cli-status');
|
|
256
256
|
if (seq !== cliStatusLoadSeq) return;
|
|
257
257
|
if (!cliStatus || typeof cliStatus !== 'object') {
|
|
258
258
|
if (el) el.innerHTML = '<div style="color:var(--text-dim);font-size:11px">Failed to load CLI status</div>';
|
|
@@ -278,7 +278,7 @@ export async function loadCliStatus(force = false): Promise<void> {
|
|
|
278
278
|
await cliStatusLoadInFlight;
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
function renderCliStatus(data: { cliStatus: Record<string,
|
|
281
|
+
function renderCliStatus(data: { cliStatus: Record<string, CliStatusInfo> | null; quota: Record<string, QuotaEntry> | null }): void {
|
|
282
282
|
const { cliStatus, quota } = data;
|
|
283
283
|
const el = document.getElementById('cliStatusList');
|
|
284
284
|
|
|
@@ -306,9 +306,14 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
306
306
|
|
|
307
307
|
for (const [name, info] of Object.entries(cliStatus)) {
|
|
308
308
|
if (SIDEBAR_HIDDEN_CLIS.has(name)) continue;
|
|
309
|
-
const
|
|
309
|
+
const probeDescription = describeCliProbe(info);
|
|
310
|
+
const checking = probeDescription === 'checking';
|
|
311
|
+
const capabilityFailed = probeDescription === 'capability-failed';
|
|
312
|
+
const q = checking ? undefined : quota?.[name];
|
|
310
313
|
let dotClass: string;
|
|
311
|
-
if (
|
|
314
|
+
if (checking) {
|
|
315
|
+
dotClass = 'checking';
|
|
316
|
+
} else if (!info.available || capabilityFailed) {
|
|
312
317
|
dotClass = 'missing';
|
|
313
318
|
} else if (!q || q.error) {
|
|
314
319
|
dotClass = 'ok';
|
|
@@ -330,7 +335,7 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
330
335
|
}
|
|
331
336
|
|
|
332
337
|
let authHint = '';
|
|
333
|
-
if (!info.available || dotClass === 'warn') {
|
|
338
|
+
if (!checking && (!info.available || dotClass === 'warn')) {
|
|
334
339
|
const hint = AUTH_HINTS[name];
|
|
335
340
|
if (hint) {
|
|
336
341
|
const isNotInstalled = !info.available;
|
|
@@ -398,6 +403,14 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
398
403
|
? `<span style="margin-left:auto;font-size:10px;color:var(--text-dim);white-space:nowrap">$${q.billing.usedUsd.toFixed(1)}/$${q.billing.limitUsd}</span>`
|
|
399
404
|
: '';
|
|
400
405
|
|
|
406
|
+
const probeLine = checking
|
|
407
|
+
? '<div role="status" aria-live="polite" style="font-size:10px;color:var(--text-dim);margin:2px 0 0 16px">상태 확인 중</div>'
|
|
408
|
+
: capabilityFailed
|
|
409
|
+
? `<div role="status" style="font-size:10px;color:var(--error);margin:2px 0 0 16px">설치됨, app-server 사용 불가${info.reason ? ` · ${escapeHtml(info.reason)}` : ''}</div>`
|
|
410
|
+
: probeDescription === 'stale'
|
|
411
|
+
? '<div role="status" style="font-size:10px;color:var(--text-dim);margin:2px 0 0 16px">이전 상태 표시 중 · 새로 확인 중</div>'
|
|
412
|
+
: '';
|
|
413
|
+
|
|
401
414
|
html += `
|
|
402
415
|
<div class="settings-group" style="margin-bottom:6px;padding:8px 10px">
|
|
403
416
|
<div class="cli-status-row" style="display:flex;align-items:center">
|
|
@@ -406,6 +419,7 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
406
419
|
<span class="cli-name" style="font-weight:600">${escapeHtml(providerLabel(name))}${quotaHelpMark}</span>${name === 'copilot' ? `<button id="copilotKeychainBtn" style="font-size:9px;margin-left:6px;padding:1px 5px;background:var(--border);color:var(--text-dim);border:1px solid var(--text-dim);border-radius:3px;cursor:pointer;vertical-align:middle;line-height:1" title="${t('copilot.keychainHint')}">${ICONS.key}</button>` : ''}${billingLabel}
|
|
407
420
|
</div>
|
|
408
421
|
${accountLine}
|
|
422
|
+
${probeLine}
|
|
409
423
|
${authHint}
|
|
410
424
|
${windowsHtml}
|
|
411
425
|
</div>
|
|
@@ -416,7 +430,7 @@ function renderCliStatus(data: { cliStatus: Record<string, { available: boolean
|
|
|
416
430
|
|
|
417
431
|
const allEntries = Object.entries(cliStatus);
|
|
418
432
|
const hasReadyCli = allEntries.some(([name, info]) => {
|
|
419
|
-
if (
|
|
433
|
+
if (describeCliProbe(info) !== 'ready') return false;
|
|
420
434
|
const q = quota?.[name];
|
|
421
435
|
return !q || q.authenticated !== false;
|
|
422
436
|
});
|
|
@@ -4,8 +4,8 @@ import type { CliEntry } from '../constants.js';
|
|
|
4
4
|
import { escapeHtml } from '../render.js';
|
|
5
5
|
import { syncStoredLocale } from '../locale.js';
|
|
6
6
|
import { t } from './i18n.js';
|
|
7
|
-
import { api, apiJson, apiFire } from '../api.js';
|
|
8
|
-
import type
|
|
7
|
+
import { API_BASE, api, apiJson, apiFire, getAuthToken } from '../api.js';
|
|
8
|
+
import { shouldHydrateRuntimeMigrationResponse, type PerCliConfig, type SettingsData } from './settings-types.js';
|
|
9
9
|
import { setCachedPi, syncPiProviderDropdown, syncPiModelDropdown, piDiscoveredModels } from './pi-settings.js';
|
|
10
10
|
import { initSttSettings } from './settings-stt.js';
|
|
11
11
|
import { loadTelegramSettings } from './settings-telegram.js';
|
|
@@ -20,6 +20,70 @@ import { loadHeaderGitStatus, refreshHeaderGitStatusFromSettingsChange } from '.
|
|
|
20
20
|
|
|
21
21
|
let activeSettingsSave: Promise<void> | null = null;
|
|
22
22
|
|
|
23
|
+
type MigrationResponse = SettingsData | { ok?: boolean; data?: SettingsData; settings?: SettingsData };
|
|
24
|
+
|
|
25
|
+
function unwrapMigrationSettings(payload: MigrationResponse | null): SettingsData | null {
|
|
26
|
+
if (!payload || typeof payload !== 'object') return null;
|
|
27
|
+
if ('settings' in payload && payload.settings) return payload.settings;
|
|
28
|
+
if ('data' in payload && payload.data) return payload.data;
|
|
29
|
+
return payload as SettingsData;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function resolvePendingRuntimeMigration(snapshot: SettingsData): Promise<SettingsData> {
|
|
33
|
+
if (snapshot.runtimeDefaultMigration?.state !== 'pending') return snapshot;
|
|
34
|
+
const action = window.confirm('신규 설치의 기본 런타임이 Codex App으로 변경되었습니다. 지금 Codex App을 사용하시겠습니까?')
|
|
35
|
+
? 'accept'
|
|
36
|
+
: 'keep';
|
|
37
|
+
try {
|
|
38
|
+
const token = await getAuthToken();
|
|
39
|
+
const response = await fetch(`${API_BASE}/api/settings/runtime-default-migration`, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({ action }),
|
|
46
|
+
});
|
|
47
|
+
const payload = await response.json().catch(() => null) as MigrationResponse | null;
|
|
48
|
+
if (shouldHydrateRuntimeMigrationResponse(response.status)) {
|
|
49
|
+
return unwrapMigrationSettings(payload) ?? snapshot;
|
|
50
|
+
}
|
|
51
|
+
window.alert(`런타임 선택을 저장하지 못했습니다 (${response.status}). 다음 설정 진입 때 다시 안내합니다.`);
|
|
52
|
+
} catch {
|
|
53
|
+
window.alert('런타임 선택을 저장하지 못했습니다. 다음 설정 진입 때 다시 안내합니다.');
|
|
54
|
+
}
|
|
55
|
+
return snapshot;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function resolvePendingMultiSessionMigration(snapshot: SettingsData): Promise<SettingsData> {
|
|
59
|
+
if (snapshot.multiSessionDefaultMigration?.state !== 'pending') return snapshot;
|
|
60
|
+
// The prompt names both halves because accepting applies both: sessions on, and a
|
|
61
|
+
// second lane so a second tab does not queue behind the first. Turning it on without
|
|
62
|
+
// the lane would change what the screen shows and nothing about how it runs.
|
|
63
|
+
const action = window.confirm(
|
|
64
|
+
'이제 대화 세션을 여러 개 열 수 있습니다. 켜면 동시 실행도 2로 올라가서, 두 번째 세션이 첫 번째가 끝나기를 기다리지 않습니다. 지금 켤까요?',
|
|
65
|
+
) ? 'accept' : 'keep';
|
|
66
|
+
try {
|
|
67
|
+
const token = await getAuthToken();
|
|
68
|
+
const response = await fetch(`${API_BASE}/api/settings/multi-session-default-migration`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({ action }),
|
|
75
|
+
});
|
|
76
|
+
const payload = await response.json().catch(() => null) as MigrationResponse | null;
|
|
77
|
+
if (shouldHydrateRuntimeMigrationResponse(response.status)) {
|
|
78
|
+
return unwrapMigrationSettings(payload) ?? snapshot;
|
|
79
|
+
}
|
|
80
|
+
window.alert(`세션 선택을 저장하지 못했습니다 (${response.status}). 다음 설정 진입 때 다시 안내합니다.`);
|
|
81
|
+
} catch {
|
|
82
|
+
window.alert('세션 선택을 저장하지 못했습니다. 다음 설정 진입 때 다시 안내합니다.');
|
|
83
|
+
}
|
|
84
|
+
return snapshot;
|
|
85
|
+
}
|
|
86
|
+
|
|
23
87
|
function setHeaderCli(cli: string): void {
|
|
24
88
|
const hdr = document.getElementById('headerCli');
|
|
25
89
|
if (!hdr) return;
|
|
@@ -362,8 +426,13 @@ function syncCliProviderControl(settings: SettingsData | null, cli: string): str
|
|
|
362
426
|
|
|
363
427
|
export async function loadSettings(): Promise<void> {
|
|
364
428
|
await loadCliRegistry();
|
|
365
|
-
|
|
429
|
+
let s = await api<SettingsData>('/api/settings');
|
|
366
430
|
if (!s) return;
|
|
431
|
+
s = await resolvePendingRuntimeMigration(s);
|
|
432
|
+
// Runtime first, then sessions: which CLI runs is the earlier decision, and a v1
|
|
433
|
+
// install has both pending at once. The second call takes the snapshot the first
|
|
434
|
+
// returned — passing the pre-call one would write back over the answer just given.
|
|
435
|
+
s = await resolvePendingMultiSessionMigration(s);
|
|
367
436
|
syncStoredLocale(s.locale ?? '');
|
|
368
437
|
syncCliOptionSelects(s);
|
|
369
438
|
setCachedPi(s.pi);
|
|
@@ -46,6 +46,35 @@ export interface QuotaEntry {
|
|
|
46
46
|
}
|
|
47
47
|
export interface PiProfileView { id: string; label: string; mode: string; endpoint: string; apiKind?: string; apiKeySet?: boolean; apiKeyLast4?: string; model: string; }
|
|
48
48
|
export interface PiSettingsView { defaultProfileId: string; profiles: PiProfileView[]; discoveredModels?: Record<string, string[]>; }
|
|
49
|
+
export interface RuntimeDefaultMigration {
|
|
50
|
+
id: 'codex-app-default-v2';
|
|
51
|
+
state: 'pending' | 'accepted' | 'kept' | 'already-codex-app';
|
|
52
|
+
fromCli: string;
|
|
53
|
+
toCli: 'codex-app';
|
|
54
|
+
}
|
|
55
|
+
export interface MultiSessionDefaultMigration {
|
|
56
|
+
id: 'multi-session-default-v3';
|
|
57
|
+
state: 'pending' | 'accepted' | 'kept' | 'already-enabled';
|
|
58
|
+
}
|
|
59
|
+
export interface CliStatusInfo {
|
|
60
|
+
available: boolean | null;
|
|
61
|
+
binaryInstalled: boolean | null;
|
|
62
|
+
capabilityReady: boolean | null;
|
|
63
|
+
authenticated: boolean | null;
|
|
64
|
+
path: string | null;
|
|
65
|
+
source: string;
|
|
66
|
+
probeState: 'checking' | 'fresh' | 'stale';
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
export function describeCliProbe(info: CliStatusInfo): 'checking' | 'capability-failed' | 'stale' | 'ready' | 'unavailable' {
|
|
70
|
+
if (info.probeState === 'checking') return 'checking';
|
|
71
|
+
if (info.binaryInstalled === true && info.capabilityReady === false) return 'capability-failed';
|
|
72
|
+
if (info.probeState === 'stale') return 'stale';
|
|
73
|
+
return info.available === true && info.capabilityReady !== false ? 'ready' : 'unavailable';
|
|
74
|
+
}
|
|
75
|
+
export function shouldHydrateRuntimeMigrationResponse(status: number): boolean {
|
|
76
|
+
return status >= 200 && status < 300 || status === 409;
|
|
77
|
+
}
|
|
49
78
|
export interface SettingsData {
|
|
50
79
|
cli: string; workingDir: string; permissions: string; locale?: string; showReasoning?: boolean;
|
|
51
80
|
perCli?: Record<string, PerCliConfig>;
|
|
@@ -59,4 +88,7 @@ export interface SettingsData {
|
|
|
59
88
|
projectDirs?: string[] | null;
|
|
60
89
|
stt?: { engine?: string; geminiKeySet?: boolean; geminiKeyLast4?: string; geminiModel?: string; whisperModel?: string; openaiKeySet?: boolean; openaiKeyLast4?: string };
|
|
61
90
|
pi?: PiSettingsView;
|
|
91
|
+
settingsSchemaVersion?: number;
|
|
92
|
+
runtimeDefaultMigration?: RuntimeDefaultMigration | null;
|
|
93
|
+
multiSessionDefaultMigration?: MultiSessionDefaultMigration | null;
|
|
62
94
|
}
|
|
@@ -3,6 +3,7 @@ import { api } from '../api.js';
|
|
|
3
3
|
import { ICONS } from '../icons.js';
|
|
4
4
|
import { t } from './i18n.js';
|
|
5
5
|
import { showSkeleton, removeSkeleton } from './chat-messages.js';
|
|
6
|
+
import { withCurrentSessionQuery } from './session-hub.js';
|
|
6
7
|
|
|
7
8
|
export function setStatus(s: string): void {
|
|
8
9
|
const badge = document.getElementById('statusBadge');
|
|
@@ -47,7 +48,7 @@ export function updateStatMsgs(count: number): void {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export async function loadStats(): Promise<void> {
|
|
50
|
-
const result = await api<{ count: number }>('/api/messages/count');
|
|
51
|
+
const result = await api<{ count: number }>(withCurrentSessionQuery('/api/messages/count'));
|
|
51
52
|
if (!result) return;
|
|
52
53
|
updateStatMsgs(result.count);
|
|
53
54
|
}
|
package/public/js/main.ts
CHANGED
|
@@ -95,6 +95,8 @@ import { initAttentionBadge } from './features/attention-badge.js';
|
|
|
95
95
|
import { initHelpDialog } from './features/help-dialog.js';
|
|
96
96
|
import { initChatSearch, toggleChatSearch, closeChatSearch } from './features/chat-search.js';
|
|
97
97
|
import { initMediaLightbox } from './features/media-lightbox.js';
|
|
98
|
+
import { currentEventScope, initializeSessionView } from './features/session-hub.js';
|
|
99
|
+
import { setEventChannelScopeProvider } from './event-channel.js';
|
|
98
100
|
|
|
99
101
|
function isLocalPreviewOrigin(origin: string): boolean {
|
|
100
102
|
if (origin === window.location.origin) return true;
|
|
@@ -559,9 +561,23 @@ async function bootstrap(): Promise<void> {
|
|
|
559
561
|
ensurePreviewCapabilityListener();
|
|
560
562
|
ensurePreviewInsertTextListener();
|
|
561
563
|
await initI18n();
|
|
564
|
+
const sessionMode = await initializeSessionView({ cancelRecording });
|
|
565
|
+
if (sessionMode === 'redirect') return;
|
|
566
|
+
// Must run before either connect() below: the channel reads this on every
|
|
567
|
+
// (re)connect, and an unregistered provider opens an unfiltered stream that
|
|
568
|
+
// would carry other sessions' output into this tab.
|
|
569
|
+
setEventChannelScopeProvider(currentEventScope);
|
|
562
570
|
const langSel = document.getElementById('langSelect') as HTMLSelectElement | null;
|
|
563
571
|
if (langSel) langSel.value = getLang();
|
|
564
572
|
initHelpDialog();
|
|
573
|
+
if (sessionMode === 'hub') {
|
|
574
|
+
// The hub has no chat surface, but the badge/help listeners are part of
|
|
575
|
+
// the documented boot order (AB-003 / HD-002): they must be installed
|
|
576
|
+
// before connect() regardless of which view is rendering.
|
|
577
|
+
initAttentionBadge();
|
|
578
|
+
connect();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
565
581
|
initChatSearch();
|
|
566
582
|
initMediaLightbox();
|
|
567
583
|
document.getElementById('chatSearchTrigger')?.addEventListener('click', toggleChatSearch);
|
|
@@ -590,6 +606,10 @@ async function bootstrap(): Promise<void> {
|
|
|
590
606
|
await initAvatar();
|
|
591
607
|
initMsgCopy();
|
|
592
608
|
initGestures();
|
|
609
|
+
if (window.location.hash === '#settings') {
|
|
610
|
+
const settingsTab = document.getElementById('tabBtnSettings');
|
|
611
|
+
if (settingsTab) switchTab('settings', settingsTab);
|
|
612
|
+
}
|
|
593
613
|
try { sessionStorage.removeItem(STALE_BUNDLE_RELOAD_KEY); } catch {}
|
|
594
614
|
|
|
595
615
|
// Phase 127-F2: prewarm Mermaid at idle so first diagram renders fast.
|