@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,924 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const DEFAULT_ROWS = 24;
|
|
5
|
+
const DEFAULT_COLS = 100;
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
7
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 10_000;
|
|
8
|
+
const DEFAULT_INPUT_DELAY_MS = 40;
|
|
9
|
+
const DEFAULT_REAL_PROVIDER_MAX_ITERATIONS = 10;
|
|
10
|
+
const ARTIFACT_SCHEMA_VERSION = 1;
|
|
11
|
+
const PYTHON_PTY_DRIVER = String.raw `
|
|
12
|
+
import os, sys, pty, select, subprocess, signal, termios, fcntl, struct, time
|
|
13
|
+
rows = int(os.environ.get("PIBO_PTY_ROWS", "24"))
|
|
14
|
+
cols = int(os.environ.get("PIBO_PTY_COLS", "100"))
|
|
15
|
+
cmd = sys.argv[1:]
|
|
16
|
+
if cmd and cmd[0] == "--":
|
|
17
|
+
cmd = cmd[1:]
|
|
18
|
+
if not cmd:
|
|
19
|
+
print("missing command", file=sys.stderr)
|
|
20
|
+
sys.exit(64)
|
|
21
|
+
master, slave = pty.openpty()
|
|
22
|
+
try:
|
|
23
|
+
fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
proc = subprocess.Popen(cmd, stdin=slave, stdout=slave, stderr=slave, close_fds=True, start_new_session=True)
|
|
27
|
+
os.close(slave)
|
|
28
|
+
os.set_blocking(master, False)
|
|
29
|
+
os.set_blocking(sys.stdin.fileno(), False)
|
|
30
|
+
stdin_open = True
|
|
31
|
+
|
|
32
|
+
def terminate(signum, frame):
|
|
33
|
+
try:
|
|
34
|
+
os.killpg(proc.pid, signum)
|
|
35
|
+
except Exception:
|
|
36
|
+
try:
|
|
37
|
+
proc.terminate()
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
signal.signal(signal.SIGTERM, terminate)
|
|
41
|
+
signal.signal(signal.SIGINT, terminate)
|
|
42
|
+
|
|
43
|
+
def drain_once():
|
|
44
|
+
try:
|
|
45
|
+
data = os.read(master, 4096)
|
|
46
|
+
except OSError:
|
|
47
|
+
return False
|
|
48
|
+
if not data:
|
|
49
|
+
return False
|
|
50
|
+
os.write(sys.stdout.fileno(), data)
|
|
51
|
+
sys.stdout.flush()
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
exit_code = None
|
|
55
|
+
while True:
|
|
56
|
+
fds = [master]
|
|
57
|
+
if stdin_open:
|
|
58
|
+
fds.append(sys.stdin.fileno())
|
|
59
|
+
try:
|
|
60
|
+
readable, _, _ = select.select(fds, [], [], 0.05)
|
|
61
|
+
except OSError:
|
|
62
|
+
readable = []
|
|
63
|
+
if master in readable:
|
|
64
|
+
if not drain_once():
|
|
65
|
+
break
|
|
66
|
+
if stdin_open and sys.stdin.fileno() in readable:
|
|
67
|
+
try:
|
|
68
|
+
data = os.read(sys.stdin.fileno(), 4096)
|
|
69
|
+
except BlockingIOError:
|
|
70
|
+
data = b""
|
|
71
|
+
except OSError:
|
|
72
|
+
stdin_open = False
|
|
73
|
+
data = b""
|
|
74
|
+
if data:
|
|
75
|
+
try:
|
|
76
|
+
os.write(master, data)
|
|
77
|
+
except OSError:
|
|
78
|
+
pass
|
|
79
|
+
else:
|
|
80
|
+
stdin_open = False
|
|
81
|
+
polled = proc.poll()
|
|
82
|
+
if polled is not None:
|
|
83
|
+
exit_code = polled
|
|
84
|
+
deadline = time.time() + 0.2
|
|
85
|
+
while time.time() < deadline and drain_once():
|
|
86
|
+
pass
|
|
87
|
+
break
|
|
88
|
+
if exit_code is None:
|
|
89
|
+
exit_code = proc.poll()
|
|
90
|
+
if exit_code is None:
|
|
91
|
+
try:
|
|
92
|
+
proc.terminate()
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
exit_code = proc.wait(timeout=1)
|
|
96
|
+
sys.exit(exit_code if exit_code is not None else 1)
|
|
97
|
+
`;
|
|
98
|
+
export async function runDebugPty(args) {
|
|
99
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
100
|
+
printPtyDiscovery();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const command = args[0];
|
|
104
|
+
if (command === "run") {
|
|
105
|
+
if (args[1] === "--help" || args[1] === "-h") {
|
|
106
|
+
printPtyDiscovery();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const options = parsePtyOptions(args.slice(1));
|
|
110
|
+
if (options.command.length === 0)
|
|
111
|
+
throw new Error("pibo debug pty run requires -- <command...>");
|
|
112
|
+
const scenario = scenarioFromRunOptions(options);
|
|
113
|
+
const result = await executePtyScenario(scenario, options);
|
|
114
|
+
printRunResult(result);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (command === "scenario") {
|
|
118
|
+
if (args[1] === "--help" || args[1] === "-h") {
|
|
119
|
+
printPtyDiscovery();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const options = parsePtyOptions(args.slice(1));
|
|
123
|
+
const scenario = await loadScenarioFromOptions(options);
|
|
124
|
+
const result = await executePtyScenario(scenario, options);
|
|
125
|
+
printRunResult(result);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (command === "list-scenarios") {
|
|
129
|
+
printBuiltinScenarios();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Unknown pibo debug pty command "${command}". Run pibo debug pty --help.`);
|
|
133
|
+
}
|
|
134
|
+
function printPtyDiscovery() {
|
|
135
|
+
console.log(`pibo debug pty - run and inspect interactive CLI/TUI commands under a pseudo-terminal
|
|
136
|
+
|
|
137
|
+
Commands:
|
|
138
|
+
run Run one command under PTY
|
|
139
|
+
scenario Run a declarative PTY scenario JSON file
|
|
140
|
+
list-scenarios List built-in scenario names
|
|
141
|
+
|
|
142
|
+
Usage:
|
|
143
|
+
pibo debug pty run [options] -- <command...>
|
|
144
|
+
pibo debug pty scenario [options] <file>
|
|
145
|
+
pibo debug pty scenario --builtin cli-session-ui-mocked-e2e
|
|
146
|
+
|
|
147
|
+
Common options:
|
|
148
|
+
--rows <n> Terminal rows (default: ${DEFAULT_ROWS})
|
|
149
|
+
--cols <n> Terminal columns (default: ${DEFAULT_COLS})
|
|
150
|
+
--timeout-ms <n> Wall-clock timeout (default: ${DEFAULT_TIMEOUT_MS})
|
|
151
|
+
--idle-timeout-ms <n> Idle timeout after no output (default: ${DEFAULT_IDLE_TIMEOUT_MS})
|
|
152
|
+
--input-delay-ms <n> Delay between typed characters (default: ${DEFAULT_INPUT_DELAY_MS})
|
|
153
|
+
--artifact-dir <path> Artifact directory
|
|
154
|
+
--artifact Write artifacts for successful runs too
|
|
155
|
+
--expect <text> Require cleaned output text (repeatable)
|
|
156
|
+
--reject <text> Forbid cleaned output text (repeatable)
|
|
157
|
+
--type <text> Type text as a scenario step
|
|
158
|
+
--press <key> Press Enter, Escape, CtrlC, Up, or Down
|
|
159
|
+
--wait-for <text> Wait until cleaned output contains text
|
|
160
|
+
--sleep-ms <n> Sleep as a scenario step
|
|
161
|
+
--docker-worker <name> Run inside a named Docker worker/container
|
|
162
|
+
--workdir <path> Workdir for host cwd or Docker -w
|
|
163
|
+
--real-provider Explicitly allow live provider scenarios
|
|
164
|
+
--max-iterations <n> Real-provider iteration cap (default: ${DEFAULT_REAL_PROVIDER_MAX_ITERATIONS})
|
|
165
|
+
|
|
166
|
+
Safety:
|
|
167
|
+
Mocked/deterministic mode is default. Real provider mode requires --real-provider and bounded iterations.
|
|
168
|
+
`);
|
|
169
|
+
}
|
|
170
|
+
function printBuiltinScenarios() {
|
|
171
|
+
console.log(`Built-in PTY scenarios:
|
|
172
|
+
cli-session-ui-mocked-e2e Runs pibo tui:sessions without --demo using a deterministic debug fixture when available
|
|
173
|
+
|
|
174
|
+
Next:
|
|
175
|
+
pibo debug pty scenario --builtin cli-session-ui-mocked-e2e --artifact
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
function parsePtyOptions(args) {
|
|
179
|
+
const options = {
|
|
180
|
+
positionals: [],
|
|
181
|
+
artifact: false,
|
|
182
|
+
realProvider: false,
|
|
183
|
+
env: [],
|
|
184
|
+
expect: [],
|
|
185
|
+
reject: [],
|
|
186
|
+
stopPatterns: [],
|
|
187
|
+
steps: [],
|
|
188
|
+
command: [],
|
|
189
|
+
};
|
|
190
|
+
let commandMode = false;
|
|
191
|
+
for (let index = 0; index < args.length; index++) {
|
|
192
|
+
const arg = args[index];
|
|
193
|
+
if (commandMode) {
|
|
194
|
+
options.command.push(arg);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg === "--") {
|
|
198
|
+
commandMode = true;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg === "--artifact")
|
|
202
|
+
options.artifact = true;
|
|
203
|
+
else if (arg === "--real-provider")
|
|
204
|
+
options.realProvider = true;
|
|
205
|
+
else if (arg === "--rows")
|
|
206
|
+
options.rows = requireOptionValue(args, ++index, arg);
|
|
207
|
+
else if (arg === "--cols")
|
|
208
|
+
options.cols = requireOptionValue(args, ++index, arg);
|
|
209
|
+
else if (arg === "--timeout-ms")
|
|
210
|
+
options.timeoutMs = requireOptionValue(args, ++index, arg);
|
|
211
|
+
else if (arg === "--idle-timeout-ms")
|
|
212
|
+
options.idleTimeoutMs = requireOptionValue(args, ++index, arg);
|
|
213
|
+
else if (arg === "--input-delay-ms")
|
|
214
|
+
options.inputDelayMs = requireOptionValue(args, ++index, arg);
|
|
215
|
+
else if (arg === "--artifact-dir")
|
|
216
|
+
options.artifactDir = requireOptionValue(args, ++index, arg);
|
|
217
|
+
else if (arg === "--docker-worker")
|
|
218
|
+
options.dockerWorker = requireOptionValue(args, ++index, arg);
|
|
219
|
+
else if (arg === "--workdir")
|
|
220
|
+
options.workdir = requireOptionValue(args, ++index, arg);
|
|
221
|
+
else if (arg === "--provider-mode")
|
|
222
|
+
options.providerMode = requireOptionValue(args, ++index, arg);
|
|
223
|
+
else if (arg === "--max-iterations")
|
|
224
|
+
options.maxIterations = requireOptionValue(args, ++index, arg);
|
|
225
|
+
else if (arg === "--name")
|
|
226
|
+
options.name = requireOptionValue(args, ++index, arg);
|
|
227
|
+
else if (arg === "--env")
|
|
228
|
+
options.env.push(requireOptionValue(args, ++index, arg));
|
|
229
|
+
else if (arg === "--expect")
|
|
230
|
+
options.expect.push(requireOptionValue(args, ++index, arg));
|
|
231
|
+
else if (arg === "--reject")
|
|
232
|
+
options.reject.push(requireOptionValue(args, ++index, arg));
|
|
233
|
+
else if (arg === "--stop-pattern")
|
|
234
|
+
options.stopPatterns.push(requireOptionValue(args, ++index, arg));
|
|
235
|
+
else if (arg === "--type")
|
|
236
|
+
options.steps.push({ typeText: requireOptionValue(args, ++index, arg) });
|
|
237
|
+
else if (arg === "--press")
|
|
238
|
+
options.steps.push({ press: requireOptionValue(args, ++index, arg) });
|
|
239
|
+
else if (arg === "--wait-for")
|
|
240
|
+
options.steps.push({ waitFor: requireOptionValue(args, ++index, arg) });
|
|
241
|
+
else if (arg === "--sleep-ms")
|
|
242
|
+
options.steps.push({ sleepMs: parseNonNegativeInteger(requireOptionValue(args, ++index, arg), arg) });
|
|
243
|
+
else if (arg === "--builtin")
|
|
244
|
+
options.builtin = requireOptionValue(args, ++index, arg);
|
|
245
|
+
else if (arg === "--help" || arg === "-h") {
|
|
246
|
+
printPtyDiscovery();
|
|
247
|
+
process.exitCode = 0;
|
|
248
|
+
return options;
|
|
249
|
+
}
|
|
250
|
+
else if (arg.startsWith("--")) {
|
|
251
|
+
throw new Error(`Unknown pibo debug pty option "${arg}"`);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
options.positionals.push(arg);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return options;
|
|
258
|
+
}
|
|
259
|
+
function requireOptionValue(args, index, option) {
|
|
260
|
+
const value = args[index];
|
|
261
|
+
if (!value || value.startsWith("--"))
|
|
262
|
+
throw new Error(`${option} requires a value`);
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
function scenarioFromRunOptions(options) {
|
|
266
|
+
return {
|
|
267
|
+
name: options.name ?? "adhoc-run",
|
|
268
|
+
command: options.command,
|
|
269
|
+
cwd: options.workdir,
|
|
270
|
+
env: envPairsToRecord(options.env),
|
|
271
|
+
rows: numberOption(options.rows, "--rows"),
|
|
272
|
+
cols: numberOption(options.cols, "--cols"),
|
|
273
|
+
timeoutMs: numberOption(options.timeoutMs, "--timeout-ms"),
|
|
274
|
+
idleTimeoutMs: numberOption(options.idleTimeoutMs, "--idle-timeout-ms"),
|
|
275
|
+
inputDelayMs: numberOption(options.inputDelayMs, "--input-delay-ms"),
|
|
276
|
+
providerMode: parseProviderMode(options.providerMode ?? (options.realProvider ? "real" : undefined)),
|
|
277
|
+
maxIterations: numberOption(options.maxIterations, "--max-iterations"),
|
|
278
|
+
artifactDir: options.artifactDir,
|
|
279
|
+
artifact: options.artifact,
|
|
280
|
+
steps: options.steps,
|
|
281
|
+
expect: options.expect,
|
|
282
|
+
reject: options.reject,
|
|
283
|
+
stopPatterns: options.stopPatterns,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function loadScenarioFromOptions(options) {
|
|
287
|
+
if (options.builtin)
|
|
288
|
+
return builtinScenario(options.builtin, options);
|
|
289
|
+
const file = options.positionals[0];
|
|
290
|
+
if (!file)
|
|
291
|
+
throw new Error("pibo debug pty scenario requires <file> or --builtin <name>");
|
|
292
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
293
|
+
const scenario = validateScenario(parsed, file);
|
|
294
|
+
return applyScenarioOverrides(scenario, options);
|
|
295
|
+
}
|
|
296
|
+
function builtinScenario(name, options) {
|
|
297
|
+
if (name !== "cli-session-ui-mocked-e2e")
|
|
298
|
+
throw new Error(`Unknown built-in PTY scenario "${name}"`);
|
|
299
|
+
return applyScenarioOverrides({
|
|
300
|
+
name,
|
|
301
|
+
command: ["pibo", "tui:sessions"],
|
|
302
|
+
rows: 24,
|
|
303
|
+
cols: 100,
|
|
304
|
+
timeoutMs: 90_000,
|
|
305
|
+
idleTimeoutMs: 15_000,
|
|
306
|
+
inputDelayMs: 45,
|
|
307
|
+
providerMode: "mocked",
|
|
308
|
+
artifactDir: "tmp/pty-smoke-artifacts/cli-session-ui-mocked-e2e",
|
|
309
|
+
env: {
|
|
310
|
+
PIBO_DEBUG_PTY_SCENARIO: "cli-session-ui-mocked-e2e",
|
|
311
|
+
PIBO_DEBUG_PTY_CLI_SESSIONS_MOCKED: "1",
|
|
312
|
+
PIBO_DEBUG_PTY_ASSISTANT_REPLY: "Mocked PTY assistant response",
|
|
313
|
+
},
|
|
314
|
+
steps: [
|
|
315
|
+
{ waitFor: "Pibo CLI Sessions", timeoutMs: 20_000 },
|
|
316
|
+
{ typeText: "/new" },
|
|
317
|
+
{ press: "Enter" },
|
|
318
|
+
{ sleepMs: 750 },
|
|
319
|
+
{ typeText: "Hi", iteration: true },
|
|
320
|
+
{ press: "Enter" },
|
|
321
|
+
{ waitFor: "Mocked PTY assistant response", timeoutMs: 10_000 },
|
|
322
|
+
{ typeText: "/status" },
|
|
323
|
+
{ press: "Enter" },
|
|
324
|
+
{ waitFor: "source=", timeoutMs: 10_000 },
|
|
325
|
+
{ typeText: "/quit" },
|
|
326
|
+
{ press: "Enter" },
|
|
327
|
+
],
|
|
328
|
+
expect: ["Pibo CLI Sessions", "Hi", "Mocked PTY assistant response"],
|
|
329
|
+
reject: ["UnhandledPromiseRejection", "source_closed"],
|
|
330
|
+
}, options);
|
|
331
|
+
}
|
|
332
|
+
function applyScenarioOverrides(scenario, options) {
|
|
333
|
+
return {
|
|
334
|
+
...scenario,
|
|
335
|
+
name: options.name ?? scenario.name,
|
|
336
|
+
cwd: options.workdir ?? scenario.cwd,
|
|
337
|
+
workdir: options.workdir ?? scenario.workdir,
|
|
338
|
+
env: { ...(scenario.env ?? {}), ...envPairsToRecord(options.env) },
|
|
339
|
+
rows: numberOption(options.rows, "--rows") ?? scenario.rows,
|
|
340
|
+
cols: numberOption(options.cols, "--cols") ?? scenario.cols,
|
|
341
|
+
timeoutMs: numberOption(options.timeoutMs, "--timeout-ms") ?? scenario.timeoutMs,
|
|
342
|
+
idleTimeoutMs: numberOption(options.idleTimeoutMs, "--idle-timeout-ms") ?? scenario.idleTimeoutMs,
|
|
343
|
+
inputDelayMs: numberOption(options.inputDelayMs, "--input-delay-ms") ?? scenario.inputDelayMs,
|
|
344
|
+
providerMode: parseProviderMode(options.providerMode ?? (options.realProvider ? "real" : undefined)) ?? scenario.providerMode,
|
|
345
|
+
maxIterations: numberOption(options.maxIterations, "--max-iterations") ?? scenario.maxIterations,
|
|
346
|
+
artifactDir: options.artifactDir ?? scenario.artifactDir,
|
|
347
|
+
artifact: options.artifact || scenario.artifact,
|
|
348
|
+
steps: [...(scenario.steps ?? []), ...options.steps],
|
|
349
|
+
expect: [...(scenario.expect ?? []), ...options.expect],
|
|
350
|
+
reject: [...(scenario.reject ?? []), ...options.reject],
|
|
351
|
+
stopPatterns: [...(scenario.stopPatterns ?? []), ...options.stopPatterns],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function validateScenario(value, source) {
|
|
355
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
356
|
+
throw new Error(`${source}: scenario must be an object`);
|
|
357
|
+
const raw = value;
|
|
358
|
+
if (!Array.isArray(raw.command) || raw.command.some((item) => typeof item !== "string") || raw.command.length === 0) {
|
|
359
|
+
throw new Error(`${source}: command must be a non-empty string array`);
|
|
360
|
+
}
|
|
361
|
+
const scenario = {
|
|
362
|
+
name: optionalString(raw.name, "name", source),
|
|
363
|
+
command: raw.command,
|
|
364
|
+
cwd: optionalString(raw.cwd, "cwd", source),
|
|
365
|
+
workdir: optionalString(raw.workdir, "workdir", source),
|
|
366
|
+
env: optionalStringRecord(raw.env, "env", source),
|
|
367
|
+
rows: optionalPositiveNumber(raw.rows, "rows", source),
|
|
368
|
+
cols: optionalPositiveNumber(raw.cols, "cols", source),
|
|
369
|
+
timeoutMs: optionalPositiveNumber(raw.timeoutMs, "timeoutMs", source),
|
|
370
|
+
idleTimeoutMs: optionalPositiveNumber(raw.idleTimeoutMs, "idleTimeoutMs", source),
|
|
371
|
+
inputDelayMs: optionalNonNegativeNumber(raw.inputDelayMs, "inputDelayMs", source),
|
|
372
|
+
providerMode: parseProviderModeValue(raw.providerMode, "providerMode", source),
|
|
373
|
+
maxIterations: optionalPositiveNumber(raw.maxIterations, "maxIterations", source),
|
|
374
|
+
artifactDir: optionalString(raw.artifactDir, "artifactDir", source),
|
|
375
|
+
artifact: optionalBoolean(raw.artifact, "artifact", source),
|
|
376
|
+
steps: parseSteps(raw.steps, source),
|
|
377
|
+
expect: optionalStringArray(raw.expect, "expect", source),
|
|
378
|
+
reject: optionalStringArray(raw.reject, "reject", source),
|
|
379
|
+
stopPatterns: optionalStringArray(raw.stopPatterns, "stopPatterns", source),
|
|
380
|
+
};
|
|
381
|
+
return scenario;
|
|
382
|
+
}
|
|
383
|
+
function parseSteps(value, source) {
|
|
384
|
+
if (value === undefined)
|
|
385
|
+
return [];
|
|
386
|
+
if (!Array.isArray(value))
|
|
387
|
+
throw new Error(`${source}: steps must be an array`);
|
|
388
|
+
return value.map((step, index) => {
|
|
389
|
+
if (!step || typeof step !== "object" || Array.isArray(step))
|
|
390
|
+
throw new Error(`${source}: steps[${index}] must be an object`);
|
|
391
|
+
const raw = step;
|
|
392
|
+
const parsed = {
|
|
393
|
+
waitFor: optionalString(raw.waitFor, `steps[${index}].waitFor`, source),
|
|
394
|
+
typeText: optionalString(raw.typeText, `steps[${index}].typeText`, source),
|
|
395
|
+
writeBytes: optionalString(raw.writeBytes, `steps[${index}].writeBytes`, source),
|
|
396
|
+
press: optionalString(raw.press, `steps[${index}].press`, source),
|
|
397
|
+
sleepMs: optionalNonNegativeNumber(raw.sleepMs, `steps[${index}].sleepMs`, source),
|
|
398
|
+
expect: optionalString(raw.expect, `steps[${index}].expect`, source),
|
|
399
|
+
reject: optionalString(raw.reject, `steps[${index}].reject`, source),
|
|
400
|
+
timeoutMs: optionalPositiveNumber(raw.timeoutMs, `steps[${index}].timeoutMs`, source),
|
|
401
|
+
iteration: optionalBoolean(raw.iteration, `steps[${index}].iteration`, source),
|
|
402
|
+
};
|
|
403
|
+
const actionCount = [parsed.waitFor, parsed.typeText, parsed.writeBytes, parsed.press, parsed.sleepMs, parsed.expect, parsed.reject].filter((item) => item !== undefined).length;
|
|
404
|
+
if (actionCount !== 1)
|
|
405
|
+
throw new Error(`${source}: steps[${index}] must define exactly one action`);
|
|
406
|
+
return parsed;
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
async function executePtyScenario(input, options) {
|
|
410
|
+
const scenario = normalizeScenario(input, options);
|
|
411
|
+
const started = Date.now();
|
|
412
|
+
const startedAt = new Date(started).toISOString();
|
|
413
|
+
let runner;
|
|
414
|
+
const assertions = [];
|
|
415
|
+
let iterations = 0;
|
|
416
|
+
let stopReason = "completed";
|
|
417
|
+
try {
|
|
418
|
+
validateProviderSafety(scenario, options);
|
|
419
|
+
runner = await startRunner(scenario);
|
|
420
|
+
let lastOutputAt = Date.now();
|
|
421
|
+
let outputLength = 0;
|
|
422
|
+
const refreshOutputTimestamp = () => {
|
|
423
|
+
const raw = runner?.getRawOutput() ?? "";
|
|
424
|
+
if (raw.length !== outputLength) {
|
|
425
|
+
outputLength = raw.length;
|
|
426
|
+
lastOutputAt = Date.now();
|
|
427
|
+
}
|
|
428
|
+
return lastOutputAt;
|
|
429
|
+
};
|
|
430
|
+
for (const step of scenario.steps) {
|
|
431
|
+
if (step.iteration === true) {
|
|
432
|
+
iterations += 1;
|
|
433
|
+
if (scenario.providerMode === "real" && scenario.maxIterations !== undefined && iterations > scenario.maxIterations) {
|
|
434
|
+
stopReason = "max_iterations";
|
|
435
|
+
throw new Error(`Real-provider PTY scenario exceeded max iterations (${scenario.maxIterations})`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
await checkIdle(runner, refreshOutputTimestamp, scenario.idleTimeoutMs);
|
|
439
|
+
await runStep(runner, scenario, step, assertions, refreshOutputTimestamp);
|
|
440
|
+
refreshOutputTimestamp();
|
|
441
|
+
const clean = cleanTerminalText(runner.getRawOutput());
|
|
442
|
+
const matchedStop = scenario.stopPatterns.find((pattern) => clean.includes(pattern));
|
|
443
|
+
if (matchedStop) {
|
|
444
|
+
stopReason = `stop_pattern:${matchedStop}`;
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const exit = await waitForExitOrIdle(runner, Math.max(1, scenario.timeoutMs - (Date.now() - started)), scenario.idleTimeoutMs, refreshOutputTimestamp);
|
|
449
|
+
if (exit.idleTimedOut) {
|
|
450
|
+
stopReason = "idle_timeout";
|
|
451
|
+
throw new Error(`PTY command produced no output for ${scenario.idleTimeoutMs}ms`);
|
|
452
|
+
}
|
|
453
|
+
if (exit.timedOut) {
|
|
454
|
+
stopReason = stopReason === "completed" ? "wall_clock_timeout" : stopReason;
|
|
455
|
+
throw new Error(`PTY command timed out after ${scenario.timeoutMs}ms`);
|
|
456
|
+
}
|
|
457
|
+
const clean = cleanTerminalText(runner.getRawOutput());
|
|
458
|
+
assertPatterns(clean, scenario.expect, scenario.reject, assertions);
|
|
459
|
+
if (exit.exitCode !== 0) {
|
|
460
|
+
stopReason = `exit_code:${exit.exitCode ?? "signal"}`;
|
|
461
|
+
throw new Error(`PTY command exited with status ${exit.exitCode ?? exit.signal ?? "unknown"}`);
|
|
462
|
+
}
|
|
463
|
+
const result = buildResult(scenario, runner, true, assertions, iterations, started, startedAt, stopReason, exit.exitCode, exit.signal);
|
|
464
|
+
if (scenario.writeArtifactsOnSuccess)
|
|
465
|
+
result.artifactDir = await writeArtifacts(scenario, result);
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
if (stopReason === "completed")
|
|
470
|
+
stopReason = runner ? "error" : "preflight_error";
|
|
471
|
+
if (runner)
|
|
472
|
+
await runner.terminate(stopReason);
|
|
473
|
+
const result = buildResult(scenario, runner, false, assertions, iterations, started, startedAt, stopReason, null, null);
|
|
474
|
+
result.artifactDir = await writeArtifacts(scenario, result, error instanceof Error ? error.message : String(error));
|
|
475
|
+
const message = `${error instanceof Error ? error.message : String(error)}\nPTY artifacts: ${result.artifactDir}`;
|
|
476
|
+
throw new Error(message);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function normalizeScenario(input, options) {
|
|
480
|
+
const providerMode = parseProviderMode(options.providerMode ?? (options.realProvider ? "real" : undefined)) ?? input.providerMode ?? "mocked";
|
|
481
|
+
const name = input.name ?? options.name ?? "pty-scenario";
|
|
482
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
483
|
+
const idleTimeoutMs = input.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
484
|
+
const artifactDir = input.artifactDir ?? options.artifactDir ?? path.join("tmp", "pty-smoke-artifacts", `${safeName(name)}-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
485
|
+
const env = { ...(input.env ?? {}) };
|
|
486
|
+
if (env.PIBO_DEBUG_PTY_SCENARIO === "cli-session-ui-mocked-e2e" && env.PIBO_HOME === undefined) {
|
|
487
|
+
env.PIBO_HOME = path.resolve(artifactDir, "pibo-home");
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
...input,
|
|
491
|
+
name,
|
|
492
|
+
command: [...input.command],
|
|
493
|
+
cwd: input.cwd ?? input.workdir ?? options.workdir,
|
|
494
|
+
workdir: options.workdir ?? input.workdir ?? input.cwd,
|
|
495
|
+
env,
|
|
496
|
+
rows: input.rows ?? DEFAULT_ROWS,
|
|
497
|
+
cols: input.cols ?? DEFAULT_COLS,
|
|
498
|
+
timeoutMs,
|
|
499
|
+
idleTimeoutMs,
|
|
500
|
+
inputDelayMs: input.inputDelayMs ?? DEFAULT_INPUT_DELAY_MS,
|
|
501
|
+
providerMode,
|
|
502
|
+
maxIterations: providerMode === "real" ? (input.maxIterations ?? DEFAULT_REAL_PROVIDER_MAX_ITERATIONS) : input.maxIterations,
|
|
503
|
+
artifactDir,
|
|
504
|
+
writeArtifactsOnSuccess: options.artifact || input.artifact === true,
|
|
505
|
+
steps: input.steps ?? [],
|
|
506
|
+
expect: input.expect ?? [],
|
|
507
|
+
reject: input.reject ?? [],
|
|
508
|
+
stopPatterns: input.stopPatterns ?? [],
|
|
509
|
+
backend: options.dockerWorker ? "docker" : "host",
|
|
510
|
+
dockerWorker: options.dockerWorker,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function validateProviderSafety(scenario, options) {
|
|
514
|
+
if (scenario.providerMode !== "real")
|
|
515
|
+
return;
|
|
516
|
+
if (!options.realProvider)
|
|
517
|
+
throw new Error("Real-provider PTY scenarios require explicit --real-provider");
|
|
518
|
+
if (!Number.isInteger(scenario.maxIterations) || (scenario.maxIterations ?? 0) < 1)
|
|
519
|
+
throw new Error("Real-provider PTY scenarios require --max-iterations to be a positive integer");
|
|
520
|
+
if (scenario.steps.length === 0 || !scenario.steps.some((step) => step.iteration === true)) {
|
|
521
|
+
throw new Error("Real-provider PTY scenarios require at least one step marked iteration: true so max iterations can be enforced");
|
|
522
|
+
}
|
|
523
|
+
if (scenario.timeoutMs <= 0 || scenario.idleTimeoutMs <= 0)
|
|
524
|
+
throw new Error("Real-provider PTY scenarios require wall-clock and idle timeouts");
|
|
525
|
+
if (scenario.expect.length === 0 && scenario.stopPatterns.length === 0 && !scenario.steps.some((step) => step.waitFor)) {
|
|
526
|
+
throw new Error("Real-provider PTY scenarios require an expected output, stop pattern, or waitFor stop condition");
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function startRunner(scenario) {
|
|
530
|
+
if (scenario.backend === "docker")
|
|
531
|
+
return startDockerRunner(scenario);
|
|
532
|
+
return startHostRunner(scenario);
|
|
533
|
+
}
|
|
534
|
+
async function startHostRunner(scenario) {
|
|
535
|
+
const python = detectHostPython();
|
|
536
|
+
const env = { ...process.env, ...scenario.env, PIBO_PTY_ROWS: String(scenario.rows), PIBO_PTY_COLS: String(scenario.cols) };
|
|
537
|
+
const child = spawn(python, ["-u", "-c", PYTHON_PTY_DRIVER, "--", ...scenario.command], {
|
|
538
|
+
cwd: scenario.cwd,
|
|
539
|
+
env,
|
|
540
|
+
stdio: "pipe",
|
|
541
|
+
});
|
|
542
|
+
return wrapChild(child, "host", `python:${python}`);
|
|
543
|
+
}
|
|
544
|
+
async function startDockerRunner(scenario) {
|
|
545
|
+
if (!scenario.dockerWorker)
|
|
546
|
+
throw new Error("Docker PTY backend requires --docker-worker <name>");
|
|
547
|
+
const python = detectDockerPython(scenario.dockerWorker);
|
|
548
|
+
const args = ["exec", "-i"];
|
|
549
|
+
if (scenario.workdir)
|
|
550
|
+
args.push("-w", scenario.workdir);
|
|
551
|
+
args.push("-e", `PIBO_PTY_ROWS=${scenario.rows}`, "-e", `PIBO_PTY_COLS=${scenario.cols}`);
|
|
552
|
+
for (const [key, value] of Object.entries(scenario.env))
|
|
553
|
+
args.push("-e", `${key}=${value}`);
|
|
554
|
+
args.push(scenario.dockerWorker, python, "-u", "-c", PYTHON_PTY_DRIVER, "--", ...scenario.command);
|
|
555
|
+
const child = spawn("docker", args, { stdio: "pipe" });
|
|
556
|
+
return wrapChild(child, "docker", `docker-exec:${python}`);
|
|
557
|
+
}
|
|
558
|
+
function detectHostPython() {
|
|
559
|
+
for (const candidate of ["python3", "python"]) {
|
|
560
|
+
const result = spawnSync(candidate, ["--version"], { stdio: "ignore" });
|
|
561
|
+
if (result.status === 0)
|
|
562
|
+
return candidate;
|
|
563
|
+
}
|
|
564
|
+
throw new Error("No Python PTY driver found on host. Install python3 or run in an environment with PTY support.");
|
|
565
|
+
}
|
|
566
|
+
function detectDockerPython(worker) {
|
|
567
|
+
const inspect = spawnSync("docker", ["inspect", "-f", "{{.State.Running}} {{.Id}}", worker], { encoding: "utf8" });
|
|
568
|
+
if (inspect.status !== 0)
|
|
569
|
+
throw new Error(`Docker worker "${worker}" is not available or not running`);
|
|
570
|
+
if (!inspect.stdout.trim().startsWith("true"))
|
|
571
|
+
throw new Error(`Docker worker "${worker}" is not running`);
|
|
572
|
+
const result = spawnSync("docker", ["exec", worker, "sh", "-lc", "command -v python3 || command -v python"], { encoding: "utf8" });
|
|
573
|
+
if (result.status !== 0 || result.stdout.trim().length === 0) {
|
|
574
|
+
throw new Error(`Docker worker "${worker}" does not provide python3/python for PTY execution. Install python3, or add an internal PTY helper/script fallback.`);
|
|
575
|
+
}
|
|
576
|
+
return result.stdout.trim().split(/\s+/)[0] ?? "python3";
|
|
577
|
+
}
|
|
578
|
+
function wrapChild(child, backend, ptyMethod) {
|
|
579
|
+
let rawOutput = "";
|
|
580
|
+
let driverStderr = "";
|
|
581
|
+
const events = [];
|
|
582
|
+
let exitState;
|
|
583
|
+
const exitPromise = new Promise((resolve) => {
|
|
584
|
+
child.on("exit", (exitCode, signal) => {
|
|
585
|
+
exitState = { exitCode, signal };
|
|
586
|
+
resolve(exitState);
|
|
587
|
+
});
|
|
588
|
+
});
|
|
589
|
+
child.stdout.on("data", (data) => {
|
|
590
|
+
const text = data.toString("utf8");
|
|
591
|
+
rawOutput += text;
|
|
592
|
+
events.push({ t: Date.now(), source: "stdout", data: text });
|
|
593
|
+
});
|
|
594
|
+
child.stderr.on("data", (data) => {
|
|
595
|
+
const text = data.toString("utf8");
|
|
596
|
+
driverStderr += text;
|
|
597
|
+
events.push({ t: Date.now(), source: "system", kind: "driver_stderr", data: text });
|
|
598
|
+
});
|
|
599
|
+
child.on("error", (error) => {
|
|
600
|
+
driverStderr += `${error.message}\n`;
|
|
601
|
+
events.push({ t: Date.now(), source: "system", kind: "error", detail: error.message });
|
|
602
|
+
});
|
|
603
|
+
return {
|
|
604
|
+
backend,
|
|
605
|
+
ptyMethod,
|
|
606
|
+
async write(data) {
|
|
607
|
+
events.push({ t: Date.now(), source: "stdin", data: Buffer.isBuffer(data) ? data.toString("utf8") : data });
|
|
608
|
+
await new Promise((resolve, reject) => {
|
|
609
|
+
child.stdin.write(data, (error) => error ? reject(error) : resolve());
|
|
610
|
+
});
|
|
611
|
+
},
|
|
612
|
+
async terminate(reason) {
|
|
613
|
+
events.push({ t: Date.now(), source: "system", kind: "terminate", detail: reason });
|
|
614
|
+
if (exitState)
|
|
615
|
+
return;
|
|
616
|
+
child.kill("SIGTERM");
|
|
617
|
+
await Promise.race([
|
|
618
|
+
exitPromise,
|
|
619
|
+
delay(1_000).then(() => {
|
|
620
|
+
if (!exitState)
|
|
621
|
+
child.kill("SIGKILL");
|
|
622
|
+
}),
|
|
623
|
+
]);
|
|
624
|
+
},
|
|
625
|
+
async waitForExit(timeoutMs) {
|
|
626
|
+
if (exitState)
|
|
627
|
+
return { ...exitState, timedOut: false };
|
|
628
|
+
const timeout = delay(timeoutMs).then(() => ({ exitCode: null, signal: null, timedOut: true }));
|
|
629
|
+
const exit = exitPromise.then((value) => ({ ...value, timedOut: false }));
|
|
630
|
+
return Promise.race([exit, timeout]);
|
|
631
|
+
},
|
|
632
|
+
getRawOutput: () => rawOutput,
|
|
633
|
+
getDriverStderr: () => driverStderr,
|
|
634
|
+
getEvents: () => events,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async function runStep(runner, scenario, step, assertions, markOutput) {
|
|
638
|
+
if (step.waitFor !== undefined) {
|
|
639
|
+
await waitForText(runner, step.waitFor, step.timeoutMs ?? scenario.timeoutMs, scenario.idleTimeoutMs, markOutput);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (step.typeText !== undefined) {
|
|
643
|
+
for (const char of step.typeText) {
|
|
644
|
+
await runner.write(char);
|
|
645
|
+
if (scenario.inputDelayMs > 0)
|
|
646
|
+
await delay(scenario.inputDelayMs);
|
|
647
|
+
}
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (step.writeBytes !== undefined) {
|
|
651
|
+
await runner.write(step.writeBytes);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (step.press !== undefined) {
|
|
655
|
+
await runner.write(keySequence(step.press));
|
|
656
|
+
if (scenario.inputDelayMs > 0)
|
|
657
|
+
await delay(scenario.inputDelayMs);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (step.sleepMs !== undefined) {
|
|
661
|
+
await delay(step.sleepMs);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const clean = cleanTerminalText(runner.getRawOutput());
|
|
665
|
+
if (step.expect !== undefined) {
|
|
666
|
+
assertPatterns(clean, [step.expect], [], assertions);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (step.reject !== undefined)
|
|
670
|
+
assertPatterns(clean, [], [step.reject], assertions);
|
|
671
|
+
}
|
|
672
|
+
async function waitForText(runner, pattern, timeoutMs, idleTimeoutMs, markOutput) {
|
|
673
|
+
const deadline = Date.now() + timeoutMs;
|
|
674
|
+
while (Date.now() < deadline) {
|
|
675
|
+
const lastOutputAt = markOutput();
|
|
676
|
+
if (cleanTerminalText(runner.getRawOutput()).includes(pattern))
|
|
677
|
+
return;
|
|
678
|
+
if (Date.now() - lastOutputAt > idleTimeoutMs) {
|
|
679
|
+
await runner.terminate("idle_timeout");
|
|
680
|
+
throw new Error(`PTY command produced no output for ${idleTimeoutMs}ms while waiting for ${JSON.stringify(pattern)}`);
|
|
681
|
+
}
|
|
682
|
+
await delay(50);
|
|
683
|
+
}
|
|
684
|
+
throw new Error(`Timed out waiting for PTY output ${JSON.stringify(pattern)} after ${timeoutMs}ms`);
|
|
685
|
+
}
|
|
686
|
+
async function waitForExitOrIdle(runner, timeoutMs, idleTimeoutMs, markOutput) {
|
|
687
|
+
const deadline = Date.now() + timeoutMs;
|
|
688
|
+
while (Date.now() < deadline) {
|
|
689
|
+
const lastOutputAt = markOutput();
|
|
690
|
+
const remaining = Math.max(1, Math.min(100, deadline - Date.now()));
|
|
691
|
+
const exit = await runner.waitForExit(remaining);
|
|
692
|
+
if (!exit.timedOut)
|
|
693
|
+
return { ...exit, timedOut: false, idleTimedOut: false };
|
|
694
|
+
if (Date.now() - lastOutputAt > idleTimeoutMs)
|
|
695
|
+
return { exitCode: null, signal: null, timedOut: false, idleTimedOut: true };
|
|
696
|
+
}
|
|
697
|
+
return { exitCode: null, signal: null, timedOut: true, idleTimedOut: false };
|
|
698
|
+
}
|
|
699
|
+
async function checkIdle(runner, lastOutputAt, idleTimeoutMs) {
|
|
700
|
+
if (Date.now() - lastOutputAt() > idleTimeoutMs) {
|
|
701
|
+
await runner.terminate("idle_timeout");
|
|
702
|
+
throw new Error(`PTY command produced no output for ${idleTimeoutMs}ms`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function assertPatterns(clean, expect, reject, assertions) {
|
|
706
|
+
for (const pattern of expect) {
|
|
707
|
+
const passed = clean.includes(pattern);
|
|
708
|
+
assertions.push({ type: "expect", pattern, passed, message: passed ? undefined : `Missing expected text: ${pattern}` });
|
|
709
|
+
if (!passed)
|
|
710
|
+
throw new Error(`Missing expected PTY output ${JSON.stringify(pattern)}`);
|
|
711
|
+
}
|
|
712
|
+
for (const pattern of reject) {
|
|
713
|
+
const passed = !clean.includes(pattern);
|
|
714
|
+
assertions.push({ type: "reject", pattern, passed, message: passed ? undefined : `Forbidden text appeared: ${pattern}` });
|
|
715
|
+
if (!passed)
|
|
716
|
+
throw new Error(`Forbidden PTY output appeared ${JSON.stringify(pattern)}`);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function keySequence(key) {
|
|
720
|
+
const normalized = key.toLowerCase();
|
|
721
|
+
if (normalized === "enter" || normalized === "return")
|
|
722
|
+
return "\r";
|
|
723
|
+
if (normalized === "escape" || normalized === "esc")
|
|
724
|
+
return "\x1b";
|
|
725
|
+
if (normalized === "ctrlc" || normalized === "ctrl-c" || normalized === "c-c")
|
|
726
|
+
return "\x03";
|
|
727
|
+
if (normalized === "up")
|
|
728
|
+
return "\x1b[A";
|
|
729
|
+
if (normalized === "down")
|
|
730
|
+
return "\x1b[B";
|
|
731
|
+
throw new Error(`Unsupported PTY key "${key}". Supported: Enter, Escape, CtrlC, Up, Down.`);
|
|
732
|
+
}
|
|
733
|
+
function cleanTerminalText(raw) {
|
|
734
|
+
return raw
|
|
735
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
736
|
+
.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
|
|
737
|
+
.replace(/\x1b[PX^_].*?\x1b\\/gs, "")
|
|
738
|
+
.replace(/\x1b[@-_]/g, "")
|
|
739
|
+
.replace(/\r\n/g, "\n")
|
|
740
|
+
.replace(/\r/g, "\n");
|
|
741
|
+
}
|
|
742
|
+
function buildResult(scenario, runner, ok, assertions, iterations, started, startedAt, stopReason, exitCode, signal) {
|
|
743
|
+
const ended = Date.now();
|
|
744
|
+
return {
|
|
745
|
+
ok,
|
|
746
|
+
name: scenario.name,
|
|
747
|
+
backend: scenario.backend,
|
|
748
|
+
exitCode,
|
|
749
|
+
signal,
|
|
750
|
+
stopReason,
|
|
751
|
+
startedAt,
|
|
752
|
+
endedAt: new Date(ended).toISOString(),
|
|
753
|
+
durationMs: ended - started,
|
|
754
|
+
rawOutput: runner?.getRawOutput() ?? "",
|
|
755
|
+
driverStderr: runner?.getDriverStderr() ?? "",
|
|
756
|
+
events: runner?.getEvents() ?? [],
|
|
757
|
+
assertions,
|
|
758
|
+
iterations,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
async function writeArtifacts(scenario, result, error) {
|
|
762
|
+
const artifactDir = path.resolve(scenario.artifactDir);
|
|
763
|
+
await mkdir(artifactDir, { recursive: true });
|
|
764
|
+
const clean = cleanTerminalText(result.rawOutput);
|
|
765
|
+
const metadata = {
|
|
766
|
+
schemaVersion: ARTIFACT_SCHEMA_VERSION,
|
|
767
|
+
name: scenario.name,
|
|
768
|
+
backend: scenario.backend,
|
|
769
|
+
ptyMethod: result.backend === "docker" ? "docker-python-pty" : "host-python-pty",
|
|
770
|
+
dockerWorker: scenario.dockerWorker,
|
|
771
|
+
command: scenario.command,
|
|
772
|
+
cwd: scenario.cwd,
|
|
773
|
+
workdir: scenario.workdir,
|
|
774
|
+
rows: scenario.rows,
|
|
775
|
+
cols: scenario.cols,
|
|
776
|
+
providerMode: scenario.providerMode,
|
|
777
|
+
realProviderRequested: scenario.providerMode === "real",
|
|
778
|
+
maxIterations: scenario.maxIterations,
|
|
779
|
+
observedIterations: result.iterations,
|
|
780
|
+
timeoutMs: scenario.timeoutMs,
|
|
781
|
+
idleTimeoutMs: scenario.idleTimeoutMs,
|
|
782
|
+
startedAt: result.startedAt,
|
|
783
|
+
endedAt: result.endedAt,
|
|
784
|
+
durationMs: result.durationMs,
|
|
785
|
+
exitCode: result.exitCode,
|
|
786
|
+
signal: result.signal,
|
|
787
|
+
ok: result.ok,
|
|
788
|
+
stopReason: result.stopReason,
|
|
789
|
+
error,
|
|
790
|
+
env: redactEnvKeys(scenario.env),
|
|
791
|
+
screenCaptureMethod: "cleaned-tail",
|
|
792
|
+
};
|
|
793
|
+
await writeFile(path.join(artifactDir, "metadata.json"), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
794
|
+
await writeFile(path.join(artifactDir, "input.json"), `${JSON.stringify({ steps: scenario.steps, inputDelayMs: scenario.inputDelayMs }, null, 2)}\n`);
|
|
795
|
+
await writeFile(path.join(artifactDir, "assertions.json"), `${JSON.stringify({ assertions: result.assertions, expect: scenario.expect, reject: scenario.reject }, null, 2)}\n`);
|
|
796
|
+
await writeFile(path.join(artifactDir, "raw.ansi.log"), result.rawOutput);
|
|
797
|
+
await writeFile(path.join(artifactDir, "clean.txt"), clean);
|
|
798
|
+
await writeFile(path.join(artifactDir, "screen.txt"), finalScreen(clean, scenario.rows));
|
|
799
|
+
if (result.events.length > 0)
|
|
800
|
+
await writeFile(path.join(artifactDir, "events.jsonl"), result.events.map((event) => JSON.stringify(event)).join("\n") + "\n");
|
|
801
|
+
if (result.driverStderr)
|
|
802
|
+
await writeFile(path.join(artifactDir, "driver.stderr.log"), result.driverStderr);
|
|
803
|
+
return artifactDir;
|
|
804
|
+
}
|
|
805
|
+
function finalScreen(clean, rows) {
|
|
806
|
+
const lines = clean.split("\n");
|
|
807
|
+
return lines.slice(Math.max(0, lines.length - rows)).join("\n");
|
|
808
|
+
}
|
|
809
|
+
function printRunResult(result) {
|
|
810
|
+
console.log(`PTY ${result.ok ? "passed" : "failed"}: ${result.name}`);
|
|
811
|
+
console.log(`backend\t${result.backend}`);
|
|
812
|
+
console.log(`exitCode\t${result.exitCode ?? "-"}`);
|
|
813
|
+
console.log(`stopReason\t${result.stopReason}`);
|
|
814
|
+
console.log(`durationMs\t${result.durationMs}`);
|
|
815
|
+
if (result.artifactDir)
|
|
816
|
+
console.log(`artifacts\t${result.artifactDir}`);
|
|
817
|
+
}
|
|
818
|
+
function envPairsToRecord(pairs) {
|
|
819
|
+
const env = {};
|
|
820
|
+
for (const pair of pairs) {
|
|
821
|
+
const index = pair.indexOf("=");
|
|
822
|
+
if (index <= 0)
|
|
823
|
+
throw new Error(`--env values must use KEY=VALUE, got ${JSON.stringify(pair)}`);
|
|
824
|
+
env[pair.slice(0, index)] = pair.slice(index + 1);
|
|
825
|
+
}
|
|
826
|
+
return env;
|
|
827
|
+
}
|
|
828
|
+
function redactEnvKeys(env) {
|
|
829
|
+
const redacted = {};
|
|
830
|
+
for (const [key, value] of Object.entries(env)) {
|
|
831
|
+
redacted[key] = /token|secret|key|password|credential/i.test(key) ? "[redacted]" : value;
|
|
832
|
+
}
|
|
833
|
+
return redacted;
|
|
834
|
+
}
|
|
835
|
+
function parseProviderMode(value) {
|
|
836
|
+
if (value === undefined)
|
|
837
|
+
return undefined;
|
|
838
|
+
if (value === "mocked" || value === "deterministic" || value === "real")
|
|
839
|
+
return value;
|
|
840
|
+
throw new Error(`providerMode must be mocked, deterministic, or real; got ${JSON.stringify(value)}`);
|
|
841
|
+
}
|
|
842
|
+
function parseProviderModeValue(value, field, source) {
|
|
843
|
+
if (value === undefined)
|
|
844
|
+
return undefined;
|
|
845
|
+
if (typeof value !== "string")
|
|
846
|
+
throw new Error(`${source}: ${field} must be a string`);
|
|
847
|
+
return parseProviderMode(value);
|
|
848
|
+
}
|
|
849
|
+
function numberOption(value, option) {
|
|
850
|
+
if (value === undefined)
|
|
851
|
+
return undefined;
|
|
852
|
+
return parsePositiveInteger(value, option);
|
|
853
|
+
}
|
|
854
|
+
function parsePositiveInteger(value, field) {
|
|
855
|
+
const parsed = Number(value);
|
|
856
|
+
if (!Number.isInteger(parsed) || parsed < 1)
|
|
857
|
+
throw new Error(`${field} must be a positive integer`);
|
|
858
|
+
return parsed;
|
|
859
|
+
}
|
|
860
|
+
function parseNonNegativeInteger(value, field) {
|
|
861
|
+
const parsed = Number(value);
|
|
862
|
+
if (!Number.isInteger(parsed) || parsed < 0)
|
|
863
|
+
throw new Error(`${field} must be a non-negative integer`);
|
|
864
|
+
return parsed;
|
|
865
|
+
}
|
|
866
|
+
function optionalString(value, field, source) {
|
|
867
|
+
if (value === undefined)
|
|
868
|
+
return undefined;
|
|
869
|
+
if (typeof value !== "string")
|
|
870
|
+
throw new Error(`${source}: ${field} must be a string`);
|
|
871
|
+
return value;
|
|
872
|
+
}
|
|
873
|
+
function optionalBoolean(value, field, source) {
|
|
874
|
+
if (value === undefined)
|
|
875
|
+
return undefined;
|
|
876
|
+
if (typeof value !== "boolean")
|
|
877
|
+
throw new Error(`${source}: ${field} must be a boolean`);
|
|
878
|
+
return value;
|
|
879
|
+
}
|
|
880
|
+
function optionalPositiveNumber(value, field, source) {
|
|
881
|
+
if (value === undefined)
|
|
882
|
+
return undefined;
|
|
883
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1)
|
|
884
|
+
throw new Error(`${source}: ${field} must be a positive integer`);
|
|
885
|
+
return value;
|
|
886
|
+
}
|
|
887
|
+
function optionalNonNegativeNumber(value, field, source) {
|
|
888
|
+
if (value === undefined)
|
|
889
|
+
return undefined;
|
|
890
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0)
|
|
891
|
+
throw new Error(`${source}: ${field} must be a non-negative integer`);
|
|
892
|
+
return value;
|
|
893
|
+
}
|
|
894
|
+
function optionalStringArray(value, field, source) {
|
|
895
|
+
if (value === undefined)
|
|
896
|
+
return [];
|
|
897
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
898
|
+
throw new Error(`${source}: ${field} must be a string array`);
|
|
899
|
+
return value;
|
|
900
|
+
}
|
|
901
|
+
function optionalStringRecord(value, field, source) {
|
|
902
|
+
if (value === undefined)
|
|
903
|
+
return undefined;
|
|
904
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
905
|
+
throw new Error(`${source}: ${field} must be an object`);
|
|
906
|
+
const record = {};
|
|
907
|
+
for (const [key, item] of Object.entries(value)) {
|
|
908
|
+
if (typeof item !== "string")
|
|
909
|
+
throw new Error(`${source}: ${field}.${key} must be a string`);
|
|
910
|
+
record[key] = item;
|
|
911
|
+
}
|
|
912
|
+
return record;
|
|
913
|
+
}
|
|
914
|
+
function safeName(name) {
|
|
915
|
+
return name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "pty-scenario";
|
|
916
|
+
}
|
|
917
|
+
function delay(ms) {
|
|
918
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
919
|
+
}
|
|
920
|
+
export const __debugPtyForTests = {
|
|
921
|
+
cleanTerminalText,
|
|
922
|
+
keySequence,
|
|
923
|
+
validateScenario,
|
|
924
|
+
};
|