@pasko70/pibo 1.0.5 → 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 -250
- 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 +42 -5
- 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 +47 -0
- 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 +3168 -1811
- 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-CI0Xj2Cp.js → dist-B4lw9z4F.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-aMr2VYOu.js → dist-BbTaHWON.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-u2OzzOTm.js → dist-BcT_c1EJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Di1ScJ22.js → dist-CGINd7XU.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-wiRpAHIv.js → dist-CrBzZkUp.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CaNOvqMY.js → dist-CyyISotB.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-zgYUl2B-.js → dist-DFbnFNpX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CNlhGq5i.js → dist-DGLWQLX8.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BGxHKHPX.js → dist-DfcIkFSN.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DrM-H3e1.js → dist-sKDHTwaT.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DpmEro5R.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 +174 -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 +252 -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 +66 -2
- 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 +214 -0
- package/dist/user-skills/manager.js +35 -0
- package/dist/user-skills/store.js +267 -0
- package/dist/user-skills/types.js +1 -0
- 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/{.codex/skills → 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-B_E935fP.js +0 -151
- package/dist/apps/chat-ui/assets/index-Dk-opWl3.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
- /package/{.codex/skills → skills/builtin}/pi-agent-harness/agents/openai.yaml +0 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
import { access, mkdir, open, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
const DEFAULT_MAX_BROWSER_PROCESSES = 1;
|
|
5
|
+
const DEFAULT_LOCK_TIMEOUT_MS = 5_000;
|
|
6
|
+
const DEFAULT_LOCK_POLL_INTERVAL_MS = 50;
|
|
7
|
+
const DEFAULT_LOCK_STALE_MS = 10 * 60_000;
|
|
8
|
+
const DEFAULT_CDP_HEALTH_TIMEOUT_MS = 2_500;
|
|
9
|
+
const DEFAULT_CDP_CLEANUP_MAX_TARGETS = 25;
|
|
10
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60_000;
|
|
11
|
+
export class BrowserPoolLockTimeoutError extends Error {
|
|
12
|
+
constructor(lockPath, timeoutMs) {
|
|
13
|
+
super(`Timed out acquiring browser pool lock ${lockPath} after ${timeoutMs}ms`);
|
|
14
|
+
this.name = "BrowserPoolLockTimeoutError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class BrowserPoolStateError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "BrowserPoolStateError";
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createEmptyBrowserPoolState(identity) {
|
|
24
|
+
return {
|
|
25
|
+
workerId: identity.workerId,
|
|
26
|
+
poolId: identity.poolId,
|
|
27
|
+
maxBrowserProcesses: identity.maxBrowserProcesses ?? DEFAULT_MAX_BROWSER_PROCESSES,
|
|
28
|
+
activeLeaseCount: 0,
|
|
29
|
+
state: "empty",
|
|
30
|
+
cleanupStatus: "not-attempted",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function browserPoolPaths(rootDir, identity) {
|
|
34
|
+
const safeWorkerId = safePathSegment(identity.workerId);
|
|
35
|
+
const safePoolId = safePathSegment(identity.poolId);
|
|
36
|
+
const base = join(rootDir, "browser-pools", safeWorkerId, safePoolId);
|
|
37
|
+
return {
|
|
38
|
+
statePath: join(base, "state.json"),
|
|
39
|
+
lockPath: join(base, "state.lock"),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function loadBrowserPoolState(statePath, options) {
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = await readFile(statePath, "utf8");
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (isNodeError(error) && error.code === "ENOENT" && (options.onMissing ?? "empty") === "empty") {
|
|
49
|
+
return createEmptyBrowserPoolState(options);
|
|
50
|
+
}
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
return normalizeBrowserPoolState(JSON.parse(raw), options);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const stateError = toBrowserPoolStateError(error);
|
|
58
|
+
if ((options.onMalformed ?? "throw") === "empty") {
|
|
59
|
+
return {
|
|
60
|
+
...createEmptyBrowserPoolState(options),
|
|
61
|
+
state: "dirty",
|
|
62
|
+
lastError: stateError.message,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
throw stateError;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function saveBrowserPoolState(statePath, state) {
|
|
69
|
+
const normalized = normalizeBrowserPoolState(state, state);
|
|
70
|
+
await mkdir(dirname(statePath), { recursive: true });
|
|
71
|
+
const temporaryPath = `${statePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
72
|
+
await writeFile(temporaryPath, `${JSON.stringify(normalized, null, 2)}\n`, "utf8");
|
|
73
|
+
await rename(temporaryPath, statePath);
|
|
74
|
+
}
|
|
75
|
+
export async function withBrowserPoolLock(lockPath, options, run) {
|
|
76
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
|
|
77
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_LOCK_POLL_INTERVAL_MS;
|
|
78
|
+
const staleMs = options.staleMs ?? DEFAULT_LOCK_STALE_MS;
|
|
79
|
+
const holder = options.holder ?? `${process.pid}`;
|
|
80
|
+
const startedAt = Date.now();
|
|
81
|
+
while (true) {
|
|
82
|
+
try {
|
|
83
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
84
|
+
const handle = await open(lockPath, "wx");
|
|
85
|
+
try {
|
|
86
|
+
await handle.writeFile(JSON.stringify({ holder, pid: process.pid, createdAt: new Date().toISOString() }), "utf8");
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
await handle.close();
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (!isNodeError(error) || error.code !== "EEXIST")
|
|
95
|
+
throw error;
|
|
96
|
+
await removeStaleLock(lockPath, staleMs);
|
|
97
|
+
if (Date.now() - startedAt >= timeoutMs)
|
|
98
|
+
throw new BrowserPoolLockTimeoutError(lockPath, timeoutMs);
|
|
99
|
+
await delay(Math.min(pollIntervalMs, Math.max(1, timeoutMs - (Date.now() - startedAt))));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return await run();
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await rm(lockPath, { force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function mutateBrowserPoolState(paths, identity, kind, mutation, lockOptions = {}) {
|
|
110
|
+
return withBrowserPoolLock(paths.lockPath, { ...lockOptions, holder: lockOptions.holder ?? kind }, async () => {
|
|
111
|
+
const current = await loadBrowserPoolState(paths.statePath, identity);
|
|
112
|
+
const next = await mutation(current);
|
|
113
|
+
await saveBrowserPoolState(paths.statePath, next.state);
|
|
114
|
+
return next.result;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export async function checkBrowserPoolCdpHealth(cdpUrl, options = {}) {
|
|
118
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_CDP_HEALTH_TIMEOUT_MS;
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetchWithTimeout(`${trimTrailingSlash(cdpUrl)}/json/version`, timeoutMs);
|
|
121
|
+
if (!response.ok)
|
|
122
|
+
return { ok: false, reason: `CDP /json/version returned HTTP ${response.status}` };
|
|
123
|
+
const payload = await response.json();
|
|
124
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
125
|
+
return { ok: false, reason: "CDP /json/version returned malformed JSON" };
|
|
126
|
+
const browser = payload.Browser;
|
|
127
|
+
if (typeof browser !== "string" || browser.length === 0)
|
|
128
|
+
return { ok: false, reason: "CDP /json/version response is missing Browser" };
|
|
129
|
+
return { ok: true, browser };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
return { ok: false, reason: `CDP health check failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
export async function cleanupBrowserPoolCdpTargets(cdpUrl, options = {}) {
|
|
136
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_CDP_HEALTH_TIMEOUT_MS;
|
|
137
|
+
const maxTargets = options.maxTargets ?? DEFAULT_CDP_CLEANUP_MAX_TARGETS;
|
|
138
|
+
try {
|
|
139
|
+
const baseUrl = trimTrailingSlash(cdpUrl);
|
|
140
|
+
const response = await fetchWithTimeout(`${baseUrl}/json/list`, timeoutMs);
|
|
141
|
+
if (!response.ok)
|
|
142
|
+
return { ok: false, status: "failed", closedTargets: 0, reason: `CDP /json/list returned HTTP ${response.status}` };
|
|
143
|
+
const payload = await response.json();
|
|
144
|
+
if (!Array.isArray(payload))
|
|
145
|
+
return { ok: false, status: "failed", closedTargets: 0, reason: "CDP /json/list returned malformed JSON" };
|
|
146
|
+
const pageTargetIds = payload
|
|
147
|
+
.slice(0, maxTargets)
|
|
148
|
+
.filter((target) => Boolean(target) && typeof target === "object" && !Array.isArray(target))
|
|
149
|
+
.filter((target) => target.type === "page" && typeof target.id === "string" && target.id.length > 0)
|
|
150
|
+
.map((target) => target.id);
|
|
151
|
+
let closedTargets = 0;
|
|
152
|
+
for (const targetId of pageTargetIds) {
|
|
153
|
+
const closeResponse = await fetchWithTimeout(`${baseUrl}/json/close/${encodeURIComponent(targetId)}`, timeoutMs);
|
|
154
|
+
if (!closeResponse.ok) {
|
|
155
|
+
return { ok: false, status: "failed", closedTargets, reason: `CDP /json/close/${targetId} returned HTTP ${closeResponse.status}` };
|
|
156
|
+
}
|
|
157
|
+
closedTargets += 1;
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, status: "success", closedTargets };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return { ok: false, status: "failed", closedTargets: 0, reason: `CDP cleanup failed: ${error instanceof Error ? error.message : String(error)}` };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export async function acquireBrowserPoolLease(paths, identity, options = {}) {
|
|
166
|
+
const leaseId = options.leaseId ?? randomUUID();
|
|
167
|
+
const now = options.now ?? (() => new Date());
|
|
168
|
+
const checkCdpHealth = options.checkCdpHealth ?? checkBrowserPoolCdpHealth;
|
|
169
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
170
|
+
const cdpTimeoutMs = options.cdpTimeoutMs ?? DEFAULT_CDP_HEALTH_TIMEOUT_MS;
|
|
171
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
172
|
+
return mutateBrowserPoolState(paths, { ...identity, onMalformed: "empty" }, "acquire", async (current) => {
|
|
173
|
+
const checkedAt = now();
|
|
174
|
+
const lastUsedAt = checkedAt.toISOString();
|
|
175
|
+
const idleExpiresAt = new Date(checkedAt.getTime() + idleTimeoutMs).toISOString();
|
|
176
|
+
const reusable = await evaluateRecordedBrowser(current, isPidAlive, checkCdpHealth, cdpTimeoutMs);
|
|
177
|
+
if (reusable.ok) {
|
|
178
|
+
const busyReason = activeLeaseBusyReason(current, leaseId, checkedAt);
|
|
179
|
+
if (busyReason) {
|
|
180
|
+
const busyState = stripUndefined({ ...current, lastUsedAt, lastError: busyReason });
|
|
181
|
+
return { state: busyState, result: { acquired: false, state: busyState, staleReason: busyReason } };
|
|
182
|
+
}
|
|
183
|
+
const nextState = stripUndefined({
|
|
184
|
+
...current,
|
|
185
|
+
state: "leased",
|
|
186
|
+
activeLeaseId: leaseId,
|
|
187
|
+
activeLeaseCount: 1,
|
|
188
|
+
holder: options.holder,
|
|
189
|
+
lastUsedAt,
|
|
190
|
+
idleExpiresAt,
|
|
191
|
+
cleanupStatus: "not-attempted",
|
|
192
|
+
lastError: undefined,
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
state: nextState,
|
|
196
|
+
result: { acquired: true, leaseId, cdpUrl: current.cdpUrl, pid: current.pid, reused: true, replaced: false, state: nextState },
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const staleState = stripUndefined({
|
|
200
|
+
...current,
|
|
201
|
+
state: current.state === "dirty" ? "dirty" : "stale",
|
|
202
|
+
activeLeaseId: undefined,
|
|
203
|
+
activeLeaseCount: 0,
|
|
204
|
+
holder: undefined,
|
|
205
|
+
lastUsedAt,
|
|
206
|
+
idleExpiresAt: undefined,
|
|
207
|
+
lastError: reusable.reason,
|
|
208
|
+
});
|
|
209
|
+
if (!options.startBrowser) {
|
|
210
|
+
return { state: staleState, result: { acquired: false, state: staleState, staleReason: reusable.reason } };
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const started = await options.startBrowser(staleState);
|
|
214
|
+
const nextState = stripUndefined({
|
|
215
|
+
workerId: current.workerId,
|
|
216
|
+
poolId: current.poolId,
|
|
217
|
+
maxBrowserProcesses: current.maxBrowserProcesses,
|
|
218
|
+
pid: started.pid,
|
|
219
|
+
processGroupId: started.processGroupId,
|
|
220
|
+
cdpPort: started.cdpPort,
|
|
221
|
+
cdpUrl: started.cdpUrl,
|
|
222
|
+
userDataDir: started.userDataDir,
|
|
223
|
+
activeLeaseId: leaseId,
|
|
224
|
+
activeLeaseCount: 1,
|
|
225
|
+
holder: options.holder,
|
|
226
|
+
lastUsedAt,
|
|
227
|
+
idleExpiresAt,
|
|
228
|
+
state: "leased",
|
|
229
|
+
cleanupStatus: "not-attempted",
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
state: nextState,
|
|
233
|
+
result: { acquired: true, leaseId, cdpUrl: started.cdpUrl, pid: started.pid, reused: false, replaced: current.state !== "empty", staleReason: reusable.reason, state: nextState },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
const dirtyState = stripUndefined({ ...staleState, state: "dirty", lastError: `Browser replacement failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
238
|
+
return { state: dirtyState, result: { acquired: false, state: dirtyState, staleReason: dirtyState.lastError } };
|
|
239
|
+
}
|
|
240
|
+
}, options.lockOptions);
|
|
241
|
+
}
|
|
242
|
+
export async function releaseBrowserPoolLease(paths, identity, options = {}) {
|
|
243
|
+
const now = options.now ?? (() => new Date());
|
|
244
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
245
|
+
const cleanupCdp = options.cleanupCdp ?? cleanupBrowserPoolCdpTargets;
|
|
246
|
+
const cdpTimeoutMs = options.cdpTimeoutMs ?? DEFAULT_CDP_HEALTH_TIMEOUT_MS;
|
|
247
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
248
|
+
return mutateBrowserPoolState(paths, { ...identity, onMissing: "empty", onMalformed: "empty" }, "release", async (current) => {
|
|
249
|
+
const releasedAt = now();
|
|
250
|
+
const lastUsedAt = releasedAt.toISOString();
|
|
251
|
+
const idleExpiresAt = new Date(releasedAt.getTime() + idleTimeoutMs).toISOString();
|
|
252
|
+
const requestedLeaseId = options.leaseId;
|
|
253
|
+
if (current.state !== "leased" || !current.activeLeaseId) {
|
|
254
|
+
const nextState = stripUndefined({
|
|
255
|
+
...current,
|
|
256
|
+
activeLeaseId: undefined,
|
|
257
|
+
activeLeaseCount: 0,
|
|
258
|
+
holder: undefined,
|
|
259
|
+
lastUsedAt,
|
|
260
|
+
cleanupStatus: "skipped",
|
|
261
|
+
lastError: "No active browser pool lease to release",
|
|
262
|
+
});
|
|
263
|
+
return { state: nextState, result: { released: false, cleanupStatus: "skipped", closedTargets: 0, state: nextState, lastError: nextState.lastError } };
|
|
264
|
+
}
|
|
265
|
+
if (requestedLeaseId && current.activeLeaseId !== requestedLeaseId) {
|
|
266
|
+
const lastError = `Active browser pool lease is ${current.activeLeaseId}, not ${requestedLeaseId}`;
|
|
267
|
+
const nextState = stripUndefined({ ...current, lastUsedAt, lastError });
|
|
268
|
+
return { state: nextState, result: { released: false, cleanupStatus: current.cleanupStatus ?? "not-attempted", closedTargets: 0, state: nextState, lastError } };
|
|
269
|
+
}
|
|
270
|
+
let cleanup = { ok: false, status: "skipped", closedTargets: 0, reason: "Browser process is not reachable" };
|
|
271
|
+
let nextLifecycleState = "ready";
|
|
272
|
+
if (!current.pid || !(await isPidAlive(current.pid))) {
|
|
273
|
+
cleanup = { ok: false, status: "skipped", closedTargets: 0, reason: current.pid ? `Recorded browser pid ${current.pid} is not alive` : "Browser pool has no recorded pid" };
|
|
274
|
+
nextLifecycleState = "stale";
|
|
275
|
+
}
|
|
276
|
+
else if (!current.cdpUrl) {
|
|
277
|
+
cleanup = { ok: false, status: "skipped", closedTargets: 0, reason: "Browser pool has no recorded CDP URL" };
|
|
278
|
+
nextLifecycleState = "stale";
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
cleanup = await cleanupCdp(current.cdpUrl, { timeoutMs: cdpTimeoutMs });
|
|
282
|
+
if (!cleanup.ok && cleanup.status === "failed")
|
|
283
|
+
nextLifecycleState = "dirty";
|
|
284
|
+
}
|
|
285
|
+
const lastError = cleanup.ok ? undefined : cleanup.reason;
|
|
286
|
+
const nextState = stripUndefined({
|
|
287
|
+
...current,
|
|
288
|
+
state: nextLifecycleState,
|
|
289
|
+
activeLeaseId: undefined,
|
|
290
|
+
activeLeaseCount: 0,
|
|
291
|
+
holder: undefined,
|
|
292
|
+
lastUsedAt,
|
|
293
|
+
idleExpiresAt: nextLifecycleState === "ready" ? idleExpiresAt : undefined,
|
|
294
|
+
cleanupStatus: cleanup.status,
|
|
295
|
+
lastError,
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
state: nextState,
|
|
299
|
+
result: { released: true, cleanupStatus: cleanup.status, closedTargets: cleanup.closedTargets, state: nextState, lastError },
|
|
300
|
+
};
|
|
301
|
+
}, options.lockOptions);
|
|
302
|
+
}
|
|
303
|
+
export async function reapIdleBrowserPool(paths, identity, options = {}) {
|
|
304
|
+
const now = options.now ?? (() => new Date());
|
|
305
|
+
const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
|
|
306
|
+
const terminateBrowserProcessTree = options.terminateBrowserProcessTree ?? defaultTerminateBrowserProcessTree;
|
|
307
|
+
const removeStaleFiles = options.removeStaleFiles ?? removeDefaultStaleBrowserFiles;
|
|
308
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
309
|
+
return mutateBrowserPoolState(paths, { ...identity, onMissing: "empty", onMalformed: "empty" }, "reap", async (current) => {
|
|
310
|
+
const reapedAt = now();
|
|
311
|
+
const lastUsedAt = reapedAt.toISOString();
|
|
312
|
+
const activeLeaseCount = current.activeLeaseId ? Math.max(1, current.activeLeaseCount ?? 1) : current.activeLeaseCount ?? 0;
|
|
313
|
+
if (current.state === "empty" || (!current.pid && !current.cdpUrl && !current.userDataDir)) {
|
|
314
|
+
const reason = "Browser pool has no recorded browser to reap";
|
|
315
|
+
const nextState = stripUndefined({ ...current, activeLeaseCount: 0, cleanupStatus: "skipped", lastError: reason });
|
|
316
|
+
return { state: nextState, result: skippedReapResult(nextState, reason) };
|
|
317
|
+
}
|
|
318
|
+
if (current.activeLeaseId || activeLeaseCount > 0 || current.state === "leased") {
|
|
319
|
+
const reason = current.activeLeaseId ? `Browser pool has active lease ${current.activeLeaseId}` : "Browser pool has active leases";
|
|
320
|
+
const nextState = stripUndefined({ ...current, cleanupStatus: "skipped", lastError: reason });
|
|
321
|
+
return { state: nextState, result: skippedReapResult(nextState, reason) };
|
|
322
|
+
}
|
|
323
|
+
const idleReason = browserPoolReapEligibilityReason(current, reapedAt, idleTimeoutMs);
|
|
324
|
+
if (!idleReason) {
|
|
325
|
+
const reason = "Browser pool is not idle long enough to reap";
|
|
326
|
+
const nextState = stripUndefined({ ...current, cleanupStatus: "skipped", lastError: reason });
|
|
327
|
+
return { state: nextState, result: skippedReapResult(nextState, reason) };
|
|
328
|
+
}
|
|
329
|
+
let terminatedProcessTrees = 0;
|
|
330
|
+
let lastError;
|
|
331
|
+
if (current.pid && await isPidAlive(current.pid)) {
|
|
332
|
+
const termination = await terminateBrowserProcessTree(current);
|
|
333
|
+
terminatedProcessTrees = termination.terminatedProcessTrees;
|
|
334
|
+
if (!termination.ok) {
|
|
335
|
+
lastError = termination.reason ?? `Failed to terminate browser process tree for pid ${current.pid}`;
|
|
336
|
+
const dirtyState = stripUndefined({ ...current, state: "dirty", cleanupStatus: "failed", lastError });
|
|
337
|
+
return {
|
|
338
|
+
state: dirtyState,
|
|
339
|
+
result: {
|
|
340
|
+
reaped: false,
|
|
341
|
+
eligible: true,
|
|
342
|
+
reason: idleReason,
|
|
343
|
+
affectedLeases: 0,
|
|
344
|
+
affectedBrowserPools: 1,
|
|
345
|
+
terminatedProcessTrees,
|
|
346
|
+
staleStateFiles: 0,
|
|
347
|
+
cleanupStatus: "failed",
|
|
348
|
+
state: dirtyState,
|
|
349
|
+
lastError,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const staleStateFiles = await removeStaleFiles(current);
|
|
355
|
+
const nextState = stripUndefined({
|
|
356
|
+
workerId: current.workerId,
|
|
357
|
+
poolId: current.poolId,
|
|
358
|
+
maxBrowserProcesses: current.maxBrowserProcesses,
|
|
359
|
+
activeLeaseCount: 0,
|
|
360
|
+
lastUsedAt,
|
|
361
|
+
state: "empty",
|
|
362
|
+
cleanupStatus: "success",
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
state: nextState,
|
|
366
|
+
result: {
|
|
367
|
+
reaped: true,
|
|
368
|
+
eligible: true,
|
|
369
|
+
reason: idleReason,
|
|
370
|
+
affectedLeases: 0,
|
|
371
|
+
affectedBrowserPools: 1,
|
|
372
|
+
terminatedProcessTrees,
|
|
373
|
+
staleStateFiles,
|
|
374
|
+
cleanupStatus: "success",
|
|
375
|
+
state: nextState,
|
|
376
|
+
},
|
|
377
|
+
};
|
|
378
|
+
}, options.lockOptions);
|
|
379
|
+
}
|
|
380
|
+
export function normalizeBrowserPoolState(value, identity) {
|
|
381
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
382
|
+
throw new BrowserPoolStateError("Browser pool state must be a JSON object");
|
|
383
|
+
const record = value;
|
|
384
|
+
const workerId = readRequiredString(record, "workerId");
|
|
385
|
+
const poolId = readRequiredString(record, "poolId");
|
|
386
|
+
if (workerId !== identity.workerId)
|
|
387
|
+
throw new BrowserPoolStateError(`Browser pool state worker id mismatch: expected ${identity.workerId}, got ${workerId}`);
|
|
388
|
+
if (poolId !== identity.poolId)
|
|
389
|
+
throw new BrowserPoolStateError(`Browser pool state pool id mismatch: expected ${identity.poolId}, got ${poolId}`);
|
|
390
|
+
const state = readLifecycleState(record.state);
|
|
391
|
+
const maxBrowserProcesses = readPositiveInteger(record.maxBrowserProcesses, "maxBrowserProcesses");
|
|
392
|
+
return stripUndefined({
|
|
393
|
+
workerId,
|
|
394
|
+
poolId,
|
|
395
|
+
maxBrowserProcesses,
|
|
396
|
+
pid: readOptionalPositiveInteger(record.pid, "pid"),
|
|
397
|
+
processGroupId: readOptionalPositiveInteger(record.processGroupId, "processGroupId"),
|
|
398
|
+
cdpPort: readOptionalPositiveInteger(record.cdpPort, "cdpPort"),
|
|
399
|
+
cdpUrl: readOptionalString(record.cdpUrl, "cdpUrl"),
|
|
400
|
+
userDataDir: readOptionalString(record.userDataDir, "userDataDir"),
|
|
401
|
+
activeLeaseId: readOptionalString(record.activeLeaseId, "activeLeaseId"),
|
|
402
|
+
activeLeaseCount: readOptionalNonNegativeInteger(record.activeLeaseCount, "activeLeaseCount"),
|
|
403
|
+
holder: readOptionalString(record.holder, "holder"),
|
|
404
|
+
lastUsedAt: readOptionalString(record.lastUsedAt, "lastUsedAt"),
|
|
405
|
+
idleExpiresAt: readOptionalString(record.idleExpiresAt, "idleExpiresAt"),
|
|
406
|
+
state,
|
|
407
|
+
cleanupStatus: readOptionalCleanupStatus(record.cleanupStatus),
|
|
408
|
+
lastError: readOptionalString(record.lastError, "lastError"),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
function activeLeaseBusyReason(state, requestedLeaseId, now) {
|
|
412
|
+
if (state.state !== "leased" || !state.activeLeaseId || state.activeLeaseId === requestedLeaseId)
|
|
413
|
+
return undefined;
|
|
414
|
+
if (leaseExpired(state.idleExpiresAt, now))
|
|
415
|
+
return undefined;
|
|
416
|
+
const holder = state.holder ? ` owned by ${state.holder}` : "";
|
|
417
|
+
const until = state.idleExpiresAt ? ` until ${state.idleExpiresAt}` : "";
|
|
418
|
+
return `pool-exhausted: browser pool ${state.poolId} is already leased by ${state.activeLeaseId}${holder}${until}`;
|
|
419
|
+
}
|
|
420
|
+
function skippedReapResult(state, reason) {
|
|
421
|
+
return {
|
|
422
|
+
reaped: false,
|
|
423
|
+
eligible: false,
|
|
424
|
+
reason,
|
|
425
|
+
affectedLeases: state.activeLeaseId ? Math.max(1, state.activeLeaseCount ?? 1) : state.activeLeaseCount ?? 0,
|
|
426
|
+
affectedBrowserPools: 0,
|
|
427
|
+
terminatedProcessTrees: 0,
|
|
428
|
+
staleStateFiles: 0,
|
|
429
|
+
cleanupStatus: "skipped",
|
|
430
|
+
state,
|
|
431
|
+
lastError: reason,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function browserPoolReapEligibilityReason(state, now, idleTimeoutMs) {
|
|
435
|
+
if (state.state === "stale" || state.state === "dirty")
|
|
436
|
+
return `Browser pool is ${state.state}`;
|
|
437
|
+
if (leaseExpired(state.idleExpiresAt, now))
|
|
438
|
+
return `Browser pool idle expiry ${state.idleExpiresAt} has passed`;
|
|
439
|
+
if (!state.lastUsedAt)
|
|
440
|
+
return undefined;
|
|
441
|
+
const lastUsedAt = Date.parse(state.lastUsedAt);
|
|
442
|
+
if (Number.isNaN(lastUsedAt))
|
|
443
|
+
return undefined;
|
|
444
|
+
if (lastUsedAt + idleTimeoutMs <= now.getTime())
|
|
445
|
+
return `Browser pool last used at ${state.lastUsedAt} is older than idle timeout ${idleTimeoutMs}ms`;
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
async function defaultTerminateBrowserProcessTree(state) {
|
|
449
|
+
if (!state.pid)
|
|
450
|
+
return { ok: true, terminatedProcessTrees: 0 };
|
|
451
|
+
if (!state.userDataDir)
|
|
452
|
+
return { ok: false, terminatedProcessTrees: 0, reason: `Refusing to terminate browser pid ${state.pid} without a recorded Pibo-managed user-data dir` };
|
|
453
|
+
if (!defaultIsPidAlive(state.pid))
|
|
454
|
+
return { ok: true, terminatedProcessTrees: 0 };
|
|
455
|
+
const targetGroup = state.processGroupId && state.processGroupId > 1 && state.processGroupId === state.pid && state.processGroupId !== process.pid ? state.processGroupId : undefined;
|
|
456
|
+
const target = targetGroup ? -targetGroup : state.pid;
|
|
457
|
+
try {
|
|
458
|
+
process.kill(target, "SIGTERM");
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
if (!isNodeError(error) || error.code !== "ESRCH")
|
|
462
|
+
return { ok: false, terminatedProcessTrees: 0, reason: `Failed to signal browser pid ${state.pid}: ${error instanceof Error ? error.message : String(error)}` };
|
|
463
|
+
}
|
|
464
|
+
if (await waitForPidExit(state.pid, 1_000))
|
|
465
|
+
return { ok: true, terminatedProcessTrees: 1 };
|
|
466
|
+
try {
|
|
467
|
+
process.kill(target, "SIGKILL");
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
if (!isNodeError(error) || error.code !== "ESRCH")
|
|
471
|
+
return { ok: false, terminatedProcessTrees: 0, reason: `Failed to kill browser pid ${state.pid}: ${error instanceof Error ? error.message : String(error)}` };
|
|
472
|
+
}
|
|
473
|
+
if (await waitForPidExit(state.pid, 1_000))
|
|
474
|
+
return { ok: true, terminatedProcessTrees: 1 };
|
|
475
|
+
return { ok: false, terminatedProcessTrees: 0, reason: `Browser pid ${state.pid} did not exit after TERM/KILL` };
|
|
476
|
+
}
|
|
477
|
+
async function removeDefaultStaleBrowserFiles(state) {
|
|
478
|
+
if (!state.userDataDir)
|
|
479
|
+
return 0;
|
|
480
|
+
let removed = 0;
|
|
481
|
+
for (const fileName of ["DevToolsActivePort", "SingletonLock", "SingletonCookie", "SingletonSocket"]) {
|
|
482
|
+
const path = join(state.userDataDir, fileName);
|
|
483
|
+
try {
|
|
484
|
+
await access(path);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
await rm(path, { force: true });
|
|
490
|
+
removed += 1;
|
|
491
|
+
}
|
|
492
|
+
return removed;
|
|
493
|
+
}
|
|
494
|
+
async function waitForPidExit(pid, timeoutMs) {
|
|
495
|
+
const startedAt = Date.now();
|
|
496
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
497
|
+
if (!defaultIsPidAlive(pid))
|
|
498
|
+
return true;
|
|
499
|
+
await delay(50);
|
|
500
|
+
}
|
|
501
|
+
return !defaultIsPidAlive(pid);
|
|
502
|
+
}
|
|
503
|
+
function leaseExpired(idleExpiresAt, now) {
|
|
504
|
+
if (!idleExpiresAt)
|
|
505
|
+
return false;
|
|
506
|
+
const expiresAt = Date.parse(idleExpiresAt);
|
|
507
|
+
if (Number.isNaN(expiresAt))
|
|
508
|
+
return false;
|
|
509
|
+
return expiresAt <= now.getTime();
|
|
510
|
+
}
|
|
511
|
+
async function evaluateRecordedBrowser(state, isPidAlive, checkCdpHealth, cdpTimeoutMs) {
|
|
512
|
+
if (state.state === "dirty")
|
|
513
|
+
return { ok: false, reason: state.lastError ?? "Browser pool state is dirty" };
|
|
514
|
+
if (!state.pid)
|
|
515
|
+
return { ok: false, reason: "Browser pool has no recorded pid" };
|
|
516
|
+
if (!state.cdpUrl)
|
|
517
|
+
return { ok: false, reason: "Browser pool has no recorded CDP URL" };
|
|
518
|
+
if (!(await isPidAlive(state.pid)))
|
|
519
|
+
return { ok: false, reason: `Recorded browser pid ${state.pid} is not alive` };
|
|
520
|
+
const health = await checkCdpHealth(state.cdpUrl, { timeoutMs: cdpTimeoutMs });
|
|
521
|
+
if (!health.ok)
|
|
522
|
+
return { ok: false, reason: health.reason ?? "Recorded browser CDP endpoint is unhealthy" };
|
|
523
|
+
return { ok: true };
|
|
524
|
+
}
|
|
525
|
+
function defaultIsPidAlive(pid) {
|
|
526
|
+
try {
|
|
527
|
+
process.kill(pid, 0);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
catch (error) {
|
|
531
|
+
return isNodeError(error) && error.code === "EPERM";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function trimTrailingSlash(value) {
|
|
535
|
+
return value.replace(/\/+$/, "");
|
|
536
|
+
}
|
|
537
|
+
async function removeStaleLock(lockPath, staleMs) {
|
|
538
|
+
if (staleMs <= 0)
|
|
539
|
+
return;
|
|
540
|
+
try {
|
|
541
|
+
const raw = await readFile(lockPath, "utf8");
|
|
542
|
+
const parsed = JSON.parse(raw);
|
|
543
|
+
if (typeof parsed.createdAt !== "string")
|
|
544
|
+
return;
|
|
545
|
+
const createdAt = Date.parse(parsed.createdAt);
|
|
546
|
+
if (Number.isNaN(createdAt))
|
|
547
|
+
return;
|
|
548
|
+
if (Date.now() - createdAt > staleMs)
|
|
549
|
+
await rm(lockPath, { force: true });
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
// A corrupt or concurrently removed lock should not make acquisition unsafe.
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function safePathSegment(value) {
|
|
556
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
557
|
+
}
|
|
558
|
+
function stripUndefined(record) {
|
|
559
|
+
return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
560
|
+
}
|
|
561
|
+
function readRequiredString(record, key) {
|
|
562
|
+
const value = record[key];
|
|
563
|
+
if (typeof value !== "string" || value.length === 0)
|
|
564
|
+
throw new BrowserPoolStateError(`Browser pool state field ${key} must be a non-empty string`);
|
|
565
|
+
return value;
|
|
566
|
+
}
|
|
567
|
+
function readOptionalString(value, key) {
|
|
568
|
+
if (value === undefined)
|
|
569
|
+
return undefined;
|
|
570
|
+
if (typeof value !== "string" || value.length === 0)
|
|
571
|
+
throw new BrowserPoolStateError(`Browser pool state field ${key} must be a non-empty string when present`);
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
function readPositiveInteger(value, key) {
|
|
575
|
+
if (!Number.isInteger(value) || typeof value !== "number" || value < 1)
|
|
576
|
+
throw new BrowserPoolStateError(`Browser pool state field ${key} must be a positive integer`);
|
|
577
|
+
return value;
|
|
578
|
+
}
|
|
579
|
+
function readOptionalPositiveInteger(value, key) {
|
|
580
|
+
if (value === undefined)
|
|
581
|
+
return undefined;
|
|
582
|
+
return readPositiveInteger(value, key);
|
|
583
|
+
}
|
|
584
|
+
function readOptionalNonNegativeInteger(value, key) {
|
|
585
|
+
if (value === undefined)
|
|
586
|
+
return undefined;
|
|
587
|
+
if (!Number.isInteger(value) || typeof value !== "number" || value < 0)
|
|
588
|
+
throw new BrowserPoolStateError(`Browser pool state field ${key} must be a non-negative integer when present`);
|
|
589
|
+
return value;
|
|
590
|
+
}
|
|
591
|
+
function readLifecycleState(value) {
|
|
592
|
+
if (value === "empty" || value === "ready" || value === "leased" || value === "stale" || value === "dirty")
|
|
593
|
+
return value;
|
|
594
|
+
throw new BrowserPoolStateError("Browser pool state field state is invalid");
|
|
595
|
+
}
|
|
596
|
+
function readOptionalCleanupStatus(value) {
|
|
597
|
+
if (value === undefined)
|
|
598
|
+
return undefined;
|
|
599
|
+
if (value === "not-attempted" || value === "success" || value === "failed" || value === "skipped")
|
|
600
|
+
return value;
|
|
601
|
+
throw new BrowserPoolStateError("Browser pool state field cleanupStatus is invalid");
|
|
602
|
+
}
|
|
603
|
+
async function fetchWithTimeout(url, timeoutMs) {
|
|
604
|
+
const controller = new AbortController();
|
|
605
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
606
|
+
try {
|
|
607
|
+
return await fetch(url, { signal: controller.signal });
|
|
608
|
+
}
|
|
609
|
+
finally {
|
|
610
|
+
clearTimeout(timer);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function isNodeError(error) {
|
|
614
|
+
return error instanceof Error && "code" in error;
|
|
615
|
+
}
|
|
616
|
+
function toBrowserPoolStateError(error) {
|
|
617
|
+
if (error instanceof BrowserPoolStateError)
|
|
618
|
+
return error;
|
|
619
|
+
return new BrowserPoolStateError(error instanceof Error ? error.message : String(error));
|
|
620
|
+
}
|
|
621
|
+
function delay(ms) {
|
|
622
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
623
|
+
}
|