@pasko70/pibo 1.1.0 → 1.2.0
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 +106 -254
- package/context/codex-base-prompt.md +4 -0
- package/context/compute-worker.md +23 -0
- package/context/pibo-native-tooling.md +18 -0
- package/context/pibo-system-prompt.md +69 -8
- package/dist/api/simple-agent-api.js +136 -0
- package/dist/app-context.js +4 -0
- package/dist/apps/chat/agent-profiles.js +18 -3
- package/dist/apps/chat/agent-store.js +113 -16
- package/dist/apps/chat/chat-api-routes.js +311 -0
- package/dist/apps/chat/chat-capability-routes.js +90 -0
- package/dist/apps/chat/chat-files.js +178 -0
- package/dist/apps/chat/chat-request-normalizers.js +681 -0
- package/dist/apps/chat/chat-settings-routes.js +75 -0
- package/dist/apps/chat/chat-trace-helpers.js +94 -0
- package/dist/apps/chat/chat-user-skill-routes.js +149 -0
- package/dist/apps/chat/cron-api.js +213 -0
- package/dist/apps/chat/data/chat-data-mappers.js +74 -0
- package/dist/apps/chat/data/event-command-service.js +75 -0
- package/dist/apps/chat/data/navigation-query-service.js +11 -0
- package/dist/apps/chat/data/project-service.js +844 -0
- package/dist/apps/chat/data/read-state-service.js +42 -0
- package/dist/apps/chat/data/room-service.js +52 -0
- package/dist/apps/chat/data/session-query-service.js +63 -0
- package/dist/apps/chat/data/timeline-query-service.js +96 -0
- package/dist/apps/chat/event-log.js +1 -307
- package/dist/apps/chat/model-catalog.js +7 -2
- package/dist/apps/chat/output-compactor.js +126 -0
- package/dist/apps/chat/output-event-policy.js +17 -0
- package/dist/apps/chat/project-workflow-human-actions.js +347 -0
- package/dist/apps/chat/project-workflow-sessions.js +260 -0
- package/dist/apps/chat/provider-auth-actions.js +43 -0
- package/dist/apps/chat/ralph-api.js +156 -0
- package/dist/apps/chat/read-model.js +1 -218
- package/dist/apps/chat/rooms.js +1 -321
- package/dist/apps/chat/static-assets.js +996 -0
- package/dist/apps/chat/stream.js +29 -6
- package/dist/apps/chat/trace.js +179 -1003
- package/dist/apps/chat/types/event-store.js +1 -0
- package/dist/apps/chat/types/read-model.js +1 -0
- package/dist/apps/chat/types/rooms.js +36 -0
- package/dist/apps/chat/web-app.js +3131 -1959
- package/dist/apps/chat/workflow-catalog.js +572 -0
- package/dist/apps/chat/workflow-edge-compatibility.js +51 -0
- package/dist/apps/chat/workflow-json-schema-validation.js +335 -0
- package/dist/apps/chat/workflow-persistence-model.js +140 -0
- package/dist/apps/chat/workflow-persistence.js +633 -0
- package/dist/apps/chat/workflow-registered-ref-params.js +130 -0
- package/dist/apps/chat/workflow-registered-ref-pickers.js +179 -0
- package/dist/apps/chat/workflow-registered-ref-validation.js +210 -0
- package/dist/apps/chat/workflow-v2-security-validation.js +93 -0
- package/dist/apps/chat/workflow-validation-helpers.js +114 -0
- package/dist/apps/chat-ui/assets/{dist-CNLWH2qh.js → dist-B4lw9z4F.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-8e4L8ciw.js → dist-BbTaHWON.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CMZOa5ik.js → dist-BcT_c1EJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DNcnQRPF.js → dist-CGINd7XU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CqqUC8Ce.js → dist-CrBzZkUp.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D2N4-LIc.js → dist-CyyISotB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CUqpST5w.js → dist-DFbnFNpX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CspjOeBj.js → dist-DGLWQLX8.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B7bbI3Qz.js → dist-DfcIkFSN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-v4pjHlB7.js → dist-sKDHTwaT.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-D9L4MSRF.js → dist-y9D6la48.js} +1 -1
- package/dist/apps/chat-ui/assets/index-C25VYnyb.css +1 -0
- package/dist/apps/chat-ui/assets/index-CUkuvI3v.js +165 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-144x144.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-192x192.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-48x48.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-512x512.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-72x72.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/android/launchericon-96x96.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/1024.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/114.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/120.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/128.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/144.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/152.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/16.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/167.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/180.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/192.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/20.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/256.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/29.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/32.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/40.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/50.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/512.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/57.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/58.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/60.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/64.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/72.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/76.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/80.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/ios/87.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/LargeTile.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/LargeTile.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/LargeTile.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/LargeTile.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/LargeTile.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SmallTile.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SmallTile.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SmallTile.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SmallTile.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SmallTile.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SplashScreen.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SplashScreen.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SplashScreen.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SplashScreen.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/SplashScreen.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square150x150Logo.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square150x150Logo.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square150x150Logo.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square150x150Logo.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square150x150Logo.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-16.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-20.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-24.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-256.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-30.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-32.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-36.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-40.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-44.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-48.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-60.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-64.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-72.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-80.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-lightunplated_targetsize-96.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-16.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-20.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-24.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-256.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-30.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-32.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-36.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-40.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-44.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-48.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-60.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-64.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-72.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-80.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.altform-unplated_targetsize-96.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-16.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-20.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-24.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-256.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-30.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-32.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-36.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-40.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-44.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-48.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-60.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-64.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-72.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-80.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Square44x44Logo.targetsize-96.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/StoreLogo.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/StoreLogo.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/StoreLogo.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/StoreLogo.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/StoreLogo.scale-400.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Wide310x150Logo.scale-100.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Wide310x150Logo.scale-125.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Wide310x150Logo.scale-150.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Wide310x150Logo.scale-200.png +0 -0
- package/dist/apps/chat-ui/assets/pwa-images/windows/Wide310x150Logo.scale-400.png +0 -0
- package/dist/apps/chat-ui/index.html +8 -3
- package/dist/apps/chat-ui/manifest.webmanifest +25 -0
- package/dist/apps/chat-ui/sw.js +41 -0
- package/dist/apps/cli-ui/InkSessionApp.js +1218 -0
- package/dist/apps/cli-ui/InkTerminalLine.js +53 -0
- package/dist/apps/cli-ui/InkTerminalRow.js +304 -0
- package/dist/apps/cli-ui/InkTerminalView.js +25 -0
- package/dist/apps/cli-ui/cliSessionsCommand.js +232 -0
- package/dist/apps/cli-ui/index.js +8 -0
- package/dist/apps/cli-ui/inkColors.js +67 -0
- package/dist/apps/cli-ui/inkJson.js +244 -0
- package/dist/apps/cli-ui/inkMarkdown.js +122 -0
- package/dist/apps/context-files-ui/assets/{dist-DFi2YGux.js → dist-BOiGdrG-.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DyfSeBMu.js → dist-DK01b7x4.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CwbH_3RN.js → dist-DPG9jOQ-.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-dTBsFYAl.js → dist-Dp5AH00p.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CxRLRmMx.js → dist-DqYw0LH7.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-MyZgX9K2.js → dist-TF5iO5ML.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-DQXzt2Ng.js → dist-TbjAhles.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-YwR9dpb-.js → dist-ZsBbt0uY.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-CKQF1zEu.js → dist-kmsvtHkH.js} +1 -1
- package/dist/apps/context-files-ui/assets/{dist-BJW3917N.js → dist-vnd7PMu3.js} +1 -1
- package/dist/apps/context-files-ui/assets/{index-BwRDoHMC.css → index-B92tL3Pp.css} +1 -1
- package/dist/apps/context-files-ui/assets/{index-GwvjXytt.js → index-BhAWD3dv.js} +2 -2
- package/dist/apps/context-files-ui/index.html +2 -2
- package/dist/auth/better-auth.js +2 -1
- package/dist/auth/login-actions.js +251 -0
- package/dist/auth/openai-codex-usage.js +147 -0
- package/dist/bin/pibo.js +0 -0
- package/dist/bin/rg.js +15 -0
- package/dist/cli-session/fakeSessionSource.js +804 -0
- package/dist/cli-session/index.js +3 -0
- package/dist/cli-session/localSessionSource.js +1144 -0
- package/dist/cli-session/sessionSource.js +13 -0
- package/dist/cli.js +142 -14
- package/dist/compute/cli.js +415 -0
- package/dist/compute/docker.js +827 -0
- package/dist/compute/resource-health.js +333 -0
- package/dist/compute/resource-policy.js +98 -0
- package/dist/config/config.js +9 -5
- package/dist/core/context-build.js +677 -0
- package/dist/core/model-defaults.js +51 -9
- package/dist/core/pibo-home.js +8 -0
- package/dist/core/profiles.js +48 -0
- package/dist/core/provider-telemetry.js +222 -0
- package/dist/core/routed-session.js +441 -47
- package/dist/core/runtime-telemetry.js +853 -0
- package/dist/core/runtime.js +103 -15
- package/dist/core/session-errors.js +47 -0
- package/dist/core/session-model.js +39 -0
- package/dist/core/session-router.js +292 -62
- package/dist/core/shared-app.js +17 -0
- package/dist/core/skill-expansion.js +43 -0
- package/dist/core/system-prompt-template.js +9 -9
- package/dist/core/telemetry-staleness.js +128 -0
- package/dist/core/test-output-compaction.js +145 -0
- package/dist/core/user-settings.js +73 -0
- package/dist/cron/channel.js +28 -0
- package/dist/cron/cli.js +230 -0
- package/dist/cron/plugin.js +11 -0
- package/dist/cron/schedule.js +214 -0
- package/dist/cron/service.js +177 -0
- package/dist/cron/store.js +349 -0
- package/dist/cron/types.js +1 -0
- package/dist/data/cli.js +184 -0
- package/dist/data/event-log.js +106 -0
- package/dist/data/final-app-space-cutover-migration.js +728 -0
- package/dist/data/ingest-service.js +389 -0
- package/dist/data/message-store.js +62 -0
- package/dist/data/navigation-store.js +75 -0
- package/dist/data/observation-store.js +46 -0
- package/dist/data/payload-store.js +144 -0
- package/dist/data/pibo-store.js +59 -0
- package/dist/data/schema.js +434 -0
- package/dist/data/session-store.js +63 -0
- package/dist/data/shared-app-migration.js +757 -0
- package/dist/data/sqlite-schema.js +12 -0
- package/dist/data/telemetry-preview.js +65 -0
- package/dist/data/telemetry-queries.js +212 -0
- package/dist/data/telemetry-retention.js +50 -0
- package/dist/data/telemetry-rows.js +158 -0
- package/dist/data/telemetry.js +357 -0
- package/dist/debug/delta-compaction.js +99 -0
- package/dist/debug/detail-format.js +86 -0
- package/dist/debug/events.js +149 -29
- package/dist/debug/failures.js +162 -0
- package/dist/debug/index.js +655 -30
- package/dist/debug/messages.js +185 -0
- package/dist/debug/next-commands.js +6 -0
- package/dist/debug/payloads.js +123 -0
- package/dist/debug/pty.js +924 -0
- package/dist/debug/session.js +45 -15
- package/dist/debug/sql.js +1 -1
- package/dist/debug/stores.js +18 -13
- package/dist/debug/summary.js +72 -0
- package/dist/debug/telemetry.js +649 -0
- package/dist/debug/tools.js +168 -0
- package/dist/debug/trace-render-check.js +151 -0
- package/dist/debug/trace.js +152 -27
- package/dist/debug/web-artifacts.js +63 -0
- package/dist/debug/web-options.js +313 -0
- package/dist/debug/web-render-analysis.js +310 -0
- package/dist/debug/web-snapshot-browser-scripts.js +303 -0
- package/dist/debug/web-streaming-benchmark-analysis.js +605 -0
- package/dist/debug/web-streaming-browser-library.js +928 -0
- package/dist/debug/web-streaming-browser-scripts.js +242 -0
- package/dist/debug/web-streaming-provider-telemetry.js +194 -0
- package/dist/debug/web-streaming-report.js +493 -0
- package/dist/debug/web-streaming-types.js +1 -0
- package/dist/debug/web.js +574 -0
- package/dist/gateway/backup.js +83 -0
- package/dist/gateway/cli.js +511 -0
- package/dist/gateway/fallback.js +155 -0
- package/dist/gateway/pidfile.js +84 -0
- package/dist/gateway/protocol.js +16 -0
- package/dist/gateway/server.js +172 -17
- package/dist/gateway/tool.js +54 -51
- package/dist/gateway/web.js +74 -2
- package/dist/index.js +3 -1
- package/dist/local/client.js +3 -3
- package/dist/local/extension.js +8 -5
- package/dist/plugins/builtin.js +251 -49
- package/dist/plugins/chat-vscode-web.js +28 -0
- package/dist/plugins/codex-compat.js +0 -41
- package/dist/plugins/context-files.js +98 -18
- package/dist/plugins/dev-auth.js +124 -0
- package/dist/plugins/native-tooling.js +15 -0
- package/dist/plugins/registry.js +49 -1
- package/dist/plugins/web-annotations.js +27 -0
- package/dist/providers/minimax.js +70 -0
- package/dist/providers/openai-compat.js +48 -0
- package/dist/ralph/channel.js +8 -0
- package/dist/ralph/cli.js +152 -0
- package/dist/ralph/plugin.js +14 -0
- package/dist/ralph/service.js +271 -0
- package/dist/ralph/stopping.js +151 -0
- package/dist/ralph/store.js +450 -0
- package/dist/ralph/templates.js +200 -0
- package/dist/ralph/types.js +1 -0
- package/dist/reliability/store.js +42 -26
- package/dist/runs/registry.js +72 -35
- package/dist/session-ui/commandCatalog.js +193 -0
- package/dist/session-ui/commandResults.js +95 -0
- package/dist/session-ui/index.js +7 -0
- package/dist/session-ui/ownerViewModel.js +27 -0
- package/dist/session-ui/roomSessionViewModel.js +69 -0
- package/dist/session-ui/statusViewModel.js +127 -0
- package/dist/session-ui/terminalCards.js +210 -0
- package/dist/session-ui/terminalRows.js +937 -0
- package/dist/session-ui/terminalValue.js +45 -0
- package/dist/sessions/pibo-data-store.js +178 -0
- package/dist/sessions/sqlite-store.js +78 -12
- package/dist/sessions/store.js +11 -4
- package/dist/sessions/workflow-session-kind.js +20 -0
- package/dist/setup/cli.js +567 -0
- package/dist/shared/trace-async-agent-runs.js +127 -0
- package/dist/shared/trace-engine.js +96 -0
- package/dist/shared/trace-event-projection.js +679 -0
- package/dist/shared/trace-live-reducer.js +191 -0
- package/dist/shared/trace-nodes.js +76 -0
- package/dist/shared/trace-order.js +11 -10
- package/dist/shared/trace-patch-nodes.js +83 -0
- package/dist/shared/trace-run-notifications.js +69 -0
- package/dist/shared/trace-subagent-links.js +46 -0
- package/dist/shared/trace-transcript.js +303 -0
- package/dist/shared/trace-types.js +1 -0
- package/dist/shared-app.js +4 -0
- package/dist/signals/aggregate.js +65 -0
- package/dist/signals/events.js +1 -0
- package/dist/signals/projector.js +158 -0
- package/dist/signals/registry.js +513 -0
- package/dist/signals/store.js +1 -0
- package/dist/signals/types.js +1 -0
- package/dist/skills/cli.js +117 -0
- package/dist/tools/agent-browser-leases.js +219 -0
- package/dist/tools/agent-browser-wrapper.js +99 -0
- package/dist/tools/browser-pool.js +623 -0
- package/dist/tools/browser-use-cdp.js +56 -4
- package/dist/tools/browser-use-leases.js +215 -37
- package/dist/tools/browser-use-wrapper.js +508 -65
- package/dist/tools/cdp-client.js +189 -0
- package/dist/tools/guides.js +203 -4
- package/dist/tools/index.js +666 -16
- package/dist/tools/npm-runtime.js +136 -0
- package/dist/tools/registry.js +107 -8
- package/dist/tools/runtime/index.js +3 -0
- package/dist/tools/runtime/node-backend.js +225 -0
- package/dist/tools/runtime/node-worker-source.js +206 -0
- package/dist/tools/runtime/python-backend.js +227 -0
- package/dist/tools/runtime/python-worker-source.js +178 -0
- package/dist/tools/runtime/registry.js +255 -0
- package/dist/tools/runtime/tool.js +168 -0
- package/dist/tools/runtime/types.js +1 -0
- package/dist/tools/web-search.js +2 -1
- package/dist/user-skills/installer.js +39 -36
- package/dist/user-skills/store.js +50 -11
- package/dist/web/auth.js +5 -1
- package/dist/web/channel.js +86 -0
- package/dist/web/http.js +79 -14
- package/dist/web-annotations/api.js +348 -0
- package/dist/web-annotations/attachments.js +201 -0
- package/dist/web-annotations/cdp.js +1107 -0
- package/dist/web-annotations/index.js +7 -0
- package/dist/web-annotations/store.js +407 -0
- package/dist/web-annotations/tools.js +350 -0
- package/dist/web-annotations/types.js +34 -0
- package/dist/web-annotations/validation.js +288 -0
- package/docs/README.md +23 -0
- package/docs/ops/install-developer-host.md +112 -0
- package/docs/ops/install-user-host.md +76 -0
- package/docs/ops/upgrade-user-to-developer-host.md +69 -0
- package/package.json +24 -10
- package/skills/builtin/pi-agent-harness/SKILL.md +24 -24
- package/skills/builtin/pibo-docker-system/SKILL.md +170 -0
- package/skills/builtin/pibo-spec-writing/SKILL.md +330 -0
- package/skills/builtin/prd/SKILL.md +143 -0
- package/skills/builtin/ralph-loop/SKILL.md +359 -0
- package/skills/builtin/ralph-prd-json/SKILL.md +123 -0
- package/skills/builtin/skill-creator/LICENSE.txt +202 -0
- package/skills/builtin/skill-creator/SKILL.md +513 -0
- package/skills/builtin/skill-creator/agents/analyzer.md +274 -0
- package/skills/builtin/skill-creator/agents/comparator.md +202 -0
- package/skills/builtin/skill-creator/agents/grader.md +223 -0
- package/skills/builtin/skill-creator/assets/eval_review.html +146 -0
- package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/builtin/skill-creator/references/schemas.md +430 -0
- package/skills/builtin/skill-creator/scripts/__init__.py +0 -0
- package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/builtin/skill-creator/scripts/generate_report.py +326 -0
- package/skills/builtin/skill-creator/scripts/improve_description.py +247 -0
- package/skills/builtin/skill-creator/scripts/package_skill.py +136 -0
- package/skills/builtin/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/builtin/skill-creator/scripts/run_eval.py +310 -0
- package/skills/builtin/skill-creator/scripts/run_loop.py +328 -0
- package/skills/builtin/skill-creator/scripts/utils.py +47 -0
- package/skills/builtin/web-annotations/SKILL.md +93 -0
- package/dist/apps/chat-ui/assets/index-D2Qxambd.js +0 -151
- package/dist/apps/chat-ui/assets/index-DXx368XK.css +0 -1
- package/dist/env.js +0 -32
- package/dist/plugins/core-tools.js +0 -94
- package/dist/plugins/example.js +0 -67
- package/dist/plugins/remote-agent.js +0 -9
- package/dist/remote/channel.js +0 -176
- package/dist/remote/client.js +0 -92
- package/dist/remote/examples/tui-controller.js +0 -263
- package/dist/remote/protocol.js +0 -55
- package/dist/remote/session-client.js +0 -189
- package/dist/sessions/bindings.js +0 -41
|
@@ -0,0 +1,1218 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
3
|
+
import { isCliSourceError } from "../../cli-session/index.js";
|
|
4
|
+
import { buildCompactTerminalRows, buildSlashCommandCatalog, buildTerminalStatusViewModel, commandSupportLabel, filterSlashCommands, formatSlashCommand, groupSlashCommandsForHelp, normalizeCommandErrorDescriptor, progressBarText, } from "../../session-ui/index.js";
|
|
5
|
+
import { InkTerminalView } from "./InkTerminalView.js";
|
|
6
|
+
import { isExpandableTerminalRow } from "./InkTerminalRow.js";
|
|
7
|
+
const IDENTIFIER_PATTERN = /^(?:user|room|ps|pi|entry|evt|run|create|provider|model|agent)[_:]/i;
|
|
8
|
+
const INITIAL_STATE = {
|
|
9
|
+
loading: true,
|
|
10
|
+
rows: [],
|
|
11
|
+
input: "",
|
|
12
|
+
mode: "transcript",
|
|
13
|
+
};
|
|
14
|
+
export function InkSessionApp({ source, initialSessionId, maxRows, maxLineChars, onExit }) {
|
|
15
|
+
const app = useApp();
|
|
16
|
+
const [state, setState] = useState(INITIAL_STATE);
|
|
17
|
+
const openedRef = useRef(undefined);
|
|
18
|
+
const unsubscribeRef = useRef(undefined);
|
|
19
|
+
const closedRef = useRef(false);
|
|
20
|
+
const stateRef = useRef(state);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
stateRef.current = state;
|
|
23
|
+
}, [state]);
|
|
24
|
+
const closeOpenSession = useCallback(() => {
|
|
25
|
+
unsubscribeRef.current?.();
|
|
26
|
+
unsubscribeRef.current = undefined;
|
|
27
|
+
void openedRef.current?.close();
|
|
28
|
+
openedRef.current = undefined;
|
|
29
|
+
}, []);
|
|
30
|
+
const cleanup = useMemo(() => createCliSessionCleanup(closeOpenSession, () => {
|
|
31
|
+
void source.close();
|
|
32
|
+
}), [closeOpenSession, source]);
|
|
33
|
+
const openSession = useCallback(async (sessionId, message, localRows) => {
|
|
34
|
+
closeOpenSession();
|
|
35
|
+
const opened = await source.openSession(sessionId);
|
|
36
|
+
openedRef.current = opened;
|
|
37
|
+
const activeRoom = opened.session.roomId
|
|
38
|
+
? (await source.listRooms()).find((room) => room.id === opened.session.roomId)
|
|
39
|
+
: undefined;
|
|
40
|
+
const rows = [...(localRows ?? []), ...buildCompactTerminalRows(opened.traceView, { showThinking: false })];
|
|
41
|
+
setState((current) => normalizeInkRowSelection({
|
|
42
|
+
...current,
|
|
43
|
+
loading: false,
|
|
44
|
+
status: opened.status,
|
|
45
|
+
activeRoom,
|
|
46
|
+
session: opened.session,
|
|
47
|
+
rows,
|
|
48
|
+
mode: "transcript",
|
|
49
|
+
picker: undefined,
|
|
50
|
+
message,
|
|
51
|
+
error: undefined,
|
|
52
|
+
}));
|
|
53
|
+
unsubscribeRef.current = opened.subscribe((sourceUpdate) => {
|
|
54
|
+
setState((current) => normalizeInkRowSelection({
|
|
55
|
+
...current,
|
|
56
|
+
session: sourceUpdate.session ?? current.session,
|
|
57
|
+
status: sourceUpdate.status ?? current.status,
|
|
58
|
+
rows: sourceUpdate.traceView === undefined ? current.rows : buildCompactTerminalRows(sourceUpdate.traceView, { showThinking: false }),
|
|
59
|
+
error: sourceUpdate.error?.message ?? current.error,
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
}, [closeOpenSession, source]);
|
|
63
|
+
const requestExit = useCallback(() => {
|
|
64
|
+
cleanup();
|
|
65
|
+
onExit?.();
|
|
66
|
+
app.exit();
|
|
67
|
+
}, [app, cleanup, onExit]);
|
|
68
|
+
const submitCommandOrMessage = useCallback(async (rawInput) => {
|
|
69
|
+
await handleCliSessionSubmittedInput(rawInput, source, stateRef.current, setState, openSession, requestExit);
|
|
70
|
+
}, [openSession, requestExit, source]);
|
|
71
|
+
const openSessionPickerForRoom = useCallback(async (room, parent) => {
|
|
72
|
+
const sessions = await source.listSessions({ roomId: room.id });
|
|
73
|
+
const createItem = {
|
|
74
|
+
id: `create:${room.id}`,
|
|
75
|
+
kind: "create-session",
|
|
76
|
+
label: "+ New session",
|
|
77
|
+
description: ["create and open", `room ${abbreviateIdentifier(room.id)}`].join(" · "),
|
|
78
|
+
roomId: room.id,
|
|
79
|
+
};
|
|
80
|
+
const status = await source.getStatus();
|
|
81
|
+
setState((current) => ({
|
|
82
|
+
...current,
|
|
83
|
+
loading: false,
|
|
84
|
+
status,
|
|
85
|
+
activeRoom: room,
|
|
86
|
+
mode: "session-picker",
|
|
87
|
+
picker: {
|
|
88
|
+
kind: "session",
|
|
89
|
+
title: `Select session in ${room.title}`,
|
|
90
|
+
items: [...sessions.map(sessionPickerItem), createItem],
|
|
91
|
+
selectedIndex: 0,
|
|
92
|
+
emptyMessage: `No sessions in ${room.title}. Create a new session to start chatting.`,
|
|
93
|
+
roomId: room.id,
|
|
94
|
+
parent,
|
|
95
|
+
},
|
|
96
|
+
message: sessions.length === 0 ? `No sessions in ${room.title}. Press Enter to create one.` : "Select a session with arrow keys, or create a new one.",
|
|
97
|
+
error: undefined,
|
|
98
|
+
}));
|
|
99
|
+
}, [source]);
|
|
100
|
+
const openRoomPicker = useCallback(async () => {
|
|
101
|
+
const rooms = await source.listRooms();
|
|
102
|
+
const defaultIndex = Math.max(0, rooms.findIndex((room) => room.isDefault));
|
|
103
|
+
const status = await source.getStatus();
|
|
104
|
+
setState((current) => ({
|
|
105
|
+
...current,
|
|
106
|
+
loading: false,
|
|
107
|
+
status,
|
|
108
|
+
activeRoom: undefined,
|
|
109
|
+
session: undefined,
|
|
110
|
+
rows: [],
|
|
111
|
+
mode: "picker",
|
|
112
|
+
picker: {
|
|
113
|
+
kind: "room",
|
|
114
|
+
title: "Select room",
|
|
115
|
+
items: rooms.map(roomPickerItem),
|
|
116
|
+
selectedIndex: defaultIndex,
|
|
117
|
+
emptyMessage: "No rooms are available.",
|
|
118
|
+
},
|
|
119
|
+
message: rooms.length === 0 ? "No rooms are available." : "Select a room with arrow keys.",
|
|
120
|
+
error: undefined,
|
|
121
|
+
}));
|
|
122
|
+
}, [source]);
|
|
123
|
+
const selectPickerItem = useCallback(async () => {
|
|
124
|
+
const picker = stateRef.current.picker;
|
|
125
|
+
const item = picker?.items[picker.selectedIndex];
|
|
126
|
+
if (!picker || !item) {
|
|
127
|
+
setState((current) => ({ ...current, mode: "transcript", picker: undefined, message: picker?.emptyMessage ?? "Nothing to select." }));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
if (picker.kind === "room") {
|
|
132
|
+
const room = { id: item.roomId ?? item.id, title: item.label, description: item.description, isDefault: item.id === stateRef.current.status?.activeRoomId };
|
|
133
|
+
if (picker.action === "create-session") {
|
|
134
|
+
const created = await source.createSession({ roomId: room.id, agentId: stateRef.current.status?.activeAgentId });
|
|
135
|
+
await openSession(created.id, `Created session ${created.title}.`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (picker.action === "select-room") {
|
|
139
|
+
const status = await source.getStatus({ sessionId: stateRef.current.session?.id });
|
|
140
|
+
setState((current) => ({ ...current, status, activeRoom: room, mode: "transcript", picker: undefined, overlayStack: undefined, message: `Selected room ${room.title}.`, error: undefined }));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await openSessionPickerForRoom(room, picker);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (picker.kind === "session") {
|
|
147
|
+
if (item.kind === "create-session") {
|
|
148
|
+
const created = await source.createSession({ roomId: item.roomId ?? picker.roomId, agentId: stateRef.current.status?.activeAgentId });
|
|
149
|
+
await openSession(created.id, `Created session ${created.title}.`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await openSession(item.id, `Opened session ${item.label}.`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (picker.kind === "command-menu") {
|
|
156
|
+
await selectCommandMenuItem(picker, item, source, stateRef.current, setState, openSession);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const sessionId = stateRef.current.session?.id;
|
|
160
|
+
if (!sessionId)
|
|
161
|
+
throw new Error("No session is open. Use /new or /session first.");
|
|
162
|
+
const session = await source.setSessionAgent(sessionId, item.id);
|
|
163
|
+
const status = await source.getStatus({ sessionId });
|
|
164
|
+
setState((current) => ({
|
|
165
|
+
...current,
|
|
166
|
+
session,
|
|
167
|
+
status,
|
|
168
|
+
mode: "transcript",
|
|
169
|
+
picker: undefined,
|
|
170
|
+
message: `Selected agent ${item.label}.`,
|
|
171
|
+
error: undefined,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
setState((current) => ({ ...current, mode: "transcript", picker: undefined, error: formatCliSessionError(error), message: undefined }));
|
|
176
|
+
}
|
|
177
|
+
}, [openSession, openSessionPickerForRoom, source]);
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
closedRef.current = false;
|
|
180
|
+
void (async () => {
|
|
181
|
+
try {
|
|
182
|
+
const slashCommands = await safeListSlashCommands(source);
|
|
183
|
+
setState((current) => ({ ...current, slashCommands }));
|
|
184
|
+
if (initialSessionId) {
|
|
185
|
+
await openSession(initialSessionId);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await openRoomPicker();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
setState((current) => ({ ...current, loading: false, error: formatCliSessionError(error) }));
|
|
192
|
+
}
|
|
193
|
+
})();
|
|
194
|
+
return () => {
|
|
195
|
+
closedRef.current = true;
|
|
196
|
+
cleanup();
|
|
197
|
+
};
|
|
198
|
+
}, [cleanup, initialSessionId, openRoomPicker, openSession, source]);
|
|
199
|
+
useInput((input, key) => {
|
|
200
|
+
if (closedRef.current)
|
|
201
|
+
return;
|
|
202
|
+
if (key.ctrl && input === "c") {
|
|
203
|
+
requestExit();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (key.escape) {
|
|
207
|
+
setState((current) => reduceInkSessionInputState(current, { type: "escape" }));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (key.upArrow) {
|
|
211
|
+
setState((current) => reduceInkSessionInputState(current, { type: "up" }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (key.downArrow) {
|
|
215
|
+
setState((current) => reduceInkSessionInputState(current, { type: "down" }));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (key.return) {
|
|
219
|
+
const submitted = stateRef.current.input;
|
|
220
|
+
if (submitted.length === 0 && !stateRef.current.picker && !stateRef.current.slashSuggestions && canToggleSelectedRowDetails(stateRef.current)) {
|
|
221
|
+
setState((current) => reduceInkSessionInputState(current, { type: "toggle-details" }));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (stateRef.current.slashSuggestions && !stateRef.current.picker) {
|
|
225
|
+
const accepted = acceptSlashSuggestion(stateRef.current);
|
|
226
|
+
if (accepted.runInput) {
|
|
227
|
+
setState((current) => ({ ...current, input: "", slashSuggestions: undefined, message: undefined, error: undefined }));
|
|
228
|
+
void submitCommandOrMessage(accepted.runInput);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
setState((current) => ({ ...current, input: accepted.input, slashSuggestions: undefined, message: `Accepted ${accepted.input.trim()}. Press Enter to run or add arguments.`, error: undefined }));
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (submitted.trimStart().startsWith("/")) {
|
|
236
|
+
setState((current) => reduceInkSessionInputState(current, { type: "enter" }));
|
|
237
|
+
void submitCommandOrMessage(submitted);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (stateRef.current.picker) {
|
|
241
|
+
void selectPickerItem();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
setState((current) => reduceInkSessionInputState(current, { type: "enter" }));
|
|
245
|
+
void submitCommandOrMessage(submitted);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (key.backspace || key.delete) {
|
|
249
|
+
setState((current) => reduceInkSessionInputState(current, { type: "backspace" }));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (input === "d" && !key.ctrl && !key.meta && stateRef.current.input.length === 0 && !stateRef.current.picker && !stateRef.current.slashSuggestions && canToggleSelectedRowDetails(stateRef.current)) {
|
|
253
|
+
setState((current) => reduceInkSessionInputState(current, { type: "toggle-details" }));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (input && !key.ctrl && !key.meta) {
|
|
257
|
+
setState((current) => reduceInkSessionInputState(current, { type: "text", value: input }));
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
return React.createElement(InkSessionAppView, { state, maxRows, maxLineChars });
|
|
261
|
+
}
|
|
262
|
+
export function InkSessionAppView({ state, maxRows = 20, maxLineChars }) {
|
|
263
|
+
const lineLimit = normalizeTerminalLineLimit(maxLineChars);
|
|
264
|
+
const statusLines = useMemo(() => formatStatusHeaderLines(state, lineLimit), [lineLimit, state]);
|
|
265
|
+
const commandSummary = useMemo(() => cliCommandHintText(), []);
|
|
266
|
+
return React.createElement(Box, { flexDirection: "column" }, ...statusLines.map((line, index) => React.createElement(Text, { color: state.status?.connected === false ? "red" : "cyan", key: `status-${index}` }, line)), React.createElement(Text, { color: "gray" }, commandSummary), state.loading ? React.createElement(Text, { color: "yellow" }, "Loading CLI session…") : null, state.error ? React.createElement(Text, { color: "red" }, `Error: ${state.error}`) : null, ...(state.message ? renderBoundedTextLines(state.message, "gray", lineLimit, "message") : []), state.picker ? React.createElement(InkSessionPickerView, { picker: state.picker, maxLineChars: lineLimit }) : null, React.createElement(InkTerminalView, { rows: state.rows, maxRows, maxLineChars: lineLimit, selectedRowId: state.selectedRowId, expandedRowIds: state.expandedRowIds }), state.slashSuggestions ? React.createElement(InkSlashSuggestionsView, { suggestions: state.slashSuggestions, maxLineChars: lineLimit }) : null, React.createElement(Text, { color: state.mode === "transcript" ? "green" : "yellow" }, `› ${state.input}`));
|
|
267
|
+
}
|
|
268
|
+
export function normalizeTerminalLineLimit(maxLineChars) {
|
|
269
|
+
if (maxLineChars === undefined || !Number.isFinite(maxLineChars))
|
|
270
|
+
return 220;
|
|
271
|
+
return Math.max(20, Math.floor(maxLineChars));
|
|
272
|
+
}
|
|
273
|
+
export function InkSlashSuggestionsView({ suggestions, maxLineChars }) {
|
|
274
|
+
const lineLimit = normalizeTerminalLineLimit(maxLineChars);
|
|
275
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "yellow" }, "slash commands"), ...suggestions.items.slice(0, 8).map((command, index) => {
|
|
276
|
+
const disabled = command.support === "deferred" || command.support === "browser-only" || Boolean(command.unsupportedReason);
|
|
277
|
+
const item = {
|
|
278
|
+
id: command.id,
|
|
279
|
+
label: formatSlashCommand(command),
|
|
280
|
+
description: [command.description, command.unsupportedReason ? `unavailable: ${command.unsupportedReason}` : undefined].filter(Boolean).join(" · "),
|
|
281
|
+
disabled,
|
|
282
|
+
};
|
|
283
|
+
return React.createElement(Text, { key: command.id, color: overlayItemColor(index === suggestions.selectedIndex, disabled) }, formatOverlayItemLine(item, index === suggestions.selectedIndex, lineLimit));
|
|
284
|
+
}), React.createElement(Text, { color: "gray" }, "↑↓ select · enter accept/run · esc close · ctrl-c exit"));
|
|
285
|
+
}
|
|
286
|
+
export function InkSessionPickerView({ picker, maxLineChars }) {
|
|
287
|
+
const lineLimit = normalizeTerminalLineLimit(maxLineChars);
|
|
288
|
+
const title = compactOverlayTitle(picker.title);
|
|
289
|
+
if (picker.items.length === 0) {
|
|
290
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "yellow" }, title), React.createElement(Text, { color: "gray" }, picker.emptyMessage), React.createElement(Text, { color: "gray" }, "esc back/cancel · ctrl-c exit"));
|
|
291
|
+
}
|
|
292
|
+
return React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, { color: "yellow" }, title), ...picker.items.map((item, index) => React.createElement(Text, { key: item.id, color: overlayItemColor(index === picker.selectedIndex, item.disabled === true) }, formatOverlayItemLine(item, index === picker.selectedIndex, lineLimit))), React.createElement(Text, { color: "gray" }, "↑↓ select · enter confirm · esc back/cancel · ctrl-c exit"));
|
|
293
|
+
}
|
|
294
|
+
export function reduceInkSessionInputState(state, action) {
|
|
295
|
+
if (action.type === "text")
|
|
296
|
+
return withSlashSuggestions({ ...state, input: state.input + action.value, message: undefined, error: undefined });
|
|
297
|
+
if (action.type === "backspace")
|
|
298
|
+
return withSlashSuggestions({ ...state, input: state.input.slice(0, -1) });
|
|
299
|
+
if (action.type === "toggle-details")
|
|
300
|
+
return toggleSelectedRowDetails(state);
|
|
301
|
+
if (action.type === "escape") {
|
|
302
|
+
if (state.slashSuggestions)
|
|
303
|
+
return { ...state, slashSuggestions: undefined, overlayStack: popInkSessionOverlay(state.overlayStack), message: "Closed slash suggestions." };
|
|
304
|
+
if (state.picker?.parent) {
|
|
305
|
+
return withPickerOverlay({ ...state, picker: state.picker.parent, mode: pickerMode(state.picker.parent), message: "Back." }, state.picker.parent);
|
|
306
|
+
}
|
|
307
|
+
return { ...state, input: "", mode: "transcript", picker: undefined, overlayStack: popInkSessionOverlay(state.overlayStack), message: "Canceled." };
|
|
308
|
+
}
|
|
309
|
+
if (action.type === "up" || action.type === "down") {
|
|
310
|
+
if (state.slashSuggestions && !state.picker && state.slashSuggestions.items.length > 0) {
|
|
311
|
+
const direction = action.type === "up" ? -1 : 1;
|
|
312
|
+
const selectedIndex = (state.slashSuggestions.selectedIndex + direction + state.slashSuggestions.items.length) % state.slashSuggestions.items.length;
|
|
313
|
+
return { ...state, slashSuggestions: { ...state.slashSuggestions, selectedIndex } };
|
|
314
|
+
}
|
|
315
|
+
if (!state.picker && state.input.length === 0)
|
|
316
|
+
return selectExpandableRow(state, action.type === "up" ? -1 : 1);
|
|
317
|
+
if (!state.picker || state.picker.items.length === 0)
|
|
318
|
+
return state;
|
|
319
|
+
const direction = action.type === "up" ? -1 : 1;
|
|
320
|
+
const selectedIndex = (state.picker.selectedIndex + direction + state.picker.items.length) % state.picker.items.length;
|
|
321
|
+
return { ...state, picker: { ...state.picker, selectedIndex } };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
...state,
|
|
325
|
+
input: "",
|
|
326
|
+
slashSuggestions: undefined,
|
|
327
|
+
message: undefined,
|
|
328
|
+
error: undefined,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
export function normalizeInkRowSelection(state) {
|
|
332
|
+
const expandableIds = expandableRowIds(state.rows);
|
|
333
|
+
const expandedRowIds = (state.expandedRowIds ?? []).filter((id) => expandableIds.includes(id));
|
|
334
|
+
if (expandableIds.length === 0)
|
|
335
|
+
return { ...state, selectedRowId: undefined, expandedRowIds };
|
|
336
|
+
const selectedRowId = state.selectedRowId && expandableIds.includes(state.selectedRowId)
|
|
337
|
+
? state.selectedRowId
|
|
338
|
+
: expandableIds[expandableIds.length - 1];
|
|
339
|
+
return { ...state, selectedRowId, expandedRowIds };
|
|
340
|
+
}
|
|
341
|
+
function selectExpandableRow(state, direction) {
|
|
342
|
+
const normalized = normalizeInkRowSelection(state);
|
|
343
|
+
const ids = expandableRowIds(normalized.rows);
|
|
344
|
+
if (ids.length === 0)
|
|
345
|
+
return normalized;
|
|
346
|
+
const currentIndex = Math.max(0, ids.indexOf(normalized.selectedRowId ?? ids[ids.length - 1]));
|
|
347
|
+
const selectedRowId = ids[(currentIndex + direction + ids.length) % ids.length];
|
|
348
|
+
return { ...normalized, selectedRowId, message: `Focused row ${currentIndexLabel(ids, selectedRowId)}. Press d or Enter for details.` };
|
|
349
|
+
}
|
|
350
|
+
function toggleSelectedRowDetails(state) {
|
|
351
|
+
const normalized = normalizeInkRowSelection(state);
|
|
352
|
+
const selectedRowId = normalized.selectedRowId;
|
|
353
|
+
if (!selectedRowId)
|
|
354
|
+
return { ...normalized, message: "No expandable row is available." };
|
|
355
|
+
const expanded = new Set(normalized.expandedRowIds ?? []);
|
|
356
|
+
const opening = !expanded.has(selectedRowId);
|
|
357
|
+
if (opening)
|
|
358
|
+
expanded.add(selectedRowId);
|
|
359
|
+
else
|
|
360
|
+
expanded.delete(selectedRowId);
|
|
361
|
+
return {
|
|
362
|
+
...normalized,
|
|
363
|
+
expandedRowIds: [...expanded],
|
|
364
|
+
message: opening ? "Opened row details." : "Closed row details.",
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function canToggleSelectedRowDetails(state) {
|
|
368
|
+
return expandableRowIds(state.rows).length > 0;
|
|
369
|
+
}
|
|
370
|
+
function expandableRowIds(rows) {
|
|
371
|
+
return rows.filter(isExpandableTerminalRow).map((row) => row.id);
|
|
372
|
+
}
|
|
373
|
+
function currentIndexLabel(ids, selectedRowId) {
|
|
374
|
+
const index = selectedRowId ? ids.indexOf(selectedRowId) : -1;
|
|
375
|
+
return index >= 0 ? `${index + 1}/${ids.length}` : `1/${ids.length}`;
|
|
376
|
+
}
|
|
377
|
+
function withSlashSuggestions(state) {
|
|
378
|
+
if (state.picker || !state.input.trimStart().startsWith("/"))
|
|
379
|
+
return { ...state, slashSuggestions: undefined, overlayStack: state.slashSuggestions ? popInkSessionOverlay(state.overlayStack) : state.overlayStack };
|
|
380
|
+
const catalog = state.slashCommands ?? buildSlashCommandCatalog();
|
|
381
|
+
const items = filterSlashCommands(catalog, state.input);
|
|
382
|
+
if (items.length === 0)
|
|
383
|
+
return { ...state, slashSuggestions: undefined, overlayStack: state.slashSuggestions ? popInkSessionOverlay(state.overlayStack) : state.overlayStack };
|
|
384
|
+
const previous = state.slashSuggestions?.items[state.slashSuggestions.selectedIndex]?.slash;
|
|
385
|
+
const selectedIndex = Math.max(0, previous ? items.findIndex((item) => item.slash === previous) : 0);
|
|
386
|
+
const suggestions = { items, selectedIndex };
|
|
387
|
+
return { ...state, slashSuggestions: suggestions, overlayStack: replaceTopOverlay(state.overlayStack, { kind: "suggestions", suggestions }) };
|
|
388
|
+
}
|
|
389
|
+
function acceptSlashSuggestion(state) {
|
|
390
|
+
const suggestion = state.slashSuggestions?.items[state.slashSuggestions.selectedIndex];
|
|
391
|
+
if (!suggestion)
|
|
392
|
+
return { input: state.input };
|
|
393
|
+
const trimmed = state.input.trim();
|
|
394
|
+
const token = trimmed.split(/\s+/, 1)[0] ?? "";
|
|
395
|
+
if (token === suggestion.slash && trimmed.length > suggestion.slash.length)
|
|
396
|
+
return { input: state.input, runInput: trimmed };
|
|
397
|
+
if (token === suggestion.slash && trimmed === suggestion.slash)
|
|
398
|
+
return { input: suggestion.slash, runInput: suggestion.slash };
|
|
399
|
+
return { input: `${suggestion.slash} ` };
|
|
400
|
+
}
|
|
401
|
+
export function pushInkSessionOverlay(stack, overlay) {
|
|
402
|
+
return [...(stack ?? []), overlay];
|
|
403
|
+
}
|
|
404
|
+
export function popInkSessionOverlay(stack) {
|
|
405
|
+
if (!stack || stack.length <= 1)
|
|
406
|
+
return undefined;
|
|
407
|
+
return stack.slice(0, -1);
|
|
408
|
+
}
|
|
409
|
+
export function activeInkSessionOverlay(stack) {
|
|
410
|
+
return stack?.[stack.length - 1];
|
|
411
|
+
}
|
|
412
|
+
function replaceTopOverlay(stack, overlay) {
|
|
413
|
+
if (!stack || stack.length === 0)
|
|
414
|
+
return [overlay];
|
|
415
|
+
return [...stack.slice(0, -1), overlay];
|
|
416
|
+
}
|
|
417
|
+
function withPickerOverlay(state, picker) {
|
|
418
|
+
return { ...state, picker, overlayStack: replaceTopOverlay(state.overlayStack, { kind: "picker", picker }) };
|
|
419
|
+
}
|
|
420
|
+
function pickerMode(picker) {
|
|
421
|
+
if (picker.kind === "agent")
|
|
422
|
+
return "agent-picker";
|
|
423
|
+
if (picker.kind === "session")
|
|
424
|
+
return "session-picker";
|
|
425
|
+
return "picker";
|
|
426
|
+
}
|
|
427
|
+
export function parseCliSessionInput(input) {
|
|
428
|
+
const trimmed = input.trim();
|
|
429
|
+
if (trimmed.length === 0)
|
|
430
|
+
return { type: "empty" };
|
|
431
|
+
if (!trimmed.startsWith("/"))
|
|
432
|
+
return { type: "message", text: trimmed };
|
|
433
|
+
const withoutSlash = trimmed.slice(1);
|
|
434
|
+
const [name = "", ...rest] = withoutSlash.split(/\s+/);
|
|
435
|
+
return { type: "command", command: { name: name.toLowerCase(), args: rest.join(" ").trim(), raw: trimmed } };
|
|
436
|
+
}
|
|
437
|
+
export function cliCommandSummaryText(catalog = buildSlashCommandCatalog()) {
|
|
438
|
+
const availableSlashes = new Set(catalog.map((command) => command.slash));
|
|
439
|
+
const preferred = ["/help", "/new", "/room", "/session", "/agent", "/profile", "/repair-user-unknown", "/status", "/clear", "/exit", "/quit"];
|
|
440
|
+
const commands = preferred.filter((slash) => availableSlashes.has(slash));
|
|
441
|
+
return `Commands: ${commands.join(" ")} (type / for suggestions, /help for catalog)`;
|
|
442
|
+
}
|
|
443
|
+
export function cliCommandHintText() {
|
|
444
|
+
return "commands: / opens palette · /status runtime · /room /session navigate · /help catalog · ↑↓ focus rows · d/enter details · ctrl-c exit";
|
|
445
|
+
}
|
|
446
|
+
export function cliSessionSlashHelpText(catalog = buildSlashCommandCatalog()) {
|
|
447
|
+
const grouped = groupSlashCommandsForHelp(catalog);
|
|
448
|
+
const format = (command) => `${formatSlashCommand(command)} — ${command.description}${command.unsupportedReason ? ` (${command.unsupportedReason})` : ""}`;
|
|
449
|
+
const available = grouped.available.map((command) => ` ${format(command)} [${commandSupportLabel(command)}]`).join("\n") || " none";
|
|
450
|
+
const navigation = grouped.navigation.map((command) => ` ${format(command)} [${commandSupportLabel(command)}]`).join("\n") || " none";
|
|
451
|
+
const unsupported = grouped.unsupported.map((command) => ` ${format(command)} [${commandSupportLabel(command)}]`).join("\n") || " none";
|
|
452
|
+
return [
|
|
453
|
+
"Slash command catalog",
|
|
454
|
+
"Available Web/session actions:",
|
|
455
|
+
available,
|
|
456
|
+
"CLI navigation and recovery commands:",
|
|
457
|
+
navigation,
|
|
458
|
+
"Unsupported or deferred terminal commands:",
|
|
459
|
+
unsupported,
|
|
460
|
+
"Keyboard controls: type / for suggestions; ↑/↓ selects suggestions, pickers, or expandable transcript rows; Enter accepts/runs or opens focused details when the composer is empty; d toggles focused details; Esc closes suggestions or backs out of pickers; room flow is room → session.",
|
|
461
|
+
].join("\n");
|
|
462
|
+
}
|
|
463
|
+
export function formatCliSessionStatus(status, session) {
|
|
464
|
+
if (!status && !session)
|
|
465
|
+
return "Status unavailable.";
|
|
466
|
+
const parts = [
|
|
467
|
+
`source=${status?.source ?? "unknown"}`,
|
|
468
|
+
`mode=${status?.mode ?? "unknown"}`,
|
|
469
|
+
`connected=${status?.connected === false ? "no" : "yes"}`,
|
|
470
|
+
`session=${session?.id ?? status?.activeSessionId ?? "none"}`,
|
|
471
|
+
`agent=${session?.agentId ?? session?.profile ?? status?.activeAgentId ?? "default"}`,
|
|
472
|
+
`model=${status?.activeModel ? `${status.activeModel.provider}/${status.activeModel.id}` : "unknown"}`,
|
|
473
|
+
`rooms=${status?.rooms ?? "unknown"}`,
|
|
474
|
+
`agents=${status?.agents ?? "unknown"}`,
|
|
475
|
+
];
|
|
476
|
+
if (status?.message)
|
|
477
|
+
parts.push(`message=${redactCliSessionStatusText(status.message)}`);
|
|
478
|
+
return parts.join(" | ");
|
|
479
|
+
}
|
|
480
|
+
export async function handleCliSessionSubmittedInput(rawInput, source, state, setState, openSession, requestExit) {
|
|
481
|
+
const parsed = parseCliSessionInput(rawInput);
|
|
482
|
+
if (parsed.type === "empty")
|
|
483
|
+
return;
|
|
484
|
+
if (parsed.type === "message") {
|
|
485
|
+
const sessionId = state.session?.id;
|
|
486
|
+
if (!sessionId) {
|
|
487
|
+
setState((current) => ({ ...current, error: "No session is open. Use /new to create one or /session to select an existing session." }));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
await source.sendMessage(sessionId, parsed.text);
|
|
492
|
+
setState((current) => ({ ...current, message: "Message sent.", error: undefined }));
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
setState((current) => ({ ...current, error: formatCliSessionError(error), message: undefined }));
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
await handleSlashCommand(parsed.command, source, state, setState, openSession, requestExit);
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
setState((current) => applyCommandResultToState(current, parsed.command, normalizeCommandErrorDescriptor(parsed.command.name, error), current.status));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function handleSlashCommand(command, source, state, setState, openSession, requestExit) {
|
|
507
|
+
if (command.name === "help") {
|
|
508
|
+
setState((current) => ({ ...current, message: cliSessionSlashHelpText(current.slashCommands ?? state.slashCommands), error: undefined }));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (command.name === "status") {
|
|
512
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
513
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
514
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (command.name === "clear") {
|
|
518
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
519
|
+
setState((current) => normalizeInkRowSelection({ ...current, rows: [], message: `${renderCommandResultDescriptorText(result.descriptor, current.session)}\nCleared local display. Session data was not deleted.`, error: undefined }));
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (command.name === "exit" || command.name === "quit") {
|
|
523
|
+
requestExit();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (command.name === "new") {
|
|
527
|
+
if (!state.activeRoom?.id) {
|
|
528
|
+
const rooms = await source.listRooms();
|
|
529
|
+
const defaultIndex = Math.max(0, rooms.findIndex((room) => room.isDefault));
|
|
530
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
531
|
+
setState((current) => ({
|
|
532
|
+
...current,
|
|
533
|
+
status,
|
|
534
|
+
mode: "picker",
|
|
535
|
+
picker: {
|
|
536
|
+
kind: "room",
|
|
537
|
+
action: "create-session",
|
|
538
|
+
title: "Select room for new session",
|
|
539
|
+
items: rooms.map(roomPickerItem),
|
|
540
|
+
selectedIndex: defaultIndex,
|
|
541
|
+
emptyMessage: "No rooms are available.",
|
|
542
|
+
},
|
|
543
|
+
message: rooms.length === 0 ? "No rooms are available." : "Select the room for the new session.",
|
|
544
|
+
error: undefined,
|
|
545
|
+
}));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const created = await source.createSession({ roomId: state.activeRoom.id, agentId: state.status?.activeAgentId });
|
|
549
|
+
await openSession(created.id, `Created session ${created.title}.`);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
if (command.name === "session" || command.name === "room") {
|
|
553
|
+
const rooms = await source.listRooms();
|
|
554
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
555
|
+
const activeRoomId = state.activeRoom?.id ?? status.activeRoomId;
|
|
556
|
+
const activeRoomIndex = rooms.findIndex((room) => room.id === activeRoomId);
|
|
557
|
+
const selectedIndex = Math.max(0, activeRoomIndex >= 0 ? activeRoomIndex : rooms.findIndex((room) => room.isDefault));
|
|
558
|
+
setState((current) => ({
|
|
559
|
+
...current,
|
|
560
|
+
status,
|
|
561
|
+
mode: "picker",
|
|
562
|
+
picker: {
|
|
563
|
+
kind: "room",
|
|
564
|
+
action: command.name === "room" ? "select-room" : undefined,
|
|
565
|
+
title: command.name === "room" ? "Select active room" : "Select room for sessions",
|
|
566
|
+
items: rooms.map(roomPickerItem),
|
|
567
|
+
selectedIndex,
|
|
568
|
+
emptyMessage: "No rooms are available.",
|
|
569
|
+
},
|
|
570
|
+
message: rooms.length === 0 ? "No rooms are available." : command.name === "room" ? "Select the active room with arrow keys." : "Select a room, then choose or create a session.",
|
|
571
|
+
error: undefined,
|
|
572
|
+
}));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (command.name === "repair-user-unknown") {
|
|
576
|
+
if (!source.repairLegacyCliSessions)
|
|
577
|
+
throw new Error("This source does not support legacy CLI session repair.");
|
|
578
|
+
const result = await source.repairLegacyCliSessions({ roomId: state.activeRoom?.id });
|
|
579
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
580
|
+
setState((current) => ({
|
|
581
|
+
...current,
|
|
582
|
+
status,
|
|
583
|
+
message: `Repaired ${result.repaired}/${result.scanned} legacy CLI session${result.scanned === 1 ? "" : "s"}${result.roomId ? ` in ${result.roomId}` : ""}.`,
|
|
584
|
+
error: undefined,
|
|
585
|
+
}));
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (command.name === "agent" || command.name === "profile") {
|
|
589
|
+
const agents = await source.listAgents();
|
|
590
|
+
setState((current) => ({
|
|
591
|
+
...current,
|
|
592
|
+
mode: "agent-picker",
|
|
593
|
+
picker: {
|
|
594
|
+
kind: "agent",
|
|
595
|
+
title: "Select existing agent/profile",
|
|
596
|
+
items: agents.map(agentPickerItem),
|
|
597
|
+
selectedIndex: 0,
|
|
598
|
+
emptyMessage: "No existing agents/profiles are available from this source.",
|
|
599
|
+
},
|
|
600
|
+
message: agents.length === 0 ? "No existing agents/profiles are available." : "Select an existing agent/profile with arrow keys.",
|
|
601
|
+
error: undefined,
|
|
602
|
+
}));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
if (command.name === "thinking") {
|
|
606
|
+
if (command.args.trim()) {
|
|
607
|
+
validateThinkingLevel(command.args);
|
|
608
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
609
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
610
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
setState((current) => withPickerOverlay({
|
|
614
|
+
...current,
|
|
615
|
+
mode: "picker",
|
|
616
|
+
message: "Select a thinking level with arrow keys, then Enter.",
|
|
617
|
+
error: undefined,
|
|
618
|
+
}, thinkingPickerState()));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (command.name === "model") {
|
|
622
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
623
|
+
if (command.args.trim()) {
|
|
624
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
625
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const providers = modelProviderItemsFromActionResult(result.rawResult, result.descriptor);
|
|
629
|
+
if (providers.length === 0) {
|
|
630
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, current.status));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
setState((current) => withPickerOverlay({
|
|
634
|
+
...current,
|
|
635
|
+
mode: "picker",
|
|
636
|
+
message: "Select a provider, then choose a model.",
|
|
637
|
+
error: undefined,
|
|
638
|
+
}, modelProviderPickerState(providers)));
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
if (command.name === "login") {
|
|
642
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
643
|
+
if (command.args.trim()) {
|
|
644
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
645
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const providers = loginProviderItemsFromActionResult(result.rawResult, result.descriptor);
|
|
649
|
+
if (providers.length === 0) {
|
|
650
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, current.status));
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
setState((current) => withPickerOverlay({
|
|
654
|
+
...current,
|
|
655
|
+
mode: "picker",
|
|
656
|
+
message: "Select a login provider, then choose a terminal-safe auth method.",
|
|
657
|
+
error: undefined,
|
|
658
|
+
}, loginProviderPickerState(providers)));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
if (command.name === "fork-candidates") {
|
|
662
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
663
|
+
const descriptor = result.descriptor;
|
|
664
|
+
if (command.args.trim() || descriptor.kind !== "menu" || descriptor.items.length === 0) {
|
|
665
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
666
|
+
if (result.openSessionId && result.openSessionId !== state.session?.id) {
|
|
667
|
+
const localRows = commandResultDescriptorRows(command, descriptor, status, state.session, 0);
|
|
668
|
+
await openSession(result.openSessionId, undefined, localRows);
|
|
669
|
+
}
|
|
670
|
+
else
|
|
671
|
+
setState((current) => applyCommandResultToState(current, command, descriptor, status));
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
setState((current) => withPickerOverlay({
|
|
675
|
+
...current,
|
|
676
|
+
mode: "picker",
|
|
677
|
+
message: "Select a fork candidate with arrow keys, then Enter.",
|
|
678
|
+
error: undefined,
|
|
679
|
+
}, forkCandidatePickerState(descriptor.items)));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
const handled = await executeSharedSlashCommand(command, source, state, setState, openSession);
|
|
683
|
+
if (handled)
|
|
684
|
+
return;
|
|
685
|
+
setState((current) => applyCommandResultToState(current, command, { kind: "unsupported", command: command.raw, reason: "Use /help for supported CLI commands." }, current.status));
|
|
686
|
+
}
|
|
687
|
+
async function executeSharedSlashCommand(command, source, state, setState, openSession) {
|
|
688
|
+
const catalog = state.slashCommands ?? buildSlashCommandCatalog();
|
|
689
|
+
const descriptor = catalog.find((candidate) => candidate.slash === `/${command.name}` || candidate.aliases?.includes(`/${command.name}`));
|
|
690
|
+
if (!descriptor || descriptor.group === "cli" || descriptor.group === "navigation")
|
|
691
|
+
return false;
|
|
692
|
+
const result = await source.executeSlashCommand({ command: command.name, args: command.args, sessionId: state.session?.id });
|
|
693
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
694
|
+
if (result.openSessionId && result.openSessionId !== state.session?.id) {
|
|
695
|
+
const localRows = commandResultDescriptorRows(command, result.descriptor, status, state.session, 0);
|
|
696
|
+
await openSession(result.openSessionId, undefined, localRows);
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
const THINKING_LEVELS = ["current/default", "off", "minimal", "low", "medium", "high", "xhigh"];
|
|
703
|
+
const APPLY_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh"]);
|
|
704
|
+
async function selectCommandMenuItem(picker, item, source, state, setState, openSession) {
|
|
705
|
+
if (item.disabled) {
|
|
706
|
+
setState((current) => ({ ...current, message: item.description ? `${item.label}: ${item.description}` : `${item.label} is unavailable.`, error: undefined }));
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
if (picker.action === "thinking-level") {
|
|
710
|
+
const level = String(item.value ?? item.id);
|
|
711
|
+
if (level === "current/default") {
|
|
712
|
+
setState((current) => ({ ...current, mode: "transcript", picker: undefined, overlayStack: popInkSessionOverlay(current.overlayStack), message: "Kept current/default thinking level.", error: undefined }));
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
validateThinkingLevel(level);
|
|
716
|
+
const result = await source.executeSlashCommand({ command: "thinking", args: level, sessionId: state.session?.id });
|
|
717
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
718
|
+
setState((current) => applyCommandResultToState(current, { name: "thinking", args: level, raw: `/thinking ${level}` }, result.descriptor, status));
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (picker.action === "model-provider") {
|
|
722
|
+
const provider = item.value;
|
|
723
|
+
const models = provider?.models ?? [];
|
|
724
|
+
const next = modelChoicePickerState(item, models, picker);
|
|
725
|
+
setState((current) => withPickerOverlay({ ...current, mode: "picker", message: models.length === 0 ? `${item.label} has no terminal-selectable models.` : `Select model for ${item.label}.`, error: undefined }, next));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (picker.action === "model-choice") {
|
|
729
|
+
const providerValue = picker.parent?.items[picker.parent.selectedIndex]?.value;
|
|
730
|
+
const itemValue = item.value && typeof item.value === "object" ? item.value : undefined;
|
|
731
|
+
const providerId = String(providerValue?.id ?? itemValue?.provider ?? "");
|
|
732
|
+
const modelId = String(itemValue?.id ?? item.id);
|
|
733
|
+
const args = providerId ? `${providerId}/${modelId}` : modelId;
|
|
734
|
+
const result = await source.executeSlashCommand({ command: "model", args, sessionId: state.session?.id });
|
|
735
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
736
|
+
const command = { name: "model", args, raw: `/model ${args}` };
|
|
737
|
+
if (result.openSessionId && result.openSessionId !== state.session?.id) {
|
|
738
|
+
const localRows = commandResultDescriptorRows(command, result.descriptor, status, state.session, 0);
|
|
739
|
+
await openSession(result.openSessionId, undefined, localRows);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (picker.action === "login-provider") {
|
|
746
|
+
const provider = item.value;
|
|
747
|
+
const methods = provider?.authMethods ?? [];
|
|
748
|
+
const next = loginMethodPickerState(item, methods, picker);
|
|
749
|
+
setState((current) => withPickerOverlay({ ...current, mode: "picker", message: methods.length === 0 ? `${item.label} has no terminal auth methods.` : `Select auth method for ${item.label}.`, error: undefined }, next));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (picker.action === "login-method") {
|
|
753
|
+
const providerValue = picker.parent?.items[picker.parent.selectedIndex]?.value;
|
|
754
|
+
const providerId = String(providerValue?.id ?? "");
|
|
755
|
+
const methodId = String(item.id);
|
|
756
|
+
const args = providerId ? `${providerId}/${methodId}` : methodId;
|
|
757
|
+
const result = await source.executeSlashCommand({ command: "login", args, sessionId: state.session?.id });
|
|
758
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
759
|
+
setState((current) => applyCommandResultToState(current, { name: "login", args, raw: `/login ${args}` }, result.descriptor, status));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (picker.action === "fork-candidate") {
|
|
763
|
+
const result = await source.executeSlashCommand({ command: "fork-candidates", args: item.id, sessionId: state.session?.id });
|
|
764
|
+
const status = await source.getStatus({ sessionId: state.session?.id });
|
|
765
|
+
const command = { name: "fork-candidates", args: item.id, raw: `/fork-candidates ${item.id}` };
|
|
766
|
+
if (result.openSessionId && result.openSessionId !== state.session?.id) {
|
|
767
|
+
const localRows = commandResultDescriptorRows(command, result.descriptor, status, state.session, 0);
|
|
768
|
+
await openSession(result.openSessionId, undefined, localRows);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
setState((current) => applyCommandResultToState(current, command, result.descriptor, status));
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
setState((current) => ({ ...current, mode: "transcript", picker: undefined, message: `${item.label} selected.`, error: undefined }));
|
|
775
|
+
}
|
|
776
|
+
function thinkingPickerState() {
|
|
777
|
+
return {
|
|
778
|
+
kind: "command-menu",
|
|
779
|
+
action: "thinking-level",
|
|
780
|
+
commandName: "thinking",
|
|
781
|
+
title: "Select thinking level",
|
|
782
|
+
items: THINKING_LEVELS.map((level) => ({ id: level, kind: "command-option", label: level, value: level, description: level === "current/default" ? "Keep current runtime/default setting" : `Set thinking to ${level}` })),
|
|
783
|
+
selectedIndex: 0,
|
|
784
|
+
emptyMessage: "No thinking levels are available.",
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function validateThinkingLevel(value) {
|
|
788
|
+
const level = value.trim().toLowerCase();
|
|
789
|
+
if (!APPLY_THINKING_LEVELS.has(level))
|
|
790
|
+
throw new Error(`Unsupported thinking level "${value}". Use off, minimal, low, medium, high, or xhigh.`);
|
|
791
|
+
}
|
|
792
|
+
function modelProviderPickerState(providers) {
|
|
793
|
+
return {
|
|
794
|
+
kind: "command-menu",
|
|
795
|
+
action: "model-provider",
|
|
796
|
+
commandName: "model",
|
|
797
|
+
title: "Select model provider",
|
|
798
|
+
items: providers.map((provider) => ({ id: provider.id, kind: "command-option", label: provider.label, description: provider.description, value: provider, disabled: provider.disabled })),
|
|
799
|
+
selectedIndex: 0,
|
|
800
|
+
emptyMessage: "No model providers are available.",
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
function modelChoicePickerState(providerItem, models, parent) {
|
|
804
|
+
return {
|
|
805
|
+
kind: "command-menu",
|
|
806
|
+
action: "model-choice",
|
|
807
|
+
commandName: "model",
|
|
808
|
+
title: `Select model for ${providerItem.label}`,
|
|
809
|
+
items: models.map((model) => ({ id: model.id, kind: "command-option", label: model.label, description: model.description, value: { ...model, provider: providerItem.id }, disabled: model.disabled })),
|
|
810
|
+
selectedIndex: 0,
|
|
811
|
+
emptyMessage: `${providerItem.label} did not return any terminal-selectable models.`,
|
|
812
|
+
parent,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
function loginProviderPickerState(providers) {
|
|
816
|
+
return {
|
|
817
|
+
kind: "command-menu",
|
|
818
|
+
action: "login-provider",
|
|
819
|
+
commandName: "login",
|
|
820
|
+
title: "Select login provider",
|
|
821
|
+
items: providers.map((provider) => ({ id: provider.id, kind: "command-option", label: provider.label, description: provider.description, value: provider, disabled: provider.disabled })),
|
|
822
|
+
selectedIndex: 0,
|
|
823
|
+
emptyMessage: "No login providers are available.",
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
function loginMethodPickerState(providerItem, methods, parent) {
|
|
827
|
+
return {
|
|
828
|
+
kind: "command-menu",
|
|
829
|
+
action: "login-method",
|
|
830
|
+
commandName: "login",
|
|
831
|
+
title: `Select auth method for ${providerItem.label}`,
|
|
832
|
+
items: methods.map((method) => ({ id: method.id, kind: "command-option", label: method.label, description: method.description, value: { ...method, provider: providerItem.id }, disabled: method.disabled })),
|
|
833
|
+
selectedIndex: 0,
|
|
834
|
+
emptyMessage: `${providerItem.label} did not return any terminal auth methods.`,
|
|
835
|
+
parent,
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function forkCandidatePickerState(items) {
|
|
839
|
+
return {
|
|
840
|
+
kind: "command-menu",
|
|
841
|
+
action: "fork-candidate",
|
|
842
|
+
commandName: "fork-candidates",
|
|
843
|
+
title: "Select fork candidate",
|
|
844
|
+
items: items.map((item) => ({ id: item.id, kind: "command-option", label: item.label, description: item.description, value: item, disabled: item.disabled })),
|
|
845
|
+
selectedIndex: 0,
|
|
846
|
+
emptyMessage: "No fork candidates are available.",
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function modelProviderItemsFromActionResult(rawResult, descriptor) {
|
|
850
|
+
const raw = unwrapActionPayload(rawResult);
|
|
851
|
+
const providers = recordsField(raw, "providers");
|
|
852
|
+
if (providers.length > 0)
|
|
853
|
+
return providers.map((provider, index) => ({
|
|
854
|
+
id: stringField(provider, "id") ?? stringField(provider, "provider") ?? `provider-${index}`,
|
|
855
|
+
label: stringField(provider, "label") ?? stringField(provider, "name") ?? stringField(provider, "provider") ?? `Provider ${index + 1}`,
|
|
856
|
+
description: stringField(provider, "description") ?? (provider.disabled === true ? stringField(provider, "reason") : undefined),
|
|
857
|
+
disabled: provider.disabled === true,
|
|
858
|
+
models: recordsField(provider, "models").map((model, modelIndex) => ({
|
|
859
|
+
id: stringField(model, "id") ?? stringField(model, "model") ?? `model-${modelIndex}`,
|
|
860
|
+
label: stringField(model, "label") ?? stringField(model, "name") ?? stringField(model, "model") ?? stringField(model, "id") ?? `Model ${modelIndex + 1}`,
|
|
861
|
+
description: stringField(model, "description") ?? (model.disabled === true ? stringField(model, "reason") : undefined),
|
|
862
|
+
disabled: model.disabled === true,
|
|
863
|
+
value: model,
|
|
864
|
+
})),
|
|
865
|
+
}));
|
|
866
|
+
return descriptor.kind === "menu" ? descriptor.items.map((item) => ({ ...item, models: [] })) : [];
|
|
867
|
+
}
|
|
868
|
+
function loginProviderItemsFromActionResult(rawResult, descriptor) {
|
|
869
|
+
const raw = unwrapActionPayload(rawResult);
|
|
870
|
+
const providers = recordsField(raw, "providers");
|
|
871
|
+
if (providers.length > 0)
|
|
872
|
+
return providers.map((provider, index) => {
|
|
873
|
+
const id = stringField(provider, "id") ?? stringField(provider, "provider") ?? `provider-${index}`;
|
|
874
|
+
const configured = provider.configured === true ? "configured" : provider.configured === false ? "not configured" : undefined;
|
|
875
|
+
return {
|
|
876
|
+
id,
|
|
877
|
+
label: stringField(provider, "label") ?? stringField(provider, "name") ?? id,
|
|
878
|
+
description: [configured, stringField(provider, "description")].filter(Boolean).join(" | ") || undefined,
|
|
879
|
+
disabled: provider.disabled === true,
|
|
880
|
+
authMethods: authMethodItems(provider),
|
|
881
|
+
};
|
|
882
|
+
});
|
|
883
|
+
return descriptor.kind === "menu" ? descriptor.items.map((item) => ({ ...item, authMethods: [] })) : [];
|
|
884
|
+
}
|
|
885
|
+
function authMethodItems(provider) {
|
|
886
|
+
const records = recordsField(provider, "authMethods");
|
|
887
|
+
if (records.length > 0)
|
|
888
|
+
return records.map((method, index) => ({
|
|
889
|
+
id: stringField(method, "id") ?? stringField(method, "method") ?? `method-${index}`,
|
|
890
|
+
label: authMethodLabel(stringField(method, "label") ?? stringField(method, "name") ?? stringField(method, "method") ?? stringField(method, "id") ?? `Method ${index + 1}`),
|
|
891
|
+
description: stringField(method, "description") ?? stringField(method, "reason"),
|
|
892
|
+
disabled: method.disabled === true,
|
|
893
|
+
value: method,
|
|
894
|
+
}));
|
|
895
|
+
return arrayStringField(provider, "authMethods").map((method) => ({ id: method, label: authMethodLabel(method), description: authMethodDescription(method) }));
|
|
896
|
+
}
|
|
897
|
+
function authMethodLabel(method) {
|
|
898
|
+
if (method === "device_code")
|
|
899
|
+
return "Device code / OAuth";
|
|
900
|
+
if (method === "api_key")
|
|
901
|
+
return "API key";
|
|
902
|
+
return method;
|
|
903
|
+
}
|
|
904
|
+
function authMethodDescription(method) {
|
|
905
|
+
if (method === "device_code")
|
|
906
|
+
return "Open a URL and complete sign-in in any browser.";
|
|
907
|
+
if (method === "api_key")
|
|
908
|
+
return "Secret input is not echoed; this flow shows safe setup instructions.";
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
function unwrapActionPayload(value) {
|
|
912
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
913
|
+
return value;
|
|
914
|
+
const record = value;
|
|
915
|
+
if (record.ok === true && "result" in record)
|
|
916
|
+
return record.result;
|
|
917
|
+
if (record.success === true && "data" in record)
|
|
918
|
+
return record.data;
|
|
919
|
+
return value;
|
|
920
|
+
}
|
|
921
|
+
function recordsField(value, key) {
|
|
922
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
923
|
+
return [];
|
|
924
|
+
const field = value[key];
|
|
925
|
+
return Array.isArray(field) ? field.filter((item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)) : [];
|
|
926
|
+
}
|
|
927
|
+
function arrayStringField(value, key) {
|
|
928
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
929
|
+
return [];
|
|
930
|
+
const field = value[key];
|
|
931
|
+
return Array.isArray(field) ? field.filter((item) => typeof item === "string") : [];
|
|
932
|
+
}
|
|
933
|
+
function stringField(record, key) {
|
|
934
|
+
const value = record[key];
|
|
935
|
+
return typeof value === "string" ? value : undefined;
|
|
936
|
+
}
|
|
937
|
+
function applyCommandResultToState(current, command, descriptor, status) {
|
|
938
|
+
return normalizeInkRowSelection({
|
|
939
|
+
...current,
|
|
940
|
+
...(status ? { status } : {}),
|
|
941
|
+
mode: "transcript",
|
|
942
|
+
picker: undefined,
|
|
943
|
+
slashSuggestions: undefined,
|
|
944
|
+
overlayStack: undefined,
|
|
945
|
+
rows: appendCommandResultRows(current.rows, command, descriptor, status, current.session),
|
|
946
|
+
message: undefined,
|
|
947
|
+
error: undefined,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
export function appendCommandResultRows(rows, command, descriptor, status, session) {
|
|
951
|
+
return [...rows, ...commandResultDescriptorRows(command, descriptor, status, session, rows.length)];
|
|
952
|
+
}
|
|
953
|
+
export function commandResultDescriptorRows(command, descriptor, status, session, startIndex = 0) {
|
|
954
|
+
const commandLabel = command.raw || `/${command.name}${command.args ? ` ${command.args}` : ""}`;
|
|
955
|
+
const commandRow = {
|
|
956
|
+
id: `cli-command:${startIndex}:${command.name}`,
|
|
957
|
+
kind: "execution.command",
|
|
958
|
+
status: descriptor.kind === "error" ? "error" : "done",
|
|
959
|
+
lines: [{ prefix: "bullet", tokens: [{ text: `Ran ${commandLabel}`, tone: "yellow", weight: "semibold" }] }],
|
|
960
|
+
sourceNodeIds: [`cli-command:${startIndex}:${command.name}`],
|
|
961
|
+
};
|
|
962
|
+
const resultRow = commandResultDescriptorRow(command, descriptor, status, session, startIndex + 1);
|
|
963
|
+
return resultRow ? [commandRow, resultRow] : [commandRow];
|
|
964
|
+
}
|
|
965
|
+
function commandResultDescriptorRow(command, descriptor, status, session, index) {
|
|
966
|
+
const id = `cli-command-result:${index}:${command.name}`;
|
|
967
|
+
if (descriptor.kind === "status") {
|
|
968
|
+
return {
|
|
969
|
+
id,
|
|
970
|
+
kind: "tool.status",
|
|
971
|
+
status: "done",
|
|
972
|
+
lines: [],
|
|
973
|
+
output: commandStatusRowOutput(descriptor.status, status, session),
|
|
974
|
+
sourceNodeIds: [id],
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
if (descriptor.kind === "error") {
|
|
978
|
+
return { id, kind: "error", status: "error", lines: [], error: descriptor.message, sourceNodeIds: [id] };
|
|
979
|
+
}
|
|
980
|
+
return {
|
|
981
|
+
id,
|
|
982
|
+
kind: "execution.command",
|
|
983
|
+
status: descriptor.kind === "unsupported" ? "error" : "done",
|
|
984
|
+
lines: [],
|
|
985
|
+
output: renderCommandResultDescriptorText(descriptor, session),
|
|
986
|
+
sourceNodeIds: [id],
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function commandStatusRowOutput(rawStatus, status, session) {
|
|
990
|
+
const descriptorStatus = rawStatus && typeof rawStatus === "object" && !Array.isArray(rawStatus) ? rawStatus : {};
|
|
991
|
+
return {
|
|
992
|
+
...descriptorStatus,
|
|
993
|
+
piboSessionId: session?.id ?? status?.activeSessionId ?? descriptorStatus.piboSessionId,
|
|
994
|
+
sessionTitle: session?.title,
|
|
995
|
+
profile: session?.profile ?? session?.agentId ?? status?.activeAgentId,
|
|
996
|
+
activeModel: status?.activeModel ?? descriptorStatus.activeModel,
|
|
997
|
+
mode: status?.mode ?? descriptorStatus.mode,
|
|
998
|
+
connected: status?.connected,
|
|
999
|
+
queuedMessages: status?.queuedMessages ?? descriptorStatus.queuedMessages,
|
|
1000
|
+
processing: status?.processing ?? descriptorStatus.processing,
|
|
1001
|
+
streaming: status?.streaming ?? descriptorStatus.streaming,
|
|
1002
|
+
cwd: status?.cwd ?? descriptorStatus.cwd,
|
|
1003
|
+
contextUsage: status?.contextUsage ?? descriptorStatus.contextUsage,
|
|
1004
|
+
providerUsage: status?.providerUsage ?? descriptorStatus.providerUsage,
|
|
1005
|
+
activeTools: status?.activeTools ?? descriptorStatus.activeTools,
|
|
1006
|
+
enabledTools: status?.enabledTools ?? descriptorStatus.enabledTools,
|
|
1007
|
+
disposed: status?.disposed ?? descriptorStatus.disposed,
|
|
1008
|
+
thinkingLevel: status?.thinkingLevel ?? descriptorStatus.thinkingLevel,
|
|
1009
|
+
fastMode: status?.fastMode ?? descriptorStatus.fastMode,
|
|
1010
|
+
warnings: status?.warnings ?? descriptorStatus.warnings,
|
|
1011
|
+
errors: status?.errors ?? descriptorStatus.errors,
|
|
1012
|
+
message: status?.message ?? descriptorStatus.message,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
export function renderCommandResultDescriptorText(descriptor, session) {
|
|
1016
|
+
if (descriptor.kind === "text")
|
|
1017
|
+
return [descriptor.title, descriptor.text].filter(Boolean).join(": ");
|
|
1018
|
+
if (descriptor.kind === "unsupported")
|
|
1019
|
+
return `${descriptor.command}: ${descriptor.reason}`;
|
|
1020
|
+
if (descriptor.kind === "error")
|
|
1021
|
+
return `${descriptor.title ?? "Error"}: ${descriptor.message}`;
|
|
1022
|
+
if (descriptor.kind === "session-link")
|
|
1023
|
+
return `${descriptor.title}: ${descriptor.label ?? "session"} ${descriptor.sessionId}${descriptor.roomLabel ? ` in ${descriptor.roomLabel}` : descriptor.roomId ? ` in room ${descriptor.roomId}` : ""}`;
|
|
1024
|
+
if (descriptor.kind === "status")
|
|
1025
|
+
return renderCliStatusCardText(descriptor.status, session, descriptor.title);
|
|
1026
|
+
if (descriptor.kind === "menu")
|
|
1027
|
+
return [descriptor.title, ...descriptor.items.map((item) => ` ${item.disabled ? "-" : "•"} ${item.label}${item.description ? ` — ${item.description}` : ""}`)].join("\n");
|
|
1028
|
+
return `${descriptor.title ?? "Result"}: ${redactCliSessionStatusText(JSON.stringify(descriptor.value, null, 2))}`;
|
|
1029
|
+
}
|
|
1030
|
+
export function renderCliStatusCardText(status, session, title = "Status") {
|
|
1031
|
+
const summary = formatCliSessionStatus(status, session);
|
|
1032
|
+
const viewModel = buildTerminalStatusViewModel(statusViewModelInput(status, session));
|
|
1033
|
+
const lines = [`${title}: ${summary}`];
|
|
1034
|
+
for (const field of viewModel.fields)
|
|
1035
|
+
lines.push(` ${field.label}: ${field.value}`);
|
|
1036
|
+
for (const progress of viewModel.progress)
|
|
1037
|
+
lines.push(` ${progress.label}: ${progressBarText(progress, progress.state === "available" ? 18 : 12)} — ${progress.text}`);
|
|
1038
|
+
for (const warning of viewModel.warnings)
|
|
1039
|
+
lines.push(` Warning: ${warning}`);
|
|
1040
|
+
for (const error of viewModel.errors)
|
|
1041
|
+
lines.push(` Error: ${error}`);
|
|
1042
|
+
return lines.join("\n");
|
|
1043
|
+
}
|
|
1044
|
+
function statusViewModelInput(status, session) {
|
|
1045
|
+
const runtimeState = status?.connected === false ? "disconnected" : status?.processing ? "processing" : status?.streaming ? "streaming" : status?.mode ?? "unknown";
|
|
1046
|
+
return {
|
|
1047
|
+
session: { id: session?.id ?? status?.activeSessionId, title: session?.title, profile: session?.profile ?? session?.agentId ?? status?.activeAgentId, status: session?.status },
|
|
1048
|
+
model: status?.activeModel ?? session?.model ?? { provider: "unknown", id: "unknown", label: "unknown" },
|
|
1049
|
+
runtime: { state: runtimeState, connected: status?.connected, queuedMessages: status?.queuedMessages, processing: status?.processing, streaming: status?.streaming, disposed: status?.disposed },
|
|
1050
|
+
cwd: status?.cwd,
|
|
1051
|
+
contextUsage: status?.contextUsage,
|
|
1052
|
+
providerUsage: status?.providerUsage,
|
|
1053
|
+
tools: { enabled: status?.enabledTools, active: status?.activeTools },
|
|
1054
|
+
thinking: status?.thinkingLevel ? { level: status.thinkingLevel } : undefined,
|
|
1055
|
+
fastMode: status?.fastMode,
|
|
1056
|
+
warnings: status?.warnings,
|
|
1057
|
+
errors: status?.errors,
|
|
1058
|
+
message: status?.message,
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
function compactOverlayTitle(title) {
|
|
1062
|
+
return title
|
|
1063
|
+
.replace(/^Select existing agent\/profile$/i, "select agent")
|
|
1064
|
+
.replace(/^Select thinking level$/i, "select thinking level")
|
|
1065
|
+
.replace(/^Select model provider$/i, "select model provider")
|
|
1066
|
+
.replace(/^Select login provider$/i, "select login provider")
|
|
1067
|
+
.replace(/^Select fork candidate$/i, "select fork candidate")
|
|
1068
|
+
.replace(/^Select auth method for /i, "select login method — ")
|
|
1069
|
+
.replace(/^Select model for /i, "select model — ")
|
|
1070
|
+
.replace(/^Select session in /i, "select session — ")
|
|
1071
|
+
.replace(/^Select room for new session$/i, "select room — new session")
|
|
1072
|
+
.replace(/^Select active room$/i, "select room")
|
|
1073
|
+
.replace(/^Select room for sessions$/i, "select room — sessions")
|
|
1074
|
+
.replace(/^Select room for /i, "select room — ")
|
|
1075
|
+
.replace(/^Select /i, "select ");
|
|
1076
|
+
}
|
|
1077
|
+
function formatOverlayItemLine(item, selected, _max) {
|
|
1078
|
+
const marker = selected ? "❯" : " ";
|
|
1079
|
+
const disabled = item.disabled === true;
|
|
1080
|
+
const availability = disabled ? "× " : "";
|
|
1081
|
+
const secondary = item.description ? ` · ${redactCliSessionStatusText(item.description)}` : "";
|
|
1082
|
+
return `${marker} ${availability}${redactCliSessionStatusText(item.label)}${secondary}`;
|
|
1083
|
+
}
|
|
1084
|
+
function overlayItemColor(selected, disabled) {
|
|
1085
|
+
if (disabled)
|
|
1086
|
+
return "gray";
|
|
1087
|
+
return selected ? "green" : "white";
|
|
1088
|
+
}
|
|
1089
|
+
function abbreviateIdentifier(value, _max = 24) {
|
|
1090
|
+
return value;
|
|
1091
|
+
}
|
|
1092
|
+
function itemMetadata(...parts) {
|
|
1093
|
+
return parts.filter(Boolean).join(" · ") || undefined;
|
|
1094
|
+
}
|
|
1095
|
+
function modelLabel(model) {
|
|
1096
|
+
if (!model)
|
|
1097
|
+
return undefined;
|
|
1098
|
+
return `${model.provider}/${model.id}`;
|
|
1099
|
+
}
|
|
1100
|
+
function roomPickerItem(room) {
|
|
1101
|
+
const label = room.title || abbreviateIdentifier(room.id) || room.id;
|
|
1102
|
+
return {
|
|
1103
|
+
id: room.id,
|
|
1104
|
+
kind: "room",
|
|
1105
|
+
roomId: room.id,
|
|
1106
|
+
label,
|
|
1107
|
+
description: itemMetadata(room.isDefault ? "default" : undefined, room.description, `room ${abbreviateIdentifier(room.id)}`),
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function sessionPickerItem(session) {
|
|
1111
|
+
return {
|
|
1112
|
+
id: session.id,
|
|
1113
|
+
kind: "session",
|
|
1114
|
+
roomId: session.roomId,
|
|
1115
|
+
label: session.title || abbreviateIdentifier(session.id) || session.id,
|
|
1116
|
+
description: itemMetadata(session.status, session.profile || session.agentId, modelLabel(session.model), session.updatedAt ? `updated ${session.updatedAt}` : undefined, abbreviateIdentifier(session.id)),
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function agentPickerItem(agent) {
|
|
1120
|
+
return {
|
|
1121
|
+
id: agent.id,
|
|
1122
|
+
label: agent.name || agent.id,
|
|
1123
|
+
description: itemMetadata(agent.profileName, agent.description, IDENTIFIER_PATTERN.test(agent.id) ? abbreviateIdentifier(agent.id) : undefined),
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function renderBoundedTextLines(value, color, _max, keyPrefix) {
|
|
1127
|
+
return value.split(/\r?\n/).map((line, index) => React.createElement(Text, { key: `${keyPrefix}-${index}`, color }, line));
|
|
1128
|
+
}
|
|
1129
|
+
export function formatStatusHeaderLines(state, max = 220) {
|
|
1130
|
+
const source = state.status?.source ?? "starting";
|
|
1131
|
+
const room = statusHeaderRoom(state);
|
|
1132
|
+
const session = state.session?.title ?? state.status?.activeSessionId ?? "no session";
|
|
1133
|
+
const agent = state.session?.agentId ?? state.session?.profile ?? state.status?.activeAgentId ?? "default";
|
|
1134
|
+
const model = state.status?.activeModel ? `${state.status.activeModel.provider}/${state.status.activeModel.id}` : "model unknown";
|
|
1135
|
+
const mode = state.mode === "transcript" ? "transcript" : state.mode;
|
|
1136
|
+
const parts = [`pibo sessions`, source, mode, room ? `room ${room}` : undefined, `session ${session}`, `agent ${agent}`, `model ${model}`].filter(Boolean);
|
|
1137
|
+
const full = parts.join(" · ");
|
|
1138
|
+
if (max >= 112 || full.length <= max)
|
|
1139
|
+
return [full];
|
|
1140
|
+
return [
|
|
1141
|
+
`pibo sessions · ${source} · ${mode}`,
|
|
1142
|
+
room ? `room ${room}` : "room none",
|
|
1143
|
+
`session ${session}`,
|
|
1144
|
+
`agent ${agent} · model ${model}`,
|
|
1145
|
+
];
|
|
1146
|
+
}
|
|
1147
|
+
function statusHeaderRoom(state) {
|
|
1148
|
+
const title = state.activeRoom?.title?.trim();
|
|
1149
|
+
if (title)
|
|
1150
|
+
return title;
|
|
1151
|
+
return state.status?.activeRoomId ?? state.session?.roomId;
|
|
1152
|
+
}
|
|
1153
|
+
export function createCliSessionCleanup(closeOpenSession, closeSource) {
|
|
1154
|
+
let cleanedUp = false;
|
|
1155
|
+
return () => {
|
|
1156
|
+
if (cleanedUp)
|
|
1157
|
+
return;
|
|
1158
|
+
cleanedUp = true;
|
|
1159
|
+
try {
|
|
1160
|
+
closeOpenSession();
|
|
1161
|
+
}
|
|
1162
|
+
finally {
|
|
1163
|
+
try {
|
|
1164
|
+
closeSource();
|
|
1165
|
+
}
|
|
1166
|
+
catch {
|
|
1167
|
+
// Exit cleanup must be best-effort so terminal shutdown can finish.
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
async function safeListSlashCommands(source) {
|
|
1173
|
+
try {
|
|
1174
|
+
return await source.listSlashCommands();
|
|
1175
|
+
}
|
|
1176
|
+
catch {
|
|
1177
|
+
return buildSlashCommandCatalog();
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
async function safeListRooms(source) {
|
|
1181
|
+
try {
|
|
1182
|
+
return await source.listRooms();
|
|
1183
|
+
}
|
|
1184
|
+
catch {
|
|
1185
|
+
return [];
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
function emptySessionRecoveryMessage(status, rooms) {
|
|
1189
|
+
if (status.rooms === "unsupported")
|
|
1190
|
+
return "No sessions found and this source cannot list rooms. Use /new to create a local CLI session.";
|
|
1191
|
+
if (rooms.length === 0)
|
|
1192
|
+
return "No sessions or rooms found. Use /new to create a local CLI session.";
|
|
1193
|
+
return "No sessions found. Use /new to create a local CLI session.";
|
|
1194
|
+
}
|
|
1195
|
+
export function formatCliSessionError(error) {
|
|
1196
|
+
const message = redactCliSessionStatusText(errorMessage(error));
|
|
1197
|
+
if (isCliSourceError(error)) {
|
|
1198
|
+
if (error.code === "source_closed")
|
|
1199
|
+
return `${message}. Recovery: restart pibo tui:sessions.`;
|
|
1200
|
+
if (error.code === "session_not_found")
|
|
1201
|
+
return `${message}. Recovery: use /session to select another session or /new to create one.`;
|
|
1202
|
+
if (error.code === "agent_not_found")
|
|
1203
|
+
return `${message}. Recovery: use /agent to pick an available existing profile.`;
|
|
1204
|
+
if (error.code === "empty_message")
|
|
1205
|
+
return `${message}. Type a message or use /help.`;
|
|
1206
|
+
return `${message}. Recovery: check local Pibo state, then use /status, /session, or /new.`;
|
|
1207
|
+
}
|
|
1208
|
+
return `${message}. Recovery: use /status for source state or restart the CLI if the problem persists.`;
|
|
1209
|
+
}
|
|
1210
|
+
function redactCliSessionStatusText(text) {
|
|
1211
|
+
return text
|
|
1212
|
+
.replace(/\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, "$1=[redacted]")
|
|
1213
|
+
.replace(/\b(api[_-]?key|token|secret|password)\s*[:=]\s*([^\s]+)/gi, "$1=[redacted]")
|
|
1214
|
+
.replace(/\b(?:sk|pk|pibo|ghp|github_pat)_[A-Za-z0-9_\-]{8,}\b/g, "[redacted]");
|
|
1215
|
+
}
|
|
1216
|
+
function errorMessage(error) {
|
|
1217
|
+
return error instanceof Error ? error.message : String(error);
|
|
1218
|
+
}
|