@pasko70/pibo 1.1.0 → 1.3.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/chat-vscode-web/assets/index-B5QK07zO.css +2 -0
- package/dist/apps/chat-vscode-web/assets/index-C3GTPyDo.js +41 -0
- package/dist/apps/chat-vscode-web/index.html +14 -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/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-1.3.0.vsix +0 -0
- 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 +159 -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/vscode/cli.js +90 -0
- package/dist/vscode/code-cli.js +119 -0
- package/dist/vscode/install.js +177 -0
- package/dist/vscode/status.js +80 -0
- package/dist/vscode/types.js +14 -0
- package/dist/vscode/uninstall.js +49 -0
- package/dist/vscode/vsix-fetcher.js +131 -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/docs/ops/vscode-extension-release.md +138 -0
- package/package.json +24 -9
- 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,152 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { createDefaultPiboRalphStore } from './store.js';
|
|
4
|
+
import { createBuiltInRalphStopConditions } from './stopping.js';
|
|
5
|
+
import { getRalphJobTemplate, listRalphJobTemplates } from './templates.js';
|
|
6
|
+
function printDiscovery() {
|
|
7
|
+
console.log(`pibo ralph
|
|
8
|
+
|
|
9
|
+
Manage continuous Ralph Pibo agent jobs.
|
|
10
|
+
|
|
11
|
+
Commands:
|
|
12
|
+
status Show Ralph status
|
|
13
|
+
list List Ralph jobs
|
|
14
|
+
add Create a Ralph job
|
|
15
|
+
edit Update a Ralph job
|
|
16
|
+
conditions List registered built-in stop conditions
|
|
17
|
+
templates List built-in Ralph job templates
|
|
18
|
+
policy Show, set, or clear a job stop policy
|
|
19
|
+
start Start a Ralph job now
|
|
20
|
+
stop Stop after the current session finishes
|
|
21
|
+
cancel Abort the current session and stop
|
|
22
|
+
remove Delete a Ralph job
|
|
23
|
+
runs List Ralph runs
|
|
24
|
+
|
|
25
|
+
Next: pibo ralph add --help`);
|
|
26
|
+
}
|
|
27
|
+
function targetFromOptions(options) { if (options.room)
|
|
28
|
+
return { kind: 'room', roomId: options.room }; if (options.defaultChat)
|
|
29
|
+
return { kind: 'default-chat' }; throw new Error('Choose a target: --room <room-id> or --default-chat'); }
|
|
30
|
+
function maybeTargetFromOptions(options) { if (options.room || options.defaultChat)
|
|
31
|
+
return targetFromOptions(options); return undefined; }
|
|
32
|
+
function printJson(value) { console.log(JSON.stringify(value, null, 2)); }
|
|
33
|
+
function maxIterations(value) { if (value === undefined)
|
|
34
|
+
return undefined; const parsed = Number(value); if (!Number.isInteger(parsed) || parsed < 1)
|
|
35
|
+
throw new Error('--max-iterations must be a positive integer'); return parsed; }
|
|
36
|
+
function templatePatch(id) {
|
|
37
|
+
if (!id)
|
|
38
|
+
return {};
|
|
39
|
+
const template = getRalphJobTemplate(id);
|
|
40
|
+
if (!template)
|
|
41
|
+
throw new Error(`Unknown Ralph template: ${id}`);
|
|
42
|
+
return { name: template.job.name, description: template.job.description, prompt: template.job.prompt, maxIterations: template.job.maxIterations ?? null, stopPolicy: template.job.stopPolicy ?? null };
|
|
43
|
+
}
|
|
44
|
+
function compactResourceText(value, max = 80) { return value.length <= max ? value : `${value.slice(0, max - 1)}…`; }
|
|
45
|
+
function shellToken(value) { return /^[A-Za-z0-9._:@/-]+$/.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`; }
|
|
46
|
+
export function formatRalphResourceSummary(resources) {
|
|
47
|
+
if (!resources)
|
|
48
|
+
return '-';
|
|
49
|
+
const parts = [
|
|
50
|
+
resources.workerId ? `worker=${resources.workerId}` : undefined,
|
|
51
|
+
resources.cleanupState ? `state=${resources.cleanupState}` : undefined,
|
|
52
|
+
resources.browserLeaseIds?.length ? `leases=${resources.browserLeaseIds.length}` : undefined,
|
|
53
|
+
resources.retainedUntil ? `retainedUntil=${resources.retainedUntil}` : undefined,
|
|
54
|
+
resources.dirtyReason ? `dirty=${compactResourceText(resources.dirtyReason)}` : undefined,
|
|
55
|
+
].filter((part) => !!part);
|
|
56
|
+
if (resources.cleanupState === 'dirty')
|
|
57
|
+
parts.push(`next=${resources.workerId ? `pibo tools browser-use pool reap --worker-id ${shellToken(resources.workerId)} --json` : 'pibo compute reap --dry-run'}`);
|
|
58
|
+
else if (resources.cleanupState === 'retained')
|
|
59
|
+
parts.push('next=pibo compute reap --dry-run --include-dev');
|
|
60
|
+
return parts.length ? parts.join(';') : '-';
|
|
61
|
+
}
|
|
62
|
+
function formatRalphJobLine(job) { return `${job.id}\t${job.enabled ? 'running' : 'stopped'}\t${job.state.runningAt ? 'active' : '-'}\tresources=${formatRalphResourceSummary(job.resources)}\t${job.name}`; }
|
|
63
|
+
function formatRalphRunLine(run) { return `${run.id}\t${run.jobId}\t${run.status}\t${run.piboSessionId ?? '-'}\t${run.completedAt ?? '-'}\tresources=${formatRalphResourceSummary(run.resources)}`; }
|
|
64
|
+
export async function runRalphCli(argv = process.argv) {
|
|
65
|
+
const program = new Command();
|
|
66
|
+
program.name('pibo ralph').description('Manage continuous Ralph Pibo jobs').helpOption('-h, --help');
|
|
67
|
+
program.option('--store <path>', 'Ralph store path');
|
|
68
|
+
program.command('status').description('Show Ralph store status').option('--json', 'Print JSON').action((options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const status = store.status(); if (options.json)
|
|
69
|
+
printJson(status);
|
|
70
|
+
else {
|
|
71
|
+
console.log(`jobs\t${status.jobs}`);
|
|
72
|
+
console.log(`running\t${status.running}`);
|
|
73
|
+
} store.close(); });
|
|
74
|
+
program.command('list').description('List Ralph jobs').option('--all', 'Include stopped jobs').option('--json', 'Print JSON').action((options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const jobs = store.listJobs({ includeDisabled: options.all }); if (options.json)
|
|
75
|
+
printJson(jobs);
|
|
76
|
+
else
|
|
77
|
+
for (const job of jobs)
|
|
78
|
+
console.log(formatRalphJobLine(job)); store.close(); });
|
|
79
|
+
program.command('add').description('Create a Ralph job').option('--template <id>', 'Built-in job template id').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile', 'default').option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts')
|
|
80
|
+
.option('--start', 'Start immediately').option('--json', 'Print JSON').action((options) => { const base = templatePatch(options.template); const prompt = options.prompt ?? base.prompt; if (typeof prompt !== 'string' || !prompt.trim())
|
|
81
|
+
throw new Error('Choose --template <id> or provide --prompt <text>'); const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.createJob({ name: options.name ?? base.name, description: options.description ?? base.description, enabled: options.start === true, target: targetFromOptions(options), profile: options.profile, prompt, maxIterations: options.maxIterations !== undefined ? maxIterations(options.maxIterations) : typeof base.maxIterations === 'number' ? base.maxIterations : undefined, stopPolicy: base.stopPolicy ?? undefined }); if (options.json)
|
|
82
|
+
printJson(job);
|
|
83
|
+
else
|
|
84
|
+
console.log(`${job.id}\t${job.enabled ? 'running' : 'stopped'}\t${job.name}`); store.close(); });
|
|
85
|
+
program.command('edit').argument('<id>', 'Ralph job id').description('Update a Ralph job').option('--template <id>', 'Apply a built-in job template before explicit overrides').option('--prompt <text>', 'Task prompt').option('--name <name>', 'Job name').option('--description <text>', 'Job description').option('--profile <profile>', 'Agent profile').option('--room <room-id>', 'Target room id').option('--default-chat', 'Target the shared default chat').option('--max-iterations <n>', 'Stop after n completed run attempts').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const patch = { ...templatePatch(options.template) }; if (options.name !== undefined)
|
|
86
|
+
patch.name = options.name; if (options.description !== undefined)
|
|
87
|
+
patch.description = options.description; if (options.profile !== undefined)
|
|
88
|
+
patch.profile = options.profile; if (options.prompt !== undefined)
|
|
89
|
+
patch.prompt = options.prompt; if (options.maxIterations !== undefined)
|
|
90
|
+
patch.maxIterations = maxIterations(options.maxIterations); const target = maybeTargetFromOptions(options); if (target)
|
|
91
|
+
patch.target = target; if (Object.keys(patch).length === 0)
|
|
92
|
+
throw new Error('No Ralph job update fields provided'); const job = store.updateJob(id, patch); if (!job)
|
|
93
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
94
|
+
printJson(job);
|
|
95
|
+
else
|
|
96
|
+
console.log(`${job.id}\tupdated\t${job.name}`); store.close(); });
|
|
97
|
+
program.command('conditions').description('List built-in Ralph stop-condition types').option('--json', 'Print JSON').action((options) => { const conditions = createBuiltInRalphStopConditions().map((condition) => ({ type: condition.type, name: condition.name, description: condition.description, phases: condition.phases, defaultOptions: condition.defaultOptions, optionsSchema: condition.optionsSchema })); if (options.json)
|
|
98
|
+
printJson(conditions);
|
|
99
|
+
else
|
|
100
|
+
for (const condition of conditions)
|
|
101
|
+
console.log(`${condition.type} ${condition.phases.join(',')} ${condition.name}`); });
|
|
102
|
+
program.command('templates').description('List built-in Ralph job templates').option('--json', 'Print JSON').action((options) => { const templates = listRalphJobTemplates(); if (options.json)
|
|
103
|
+
printJson(templates);
|
|
104
|
+
else
|
|
105
|
+
for (const template of templates)
|
|
106
|
+
console.log(`${template.id}\t${template.category}\t${template.name}\t${template.description}`); });
|
|
107
|
+
const policy = program.command('policy').description('Manage a Ralph job stop policy');
|
|
108
|
+
policy.command('show').argument('<id>').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.getJob(id); if (!job)
|
|
109
|
+
throw new Error('Ralph job not found'); const value = job.stopPolicy ?? null; if (options.json)
|
|
110
|
+
printJson(value);
|
|
111
|
+
else
|
|
112
|
+
console.log(value ? JSON.stringify(value, null, 2) : 'default'); store.close(); });
|
|
113
|
+
policy.command('set').argument('<id>').requiredOption('--file <path>', 'JSON stop policy file').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const stopPolicy = JSON.parse(readFileSync(options.file, 'utf8')); const job = store.updateJob(id, { stopPolicy }); if (!job)
|
|
114
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
115
|
+
printJson(job);
|
|
116
|
+
else
|
|
117
|
+
console.log(`${job.id} policy-updated ${job.name}`); store.close(); });
|
|
118
|
+
policy.command('clear').argument('<id>').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.updateJob(id, { stopPolicy: null }); if (!job)
|
|
119
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
120
|
+
printJson(job);
|
|
121
|
+
else
|
|
122
|
+
console.log(`${job.id} policy-cleared ${job.name}`); store.close(); });
|
|
123
|
+
program.command('start').argument('<id>').description('Mark a Ralph job running; the gateway service starts the next session').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.updateJob(id, { enabled: true }); if (!job)
|
|
124
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
125
|
+
printJson(job);
|
|
126
|
+
else
|
|
127
|
+
console.log(`${job.id}\trunning\t${job.name}`); store.close(); });
|
|
128
|
+
program.command('stop').argument('<id>').description('Stop after the current session finishes').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.requestStop(id); if (!job)
|
|
129
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
130
|
+
printJson(job);
|
|
131
|
+
else
|
|
132
|
+
console.log(`${job.id}\tstopping\t${job.name}`); store.close(); });
|
|
133
|
+
program.command('cancel').argument('<id>').description('Abort the current session and stop').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const job = store.requestCancel(id); if (!job)
|
|
134
|
+
throw new Error('Ralph job not found'); if (options.json)
|
|
135
|
+
printJson(job);
|
|
136
|
+
else
|
|
137
|
+
console.log(`${job.id}\tcancel-requested\t${job.name}`); store.close(); });
|
|
138
|
+
program.command('remove').argument('<id>').description('Delete a Ralph job').option('--json', 'Print JSON').action((id, options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const removed = store.removeJob(id); if (options.json)
|
|
139
|
+
printJson({ removed });
|
|
140
|
+
else
|
|
141
|
+
console.log(removed ? 'removed' : 'not found'); store.close(); });
|
|
142
|
+
program.command('runs').description('List Ralph runs').option('--job <id>', 'Filter by job').option('--json', 'Print JSON').action((options) => { const store = createDefaultPiboRalphStore({ path: program.opts().store }); const runs = store.listRuns({ jobId: options.job }); if (options.json)
|
|
143
|
+
printJson(runs);
|
|
144
|
+
else
|
|
145
|
+
for (const run of runs)
|
|
146
|
+
console.log(formatRalphRunLine(run)); store.close(); });
|
|
147
|
+
if (argv.length <= 2 || argv.includes('--help') || argv.includes('-h')) {
|
|
148
|
+
printDiscovery();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
await program.parseAsync(argv);
|
|
152
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { definePiboPlugin } from '../plugins/registry.js';
|
|
2
|
+
import { createPiboRalphChannel } from './channel.js';
|
|
3
|
+
import { createBuiltInRalphStopConditions } from './stopping.js';
|
|
4
|
+
export function createPiboRalphPlugin(options = {}) {
|
|
5
|
+
return definePiboPlugin({
|
|
6
|
+
id: 'pibo.ralph',
|
|
7
|
+
name: 'Pibo Ralph',
|
|
8
|
+
register(api) {
|
|
9
|
+
for (const condition of createBuiltInRalphStopConditions())
|
|
10
|
+
api.registerRalphStopCondition(condition);
|
|
11
|
+
api.registerChannel(createPiboRalphChannel(options));
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getDefaultPiboWorkspace } from '../core/workspace.js';
|
|
5
|
+
import { PiboDataStore } from '../data/pibo-store.js';
|
|
6
|
+
import { ChatRoomService } from '../apps/chat/data/room-service.js';
|
|
7
|
+
import { isPiboRoomArchived } from '../apps/chat/types/rooms.js';
|
|
8
|
+
import { browserPoolPaths, releaseBrowserPoolLease } from '../tools/browser-pool.js';
|
|
9
|
+
import { createDefaultPiboRalphStore } from './store.js';
|
|
10
|
+
import { createBuiltInRalphStopConditions, evaluateRalphStopPolicy } from './stopping.js';
|
|
11
|
+
const CHAT_WEB_CHANNEL = 'pibo.chat-web';
|
|
12
|
+
function errorMessage(error) { return error instanceof Error ? error.message : String(error); }
|
|
13
|
+
function buildRalphPrompt(job) { return ['You are running a continuous Pibo Ralph job.', `Job: ${job.name}`, `Target: ${job.target.kind}`, '', 'Complete the task below. Return the result in this session. When this session finishes, Ralph may start a fresh session with the same task unless a configured stop condition is satisfied.', 'Important: if this job uses a promise-complete stop condition, do not quote, negate, explain, or mention its literal completion marker unless the task is fully complete and you intend to stop the job.', '', 'Task:', job.prompt].join('\n'); }
|
|
14
|
+
function isJsonObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
|
|
15
|
+
function mergeRunResources(jobResources, runResources) {
|
|
16
|
+
if (!jobResources && !runResources)
|
|
17
|
+
return undefined;
|
|
18
|
+
const browserLeaseIds = [...new Set([...(jobResources?.browserLeaseIds ?? []), ...(runResources?.browserLeaseIds ?? [])])];
|
|
19
|
+
return {
|
|
20
|
+
...(jobResources ?? {}),
|
|
21
|
+
...(runResources ?? {}),
|
|
22
|
+
...(browserLeaseIds.length ? { browserLeaseIds } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function clearDirtyReason(resources) {
|
|
26
|
+
const clean = { ...resources };
|
|
27
|
+
delete clean.dirtyReason;
|
|
28
|
+
return clean;
|
|
29
|
+
}
|
|
30
|
+
export class PiboRalphService {
|
|
31
|
+
options;
|
|
32
|
+
store;
|
|
33
|
+
dataStore;
|
|
34
|
+
roomService;
|
|
35
|
+
intervalMs;
|
|
36
|
+
maxConcurrentRuns;
|
|
37
|
+
runTimeoutMs;
|
|
38
|
+
timer;
|
|
39
|
+
activeRuns = 0;
|
|
40
|
+
stopped = true;
|
|
41
|
+
cancelledRuns = new Set();
|
|
42
|
+
unsubscribeProductEvents;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.store = options.store ?? createDefaultPiboRalphStore();
|
|
46
|
+
this.dataStore = new PiboDataStore(options.dataStorePath, { payloadRootDir: options.dataPayloadRootDir });
|
|
47
|
+
this.roomService = new ChatRoomService(this.dataStore);
|
|
48
|
+
this.intervalMs = options.intervalMs ?? 5_000;
|
|
49
|
+
this.maxConcurrentRuns = Math.max(1, options.maxConcurrentRuns ?? 2);
|
|
50
|
+
this.runTimeoutMs = options.runTimeoutMs ?? 30 * 60_000;
|
|
51
|
+
}
|
|
52
|
+
start() { if (!this.stopped)
|
|
53
|
+
return; this.stopped = false; this.store.recoverInterruptedRuns(); this.unsubscribeProductEvents = this.options.context.subscribeProductEvents?.((event) => this.handleProductEvent(event)); this.arm(250); }
|
|
54
|
+
stop() { this.stopped = true; if (this.timer)
|
|
55
|
+
clearTimeout(this.timer); this.timer = undefined; this.unsubscribeProductEvents?.(); this.unsubscribeProductEvents = undefined; this.dataStore.close(); this.store.close(); }
|
|
56
|
+
status() { return { enabled: !this.stopped, ...this.store.status() }; }
|
|
57
|
+
async startJob(id) { const job = this.store.updateJob(id, { enabled: true }); if (!job)
|
|
58
|
+
return undefined; const reserved = await this.reserveAfterBeforeRunEvaluation(job); if (!reserved)
|
|
59
|
+
return undefined; void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon()); return reserved.run; }
|
|
60
|
+
stopJob(id) { const job = this.store.requestStop(id); this.armSoon(); return job; }
|
|
61
|
+
async cancelJob(id) {
|
|
62
|
+
const job = this.store.requestCancel(id);
|
|
63
|
+
if (!job)
|
|
64
|
+
return undefined;
|
|
65
|
+
await this.abortJobIfRunning(job);
|
|
66
|
+
this.armSoon();
|
|
67
|
+
return this.store.getJob(id);
|
|
68
|
+
}
|
|
69
|
+
arm(delayMs) { if (this.stopped)
|
|
70
|
+
return; if (this.timer)
|
|
71
|
+
clearTimeout(this.timer); this.timer = setTimeout(() => void this.tick(), delayMs ?? this.intervalMs); }
|
|
72
|
+
armSoon() { this.arm(250); }
|
|
73
|
+
async tick() { if (this.stopped)
|
|
74
|
+
return; try {
|
|
75
|
+
await this.abortCancelRequestedJobs();
|
|
76
|
+
const capacity = this.maxConcurrentRuns - this.activeRuns;
|
|
77
|
+
if (capacity > 0) {
|
|
78
|
+
const jobs = this.store.listJobs({ includeDisabled: false }).filter((job) => !job.state.runningAt).slice(0, capacity);
|
|
79
|
+
for (const job of jobs) {
|
|
80
|
+
const reserved = await this.reserveAfterBeforeRunEvaluation(job);
|
|
81
|
+
if (reserved)
|
|
82
|
+
void this.executeReserved(reserved.job, reserved.run).finally(() => this.armSoon());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error('[ralph] scheduler tick failed', error);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
this.arm();
|
|
91
|
+
} }
|
|
92
|
+
async reserveAfterBeforeRunEvaluation(job) {
|
|
93
|
+
const fresh = this.store.getJob(job.id) ?? job;
|
|
94
|
+
if (!fresh.enabled || fresh.state.runningAt)
|
|
95
|
+
return undefined;
|
|
96
|
+
const { evaluation, conditionStates } = await this.evaluateStopPolicy(fresh, 'before-run');
|
|
97
|
+
if (evaluation.finalAction !== 'continue') {
|
|
98
|
+
this.store.applyStopEvaluation({ jobId: fresh.id, evaluation, conditionStates, disable: true });
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
this.store.applyStopEvaluation({ jobId: fresh.id, evaluation, conditionStates, disable: false });
|
|
102
|
+
return this.store.reserveRun(fresh.id);
|
|
103
|
+
}
|
|
104
|
+
async abortCancelRequestedJobs() { for (const job of this.store.listJobs({ includeDisabled: true })) {
|
|
105
|
+
if (job.state.cancelRequestedAt)
|
|
106
|
+
await this.abortJobIfRunning(job);
|
|
107
|
+
} }
|
|
108
|
+
async abortJobIfRunning(job) {
|
|
109
|
+
if (!job.state.runningAt || !job.state.lastRunId)
|
|
110
|
+
return;
|
|
111
|
+
const sessionId = job.state.lastPiboSessionId;
|
|
112
|
+
if (!sessionId) {
|
|
113
|
+
this.markRunResourcesDirty(job, 'Cancel requested but active session is unavailable; browser lease may still exist');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.cancelledRuns.add(job.state.lastRunId);
|
|
117
|
+
try {
|
|
118
|
+
await this.options.context.emit({ type: 'execution', piboSessionId: sessionId, action: 'abort', id: `ralph_cancel_${randomUUID()}` });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
this.cancelledRuns.delete(job.state.lastRunId);
|
|
122
|
+
this.markRunResourcesDirty(job, `Cancel requested but abort failed: ${errorMessage(error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async executeReserved(job, run) {
|
|
126
|
+
this.activeRuns += 1;
|
|
127
|
+
try {
|
|
128
|
+
const result = await this.executeJob(job, run);
|
|
129
|
+
const cancelled = this.cancelledRuns.delete(run.id);
|
|
130
|
+
const outcome = { status: cancelled ? 'cancelled' : 'ok', piboSessionId: result.piboSessionId, finalAnswer: result.finalAnswer };
|
|
131
|
+
const { evaluation, conditionStates } = await this.evaluateStopPolicy(this.store.getJob(job.id) ?? job, 'after-run', run, outcome);
|
|
132
|
+
this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, piboSessionId: result.piboSessionId, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates });
|
|
133
|
+
await this.cleanupRunResources(job, run);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const cancelled = this.cancelledRuns.delete(run.id);
|
|
137
|
+
const outcome = { status: cancelled ? 'cancelled' : 'error', error: cancelled ? undefined : errorMessage(error) };
|
|
138
|
+
const { evaluation, conditionStates } = await this.evaluateStopPolicy(this.store.getJob(job.id) ?? job, 'after-run', run, outcome);
|
|
139
|
+
this.store.completeRun({ jobId: job.id, runId: run.id, status: outcome.status, error: outcome.error, reason: cancelled ? 'cancelled' : evaluation.reason, stopAfterRun: evaluation.finalAction !== 'continue', stopEvaluation: evaluation, conditionStates });
|
|
140
|
+
await this.cleanupRunResources(job, run);
|
|
141
|
+
if (!cancelled)
|
|
142
|
+
console.error(`[ralph] job ${job.id} failed`, error);
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
this.activeRuns -= 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
markRunResourcesDirty(job, dirtyReason) {
|
|
149
|
+
const latestJob = this.store.getJob(job.id) ?? job;
|
|
150
|
+
const runId = latestJob.state.lastRunId ?? job.state.lastRunId;
|
|
151
|
+
const latestRun = runId ? this.store.listRuns({ jobId: latestJob.id, limit: 100 }).find((candidate) => candidate.id === runId) : undefined;
|
|
152
|
+
const resources = mergeRunResources(latestJob.resources, latestRun?.resources);
|
|
153
|
+
if (!resources || (!resources.workerId && (resources.browserLeaseIds ?? []).length === 0))
|
|
154
|
+
return;
|
|
155
|
+
const nextResources = { ...resources, cleanupState: 'dirty', dirtyReason, updatedAt: new Date().toISOString() };
|
|
156
|
+
try {
|
|
157
|
+
if (latestRun)
|
|
158
|
+
this.store.updateRunResources({ jobId: latestJob.id, runId: latestRun.id, resources: nextResources });
|
|
159
|
+
this.store.updateJobResources(latestJob.id, nextResources);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
console.error(`[ralph] failed to mark resource cleanup dirty for job ${latestJob.id}`, error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async cleanupRunResources(job, run) {
|
|
166
|
+
const latestJob = this.store.getJob(job.id) ?? job;
|
|
167
|
+
const latestRun = this.store.listRuns({ jobId: job.id, limit: 100 }).find((candidate) => candidate.id === run.id) ?? run;
|
|
168
|
+
const resources = mergeRunResources(latestJob.resources, latestRun.resources);
|
|
169
|
+
const leaseIds = resources?.browserLeaseIds ?? [];
|
|
170
|
+
if (!resources || leaseIds.length === 0)
|
|
171
|
+
return;
|
|
172
|
+
const workerId = resources.workerId || process.env.PIBO_BROWSER_POOL_WORKER_ID || process.env.PIBO_COMPUTE_WORKER_ID || process.env.HOSTNAME || 'local';
|
|
173
|
+
const poolId = this.options.resourceCleanup?.browserPoolId || process.env.PIBO_BROWSER_POOL_ID || 'default';
|
|
174
|
+
const rootDir = this.options.resourceCleanup?.browserPoolRootDir || process.env.PIBO_BROWSER_POOL_ROOT || join(process.env.BROWSER_USE_HOME || join(homedir(), '.browser-use'), 'pibo-browser-pool');
|
|
175
|
+
const identity = { workerId, poolId };
|
|
176
|
+
const paths = browserPoolPaths(rootDir, identity);
|
|
177
|
+
const release = this.options.resourceCleanup?.releaseBrowserPoolLease ?? releaseBrowserPoolLease;
|
|
178
|
+
let dirtyReason;
|
|
179
|
+
for (const leaseId of leaseIds) {
|
|
180
|
+
try {
|
|
181
|
+
const result = await release(paths, identity, { leaseId, lockOptions: { holder: `ralph:${run.id}` } });
|
|
182
|
+
if (result.cleanupStatus === 'failed' || result.state.state === 'dirty' || (!result.released && !!result.state.activeLeaseId))
|
|
183
|
+
dirtyReason = result.lastError || `Browser lease ${leaseId} cleanup failed`;
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
dirtyReason = `Browser lease ${leaseId} cleanup failed: ${errorMessage(error)}`;
|
|
187
|
+
}
|
|
188
|
+
if (dirtyReason)
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
const updatedAt = new Date().toISOString();
|
|
192
|
+
const nextResources = dirtyReason
|
|
193
|
+
? { ...resources, workerId, cleanupState: 'dirty', dirtyReason, updatedAt }
|
|
194
|
+
: clearDirtyReason({ ...resources, workerId, cleanupState: 'released', updatedAt });
|
|
195
|
+
try {
|
|
196
|
+
this.store.updateRunResources({ jobId: job.id, runId: run.id, resources: nextResources });
|
|
197
|
+
this.store.updateJobResources(job.id, nextResources);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.error(`[ralph] failed to record resource cleanup for run ${run.id}`, error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async evaluateStopPolicy(job, phase, run, outcome) {
|
|
204
|
+
return await evaluateRalphStopPolicy({ job, phase, definitions: this.getStopConditionDefinitions(), facts: this.store.createFactReader(job), run, outcome });
|
|
205
|
+
}
|
|
206
|
+
getStopConditionDefinitions() { return this.options.context.getRalphStopConditionDefinitions?.() ?? createBuiltInRalphStopConditions(); }
|
|
207
|
+
handleProductEvent(event) {
|
|
208
|
+
if (event.type !== 'pibo.ralph.fact' && event.type !== 'ralph.fact')
|
|
209
|
+
return;
|
|
210
|
+
const payload = event.payload;
|
|
211
|
+
if (!isJsonObject(payload.payload))
|
|
212
|
+
return;
|
|
213
|
+
if (typeof payload.jobId !== 'string' || typeof payload.type !== 'string')
|
|
214
|
+
return;
|
|
215
|
+
const source = payload.source === 'pi-extension' || payload.source === 'tool' || payload.source === 'plugin' || payload.source === 'pibo' ? payload.source : 'plugin';
|
|
216
|
+
try {
|
|
217
|
+
this.store.appendRunFact({ jobId: payload.jobId, runId: typeof payload.runId === 'string' ? payload.runId : undefined, piboSessionId: typeof payload.piboSessionId === 'string' ? payload.piboSessionId : undefined, type: payload.type, source: source, payload: payload.payload });
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
console.error('[ralph] failed to append run fact', error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async executeJob(job, run) {
|
|
224
|
+
const target = this.resolveTarget(job);
|
|
225
|
+
const session = this.options.context.createSession({
|
|
226
|
+
channel: CHAT_WEB_CHANNEL,
|
|
227
|
+
kind: 'ralph',
|
|
228
|
+
profile: job.profile,
|
|
229
|
+
workspace: target.workspace ?? getDefaultPiboWorkspace(),
|
|
230
|
+
title: job.name,
|
|
231
|
+
activeModel: job.modelOverride ? { ...job.modelOverride } : undefined,
|
|
232
|
+
metadata: {
|
|
233
|
+
...(target.metadata ?? {}),
|
|
234
|
+
chatRoomId: target.roomId,
|
|
235
|
+
ralphJobId: job.id,
|
|
236
|
+
ralphRunId: run.id,
|
|
237
|
+
ralphTargetKind: job.target.kind,
|
|
238
|
+
...(job.thinkingLevel ? { initialThinkingLevel: job.thinkingLevel } : {}),
|
|
239
|
+
...(job.fastMode !== undefined ? { initialFastMode: job.fastMode } : {}),
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
this.store.attachRunSession(job.id, run.id, session.id);
|
|
243
|
+
const finalAnswer = await this.emitMessageAndWait(session.id, buildRalphPrompt(job), { isCancelled: () => this.cancelledRuns.has(run.id) });
|
|
244
|
+
return { piboSessionId: session.id, finalAnswer };
|
|
245
|
+
}
|
|
246
|
+
resolveTarget(job) { if (job.target.kind === 'room') {
|
|
247
|
+
const room = this.roomService.getRoom(job.target.roomId);
|
|
248
|
+
if (!room)
|
|
249
|
+
throw new Error('Target room no longer exists');
|
|
250
|
+
if (isPiboRoomArchived(room))
|
|
251
|
+
throw new Error('Target room is archived');
|
|
252
|
+
return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() };
|
|
253
|
+
} const room = this.roomService.ensureDefaultRoom({ name: 'Shared Chat' }); return { roomId: room.id, workspace: room.workspace ?? getDefaultPiboWorkspace() }; }
|
|
254
|
+
async emitMessageAndWait(piboSessionId, text, options = {}) {
|
|
255
|
+
const eventId = `ralph_msg_${randomUUID()}`;
|
|
256
|
+
return await new Promise((resolve, reject) => { let settled = false; let deltaAnswer = ''; let finalAnswer = ''; let lastSessionError; let unsubscribe; const timeout = setTimeout(() => finish(new Error(lastSessionError ? `Ralph run timed out after session error: ${lastSessionError}` : 'Ralph run timed out')), this.runTimeoutMs); const finish = (error) => { if (settled)
|
|
257
|
+
return; settled = true; clearTimeout(timeout); unsubscribe?.(); if (error)
|
|
258
|
+
reject(error);
|
|
259
|
+
else
|
|
260
|
+
resolve(finalAnswer || deltaAnswer); }; unsubscribe = this.options.context.subscribe((event) => { if (event.piboSessionId !== piboSessionId)
|
|
261
|
+
return; if ('eventId' in event && event.eventId !== eventId)
|
|
262
|
+
return; if (event.type === 'assistant_delta')
|
|
263
|
+
deltaAnswer += event.text; if (event.type === 'assistant_message')
|
|
264
|
+
finalAnswer = event.text; if (event.type === 'message_finished')
|
|
265
|
+
finish(); if (event.type === 'session_error') {
|
|
266
|
+
lastSessionError = event.error;
|
|
267
|
+
if (options.isCancelled?.())
|
|
268
|
+
finish(new Error(event.error));
|
|
269
|
+
} }); this.options.context.emit({ type: 'message', piboSessionId, id: eventId, source: 'service', text }).catch((error) => finish(error instanceof Error ? error : new Error(String(error)))); });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export const PROMISE_COMPLETE_STOP_TOKEN = '<promise>COMPLETE</promise>';
|
|
3
|
+
export const MAX_ITERATIONS_STOP_CONDITION = 'pibo.ralph.max-iterations';
|
|
4
|
+
export const PROMISE_COMPLETE_STOP_CONDITION = 'pibo.ralph.promise-complete';
|
|
5
|
+
export const FACT_COUNT_STOP_CONDITION = 'pibo.ralph.fact-count';
|
|
6
|
+
function hasPromiseCompleteMarker(finalAnswer) { return finalAnswer.split(/\r?\n/).some((line) => line.trim() === PROMISE_COMPLETE_STOP_TOKEN); }
|
|
7
|
+
function isObject(value) { return !!value && typeof value === 'object' && !Array.isArray(value); }
|
|
8
|
+
function stringOption(options, key) { const value = options?.[key]; return typeof value === 'string' && value.trim() ? value.trim() : undefined; }
|
|
9
|
+
function numberOption(options, key) { const value = options?.[key]; return typeof value === 'number' && Number.isFinite(value) ? value : undefined; }
|
|
10
|
+
function boolOption(options, key) { const value = options?.[key]; return typeof value === 'boolean' ? value : undefined; }
|
|
11
|
+
export function getDefaultRalphStopPolicy(job) {
|
|
12
|
+
const conditions = [];
|
|
13
|
+
if (job.maxIterations !== undefined)
|
|
14
|
+
conditions.push({ id: 'max-iterations', type: MAX_ITERATIONS_STOP_CONDITION });
|
|
15
|
+
conditions.push({ id: 'promise-complete', type: PROMISE_COMPLETE_STOP_CONDITION });
|
|
16
|
+
return { mode: 'any', conditions };
|
|
17
|
+
}
|
|
18
|
+
export function getEffectiveRalphStopPolicy(job) {
|
|
19
|
+
return job.stopPolicy ?? getDefaultRalphStopPolicy(job);
|
|
20
|
+
}
|
|
21
|
+
export function createBuiltInRalphStopConditions() {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
type: MAX_ITERATIONS_STOP_CONDITION,
|
|
25
|
+
name: 'Max iterations',
|
|
26
|
+
description: 'Stops a Ralph job after its configured number of completed run attempts, regardless of outcome.',
|
|
27
|
+
phases: ['before-run', 'after-run'],
|
|
28
|
+
evaluate(context) {
|
|
29
|
+
const maxIterations = context.job.maxIterations;
|
|
30
|
+
if (maxIterations === undefined)
|
|
31
|
+
return { action: 'continue' };
|
|
32
|
+
const current = context.job.state.completedIterations ?? 0;
|
|
33
|
+
const completed = context.phase === 'after-run' && context.outcome ? current + 1 : current;
|
|
34
|
+
if (completed >= maxIterations)
|
|
35
|
+
return { action: 'stop-after-run', reason: 'max-iterations', details: { maxIterations, completedIterations: completed } };
|
|
36
|
+
return { action: 'continue', details: { maxIterations, completedIterations: completed } };
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: PROMISE_COMPLETE_STOP_CONDITION,
|
|
41
|
+
name: 'Promise complete token',
|
|
42
|
+
description: 'Stops after a successful run whose final answer contains the promise-complete marker on its own line.',
|
|
43
|
+
phases: ['after-run'],
|
|
44
|
+
evaluate(context) {
|
|
45
|
+
const finalAnswer = context.outcome?.finalAnswer ?? '';
|
|
46
|
+
if (context.outcome?.status === 'ok' && hasPromiseCompleteMarker(finalAnswer))
|
|
47
|
+
return { action: 'stop-after-run', reason: 'promise-complete', details: { token: PROMISE_COMPLETE_STOP_TOKEN } };
|
|
48
|
+
return { action: 'continue' };
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: FACT_COUNT_STOP_CONDITION,
|
|
53
|
+
name: 'Run fact count',
|
|
54
|
+
description: 'Stops when the current run or job history contains at least a configured number of matching Ralph run facts.',
|
|
55
|
+
phases: ['after-run'],
|
|
56
|
+
defaultOptions: { factType: 'example.fact', threshold: 1, currentRunOnly: true },
|
|
57
|
+
optionsSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
factType: { type: 'string', description: 'Fact type to count.' },
|
|
61
|
+
threshold: { type: 'number', description: 'Stop after at least this many facts.' },
|
|
62
|
+
currentRunOnly: { type: 'boolean', description: 'Only count facts for the current run.' },
|
|
63
|
+
reason: { type: 'string', description: 'Optional stop reason.' },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
evaluate(context) {
|
|
67
|
+
const factType = stringOption(context.instance.options, 'factType');
|
|
68
|
+
if (!factType)
|
|
69
|
+
return { action: 'continue', details: { missingOption: 'factType' } };
|
|
70
|
+
const threshold = Math.max(1, Math.floor(numberOption(context.instance.options, 'threshold') ?? 1));
|
|
71
|
+
const currentRunOnly = boolOption(context.instance.options, 'currentRunOnly') ?? true;
|
|
72
|
+
const runId = currentRunOnly ? context.run?.id : undefined;
|
|
73
|
+
const count = context.facts.count({ type: factType, runId });
|
|
74
|
+
if (count >= threshold)
|
|
75
|
+
return { action: 'stop-after-run', reason: stringOption(context.instance.options, 'reason') ?? `fact:${factType}`, details: { factType, threshold, count, currentRunOnly } };
|
|
76
|
+
return { action: 'continue', details: { factType, threshold, count, currentRunOnly } };
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
function severity(action) {
|
|
82
|
+
if (action === 'cancel-current-run')
|
|
83
|
+
return 2;
|
|
84
|
+
if (action === 'stop-after-run')
|
|
85
|
+
return 1;
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
function highest(left, right) { return severity(right) > severity(left) ? right : left; }
|
|
89
|
+
function sanitizeDecision(value) {
|
|
90
|
+
if (!isObject(value))
|
|
91
|
+
throw new Error('condition returned a non-object decision');
|
|
92
|
+
if (value.action !== 'continue' && value.action !== 'stop-after-run' && value.action !== 'cancel-current-run')
|
|
93
|
+
throw new Error('condition returned an invalid action');
|
|
94
|
+
return { action: value.action, reason: typeof value.reason === 'string' ? value.reason : undefined, details: isObject(value.details) ? value.details : undefined, nextState: isObject(value.nextState) ? value.nextState : undefined };
|
|
95
|
+
}
|
|
96
|
+
async function withTimeout(promise, timeoutMs, signal) {
|
|
97
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
98
|
+
return await promise;
|
|
99
|
+
return await new Promise((resolve, reject) => {
|
|
100
|
+
const timeout = setTimeout(() => reject(new Error(`condition timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
101
|
+
if (signal)
|
|
102
|
+
signal.addEventListener('abort', () => reject(new Error('condition aborted')), { once: true });
|
|
103
|
+
promise.then((value) => { clearTimeout(timeout); resolve(value); }, (error) => { clearTimeout(timeout); reject(error); });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export async function evaluateRalphStopPolicy(input) {
|
|
107
|
+
const now = (input.now ?? new Date()).toISOString();
|
|
108
|
+
const policy = getEffectiveRalphStopPolicy(input.job);
|
|
109
|
+
const definitions = new Map(input.definitions.map((definition) => [definition.type, definition]));
|
|
110
|
+
const decisions = [];
|
|
111
|
+
const conditionStates = { ...(input.job.state.conditionStates ?? {}) };
|
|
112
|
+
let finalAction = 'continue';
|
|
113
|
+
for (const instance of policy.conditions) {
|
|
114
|
+
const base = { id: instance.id, type: instance.type, phase: input.phase };
|
|
115
|
+
if (instance.enabled === false) {
|
|
116
|
+
decisions.push({ ...base, action: 'continue', skipped: true, reason: 'disabled' });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const definition = definitions.get(instance.type);
|
|
120
|
+
if (!definition) {
|
|
121
|
+
decisions.push({ ...base, action: 'continue', skipped: true, error: `Unknown Ralph stop condition type: ${instance.type}` });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!definition.phases.includes(input.phase)) {
|
|
125
|
+
decisions.push({ ...base, action: 'continue', skipped: true, reason: 'unsupported-phase' });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const state = conditionStates[instance.id] ?? {};
|
|
130
|
+
const decision = sanitizeDecision(await withTimeout(Promise.resolve(definition.evaluate({ phase: input.phase, job: input.job, policy, instance, state, now, run: input.run, outcome: input.outcome, facts: input.facts, signal: input.signal })), instance.timeoutMs ?? definition.timeoutMs, input.signal));
|
|
131
|
+
if (decision.nextState)
|
|
132
|
+
conditionStates[instance.id] = decision.nextState;
|
|
133
|
+
decisions.push({ ...base, action: decision.action, reason: decision.reason, details: decision.details });
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const failClosed = instance.failClosed ?? definition.failClosedDefault ?? false;
|
|
137
|
+
const action = failClosed ? 'stop-after-run' : 'continue';
|
|
138
|
+
decisions.push({ ...base, action, reason: failClosed ? 'condition-error-fail-closed' : 'condition-error', error: error instanceof Error ? error.message : String(error) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const active = decisions.filter((decision) => !decision.skipped);
|
|
142
|
+
if (policy.mode === 'all' && active.length > 0) {
|
|
143
|
+
const allStop = active.every((decision) => severity(decision.action) >= severity('stop-after-run'));
|
|
144
|
+
finalAction = allStop ? active.reduce((action, decision) => highest(action, decision.action), 'stop-after-run') : 'continue';
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
finalAction = active.reduce((action, decision) => highest(action, decision.action), 'continue');
|
|
148
|
+
}
|
|
149
|
+
const contributing = finalAction === 'continue' ? active.find((decision) => decision.error || decision.reason) : active.find((decision) => decision.action === finalAction) ?? active.find((decision) => severity(decision.action) > 0);
|
|
150
|
+
return { evaluation: { id: `rse_${randomUUID()}`, phase: input.phase, at: now, mode: policy.mode, finalAction, reason: contributing?.reason ?? (contributing?.error ? 'condition-error' : undefined), decisions }, conditionStates };
|
|
151
|
+
}
|