@gajae-code/coding-agent 0.10.2 → 0.11.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/CHANGELOG.md +50 -2
- package/dist/types/append-only-mode.d.ts +1 -1
- package/dist/types/cli/args.d.ts +4 -5
- package/dist/types/cli/daemon-cli.d.ts +12 -1
- package/dist/types/cli/notify-cli.d.ts +21 -1
- package/dist/types/cli.d.ts +3 -0
- package/dist/types/commands/daemon.d.ts +39 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/notify.d.ts +28 -1
- package/dist/types/commands/sdk.d.ts +33 -0
- package/dist/types/config/atomic-yaml-patch.d.ts +85 -0
- package/dist/types/config/model-profile-activation.d.ts +35 -15
- package/dist/types/config/model-profiles.d.ts +11 -5
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +39 -6
- package/dist/types/config/model-selector-value.d.ts +17 -0
- package/dist/types/config/models-config-schema.d.ts +10 -6
- package/dist/types/config/settings-schema.d.ts +133 -6
- package/dist/types/config/settings.d.ts +63 -35
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -6
- package/dist/types/coordinator-mcp/server.d.ts +6 -49
- package/dist/types/daemon/builtin.d.ts +6 -5
- package/dist/types/daemon/control-types.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +12 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/compact-handler.d.ts +4 -2
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +10 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -3
- package/dist/types/extensibility/extensions/types.d.ts +141 -16
- package/dist/types/extensibility/hooks/types.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -2
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +3 -4
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +13 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +4 -1
- package/dist/types/gjc-runtime/windows-powershell-command.d.ts +9 -0
- package/dist/types/harness-control-plane/operate.d.ts +5 -5
- package/dist/types/harness-control-plane/owner.d.ts +18 -4
- package/dist/types/harness-control-plane/receipts.d.ts +0 -1
- package/dist/types/harness-control-plane/sdk-transport.d.ts +43 -0
- package/dist/types/harness-control-plane/session-transport.d.ts +48 -0
- package/dist/types/harness-control-plane/types.d.ts +3 -4
- package/dist/types/lsp/index.d.ts +1 -1
- package/dist/types/main.d.ts +32 -38
- package/dist/types/modes/acp/acp-agent.d.ts +224 -53
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -0
- package/dist/types/modes/acp/acp-mode.d.ts +6 -15
- package/dist/types/modes/acp/permission-mode.d.ts +4 -0
- package/dist/types/modes/acp/startup-options.d.ts +6 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +3 -0
- package/dist/types/modes/components/model-selector.d.ts +1 -0
- package/dist/types/modes/components/notifications-settings-editor.d.ts +135 -0
- package/dist/types/modes/components/settings-selector.d.ts +6 -4
- package/dist/types/modes/components/welcome.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +35 -1
- package/dist/types/modes/index.d.ts +0 -4
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/runtime-init.d.ts +2 -2
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +6 -9
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -6
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +6 -6
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +4 -9
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +169 -30
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +7 -7
- package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +105 -0
- package/dist/types/modes/types.d.ts +5 -0
- package/dist/types/runtime-mcp/client.d.ts +2 -0
- package/dist/types/runtime-mcp/config.d.ts +5 -1
- package/dist/types/runtime-mcp/manager.d.ts +8 -1
- package/dist/types/runtime-mcp/types.d.ts +4 -0
- package/dist/types/sdk/acp/adapter.d.ts +67 -0
- package/dist/types/sdk/acp/index.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +53 -0
- package/dist/types/sdk/broker/discovery.d.ts +30 -0
- package/dist/types/sdk/broker/ensure.d.ts +33 -0
- package/dist/types/sdk/broker/identity.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +8 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +95 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +85 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +14 -0
- package/dist/types/sdk/broker/runtime.d.ts +33 -0
- package/dist/types/sdk/broker/session-index.d.ts +57 -0
- package/dist/types/sdk/broker/state-version.d.ts +8 -0
- package/dist/types/sdk/broker/transport.d.ts +9 -0
- package/dist/types/sdk/bus/chat-command-policy.d.ts +49 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +21 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +111 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +77 -0
- package/dist/types/sdk/bus/chat-effect-journal.d.ts +84 -0
- package/dist/types/{notifications → sdk/bus}/config-commands.d.ts +7 -2
- package/dist/types/sdk/bus/config.d.ts +274 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +72 -0
- package/dist/types/sdk/bus/discord-conversation.d.ts +58 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +45 -0
- package/dist/types/sdk/bus/discord-live-provider.d.ts +70 -0
- package/dist/types/sdk/bus/discord-provider.d.ts +85 -0
- package/dist/types/{notifications → sdk/bus}/html-format.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/index.d.ts +113 -13
- package/dist/types/{notifications → sdk/bus}/lifecycle-control-runtime.d.ts +4 -2
- package/dist/types/{notifications → sdk/bus}/lifecycle-orchestrator.d.ts +18 -21
- package/dist/types/{notifications → sdk/bus}/managed-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/native-runtime-compatibility.d.ts +13 -0
- package/dist/types/sdk/bus/notification-orchestration.d.ts +170 -0
- package/dist/types/{notifications → sdk/bus}/notification-service.d.ts +7 -1
- package/dist/types/sdk/bus/session-control.d.ts +83 -0
- package/dist/types/sdk/bus/slack-conversation.d.ts +56 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +60 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +59 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +55 -0
- package/dist/types/{notifications → sdk/bus}/telegram-cli.d.ts +4 -4
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-cli.d.ts +14 -5
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -0
- package/dist/types/{notifications → sdk/bus}/telegram-daemon-control.d.ts +2 -2
- package/dist/types/{notifications → sdk/bus}/telegram-daemon.d.ts +55 -17
- package/dist/types/{notifications → sdk/bus}/telegram-reference.d.ts +2 -2
- package/dist/types/sdk/bus/telegram-setup.d.ts +128 -0
- package/dist/types/{notifications → sdk/bus}/topic-registry.d.ts +11 -0
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/session-cli.d.ts +19 -0
- package/dist/types/sdk/client/client.d.ts +1 -0
- package/dist/types/sdk/client/discovery.d.ts +27 -0
- package/dist/types/sdk/client/index.d.ts +2 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +34 -0
- package/dist/types/sdk/host/control/index.d.ts +2 -0
- package/dist/types/sdk/host/control/operations.d.ts +65 -0
- package/dist/types/sdk/host/events.d.ts +35 -0
- package/dist/types/sdk/host/host.d.ts +35 -0
- package/dist/types/sdk/host/index.d.ts +4 -0
- package/dist/types/sdk/host/query/cursor.d.ts +62 -0
- package/dist/types/sdk/host/query/handlers.d.ts +70 -0
- package/dist/types/sdk/host/query/index.d.ts +3 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +60 -0
- package/dist/types/sdk/host/reverse-leases.d.ts +45 -0
- package/dist/types/sdk/host/types.d.ts +32 -0
- package/dist/types/sdk/index.d.ts +6 -0
- package/dist/types/sdk/lifecycle-session.d.ts +14 -0
- package/dist/types/sdk/mcp/index.d.ts +1 -0
- package/dist/types/sdk/mcp/server.d.ts +37 -0
- package/dist/types/sdk/models.d.ts +44 -0
- package/dist/types/sdk/protocol/adapter-validation.d.ts +14 -0
- package/dist/types/sdk/protocol/operation-registry.d.ts +24 -0
- package/dist/types/{sdk.d.ts → sdk/session.d.ts} +44 -38
- package/dist/types/sdk/startup-capability.d.ts +50 -0
- package/dist/types/session/agent-session.d.ts +114 -21
- package/dist/types/session/default-model-selection.d.ts +25 -0
- package/dist/types/session/fallback-chain-controller.d.ts +52 -0
- package/dist/types/session/session-manager.d.ts +58 -1
- package/dist/types/setup/credential-auto-import.d.ts +36 -6
- package/dist/types/tools/ask-answer-registry.d.ts +1 -1
- package/dist/types/tools/browser/benchmark/detector-report.d.ts +63 -0
- package/dist/types/tools/browser/benchmark/run-suite.d.ts +18 -0
- package/dist/types/tools/browser/launch.d.ts +22 -0
- package/dist/types/tools/browser/profile-discovery.d.ts +31 -0
- package/dist/types/tools/browser/profile-posture.d.ts +37 -0
- package/dist/types/tools/browser/profile-reuse.d.ts +39 -0
- package/dist/types/tools/browser/profile-warmup.d.ts +39 -0
- package/dist/types/tools/browser/registry.d.ts +19 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +8 -8
- package/package.json +34 -13
- package/scripts/build-sdk-package-smoke.ts +94 -0
- package/scripts/capture-notifications-settings-showcase.ts +435 -0
- package/scripts/compile-args.ts +4 -2
- package/scripts/g005-daemon-smoke.ts +4 -5
- package/scripts/g011-daemon-path-smoke.ts +5 -5
- package/scripts/generate-sdk-adapter-parity-manifest.ts +163 -0
- package/scripts/generate-sdk-operation-inventory.ts +867 -0
- package/scripts/generate-telegram-baseline-manifest.ts +128 -0
- package/scripts/manifest-schema.ts +124 -0
- package/scripts/run-test-manifest.ts +153 -0
- package/scripts/run-threat-model-check.ts +157 -0
- package/scripts/verify-gjc-sdk-canonicalization.ts +3467 -0
- package/scripts/verify-gjc-sdk-rename.ts +90 -0
- package/src/append-only-mode.ts +1 -1
- package/src/cli/args.ts +43 -12
- package/src/cli/auth-gateway-cli.ts +2 -2
- package/src/cli/config-cli.ts +2 -1
- package/src/cli/daemon-cli.ts +63 -14
- package/src/cli/list-models.ts +1 -1
- package/src/cli/notify-cli.ts +288 -286
- package/src/cli.ts +21 -2
- package/src/commands/daemon.ts +50 -6
- package/src/commands/harness.ts +93 -36
- package/src/commands/launch.ts +5 -2
- package/src/commands/mcp-serve.ts +19 -12
- package/src/commands/notify.ts +36 -3
- package/src/commands/sdk.ts +426 -0
- package/src/commit/agentic/agent.ts +1 -0
- package/src/config/atomic-yaml-patch.ts +423 -0
- package/src/config/model-profile-activation.ts +240 -89
- package/src/config/model-profiles.ts +18 -15
- package/src/config/model-registry.ts +51 -26
- package/src/config/model-resolver.ts +138 -51
- package/src/config/model-selector-value.ts +50 -0
- package/src/config/models-config-schema.ts +14 -21
- package/src/config/settings-schema.ts +128 -10
- package/src/config/settings.ts +1120 -158
- package/src/coordinator-mcp/model-preset.ts +74 -20
- package/src/coordinator-mcp/server.ts +1597 -3052
- package/src/daemon/builtin.ts +8 -5
- package/src/daemon/control-types.ts +1 -1
- package/src/defaults/gjc/skills/team/SKILL.md +4 -4
- package/src/discovery/mcp-json.ts +327 -20
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/compact-handler.ts +3 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -2
- package/src/extensibility/extensions/loader.ts +69 -5
- package/src/extensibility/extensions/runner.ts +112 -4
- package/src/extensibility/extensions/types.ts +176 -17
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +170 -86
- package/src/gjc-runtime/psmux-detect.ts +82 -4
- package/src/gjc-runtime/session-state-sidecar.ts +41 -31
- package/src/gjc-runtime/team-runtime.ts +54 -35
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +53 -17
- package/src/gjc-runtime/tmux-owner-isolation.ts +1 -1
- package/src/gjc-runtime/tmux-sessions.ts +19 -13
- package/src/gjc-runtime/ultragoal-guard.ts +53 -6
- package/src/gjc-runtime/ultragoal-runtime.ts +155 -3
- package/src/gjc-runtime/windows-powershell-command.ts +58 -0
- package/src/harness-control-plane/operate.ts +9 -9
- package/src/harness-control-plane/owner.ts +283 -39
- package/src/harness-control-plane/receipts.ts +0 -1
- package/src/harness-control-plane/sdk-transport.ts +395 -0
- package/src/harness-control-plane/session-transport.ts +94 -0
- package/src/harness-control-plane/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +34 -35
- package/src/lsp/client.ts +42 -6
- package/src/lsp/index.ts +2 -2
- package/src/main.ts +299 -204
- package/src/modes/DESIGN.md +258 -0
- package/src/modes/acp/acp-agent.ts +1228 -2612
- package/src/modes/acp/acp-client-bridge.ts +2 -20
- package/src/modes/acp/acp-event-mapper.ts +18 -2
- package/src/modes/acp/acp-mode.ts +8 -36
- package/src/modes/acp/permission-mode.ts +25 -0
- package/src/modes/acp/startup-options.ts +6 -0
- package/src/modes/components/agent-dashboard.ts +37 -17
- package/src/modes/components/model-selector.ts +93 -35
- package/src/modes/components/notifications-settings-editor.ts +1379 -0
- package/src/modes/components/settings-selector.ts +60 -11
- package/src/modes/components/welcome.ts +4 -4
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/event-controller.ts +11 -16
- package/src/modes/controllers/extension-ui-controller.ts +348 -4
- package/src/modes/controllers/selector-controller.ts +674 -36
- package/src/modes/index.ts +0 -22
- package/src/modes/interactive-mode.ts +59 -20
- package/src/modes/runtime-init.ts +298 -6
- package/src/modes/shared/agent-wire/approval-gate.ts +9 -11
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +10 -11
- package/src/modes/shared/agent-wire/event-contract.ts +7 -8
- package/src/modes/shared/agent-wire/event-envelope.ts +9 -17
- package/src/modes/shared/agent-wire/event-observation.ts +16 -7
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +1254 -87
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +15 -15
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +113 -0
- package/src/modes/types.ts +5 -0
- package/src/runtime-mcp/client.ts +94 -19
- package/src/runtime-mcp/config.ts +27 -15
- package/src/runtime-mcp/manager.ts +343 -105
- package/src/runtime-mcp/oauth-flow.ts +42 -12
- package/src/runtime-mcp/transports/http.ts +49 -19
- package/src/runtime-mcp/transports/stdio.ts +47 -28
- package/src/runtime-mcp/types.ts +10 -0
- package/src/sdk/acp/adapter.ts +418 -0
- package/src/sdk/acp/index.ts +2 -0
- package/src/sdk/broker/broker.ts +642 -0
- package/src/sdk/broker/discovery.ts +94 -0
- package/src/sdk/broker/ensure.ts +249 -0
- package/src/sdk/broker/identity.ts +42 -0
- package/src/sdk/broker/index.ts +8 -0
- package/src/sdk/broker/internal-source-marker-2178.txt +1 -0
- package/src/sdk/broker/internal-source.bunfig.toml +1 -0
- package/src/sdk/broker/lifecycle-ledger.ts +286 -0
- package/src/sdk/broker/lifecycle.ts +2157 -0
- package/src/sdk/broker/process-incarnation.ts +159 -0
- package/src/sdk/broker/runtime.ts +164 -0
- package/src/sdk/broker/session-index.ts +318 -0
- package/src/sdk/broker/state-version.ts +23 -0
- package/src/sdk/broker/transport.ts +125 -0
- package/src/sdk/bus/chat-command-policy.ts +107 -0
- package/src/sdk/bus/chat-daemon-cli.ts +214 -0
- package/src/sdk/bus/chat-daemon-control.ts +713 -0
- package/src/sdk/bus/chat-daemon-runtime.ts +526 -0
- package/src/sdk/bus/chat-effect-journal.ts +353 -0
- package/src/{notifications → sdk/bus}/config-commands.ts +39 -10
- package/src/sdk/bus/config.ts +433 -0
- package/src/sdk/bus/conversation-store.ts +391 -0
- package/src/sdk/bus/discord-conversation.ts +85 -0
- package/src/sdk/bus/discord-daemon.ts +2275 -0
- package/src/sdk/bus/discord-live-provider.ts +521 -0
- package/src/sdk/bus/discord-provider.ts +56 -0
- package/src/{notifications → sdk/bus}/html-format.ts +2 -2
- package/src/sdk/bus/index.ts +3943 -0
- package/src/{notifications → sdk/bus}/lifecycle-commands.ts +2 -0
- package/src/{notifications → sdk/bus}/lifecycle-control-runtime.ts +225 -196
- package/src/{notifications → sdk/bus}/lifecycle-orchestrator.ts +161 -74
- package/src/{notifications → sdk/bus}/managed-daemon.ts +2 -2
- package/src/sdk/bus/native-runtime-compatibility.ts +42 -0
- package/src/sdk/bus/natives-augment.d.ts +38 -0
- package/src/sdk/bus/notification-orchestration.ts +535 -0
- package/src/{notifications → sdk/bus}/notification-service.ts +106 -12
- package/src/sdk/bus/session-control.ts +333 -0
- package/src/sdk/bus/slack-conversation.ts +74 -0
- package/src/sdk/bus/slack-daemon.ts +2144 -0
- package/src/sdk/bus/slack-live-provider.ts +380 -0
- package/src/sdk/bus/slack-provider.ts +76 -0
- package/src/{notifications → sdk/bus}/telegram-cli.ts +8 -8
- package/src/sdk/bus/telegram-daemon-cli.ts +330 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +14 -0
- package/src/{notifications → sdk/bus}/telegram-daemon-control.ts +4 -4
- package/src/{notifications → sdk/bus}/telegram-daemon.ts +502 -54
- package/src/{notifications → sdk/bus}/telegram-reference.ts +2 -2
- package/src/sdk/bus/telegram-setup.ts +663 -0
- package/src/{notifications → sdk/bus}/topic-registry.ts +32 -0
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/session-cli.ts +275 -0
- package/src/sdk/client/client.ts +1 -0
- package/src/sdk/client/discovery.ts +108 -0
- package/src/sdk/client/index.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +367 -0
- package/src/sdk/host/control/index.ts +10 -0
- package/src/sdk/host/control/operations.ts +63 -0
- package/src/sdk/host/events.ts +91 -0
- package/src/sdk/host/host.ts +296 -0
- package/src/sdk/host/index.ts +4 -0
- package/src/sdk/host/query/cursor.ts +189 -0
- package/src/sdk/host/query/handlers.ts +533 -0
- package/src/sdk/host/query/index.ts +3 -0
- package/src/sdk/host/query/revision-store.ts +1126 -0
- package/src/sdk/host/reverse-leases.ts +325 -0
- package/src/sdk/host/types.ts +29 -0
- package/src/sdk/index.ts +13 -0
- package/src/sdk/lifecycle-session.ts +39 -0
- package/src/sdk/mcp/index.ts +1 -0
- package/src/sdk/mcp/server.ts +332 -0
- package/src/sdk/models.ts +168 -0
- package/src/sdk/protocol/adapter-validation.ts +48 -0
- package/src/sdk/protocol/operation-inventory.generated.json +5654 -0
- package/src/sdk/protocol/operation-registry.ts +312 -0
- package/src/{sdk.ts → sdk/session.ts} +339 -212
- package/src/sdk/startup-capability.ts +189 -0
- package/src/session/agent-session.ts +2453 -894
- package/src/session/default-model-selection.ts +93 -0
- package/src/session/fallback-chain-controller.ts +136 -0
- package/src/session/session-manager.ts +438 -11
- package/src/session/session-storage.ts +8 -1
- package/src/setup/credential-auto-import.ts +290 -35
- package/src/setup/hermes/templates/operator-instructions.v1.md +3 -5
- package/src/slash-commands/builtin-registry.ts +2 -1
- package/src/task/executor.ts +62 -13
- package/src/tools/ask-answer-registry.ts +1 -1
- package/src/tools/ask.ts +13 -8
- package/src/tools/browser/STEALTH-THIRD-PARTY-NOTICES.md +26 -0
- package/src/tools/browser/benchmark/detector-report.ts +157 -0
- package/src/tools/browser/benchmark/run-suite.ts +39 -0
- package/src/tools/browser/launch.ts +124 -19
- package/src/tools/browser/profile-discovery.ts +63 -0
- package/src/tools/browser/profile-posture.ts +59 -0
- package/src/tools/browser/profile-reuse.ts +102 -0
- package/src/tools/browser/profile-warmup.ts +109 -0
- package/src/tools/browser/registry.ts +87 -11
- package/src/tools/browser/tab-protocol.ts +1 -0
- package/src/tools/browser/tab-supervisor.ts +1 -0
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +6 -0
- package/src/tools/index.ts +8 -8
- package/src/tools/puppeteer/14_stealth_permissions.txt +57 -0
- package/src/tools/puppeteer/15_stealth_webrtc.txt +60 -0
- package/src/tools/telegram-send.ts +2 -2
- package/vendor/insane-search/MANIFEST.json +2 -1
- package/vendor/insane-search/engine/bias_check.py +4 -1
- package/vendor/insane-search/engine/phase0.py +26 -1
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +0 -69
- package/dist/types/modes/bridge/auth.d.ts +0 -12
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +0 -9
- package/dist/types/modes/bridge/bridge-mode.d.ts +0 -47
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +0 -88
- package/dist/types/modes/bridge/event-stream.d.ts +0 -8
- package/dist/types/modes/rpc/host-tools.d.ts +0 -1
- package/dist/types/modes/rpc/host-uris.d.ts +0 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +0 -299
- package/dist/types/modes/rpc/rpc-mode.d.ts +0 -86
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +0 -10
- package/dist/types/modes/rpc/rpc-types.d.ts +0 -810
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +0 -37
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +0 -2
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +0 -56
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +0 -16
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +0 -17
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/responses.d.ts +0 -4
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +0 -18
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +0 -25
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +0 -42
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +0 -27
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +0 -29
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +0 -68
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +0 -166
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +0 -127
- package/dist/types/notifications/config.d.ts +0 -132
- package/src/harness-control-plane/rpc-adapter.ts +0 -290
- package/src/modes/bridge/auth.ts +0 -41
- package/src/modes/bridge/bridge-client-bridge.ts +0 -47
- package/src/modes/bridge/bridge-mode.ts +0 -675
- package/src/modes/bridge/bridge-ui-context.ts +0 -200
- package/src/modes/bridge/event-stream.ts +0 -70
- package/src/modes/rpc/host-tools.ts +0 -1
- package/src/modes/rpc/host-uris.ts +0 -1
- package/src/modes/rpc/rpc-client.ts +0 -1166
- package/src/modes/rpc/rpc-mode.ts +0 -1029
- package/src/modes/rpc/rpc-socket-security.ts +0 -115
- package/src/modes/rpc/rpc-types.ts +0 -637
- package/src/modes/shared/agent-wire/command-contract.ts +0 -18
- package/src/modes/shared/agent-wire/command-dispatch.ts +0 -498
- package/src/modes/shared/agent-wire/command-validation.ts +0 -181
- package/src/modes/shared/agent-wire/handshake.ts +0 -157
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +0 -197
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +0 -236
- package/src/modes/shared/agent-wire/protocol.ts +0 -46
- package/src/modes/shared/agent-wire/responses.ts +0 -17
- package/src/modes/shared/agent-wire/scopes.ts +0 -94
- package/src/modes/shared/agent-wire/session-registry.ts +0 -109
- package/src/modes/shared/agent-wire/ui-request-broker.ts +0 -150
- package/src/modes/shared/agent-wire/ui-result.ts +0 -48
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +0 -365
- package/src/modes/shared/agent-wire/unattended-audit.ts +0 -176
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +0 -426
- package/src/modes/shared/agent-wire/unattended-session.ts +0 -528
- package/src/notifications/config.ts +0 -219
- package/src/notifications/index.ts +0 -1835
- package/src/notifications/telegram-daemon-cli.ts +0 -243
- package/src/tools/puppeteer/09_stealth_locale.txt +0 -46
- /package/dist/types/{notifications → sdk/bus}/attachment-registry.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/chat-adapters.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/daemon-paths.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/engine.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/helpers.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/lifecycle-commands.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/operator-runtime.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rate-limit-pool.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/recent-activity.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/reply-sent-store.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-draft.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/rich-render.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-inbound.d.ts +0 -0
- /package/dist/types/{notifications → sdk/bus}/threaded-render.d.ts +0 -0
- /package/src/{notifications → sdk/bus}/attachment-registry.ts +0 -0
- /package/src/{notifications → sdk/bus}/chat-adapters.ts +0 -0
- /package/src/{notifications → sdk/bus}/daemon-paths.ts +0 -0
- /package/src/{notifications → sdk/bus}/engine.ts +0 -0
- /package/src/{notifications → sdk/bus}/helpers.ts +0 -0
- /package/src/{notifications → sdk/bus}/operator-runtime.ts +0 -0
- /package/src/{notifications → sdk/bus}/rate-limit-pool.ts +0 -0
- /package/src/{notifications → sdk/bus}/recent-activity.ts +0 -0
- /package/src/{notifications → sdk/bus}/reply-sent-store.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-draft.ts +0 -0
- /package/src/{notifications → sdk/bus}/rich-render.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-inbound.ts +0 -0
- /package/src/{notifications → sdk/bus}/threaded-render.ts +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile-reuse orchestration (plan Phase 2 wiring).
|
|
3
|
+
*
|
|
4
|
+
* Single entry point the launch path calls to decide, for a default headless
|
|
5
|
+
* session, whether to warm up from an isolated copy of the user's real Chrome
|
|
6
|
+
* profile. Ties together discovery + posture + isolated warm-up. Filesystem
|
|
7
|
+
* effects (the copy) are performed only when the posture resolves to `real`.
|
|
8
|
+
* The heavy pieces are injectable so this is unit-testable without a browser.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as os from "node:os";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { type DiscoveredProfile, type DiscoveryEnv, discoverDefaultChromeProfile } from "./profile-discovery";
|
|
15
|
+
import { decideProfilePosture, type ProfileReusePosture } from "./profile-posture";
|
|
16
|
+
import { collectWarmupArtifacts, type WarmupManifest } from "./profile-warmup";
|
|
17
|
+
|
|
18
|
+
export interface ResolveProfileReuseInputs {
|
|
19
|
+
posture?: ProfileReusePosture;
|
|
20
|
+
/** Caller explicitly requested real-profile reuse (e.g. app.browser === "chrome"). */
|
|
21
|
+
explicitlyRequested?: boolean;
|
|
22
|
+
discoveryEnv: DiscoveryEnv;
|
|
23
|
+
/** Destination for the isolated warm-up copy; defaults to an ephemeral temp dir. */
|
|
24
|
+
destDir?: string;
|
|
25
|
+
/** Injectable copier (defaults to collectWarmupArtifacts). */
|
|
26
|
+
copy?: (sourceProfileDir: string, destDir: string) => WarmupManifest;
|
|
27
|
+
/** Injectable temp-dir factory (defaults to fs.mkdtemp under os.tmpdir()). */
|
|
28
|
+
makeTempDir?: () => string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ProfileReuseResult {
|
|
32
|
+
mode: "real" | "synthetic";
|
|
33
|
+
reason: string;
|
|
34
|
+
warning: string | null;
|
|
35
|
+
discovered: DiscoveredProfile | null;
|
|
36
|
+
/** Isolated copy directory when mode === "real", else null. */
|
|
37
|
+
warmupDir: string | null;
|
|
38
|
+
manifest: WarmupManifest | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function defaultTempDir(): string {
|
|
42
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), "gjc-profile-warmup-"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function errorMessage(error: unknown): string {
|
|
46
|
+
return error instanceof Error ? error.message : String(error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve profile reuse for a default session. When the posture resolves to
|
|
51
|
+
* `real`, copies an allowlisted, lock-free subset of the discovered profile
|
|
52
|
+
* into an isolated dir and returns that dir for the launcher to use.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveProfileReuse(inputs: ResolveProfileReuseInputs): ProfileReuseResult {
|
|
55
|
+
const discovered = discoverDefaultChromeProfile(inputs.discoveryEnv);
|
|
56
|
+
const decision = decideProfilePosture({
|
|
57
|
+
posture: inputs.posture,
|
|
58
|
+
realProfileAvailable: discovered !== null,
|
|
59
|
+
explicitlyRequested: inputs.explicitlyRequested,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (decision.mode !== "real" || !discovered) {
|
|
63
|
+
return {
|
|
64
|
+
mode: "synthetic",
|
|
65
|
+
reason: decision.reason,
|
|
66
|
+
warning: null,
|
|
67
|
+
discovered,
|
|
68
|
+
warmupDir: null,
|
|
69
|
+
manifest: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const ownsDestDir = inputs.destDir === undefined;
|
|
74
|
+
let destDir: string | undefined;
|
|
75
|
+
const copy = inputs.copy ?? collectWarmupArtifacts;
|
|
76
|
+
try {
|
|
77
|
+
destDir = inputs.destDir ?? (inputs.makeTempDir ?? defaultTempDir)();
|
|
78
|
+
const manifest = copy(discovered.profileDir, destDir);
|
|
79
|
+
return {
|
|
80
|
+
mode: "real",
|
|
81
|
+
reason: decision.reason,
|
|
82
|
+
warning: decision.warning,
|
|
83
|
+
discovered,
|
|
84
|
+
warmupDir: destDir,
|
|
85
|
+
manifest,
|
|
86
|
+
};
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (ownsDestDir && destDir) {
|
|
89
|
+
try {
|
|
90
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
mode: "synthetic",
|
|
95
|
+
reason: "synthetic-copy-failed",
|
|
96
|
+
warning: `Could not safely copy the discovered Chrome profile; using synthetic browser state instead: ${errorMessage(error)}`,
|
|
97
|
+
discovered,
|
|
98
|
+
warmupDir: null,
|
|
99
|
+
manifest: null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated profile warm-up (plan Phase 2, R-C3).
|
|
3
|
+
*
|
|
4
|
+
* Aggressive real-profile reuse seeds a session with the user's cookies /
|
|
5
|
+
* localStorage / cache. To stay safe (never lock or corrupt the live browser
|
|
6
|
+
* session) we NEVER bind the source profile directly: we copy an allowlisted
|
|
7
|
+
* subset of artifacts into an ephemeral, isolated directory and drive Chromium
|
|
8
|
+
* against that copy. Chromium single-instance lock files (SingletonLock/Socket/
|
|
9
|
+
* Cookie) are explicitly excluded so the isolated copy can never contend with a
|
|
10
|
+
* running browser.
|
|
11
|
+
*
|
|
12
|
+
* This module is pure filesystem logic (no browser, no network) so it is
|
|
13
|
+
* deterministically unit-testable.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
|
|
19
|
+
/** Chromium single-instance lock artifacts — must never be copied. */
|
|
20
|
+
export const CHROME_LOCK_ARTIFACTS = ["SingletonLock", "SingletonSocket", "SingletonCookie"] as const;
|
|
21
|
+
const CHROME_LOCK_ARTIFACT_SET = new Set<string>(CHROME_LOCK_ARTIFACTS);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Profile-relative artifacts worth seeding for warm-up. Kept intentionally
|
|
25
|
+
* small: session state (cookies, storage) plus a light cache. Anything not
|
|
26
|
+
* listed is skipped, which also keeps the isolated copy cheap.
|
|
27
|
+
*/
|
|
28
|
+
export const WARMUP_ARTIFACTS = [
|
|
29
|
+
"Cookies",
|
|
30
|
+
"Cookies-journal",
|
|
31
|
+
"Local Storage",
|
|
32
|
+
"Session Storage",
|
|
33
|
+
"IndexedDB",
|
|
34
|
+
"Network",
|
|
35
|
+
"Preferences",
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
export interface WarmupManifest {
|
|
39
|
+
sourceProfileDir: string;
|
|
40
|
+
destDir: string;
|
|
41
|
+
copied: string[];
|
|
42
|
+
skippedMissing: string[];
|
|
43
|
+
/** Always empty unless a lock artifact was seen (never copied). */
|
|
44
|
+
excludedLocks: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function copyRecursive(src: string, dest: string, manifest: WarmupManifest): void {
|
|
48
|
+
const stat = fs.statSync(src);
|
|
49
|
+
if (stat.isDirectory()) {
|
|
50
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
51
|
+
for (const entry of fs.readdirSync(src)) {
|
|
52
|
+
if (CHROME_LOCK_ARTIFACT_SET.has(entry)) {
|
|
53
|
+
if (!manifest.excludedLocks.includes(entry)) manifest.excludedLocks.push(entry);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry), manifest);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
60
|
+
fs.copyFileSync(src, dest);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Copy warm-up artifacts from `sourceProfileDir` into an isolated `destDir`.
|
|
66
|
+
*
|
|
67
|
+
* Guarantees:
|
|
68
|
+
* - The source profile is only ever read, never written or locked.
|
|
69
|
+
* - Chromium lock artifacts are never copied (recorded in `excludedLocks`).
|
|
70
|
+
* - Missing artifacts are skipped, not fatal (recorded in `skippedMissing`).
|
|
71
|
+
*/
|
|
72
|
+
export function collectWarmupArtifacts(sourceProfileDir: string, destDir: string): WarmupManifest {
|
|
73
|
+
const manifest: WarmupManifest = {
|
|
74
|
+
sourceProfileDir,
|
|
75
|
+
destDir,
|
|
76
|
+
copied: [],
|
|
77
|
+
skippedMissing: [],
|
|
78
|
+
excludedLocks: [],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(sourceProfileDir)) {
|
|
82
|
+
throw new Error(`source profile directory does not exist: ${sourceProfileDir}`);
|
|
83
|
+
}
|
|
84
|
+
const resolvedSource = path.resolve(sourceProfileDir);
|
|
85
|
+
const resolvedDest = path.resolve(destDir);
|
|
86
|
+
if (resolvedDest === resolvedSource || resolvedDest.startsWith(`${resolvedSource}${path.sep}`)) {
|
|
87
|
+
throw new Error("warm-up destination must be outside the source profile directory");
|
|
88
|
+
}
|
|
89
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
// Record (but never copy) any lock artifacts present in the source.
|
|
92
|
+
for (const lock of CHROME_LOCK_ARTIFACTS) {
|
|
93
|
+
if (fs.existsSync(path.join(sourceProfileDir, lock))) {
|
|
94
|
+
manifest.excludedLocks.push(lock);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const artifact of WARMUP_ARTIFACTS) {
|
|
99
|
+
const src = path.join(sourceProfileDir, artifact);
|
|
100
|
+
if (!fs.existsSync(src)) {
|
|
101
|
+
manifest.skippedMissing.push(artifact);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
copyRecursive(src, path.join(destDir, artifact), manifest);
|
|
105
|
+
manifest.copied.push(artifact);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return manifest;
|
|
109
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { logger } from "@gajae-code/utils";
|
|
3
4
|
import type { Subprocess } from "bun";
|
|
@@ -12,6 +13,9 @@ import {
|
|
|
12
13
|
waitForCdp,
|
|
13
14
|
} from "./attach";
|
|
14
15
|
import { BROWSER_PROTOCOL_TIMEOUT_MS, launchHeadlessBrowser, loadPuppeteer, type UserAgentOverride } from "./launch";
|
|
16
|
+
import { defaultDiscoveryEnv } from "./profile-discovery";
|
|
17
|
+
import type { ProfileReusePosture } from "./profile-posture";
|
|
18
|
+
import { resolveProfileReuse } from "./profile-reuse";
|
|
15
19
|
|
|
16
20
|
export type BrowserKind =
|
|
17
21
|
| { kind: "headless"; headless: boolean }
|
|
@@ -29,9 +33,15 @@ export type BrowserKind =
|
|
|
29
33
|
|
|
30
34
|
export type BrowserKindTag = BrowserKind["kind"];
|
|
31
35
|
|
|
36
|
+
export interface BrowserGeo {
|
|
37
|
+
readonly timezone?: string;
|
|
38
|
+
readonly locale?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
export interface BrowserHandle {
|
|
33
42
|
key: string;
|
|
34
43
|
kind: BrowserKind;
|
|
44
|
+
readonly geo?: BrowserGeo;
|
|
35
45
|
browser: Browser;
|
|
36
46
|
cdpUrl?: string;
|
|
37
47
|
pid?: number;
|
|
@@ -43,6 +53,7 @@ export interface BrowserHandle {
|
|
|
43
53
|
type SpawnedChromeProfileKind = Extract<BrowserKind, { kind: "chrome-profile" }>;
|
|
44
54
|
|
|
45
55
|
const browsers = new Map<string, BrowserHandle>();
|
|
56
|
+
const browserOpenPromises = new Map<string, Promise<BrowserHandle>>();
|
|
46
57
|
|
|
47
58
|
/**
|
|
48
59
|
* Upper bound on the CDP `browser.close()` round-trip during a forced (signal-path)
|
|
@@ -51,10 +62,10 @@ const browsers = new Map<string, BrowserHandle>();
|
|
|
51
62
|
*/
|
|
52
63
|
const HEADLESS_FORCE_CLOSE_GRACE_MS = 1_500;
|
|
53
64
|
|
|
54
|
-
function browserKey(kind: BrowserKind): string {
|
|
65
|
+
function browserKey(kind: BrowserKind, geo?: BrowserGeo, profileReuse?: ProfileReusePosture): string {
|
|
55
66
|
switch (kind.kind) {
|
|
56
67
|
case "headless":
|
|
57
|
-
return `headless:${kind.headless ? "1" : "0"}`;
|
|
68
|
+
return `headless:${kind.headless ? "1" : "0"}:${profileReuse ?? "synthetic"}:${geo?.timezone ?? ""}:${geo?.locale ?? ""}`;
|
|
58
69
|
case "spawned":
|
|
59
70
|
return `spawned:${kind.path}`;
|
|
60
71
|
case "chrome-profile":
|
|
@@ -69,28 +80,90 @@ export interface AcquireBrowserOptions {
|
|
|
69
80
|
viewport?: { width: number; height: number; deviceScaleFactor?: number };
|
|
70
81
|
appArgs?: string[];
|
|
71
82
|
signal?: AbortSignal;
|
|
83
|
+
/**
|
|
84
|
+
* Profile-reuse posture for the default headless path (from settings
|
|
85
|
+
* `browser.profileReuse`, resolved by the settings-aware caller). When set,
|
|
86
|
+
* the headless browser may warm up from an isolated copy of the user's real
|
|
87
|
+
* Chrome profile (see profile-reuse.ts). Omitted = synthetic session.
|
|
88
|
+
*/
|
|
89
|
+
profileReuse?: ProfileReusePosture;
|
|
90
|
+
/**
|
|
91
|
+
* Opt-in geo alignment for the default headless path (from settings
|
|
92
|
+
* `browser.geo.*`). Default unset = no-op (real timezone/locale preserved).
|
|
93
|
+
*/
|
|
94
|
+
geo?: BrowserGeo;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizeGeo(geo: AcquireBrowserOptions["geo"]): BrowserGeo | undefined {
|
|
98
|
+
const timezone = geo?.timezone
|
|
99
|
+
? new Intl.DateTimeFormat(undefined, { timeZone: geo.timezone }).resolvedOptions().timeZone
|
|
100
|
+
: undefined;
|
|
101
|
+
const locale = geo?.locale ? Intl.getCanonicalLocales(geo.locale)[0] : undefined;
|
|
102
|
+
return timezone || locale ? Object.freeze({ timezone, locale }) : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function browserKeyForTest(
|
|
106
|
+
kind: BrowserKind,
|
|
107
|
+
opts: Pick<AcquireBrowserOptions, "geo" | "profileReuse">,
|
|
108
|
+
): string {
|
|
109
|
+
const geo = kind.kind === "headless" ? normalizeGeo(opts.geo) : undefined;
|
|
110
|
+
return browserKey(kind, geo, opts.profileReuse);
|
|
72
111
|
}
|
|
73
112
|
|
|
74
113
|
export async function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
|
|
75
|
-
const
|
|
114
|
+
const geo = kind.kind === "headless" ? normalizeGeo(opts.geo) : undefined;
|
|
115
|
+
const key = browserKey(kind, geo, opts.profileReuse);
|
|
76
116
|
const existing = browsers.get(key);
|
|
77
117
|
if (existing) {
|
|
78
118
|
if (existing.browser.connected) return existing;
|
|
79
119
|
browsers.delete(key);
|
|
80
120
|
await disposeBrowserHandle(existing, { kill: false });
|
|
81
121
|
}
|
|
122
|
+
const pending = browserOpenPromises.get(key);
|
|
123
|
+
if (pending) return await pending;
|
|
82
124
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
125
|
+
const opening = openBrowserHandle(kind, opts, geo, key).then(handle => {
|
|
126
|
+
browsers.set(key, handle);
|
|
127
|
+
return handle;
|
|
128
|
+
});
|
|
129
|
+
browserOpenPromises.set(key, opening);
|
|
130
|
+
try {
|
|
131
|
+
return await opening;
|
|
132
|
+
} finally {
|
|
133
|
+
if (browserOpenPromises.get(key) === opening) browserOpenPromises.delete(key);
|
|
134
|
+
}
|
|
86
135
|
}
|
|
87
136
|
|
|
88
|
-
async function openBrowserHandle(
|
|
137
|
+
async function openBrowserHandle(
|
|
138
|
+
kind: BrowserKind,
|
|
139
|
+
opts: AcquireBrowserOptions,
|
|
140
|
+
geo: BrowserGeo | undefined,
|
|
141
|
+
key: string,
|
|
142
|
+
): Promise<BrowserHandle> {
|
|
89
143
|
if (kind.kind === "headless") {
|
|
90
|
-
|
|
144
|
+
let profileWarmupDir: string | undefined;
|
|
145
|
+
if (opts.profileReuse) {
|
|
146
|
+
const reuse = resolveProfileReuse({
|
|
147
|
+
posture: opts.profileReuse,
|
|
148
|
+
discoveryEnv: defaultDiscoveryEnv(fs.existsSync),
|
|
149
|
+
});
|
|
150
|
+
if (reuse.warning) logger.warn(reuse.warning);
|
|
151
|
+
if (reuse.mode === "real" && reuse.warmupDir) {
|
|
152
|
+
profileWarmupDir = reuse.warmupDir;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const browser = await launchHeadlessBrowser({
|
|
156
|
+
headless: kind.headless,
|
|
157
|
+
viewport: opts.viewport,
|
|
158
|
+
profileWarmupDir,
|
|
159
|
+
...(geo ? { geo } : {}),
|
|
160
|
+
});
|
|
91
161
|
return {
|
|
92
|
-
key
|
|
162
|
+
key,
|
|
163
|
+
|
|
93
164
|
kind,
|
|
165
|
+
geo,
|
|
166
|
+
|
|
94
167
|
browser,
|
|
95
168
|
refCount: 0,
|
|
96
169
|
stealth: { browserSession: null, override: null },
|
|
@@ -106,7 +179,8 @@ async function openBrowserHandle(kind: BrowserKind, opts: AcquireBrowserOptions)
|
|
|
106
179
|
protocolTimeout: BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
107
180
|
});
|
|
108
181
|
return {
|
|
109
|
-
key
|
|
182
|
+
key,
|
|
183
|
+
|
|
110
184
|
kind,
|
|
111
185
|
browser,
|
|
112
186
|
cdpUrl,
|
|
@@ -254,6 +328,7 @@ export async function openChromeProfileHandle(
|
|
|
254
328
|
}
|
|
255
329
|
return {
|
|
256
330
|
key: browserKey(kind),
|
|
331
|
+
|
|
257
332
|
kind,
|
|
258
333
|
browser,
|
|
259
334
|
cdpUrl,
|
|
@@ -320,6 +395,7 @@ async function openSpawnedBrowserHandle(
|
|
|
320
395
|
}
|
|
321
396
|
return {
|
|
322
397
|
key: browserKey(kind),
|
|
398
|
+
|
|
323
399
|
kind,
|
|
324
400
|
browser,
|
|
325
401
|
cdpUrl,
|
|
@@ -337,7 +413,7 @@ export function holdBrowser(handle: BrowserHandle): void {
|
|
|
337
413
|
export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
|
|
338
414
|
handle.refCount = Math.max(0, handle.refCount - 1);
|
|
339
415
|
if (handle.refCount === 0) {
|
|
340
|
-
browsers.delete(handle.key);
|
|
416
|
+
if (browsers.get(handle.key) === handle) browsers.delete(handle.key);
|
|
341
417
|
await disposeBrowserHandle(handle, opts);
|
|
342
418
|
}
|
|
343
419
|
}
|
|
@@ -46,6 +46,7 @@ export type WorkerInitPayload =
|
|
|
46
46
|
browserWSEndpoint: string;
|
|
47
47
|
safeDir: string;
|
|
48
48
|
viewport?: { width: number; height: number; deviceScaleFactor?: number };
|
|
49
|
+
geo?: { readonly timezone?: string; readonly locale?: string };
|
|
49
50
|
dialogs?: "accept" | "dismiss";
|
|
50
51
|
url?: string;
|
|
51
52
|
waitUntil?: "load" | "domcontentloaded" | "networkidle0" | "networkidle2";
|
|
@@ -427,6 +427,7 @@ async function buildInitPayload(browser: BrowserHandle, opts: AcquireTabOptions)
|
|
|
427
427
|
browserWSEndpoint,
|
|
428
428
|
safeDir,
|
|
429
429
|
viewport: opts.viewport,
|
|
430
|
+
...(browser.geo ? { geo: browser.geo } : {}),
|
|
430
431
|
dialogs: opts.dialogs,
|
|
431
432
|
url: opts.url,
|
|
432
433
|
waitUntil: opts.waitUntil,
|
|
@@ -454,7 +454,7 @@ export class WorkerCore {
|
|
|
454
454
|
});
|
|
455
455
|
if (payload.mode === "headless") {
|
|
456
456
|
this.#page = await this.#browser.newPage();
|
|
457
|
-
await applyStealthPatches(this.#browser, this.#page, { browserSession: null, override: null });
|
|
457
|
+
await applyStealthPatches(this.#browser, this.#page, { browserSession: null, override: null }, payload.geo);
|
|
458
458
|
await applyViewport(this.#page, payload.viewport);
|
|
459
459
|
if (payload.dialogs) this.#applyDialogPolicy(payload.dialogs);
|
|
460
460
|
if (payload.url) {
|
package/src/tools/browser.ts
CHANGED
|
@@ -233,6 +233,12 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
|
|
|
233
233
|
}
|
|
234
234
|
: undefined,
|
|
235
235
|
appArgs: params.app?.args,
|
|
236
|
+
profileReuse:
|
|
237
|
+
(this.session.settings.get("browser.profileReuse") as "auto" | "opt-in" | undefined) ?? "auto",
|
|
238
|
+
geo: {
|
|
239
|
+
timezone: this.session.settings.get("browser.geo.timezone") as string | undefined,
|
|
240
|
+
locale: this.session.settings.get("browser.geo.locale") as string | undefined,
|
|
241
|
+
},
|
|
236
242
|
signal,
|
|
237
243
|
}),
|
|
238
244
|
);
|
package/src/tools/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { GoalModeState, GoalRuntime } from "../goals";
|
|
|
10
10
|
import { GoalTool } from "../goals/tools/goal-tool";
|
|
11
11
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
12
12
|
import { LspTool } from "../lsp";
|
|
13
|
-
import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/
|
|
13
|
+
import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/workflow-gate-broker";
|
|
14
14
|
import type { PlanModeState } from "../plan-mode/state";
|
|
15
15
|
import type { AgentRegistry } from "../registry/agent-registry";
|
|
16
16
|
import type {
|
|
@@ -200,9 +200,10 @@ export interface AskRemoteReceipt {
|
|
|
200
200
|
export type AskAnswerSourceResult = AskRemoteReceipt | string | undefined;
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
|
-
* Source of remote answers. `awaitAnswer` remains the legacy
|
|
204
|
-
* integrations; typed sources opt into `awaitAnswerRequest`.
|
|
205
|
-
* string-only source from accidentally acquiring acknowledgement
|
|
203
|
+
* Source of remote answers for interactive asks. `awaitAnswer` remains the legacy
|
|
204
|
+
* wire for existing integrations; typed sources opt into `awaitAnswerRequest`.
|
|
205
|
+
* This keeps a string-only source from accidentally acquiring acknowledgement
|
|
206
|
+
* authority while allowing SDK-routed interactions to settle durably.
|
|
206
207
|
*/
|
|
207
208
|
export interface AskAnswerSource {
|
|
208
209
|
awaitAnswer(question: string, options: string[], signal?: AbortSignal): Promise<string | undefined>;
|
|
@@ -305,12 +306,11 @@ export interface ToolSession {
|
|
|
305
306
|
getPlanModeState?: () => PlanModeState | undefined;
|
|
306
307
|
/** Goal mode state (if active or paused) */
|
|
307
308
|
getGoalModeState?: () => GoalModeState | undefined;
|
|
308
|
-
/**
|
|
309
|
+
/** SDK workflow-gate emitter, when a remote gate responder is connected. */
|
|
309
310
|
getWorkflowGateEmitter?: () => WorkflowGateEmitter | undefined;
|
|
310
311
|
/**
|
|
311
|
-
* Optional
|
|
312
|
-
* tool races the local UI selection against
|
|
313
|
-
* reply via the notifications SDK) so asks can be answered without RPC mode.
|
|
312
|
+
* Optional SDK-routed answer source for interactive asks. When present, the
|
|
313
|
+
* tool races the local UI selection against the remote SDK answer.
|
|
314
314
|
* No-op when undefined: the ask path behaves exactly as before.
|
|
315
315
|
*/
|
|
316
316
|
getAskAnswerSource?: () => AskAnswerSource | undefined;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Permissions / notification headless tell.
|
|
2
|
+
//
|
|
3
|
+
// Headless Chrome reports `Notification.permission === "denied"` by default and
|
|
4
|
+
// a matching `permissions.query({name:"notifications"})` state, while a real
|
|
5
|
+
// user browser reports `"default"` / `"prompt"`. Detectors such as CreepJS key
|
|
6
|
+
// on this exact mismatch to flag headless. Normalize both to the human default.
|
|
7
|
+
//
|
|
8
|
+
// Technique adapted (reimplemented) from puppeteer-extra-plugin-stealth
|
|
9
|
+
// (navigator.permissions) — see STEALTH-THIRD-PARTY-NOTICES.md.
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
if (typeof Notification !== "undefined" && Notification.permission === "denied") {
|
|
13
|
+
Object_defineProperty(Notification, "permission", {
|
|
14
|
+
get() {
|
|
15
|
+
return "default";
|
|
16
|
+
},
|
|
17
|
+
configurable: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
} catch (e) {}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const permissions = navigator.permissions;
|
|
24
|
+
const originalQuery = permissions && permissions.query;
|
|
25
|
+
if (originalQuery && typeof PermissionStatus !== "undefined") {
|
|
26
|
+
permissions.query = new Window_Proxy(originalQuery, {
|
|
27
|
+
apply(target, ctx, args) {
|
|
28
|
+
const params = args[0];
|
|
29
|
+
if (params && params.name === "notifications") {
|
|
30
|
+
return Promise_resolve(
|
|
31
|
+
Object_create(PermissionStatus.prototype, {
|
|
32
|
+
state: {
|
|
33
|
+
get() {
|
|
34
|
+
return "prompt";
|
|
35
|
+
},
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
},
|
|
39
|
+
name: {
|
|
40
|
+
value: "notifications",
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
},
|
|
44
|
+
onchange: {
|
|
45
|
+
value: null,
|
|
46
|
+
writable: true,
|
|
47
|
+
enumerable: true,
|
|
48
|
+
configurable: true,
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return Reflect.apply(target, ctx, args);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// WebRTC IP-leak guard.
|
|
2
|
+
//
|
|
3
|
+
// Even with --force-webrtc-ip-handling-policy, RTCPeerConnection can surface
|
|
4
|
+
// ICE candidates that embed a raw local/public IP literal — a bot/deanon tell
|
|
5
|
+
// that CreepJS and others read. This wrapper strips ONLY candidates that carry
|
|
6
|
+
// a non-mDNS raw IP literal; mDNS ".local" candidates and all other WebRTC
|
|
7
|
+
// behavior (offer/answer, data channels) are left intact so real WebRTC keeps
|
|
8
|
+
// working.
|
|
9
|
+
//
|
|
10
|
+
// Technique adapted (reimplemented) from puppeteer-extra-plugin-stealth
|
|
11
|
+
// (navigator.mediaDevices / webrtc) — see STEALTH-THIRD-PARTY-NOTICES.md.
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const RTCP = typeof RTCPeerConnection !== "undefined" ? RTCPeerConnection : undefined;
|
|
15
|
+
if (RTCP && RTCP.prototype) {
|
|
16
|
+
// A raw IPv4/IPv6 literal that is NOT an mDNS ".local" hostname.
|
|
17
|
+
const rawIpInCandidate = (candidate) => {
|
|
18
|
+
if (typeof candidate !== "string" || candidate.length === 0) return false;
|
|
19
|
+
if (/\.local\b/i.test(candidate)) return false; // mDNS obfuscated — safe
|
|
20
|
+
const ipv4 = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/;
|
|
21
|
+
const ipv6 = /\b(?:[0-9a-f]{1,4}:){2,}[0-9a-f]{0,4}\b/i;
|
|
22
|
+
return ipv4.test(candidate) || ipv6.test(candidate);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const origAddEventListener = RTCP.prototype.addEventListener;
|
|
26
|
+
RTCP.prototype.addEventListener = new Window_Proxy(origAddEventListener, {
|
|
27
|
+
apply(target, ctx, args) {
|
|
28
|
+
if (args[0] === "icecandidate" && typeof args[1] === "function") {
|
|
29
|
+
const userCb = args[1];
|
|
30
|
+
const wrapped = function (event) {
|
|
31
|
+
if (event && event.candidate && rawIpInCandidate(event.candidate.candidate)) {
|
|
32
|
+
return; // drop the raw-IP candidate; keep gathering others
|
|
33
|
+
}
|
|
34
|
+
return Reflect.apply(userCb, this, arguments);
|
|
35
|
+
};
|
|
36
|
+
return Reflect.apply(target, ctx, [args[0], wrapped, args[2]]);
|
|
37
|
+
}
|
|
38
|
+
return Reflect.apply(target, ctx, args);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Also guard the onicecandidate setter path.
|
|
43
|
+
const desc = Object_getOwnPropertyDescriptor(RTCP.prototype, "onicecandidate");
|
|
44
|
+
if (desc && desc.set) {
|
|
45
|
+
Object_defineProperty(RTCP.prototype, "onicecandidate", {
|
|
46
|
+
configurable: true,
|
|
47
|
+
enumerable: desc.enumerable,
|
|
48
|
+
get: desc.get,
|
|
49
|
+
set(cb) {
|
|
50
|
+
if (typeof cb !== "function") return desc.set.call(this, cb);
|
|
51
|
+
const wrapped = function (event) {
|
|
52
|
+
if (event && event.candidate && rawIpInCandidate(event.candidate.candidate)) return;
|
|
53
|
+
return Reflect.apply(cb, this, arguments);
|
|
54
|
+
};
|
|
55
|
+
return desc.set.call(this, wrapped);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {}
|
|
@@ -2,8 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
4
4
|
import { z } from "zod/v4";
|
|
5
|
-
import { getTelegramFileSink } from "../
|
|
6
|
-
import { getNotificationConfig, isGloballyConfigured } from "../
|
|
5
|
+
import { getTelegramFileSink } from "../sdk/bus/attachment-registry";
|
|
6
|
+
import { getNotificationConfig, isGloballyConfigured } from "../sdk/bus/config";
|
|
7
7
|
import type { ToolSession } from "./index";
|
|
8
8
|
|
|
9
9
|
const TELEGRAM_SEND_MAX_FILE_BYTES = 50 * 1024 * 1024;
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
],
|
|
21
21
|
"exclusionRationale": "Excludes upstream install hooks (SessionStart settings.json mutation), GitHub star-baiting (gh api user/starred), the update-notifier, and the past-session transcript-language scanner. Only the runtime Phase 0-3 engine and its Playwright/stealth templates are vendored.",
|
|
22
22
|
"localPatches": [
|
|
23
|
-
"engine/content_safety.py + FetchResult trust/risk metadata (content_trust, prompt_injection_risk, prompt_injection_signals, untrusted_content_boundary) and to_untrusted_text(): cherry-picked from insane-search a16f7c1 (upstream PR #5, v0.9.0). Adds prompt-injection labelling to the JSON surface (to_dict); the default CLI text output is intentionally left unchanged (plain content preserved, no envelope)."
|
|
23
|
+
"engine/content_safety.py + FetchResult trust/risk metadata (content_trust, prompt_injection_risk, prompt_injection_signals, untrusted_content_boundary) and to_untrusted_text(): cherry-picked from insane-search a16f7c1 (upstream PR #5, v0.9.0). Adds prompt-injection labelling to the JSON surface (to_dict); the default CLI text output is intentionally left unchanged (plain content preserved, no envelope).",
|
|
24
|
+
"engine/phase0.py _ytdlp_argv() console-script→module fallback + engine/bias_check.py rel.as_posix() Windows exemption + engine/tests/test_u9.py: cherry-picked from insane-search 33f898d (upstream PR #6, released v0.9.2). Cross-platform yt-dlp resolution; no capability or site logic added."
|
|
24
25
|
],
|
|
25
26
|
"notes": "Runtime engine is invoked via `python3 -m engine \"<url>\" --json` with cwd=this directory and PYTHONPATH pointed at this directory. Phase 0-2 require python3 + curl_cffi; Phase 3 requires node + playwright/playwright-extra/puppeteer-extra-plugin-stealth installed under engine/templates. GJC never auto-installs these dependencies."
|
|
26
27
|
}
|
|
@@ -98,7 +98,10 @@ def _line_is_exempt(line: str, ext: str) -> bool:
|
|
|
98
98
|
def _scan_file(path: Path, root: Path) -> list[str]:
|
|
99
99
|
"""Return list of violation strings for this file."""
|
|
100
100
|
rel = path.relative_to(root.parent)
|
|
101
|
-
|
|
101
|
+
# Compare with forward slashes so the exemption matches on Windows too —
|
|
102
|
+
# `str(rel)` yields backslashes there, silently defeating the phase0.py
|
|
103
|
+
# exemption (EXPLICIT_ALLOW_FILES uses POSIX-style paths).
|
|
104
|
+
if rel.as_posix() in EXPLICIT_ALLOW_FILES:
|
|
102
105
|
return []
|
|
103
106
|
|
|
104
107
|
ext = path.suffix.lower()
|
|
@@ -24,8 +24,11 @@ Each attempt dict: {"route","platform","ok","status","bytes","note"}.
|
|
|
24
24
|
"""
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import importlib.util
|
|
27
28
|
import re
|
|
29
|
+
import shutil
|
|
28
30
|
import subprocess
|
|
31
|
+
import sys
|
|
29
32
|
from typing import Optional
|
|
30
33
|
from urllib.parse import urlsplit
|
|
31
34
|
|
|
@@ -182,11 +185,33 @@ def _x(url: str, timeout: int) -> dict:
|
|
|
182
185
|
|
|
183
186
|
|
|
184
187
|
# --- youtube -----------------------------------------------------------------
|
|
188
|
+
def _ytdlp_argv() -> Optional[list[str]]:
|
|
189
|
+
"""Best yt-dlp invocation for this environment, or None if unavailable.
|
|
190
|
+
|
|
191
|
+
Prefers the ``yt-dlp`` console script on PATH; falls back to
|
|
192
|
+
``<python> -m yt_dlp`` when the script dir is not on PATH but the module is
|
|
193
|
+
importable — the common case for ``pip install --user`` and Windows / venv
|
|
194
|
+
installs, where the Scripts/bin dir is frequently absent from PATH. Mirrors
|
|
195
|
+
the ``which yt-dlp || python3 -m yt_dlp`` fallback already documented in
|
|
196
|
+
references/media.md."""
|
|
197
|
+
exe = shutil.which("yt-dlp")
|
|
198
|
+
if exe:
|
|
199
|
+
return [exe]
|
|
200
|
+
if importlib.util.find_spec("yt_dlp") is not None:
|
|
201
|
+
return [sys.executable, "-m", "yt_dlp"]
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
185
205
|
def _youtube(url: str, timeout: int) -> dict:
|
|
186
206
|
attempts: list[dict] = []
|
|
207
|
+
argv = _ytdlp_argv()
|
|
208
|
+
if argv is None:
|
|
209
|
+
attempts.append(_attempt("youtube", "yt-dlp", False, 0, "", "yt-dlp not installed"))
|
|
210
|
+
return {"platform": "youtube", "ok": False, "route": None, "content": "",
|
|
211
|
+
"final_url": url, "attempts": attempts}
|
|
187
212
|
try:
|
|
188
213
|
p = subprocess.run(
|
|
189
|
-
["
|
|
214
|
+
argv + ["--dump-json", "--skip-download", url],
|
|
190
215
|
capture_output=True, text=True, timeout=max(timeout, 60),
|
|
191
216
|
)
|
|
192
217
|
ok = p.returncode == 0 and p.stdout.strip().startswith("{")
|