@reticlehq/server 3.9.0 → 3.10.1
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/SKILL.md +13 -12
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -187
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/cli-doctor.js +1 -1
- package/dist/command/cli/cli-flow-commands.js +2 -9
- package/dist/command/cli/cli-kill.js +1 -1
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +17 -15
- package/dist/command/cli/cli-parse.js +44 -27
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +58 -29
- package/dist/command/cli/cli-verify.d.ts +11 -2
- package/dist/command/cli/cli-verify.js +27 -8
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor-repairs.js +1 -1
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +1 -1
- package/dist/command/cli/ports/port-holder.d.ts +9 -0
- package/dist/command/cli/ports/port-holder.js +27 -3
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +22 -0
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +7 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +46 -41
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +12 -1
- package/dist/command/daemon/daemon.js +35 -4
- package/dist/command/daemon/lifetime/death-cause.js +21 -2
- package/dist/command/daemon/lifetime/heartbeat.js +7 -2
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-runtime.js +43 -2
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +6 -3
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/version-skew.js +1 -1
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/visual/visual-tools.js +66 -19
- package/dist/index.js +7 -4
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +44 -0
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +19 -3
- package/dist/language/flows/recording/tape/recordings.js +71 -5
- package/dist/language/flows/replay.d.ts +19 -5
- package/dist/language/flows/replay.js +99 -7
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +28 -9
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +22 -20
- package/dist/portal/input/real-input.js +41 -31
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +75 -18
- package/dist/portal/pool/browser-pool.d.ts +11 -29
- package/dist/portal/pool/browser-pool.js +35 -17
- package/dist/portal/pool/context-permissions.d.ts +17 -3
- package/dist/portal/pool/context-permissions.js +36 -5
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +44 -17
- package/dist/portal/pool/lease-page-error.js +147 -5
- package/dist/portal/pool/page-error-journal.d.ts +7 -1
- package/dist/portal/pool/page-error-journal.js +27 -0
- package/dist/portal/pool/playwright-launcher.js +51 -8
- package/dist/portal/pool/pool-contract.d.ts +27 -1
- package/dist/portal/pool/pool-lease.d.ts +36 -1
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +23 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +7 -6
- package/dist/portal/session/dev-server/dev-command.js +61 -15
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +67 -25
- package/dist/portal/session/no-session-next-action.d.ts +2 -0
- package/dist/portal/session/no-session-next-action.js +12 -3
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +38 -3
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +174 -63
- package/dist/start-wiring.d.ts +2 -1
- package/dist/start-wiring.js +2 -1
- package/dist/surface/mcp/mcp.js +3 -2
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +1 -1
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/after-match.d.ts +4 -1
- package/dist/surface/tools/act/after-match.js +8 -1
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +15 -1
- package/dist/surface/tools/act/resolve-target.js +24 -1
- package/dist/surface/tools/act/sequence-grade.d.ts +12 -0
- package/dist/surface/tools/act/sequence-grade.js +37 -26
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +78 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +74 -86
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +16 -37
- package/dist/surface/tools/browser-tools.js +12 -6
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +9 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +91 -21
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +3 -2
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +35 -1
- package/dist/surface/tools/lease-hint.js +42 -0
- package/dist/surface/tools/lease-readiness.d.ts +26 -0
- package/dist/surface/tools/lease-readiness.js +79 -0
- package/dist/surface/tools/lease-seed.d.ts +14 -1
- package/dist/surface/tools/lease-seed.js +35 -3
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +62 -110
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +6 -0
- package/dist/surface/tools/tool-kit.js +2 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +28 -5
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +1 -0
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +8 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +1 -1
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/init.mdx +2 -2
- package/docs/cli/license.mdx +9 -7
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +33 -23
- package/docs/deploy-checks.md +1 -1
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +2 -2
- package/docs/environment.mdx +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +2 -2
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +30 -1
- package/docs/usage.md +17 -14
- package/package.json +11 -14
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- package/dist/features/exhaust/exhaust-run.js +0 -82
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Pure apart from one injected reader, so every case is a unit test rather than a temp directory.
|
|
10
10
|
*/
|
|
11
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
11
|
+
import { accessSync, constants, readFileSync, readdirSync } from 'node:fs';
|
|
12
12
|
import { join } from 'node:path';
|
|
13
|
-
import { RETICLE_NPM_PACKAGE, desktopLaunch, workspacePackageDirs } from '@reticlehq/init';
|
|
13
|
+
import { RETICLE_NPM_PACKAGE, desktopLaunch, isWebApp, workspacePackageDirs, } from '@reticlehq/init';
|
|
14
14
|
/**
|
|
15
15
|
* Script names checked, in order.
|
|
16
16
|
*
|
|
@@ -129,24 +129,66 @@ function devCommandFor(directory, manager, read) {
|
|
|
129
129
|
/** Folders never read as an app: dependencies, and anything hidden (`.git`, `.cache`). */
|
|
130
130
|
const NODE_MODULES = 'node_modules';
|
|
131
131
|
const HIDDEN_PREFIX = '.';
|
|
132
|
+
/** Whether the process can write in `path`. Asked of every web-app candidate — see `preferWebApps`. */
|
|
133
|
+
function isWritableDirectory(path) {
|
|
134
|
+
try {
|
|
135
|
+
accessSync(path, constants.W_OK);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
132
142
|
/**
|
|
133
|
-
*
|
|
143
|
+
* Whether the package in `directory` is a web app, using init's own signal.
|
|
144
|
+
*
|
|
145
|
+
* A backend can have a dev script too, so the script alone is not enough. init already answers
|
|
146
|
+
* "is this a page somebody can open", and a second answer here would let the two disagree.
|
|
147
|
+
*/
|
|
148
|
+
function isWebAppAt(directory, read) {
|
|
149
|
+
return isWebApp('.', {
|
|
150
|
+
exists: (path) => read(join(directory, path)) !== undefined,
|
|
151
|
+
readFile: (path) => read(join(directory, path)) ?? null,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Web apps first, and of those the ones this process can write. When nothing here reads as a web
|
|
156
|
+
* app, every candidate comes back, in its original order.
|
|
157
|
+
*
|
|
158
|
+
* `isWebApp` is a detector, and a detector has gaps: a custom webpack or esbuild setup, a framework
|
|
159
|
+
* it does not know yet, a repository whose only candidate is a backend. Gating on it — as this code
|
|
160
|
+
* first did — hands the agent no next action where `main` hands it a command, and silence is the
|
|
161
|
+
* worse failure: a command that starts the wrong thing can be corrected, no command cannot. So the
|
|
162
|
+
* web-app check RANKS candidates instead of gating them.
|
|
163
|
+
*
|
|
164
|
+
* Writability is the exception: asked of the web apps only, where it is absolute. The reported bug
|
|
165
|
+
* (#2017) is a read-only backup copy sitting beside the real app, and a folder nobody can write must
|
|
166
|
+
* never be the one chosen while a writable sibling exists.
|
|
167
|
+
*/
|
|
168
|
+
function preferWebApps(candidates, isWebAppCandidate, isWritable) {
|
|
169
|
+
const apps = candidates.filter(isWebAppCandidate);
|
|
170
|
+
return 0 < apps.length ? apps.filter(isWritable) : candidates;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The direct child folders of `directory` that are apps with a dev command, sorted by name: the web
|
|
174
|
+
* apps when there are any, every dev-script folder when there are none (see `preferWebApps`).
|
|
134
175
|
*
|
|
135
176
|
* Only for a folder with no package.json of its own. A task folder holds the app one level down, and
|
|
136
|
-
* no workspace file lists it, so `workspacePackageDirs` never finds it.
|
|
137
|
-
*
|
|
177
|
+
* no workspace file lists it, so `workspacePackageDirs` never finds it. `node_modules`, hidden
|
|
178
|
+
* folders and folders with no dev script are never candidates.
|
|
138
179
|
*/
|
|
139
|
-
export function childDevApps(directory, read = readTextFile, listDirs = readDirNames) {
|
|
180
|
+
export function childDevApps(directory, read = readTextFile, listDirs = readDirNames, writable = isWritableDirectory) {
|
|
140
181
|
if (read(join(directory, PACKAGE_JSON)) !== undefined) {
|
|
141
182
|
return [];
|
|
142
183
|
}
|
|
143
|
-
|
|
184
|
+
const candidates = listDirs(directory)
|
|
144
185
|
.filter((name) => NODE_MODULES !== name && !name.startsWith(HIDDEN_PREFIX))
|
|
145
186
|
.filter((name) => {
|
|
146
187
|
const child = join(directory, name);
|
|
147
188
|
return devCommandFor(child, packageManager(child, read), read) !== undefined;
|
|
148
189
|
})
|
|
149
190
|
.sort();
|
|
191
|
+
return preferWebApps(candidates, (name) => isWebAppAt(join(directory, name), read), (name) => writable(join(directory, name)));
|
|
150
192
|
}
|
|
151
193
|
/** Directory names under `path`, or none when it cannot be read. */
|
|
152
194
|
function readDirNames(path) {
|
|
@@ -177,7 +219,7 @@ export function detectDevCommand(directory, read = readTextFile) {
|
|
|
177
219
|
* init's own workspace listing answers which directories to consider; the root's lockfile picks the
|
|
178
220
|
* package manager, because a workspace package usually has no lockfile of its own.
|
|
179
221
|
*/
|
|
180
|
-
export function detectDevCommandInProject(directory, read = readTextFile, listDirs = readDirNames) {
|
|
222
|
+
export function detectDevCommandInProject(directory, read = readTextFile, listDirs = readDirNames, writable = isWritableDirectory) {
|
|
181
223
|
const manager = packageManager(directory, read);
|
|
182
224
|
const here = devCommandFor(directory, manager, read);
|
|
183
225
|
if (here !== undefined) {
|
|
@@ -188,7 +230,7 @@ export function detectDevCommandInProject(directory, read = readTextFile, listDi
|
|
|
188
230
|
// `childDevApps`. With none, the workspace search below still looks a level deeper.
|
|
189
231
|
if (read(join(directory, PACKAGE_JSON)) === undefined &&
|
|
190
232
|
read(join(directory, PNPM_WORKSPACE)) === undefined) {
|
|
191
|
-
const [only, ...others] = childDevApps(directory, read, listDirs);
|
|
233
|
+
const [only, ...others] = childDevApps(directory, read, listDirs, writable);
|
|
192
234
|
if (0 < others.length) {
|
|
193
235
|
return undefined;
|
|
194
236
|
}
|
|
@@ -204,13 +246,17 @@ export function detectDevCommandInProject(directory, read = readTextFile, listDi
|
|
|
204
246
|
readFile: (path) => read(join(directory, path)) ?? null,
|
|
205
247
|
listDirs: (path) => listDirs(join(directory, path)),
|
|
206
248
|
});
|
|
249
|
+
const candidates = [];
|
|
207
250
|
for (const sub of packages) {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
251
|
+
const child = join(directory, sub);
|
|
252
|
+
const command = devCommandFor(child, manager, read);
|
|
253
|
+
if (command !== undefined) {
|
|
254
|
+
candidates.push({ sub, child, command });
|
|
211
255
|
}
|
|
212
256
|
}
|
|
213
|
-
|
|
257
|
+
// Same rule as the child folders above, so a workspace and a task folder cannot disagree.
|
|
258
|
+
const [first] = preferWebApps(candidates, (entry) => isWebAppAt(entry.child, read), (entry) => writable(entry.child));
|
|
259
|
+
return first === undefined ? undefined : { ...first.command, directory: first.sub };
|
|
214
260
|
}
|
|
215
261
|
/**
|
|
216
262
|
* The command that wires THIS project for good: `init`, in the app's own folder, through the
|
|
@@ -220,11 +266,11 @@ export function detectDevCommandInProject(directory, read = readTextFile, listDi
|
|
|
220
266
|
* time: `init` run in a folder that is not the app stops on a missing package.json, and a runner the
|
|
221
267
|
* machine does not have is "command not found". npx whenever there is doubt — it ships with Node.
|
|
222
268
|
*/
|
|
223
|
-
export function initCommandIn(directory, read = readTextFile, listDirs = readDirNames) {
|
|
269
|
+
export function initCommandIn(directory, read = readTextFile, listDirs = readDirNames, writable = isWritableDirectory) {
|
|
224
270
|
const manager = packageManager(directory, read);
|
|
225
271
|
const berry = read(join(directory, YARN_BERRY_MARKER)) !== undefined;
|
|
226
272
|
const runner = YARN === manager ? (berry ? YARN_DLX : NPX) : (RUNNER[manager] ?? NPX);
|
|
227
273
|
const command = `${runner} ${RETICLE_NPM_PACKAGE} ${INIT_VERB}`;
|
|
228
|
-
const app = detectDevCommandInProject(directory, read, listDirs)?.directory;
|
|
274
|
+
const app = detectDevCommandInProject(directory, read, listDirs, writable)?.directory;
|
|
229
275
|
return app === undefined ? command : `cd ${app} && ${command}`;
|
|
230
276
|
}
|
|
@@ -69,6 +69,13 @@ export declare function probeDevServerStates(ports?: readonly number[], probe?:
|
|
|
69
69
|
serving: number[];
|
|
70
70
|
slow: number[];
|
|
71
71
|
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Whether anything accepted a connection on this port, on either loopback family. Never rejects.
|
|
74
|
+
*
|
|
75
|
+
* A server compiling its first route answers `CONNECTED_NO_ANSWER`, and that counts: the lease uses
|
|
76
|
+
* this to stop telling an agent to start a dev server that is already running (#2005).
|
|
77
|
+
*/
|
|
78
|
+
export declare function portAcceptsConnection(port: number, probe?: (port: number) => Promise<PortState>): Promise<boolean>;
|
|
72
79
|
/** Every candidate port serving a document, in ascending order. Never rejects. */
|
|
73
80
|
export declare function probeDevServers(ports?: readonly number[], probe?: (port: number) => Promise<boolean>): Promise<number[]>;
|
|
74
81
|
export {};
|
|
@@ -156,6 +156,15 @@ async function anyFamilyState(port, probe = probeState) {
|
|
|
156
156
|
return PortState.CONNECTED_NO_ANSWER;
|
|
157
157
|
return PortState.CLOSED;
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Whether anything accepted a connection on this port, on either loopback family. Never rejects.
|
|
161
|
+
*
|
|
162
|
+
* A server compiling its first route answers `CONNECTED_NO_ANSWER`, and that counts: the lease uses
|
|
163
|
+
* this to stop telling an agent to start a dev server that is already running (#2005).
|
|
164
|
+
*/
|
|
165
|
+
export async function portAcceptsConnection(port, probe = (p) => anyFamilyState(p)) {
|
|
166
|
+
return PortState.CLOSED !== (await classifyPort(port, probe));
|
|
167
|
+
}
|
|
159
168
|
/** Every candidate port serving a document, in ascending order. Never rejects. */
|
|
160
169
|
export async function probeDevServers(ports = [...DEV_SERVER_PORTS], probe = (p) => anyFamilyServes(p)) {
|
|
161
170
|
const results = await Promise.all(ports.map((p) => probe(p).then((up) => (up ? p : null))));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ports this project sets in its `.env` files (reticle#1543).
|
|
3
|
+
*
|
|
4
|
+
* The no-session scan probes a list of well-known ports, the ports the build plugins announced, a
|
|
5
|
+
* port pinned in the `dev` script (#1367), and the ports of earlier sessions. An app that reads its
|
|
6
|
+
* port from `.env` is on none of those. Reported from React apps on Windows: a preview server on
|
|
7
|
+
* :3020, started by a Node script, and the answer said no app was running and suggested a dev
|
|
8
|
+
* server, which would start a duplicate.
|
|
9
|
+
*
|
|
10
|
+
* This only ADDS candidates to the scan. The scan still decides which of them answer like a dev
|
|
11
|
+
* server.
|
|
12
|
+
*/
|
|
13
|
+
/** The `.env` files a Node dev setup reads, in the order dotenv-style loaders read them. */
|
|
14
|
+
export declare const ENV_PORT_FILES: readonly [".env", ".env.local", ".env.development", ".env.development.local"];
|
|
15
|
+
/**
|
|
16
|
+
* The ports the project's `.env` files set with `PORT=`.
|
|
17
|
+
*
|
|
18
|
+
* Every file is read, not only the one a given loader would pick: the scan only probes candidates,
|
|
19
|
+
* so an extra port costs one connection attempt, while a missed one is the defect this fixes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function envFilePorts(directory: string, read?: (path: string) => string | undefined): number[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ports this project sets in its `.env` files (reticle#1543).
|
|
3
|
+
*
|
|
4
|
+
* The no-session scan probes a list of well-known ports, the ports the build plugins announced, a
|
|
5
|
+
* port pinned in the `dev` script (#1367), and the ports of earlier sessions. An app that reads its
|
|
6
|
+
* port from `.env` is on none of those. Reported from React apps on Windows: a preview server on
|
|
7
|
+
* :3020, started by a Node script, and the answer said no app was running and suggested a dev
|
|
8
|
+
* server, which would start a duplicate.
|
|
9
|
+
*
|
|
10
|
+
* This only ADDS candidates to the scan. The scan still decides which of them answer like a dev
|
|
11
|
+
* server.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
/** The `.env` files a Node dev setup reads, in the order dotenv-style loaders read them. */
|
|
16
|
+
export const ENV_PORT_FILES = [
|
|
17
|
+
'.env',
|
|
18
|
+
'.env.local',
|
|
19
|
+
'.env.development',
|
|
20
|
+
'.env.development.local',
|
|
21
|
+
];
|
|
22
|
+
/** One `PORT=3020` line. `export`, quotes and a trailing comment are allowed. */
|
|
23
|
+
const ENV_PORT_LINE = /^\s*(?:export\s+)?PORT\s*=\s*["']?(\d{1,5})["']?\s*(?:#.*)?$/gm;
|
|
24
|
+
/** The highest TCP port. */
|
|
25
|
+
const MAX_PORT = 65_535;
|
|
26
|
+
function toPort(digits) {
|
|
27
|
+
if (digits === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const port = Number(digits);
|
|
31
|
+
return Number.isInteger(port) && port > 0 && port <= MAX_PORT ? port : undefined;
|
|
32
|
+
}
|
|
33
|
+
function readText(path) {
|
|
34
|
+
try {
|
|
35
|
+
return readFileSync(path, 'utf8');
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The ports the project's `.env` files set with `PORT=`.
|
|
43
|
+
*
|
|
44
|
+
* Every file is read, not only the one a given loader would pick: the scan only probes candidates,
|
|
45
|
+
* so an extra port costs one connection attempt, while a missed one is the defect this fixes.
|
|
46
|
+
*/
|
|
47
|
+
export function envFilePorts(directory, read = readText) {
|
|
48
|
+
const ports = new Set();
|
|
49
|
+
for (const file of ENV_PORT_FILES) {
|
|
50
|
+
const text = read(join(directory, file));
|
|
51
|
+
if (text === undefined) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const match of text.matchAll(ENV_PORT_LINE)) {
|
|
55
|
+
const port = toPort(match[1]);
|
|
56
|
+
if (port !== undefined) {
|
|
57
|
+
ports.add(port);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [...ports];
|
|
62
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carry the network record of a document that unloaded into the session of the next document (#1944).
|
|
3
|
+
*
|
|
4
|
+
* A click on "Sign out" sends a POST and then loads `/sign-in`. The old document sends the POST's
|
|
5
|
+
* records before it unloads, so the daemon has them. But the new document connects as a new
|
|
6
|
+
* `Session` under the same id, and the act's verdict, every later `reticle_observe { action:
|
|
7
|
+
* "network" }` and every later `reticle_assert` read only that session. The POST went away with the
|
|
8
|
+
* old `Session` object, and a `net` check on a real request answered "no call matched".
|
|
9
|
+
*
|
|
10
|
+
* What is carried, and why only that:
|
|
11
|
+
*
|
|
12
|
+
* - Only the network records from the start of the agent's newest action on the old document. Those
|
|
13
|
+
* are the window that the act's verdict and the agent's next check read. Nothing is carried when
|
|
14
|
+
* no agent acted, so a person's own reloads do not move old traffic into a new page.
|
|
15
|
+
* - Not the departure record (`NetInitiator.NAVIGATION`). The new document records its own load.
|
|
16
|
+
* - Only when the new session's first stamped event is from a DIFFERENT document. The same document
|
|
17
|
+
* reconnecting after a bridge outage did not unload, and its requests can still be answered.
|
|
18
|
+
*
|
|
19
|
+
* How the loss is declared (AGENTS.md rule 11):
|
|
20
|
+
*
|
|
21
|
+
* - Every carried record has `fromDepartedDocument: true`. Its `t` is when it was carried, so it
|
|
22
|
+
* cannot show where the request was in the old document's timeline.
|
|
23
|
+
* - A request that failed with status 0 is not carried as a failure: the browser cancels a leaving
|
|
24
|
+
* document's requests, so that status is not the server's answer (see `abortedByUnload`).
|
|
25
|
+
* - A request that had no answer is marked `pageUnloaded: true`. Its answer went to a document that
|
|
26
|
+
* no longer exists. Its outcome is unknown: `net` checks that rest on it are `unknown`, never `no`,
|
|
27
|
+
* and settle does not wait for it (`neverAnswered`).
|
|
28
|
+
* - `documentId`, `seq`, `actionId` and `attribution` are removed. They are about the old document
|
|
29
|
+
* and its journal, and kept here they would make the old document the current one again.
|
|
30
|
+
*/
|
|
31
|
+
import { type ReticleEvent } from '@reticlehq/core';
|
|
32
|
+
/** What the old document sent, and which document it was. */
|
|
33
|
+
export interface DepartedNetwork {
|
|
34
|
+
/** The old document's id, or undefined when its SDK does not stamp one. */
|
|
35
|
+
readonly documentId: string | undefined;
|
|
36
|
+
readonly events: readonly ReticleEvent[];
|
|
37
|
+
}
|
|
38
|
+
/** The part of a `Session` this needs. A real `Session` satisfies it. */
|
|
39
|
+
export interface DepartingSession {
|
|
40
|
+
readonly currentDocumentId: string | undefined;
|
|
41
|
+
readonly lastAct: {
|
|
42
|
+
cursor(): number | undefined;
|
|
43
|
+
};
|
|
44
|
+
/** The newest action id this session minted. Events in an open action window carry it. */
|
|
45
|
+
readonly lastActionId: string | undefined;
|
|
46
|
+
eventsSince(cursor: number): ReticleEvent[];
|
|
47
|
+
}
|
|
48
|
+
/** The part of a `Session` that receives the record. A real `Session` satisfies it. */
|
|
49
|
+
export interface ArrivingSession {
|
|
50
|
+
pushEvent(event: ReticleEvent): void;
|
|
51
|
+
onEvent(listener: (event: ReticleEvent) => void): () => void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The network records the old document made after the agent's last act on it, ready to carry.
|
|
55
|
+
* Undefined when nothing is to be carried.
|
|
56
|
+
*/
|
|
57
|
+
export declare function departedNetworkOf(session: DepartingSession): DepartedNetwork | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Give the new session the old document's record once it shows that it is a new document.
|
|
60
|
+
*
|
|
61
|
+
* Waits for the first event that carries a `documentId`. A different id means the old document
|
|
62
|
+
* unloaded: the record goes in, right after that event. The same id means the same document
|
|
63
|
+
* reconnected, and nothing goes in. An old document with no id cannot be told apart, so nothing
|
|
64
|
+
* goes in then either. Returns a function that stops the wait.
|
|
65
|
+
*/
|
|
66
|
+
export declare function carryOnNewDocument(next: ArrivingSession, departed: DepartedNetwork): () => void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carry the network record of a document that unloaded into the session of the next document (#1944).
|
|
3
|
+
*
|
|
4
|
+
* A click on "Sign out" sends a POST and then loads `/sign-in`. The old document sends the POST's
|
|
5
|
+
* records before it unloads, so the daemon has them. But the new document connects as a new
|
|
6
|
+
* `Session` under the same id, and the act's verdict, every later `reticle_observe { action:
|
|
7
|
+
* "network" }` and every later `reticle_assert` read only that session. The POST went away with the
|
|
8
|
+
* old `Session` object, and a `net` check on a real request answered "no call matched".
|
|
9
|
+
*
|
|
10
|
+
* What is carried, and why only that:
|
|
11
|
+
*
|
|
12
|
+
* - Only the network records from the start of the agent's newest action on the old document. Those
|
|
13
|
+
* are the window that the act's verdict and the agent's next check read. Nothing is carried when
|
|
14
|
+
* no agent acted, so a person's own reloads do not move old traffic into a new page.
|
|
15
|
+
* - Not the departure record (`NetInitiator.NAVIGATION`). The new document records its own load.
|
|
16
|
+
* - Only when the new session's first stamped event is from a DIFFERENT document. The same document
|
|
17
|
+
* reconnecting after a bridge outage did not unload, and its requests can still be answered.
|
|
18
|
+
*
|
|
19
|
+
* How the loss is declared (AGENTS.md rule 11):
|
|
20
|
+
*
|
|
21
|
+
* - Every carried record has `fromDepartedDocument: true`. Its `t` is when it was carried, so it
|
|
22
|
+
* cannot show where the request was in the old document's timeline.
|
|
23
|
+
* - A request that failed with status 0 is not carried as a failure: the browser cancels a leaving
|
|
24
|
+
* document's requests, so that status is not the server's answer (see `abortedByUnload`).
|
|
25
|
+
* - A request that had no answer is marked `pageUnloaded: true`. Its answer went to a document that
|
|
26
|
+
* no longer exists. Its outcome is unknown: `net` checks that rest on it are `unknown`, never `no`,
|
|
27
|
+
* and settle does not wait for it (`neverAnswered`).
|
|
28
|
+
* - `documentId`, `seq`, `actionId` and `attribution` are removed. They are about the old document
|
|
29
|
+
* and its journal, and kept here they would make the old document the current one again.
|
|
30
|
+
*/
|
|
31
|
+
import { DepartedNetField, EventType, NetInitiator, asString, } from '@reticlehq/core';
|
|
32
|
+
/** The records of one request. Streams are left out: an open stream would hold settle open. */
|
|
33
|
+
const CARRIED_TYPES = new Set([
|
|
34
|
+
EventType.NET_PENDING,
|
|
35
|
+
EventType.NET_REQUEST,
|
|
36
|
+
EventType.NET_RESPONSE_BODY,
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* A request that failed with no HTTP status (status 0) in a document that then unloaded.
|
|
40
|
+
*
|
|
41
|
+
* A browser cancels the requests of a document it leaves, and the page sees that as a network
|
|
42
|
+
* error ("Failed to fetch"). The server can have received the request and answered it: driven
|
|
43
|
+
* against a POST that took 2 s to answer, the server logged the POST while the page recorded
|
|
44
|
+
* status 0. So the status 0 is not the server's answer. The record is left out, and its pending
|
|
45
|
+
* half is carried as `pageUnloaded`: unknown, not failed.
|
|
46
|
+
*/
|
|
47
|
+
function abortedByUnload(event) {
|
|
48
|
+
return event.type === EventType.NET_REQUEST && 0 === event.data['status'];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Where the newest action's records start in the departed session, or undefined when no agent acted.
|
|
52
|
+
*
|
|
53
|
+
* Two sources, and the later one wins. `lastAct.cursor()` is set when an act's reply arrives. A
|
|
54
|
+
* click that calls `location.assign()` can unload the page before that reply, so the cursor stays
|
|
55
|
+
* at an OLDER act (or unset), and the request is left behind (driven: it was). The events of an
|
|
56
|
+
* open action window carry its id, so the first event with the newest id marks where that action
|
|
57
|
+
* began to have effects. The later of the two is the newest action, so older traffic stays out.
|
|
58
|
+
*/
|
|
59
|
+
function newestActionStart(session) {
|
|
60
|
+
const cursor = session.lastAct.cursor();
|
|
61
|
+
const id = session.lastActionId;
|
|
62
|
+
const firstOfNewest = id === undefined ? undefined : session.eventsSince(0).find((e) => e.actionId === id)?.t;
|
|
63
|
+
if (cursor === undefined) {
|
|
64
|
+
return firstOfNewest;
|
|
65
|
+
}
|
|
66
|
+
if (firstOfNewest === undefined) {
|
|
67
|
+
return cursor;
|
|
68
|
+
}
|
|
69
|
+
return Math.max(cursor, firstOfNewest);
|
|
70
|
+
}
|
|
71
|
+
/** One carried record: the loss is declared and the old document's coordinates are removed. */
|
|
72
|
+
function carried(event, unanswered) {
|
|
73
|
+
const id = asString(event.data['id']);
|
|
74
|
+
const lost = event.type === EventType.NET_PENDING && id !== undefined && unanswered.has(id)
|
|
75
|
+
? { [DepartedNetField.PAGE_UNLOADED]: true }
|
|
76
|
+
: {};
|
|
77
|
+
return {
|
|
78
|
+
t: event.t,
|
|
79
|
+
type: event.type,
|
|
80
|
+
sessionId: event.sessionId,
|
|
81
|
+
data: { ...event.data, [DepartedNetField.FROM_DEPARTED_DOCUMENT]: true, ...lost },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The network records the old document made after the agent's last act on it, ready to carry.
|
|
86
|
+
* Undefined when nothing is to be carried.
|
|
87
|
+
*/
|
|
88
|
+
export function departedNetworkOf(session) {
|
|
89
|
+
const floor = newestActionStart(session);
|
|
90
|
+
if (floor === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const window = session
|
|
94
|
+
.eventsSince(floor)
|
|
95
|
+
.filter((e) => CARRIED_TYPES.has(e.type) &&
|
|
96
|
+
e.data['initiator'] !== NetInitiator.NAVIGATION &&
|
|
97
|
+
!abortedByUnload(e));
|
|
98
|
+
if (0 === window.length) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
const answered = new Set();
|
|
102
|
+
for (const e of window) {
|
|
103
|
+
const id = asString(e.data['id']);
|
|
104
|
+
if (e.type === EventType.NET_REQUEST && id !== undefined) {
|
|
105
|
+
answered.add(id);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const unanswered = new Set();
|
|
109
|
+
for (const e of window) {
|
|
110
|
+
const id = asString(e.data['id']);
|
|
111
|
+
if (e.type === EventType.NET_PENDING && id !== undefined && !answered.has(id)) {
|
|
112
|
+
unanswered.add(id);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
documentId: session.currentDocumentId,
|
|
117
|
+
events: window.map((e) => carried(e, unanswered)),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Give the new session the old document's record once it shows that it is a new document.
|
|
122
|
+
*
|
|
123
|
+
* Waits for the first event that carries a `documentId`. A different id means the old document
|
|
124
|
+
* unloaded: the record goes in, right after that event. The same id means the same document
|
|
125
|
+
* reconnected, and nothing goes in. An old document with no id cannot be told apart, so nothing
|
|
126
|
+
* goes in then either. Returns a function that stops the wait.
|
|
127
|
+
*/
|
|
128
|
+
export function carryOnNewDocument(next, departed) {
|
|
129
|
+
const stop = next.onEvent((event) => {
|
|
130
|
+
if (event.documentId === undefined) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
stop();
|
|
134
|
+
if (departed.documentId === undefined || event.documentId === departed.documentId) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
for (const record of departed.events) {
|
|
138
|
+
next.pushEvent(record);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return stop;
|
|
142
|
+
}
|
|
@@ -29,6 +29,15 @@ export interface NoSessionFacts {
|
|
|
29
29
|
* lock. Named in the message so the reader can tell the two apart.
|
|
30
30
|
*/
|
|
31
31
|
slowListeners?: readonly number[];
|
|
32
|
+
/**
|
|
33
|
+
* Ports that processes in this project's folder listen on, and the scan did not hear a page on.
|
|
34
|
+
*
|
|
35
|
+
* Never a place to send the agent, so never in `listening`: agent tools run in this folder and
|
|
36
|
+
* listen too, and one serves a page. An agent-browser viewer in `listening` produced "just open
|
|
37
|
+
* http://localhost:<port>" while the app was not running (reticle#1543). What these ports prove
|
|
38
|
+
* is less: the answer cannot say that nothing is listening.
|
|
39
|
+
*/
|
|
40
|
+
folderListeners?: readonly number[];
|
|
32
41
|
/** The port this daemon is on — half of the mismatch the old message asked about. */
|
|
33
42
|
port: number;
|
|
34
43
|
/**
|