@reticlehq/server 3.4.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +16 -8
- package/dist/chromium-launch-options.d.ts +3 -1
- package/dist/chromium-launch-options.js +2 -1
- package/dist/command/cli/adhoc-verdict.d.ts +1 -0
- package/dist/command/cli/adhoc-verdict.js +6 -3
- package/dist/command/cli/answers/open-note.d.ts +2 -0
- package/dist/command/cli/answers/open-note.js +22 -0
- package/dist/command/cli/auth/cloud-session.d.ts +4 -4
- package/dist/command/cli/auth/device-flow.d.ts +2 -2
- package/dist/command/cli/cli-flow-commands.js +4 -8
- package/dist/command/cli/cli-parse.d.ts +1 -1
- package/dist/command/cli/cli-parse.js +13 -11
- package/dist/command/cli/cli-verify.d.ts +1 -1
- package/dist/command/cli/cli-verify.js +35 -25
- package/dist/command/cli/cloud-cli.js +110 -12
- package/dist/command/cli/cloud-login.d.ts +9 -1
- package/dist/command/cli/cloud-login.js +43 -8
- package/dist/command/cli/demo/demo-page.js +11 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
- package/dist/command/cli/drive/drive-attach.d.ts +27 -2
- package/dist/command/cli/drive/drive-attach.js +71 -4
- package/dist/command/cli/drive/drive-command.d.ts +2 -1
- package/dist/command/cli/drive/drive-command.js +13 -7
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
- package/dist/command/cli/ports/port-holder.d.ts +10 -0
- package/dist/command/cli/ports/port-holder.js +24 -0
- package/dist/command/cli/setup-mcp-cli.js +15 -5
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +6 -9
- package/dist/command/cli.d.ts +6 -0
- package/dist/command/cli.js +45 -23
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
- package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
- package/dist/command/daemon/daemon-resolve.d.ts +14 -0
- package/dist/command/daemon/daemon-resolve.js +33 -2
- package/dist/command/setup/agent-writer.d.ts +1 -1
- package/dist/command/setup/agent-writer.js +3 -2
- package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
- package/dist/command/setup/bringup/connect-lease.js +1 -0
- package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
- package/dist/command/setup/bringup/dev-server-wait.js +20 -1
- package/dist/command/setup/init/init-port.d.ts +4 -0
- package/dist/command/setup/init/init-port.js +10 -0
- package/dist/command/setup/init/init-runtime.d.ts +10 -0
- package/dist/command/setup/init/init-runtime.js +71 -9
- package/dist/command/setup/node-effects.js +8 -2
- package/dist/command/setup/remaining-steps.js +2 -1
- package/dist/command/setup/run-setup.d.ts +7 -0
- package/dist/command/setup/run-setup.js +38 -25
- package/dist/command/setup/setup-command.d.ts +2 -1
- package/dist/command/setup/setup-command.js +2 -0
- package/dist/command/setup/setup-mcp.d.ts +2 -0
- package/dist/command/setup/setup-mcp.js +14 -5
- package/dist/command/version/peer-announce.d.ts +6 -2
- package/dist/command/version/peer-announce.js +8 -6
- package/dist/command/version/version-nudge.d.ts +17 -3
- package/dist/command/version/version-nudge.js +25 -4
- package/dist/command/version/version-skew.js +11 -2
- package/dist/features/crawl/crawl-tools.js +4 -0
- package/dist/features/crawl/crawl.d.ts +6 -0
- package/dist/features/crawl/crawl.js +9 -1
- package/dist/features/exhaust/ledger.d.ts +7 -0
- package/dist/features/exhaust/ledger.js +35 -9
- package/dist/features/harness/app-findings.d.ts +2 -0
- package/dist/features/harness/app-findings.js +52 -0
- package/dist/features/harness/drive-report.d.ts +27 -1
- package/dist/features/harness/drive-report.js +162 -11
- package/dist/features/harness/drivers.d.ts +10 -14
- package/dist/features/harness/drivers.js +10 -14
- package/dist/features/harness/fill-values.d.ts +3 -69
- package/dist/features/harness/fill-values.js +3 -133
- package/dist/features/harness/goals.d.ts +21 -0
- package/dist/features/harness/goals.js +77 -0
- package/dist/features/harness/harness.d.ts +21 -0
- package/dist/features/harness/harness.js +69 -2
- package/dist/features/harness/platform/drive-target.d.ts +30 -0
- package/dist/features/harness/platform/drive-target.js +81 -0
- package/dist/features/harness/platform/local-apps.d.ts +94 -0
- package/dist/features/harness/platform/local-apps.js +204 -0
- package/dist/features/harness/platform/remote-drive.d.ts +180 -0
- package/dist/features/harness/platform/remote-drive.js +365 -0
- package/dist/features/harness/platform/script.d.ts +48 -0
- package/dist/features/harness/platform/script.js +128 -0
- package/dist/features/harness/platform/server-driver.d.ts +41 -0
- package/dist/features/harness/platform/server-driver.js +179 -0
- package/dist/features/harness/platform-config.d.ts +7 -0
- package/dist/features/harness/platform-config.js +14 -2
- package/dist/features/harness/script/app-controls.d.ts +5 -0
- package/dist/features/harness/script/app-controls.js +40 -0
- package/dist/features/harness/script/lane-ids.d.ts +14 -0
- package/dist/features/harness/script/lane-ids.js +16 -0
- package/dist/features/harness/script/script-run.d.ts +41 -0
- package/dist/features/harness/script/script-run.js +299 -0
- package/dist/features/license/license.d.ts +2 -2
- package/dist/features/visual/visual-tools.d.ts +10 -1
- package/dist/features/visual/visual-tools.js +40 -3
- package/dist/hooks/driven-by.d.ts +4 -0
- package/dist/hooks/driven-by.js +16 -0
- package/dist/hooks/hook-commands.js +15 -4
- package/dist/hooks/tool-log.d.ts +9 -0
- package/dist/hooks/tool-log.js +29 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +91 -89
- package/dist/judgement/capsule/capsule-store.d.ts +4 -4
- package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
- package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
- package/dist/judgement/runs/artifact/run-store.js +26 -1
- package/dist/judgement/runs/drive-run.d.ts +22 -0
- package/dist/judgement/runs/drive-run.js +99 -1
- package/dist/judgement/runs/repair-prompt.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +8 -1
- package/dist/judgement/runs/verification-sync.d.ts +3 -2
- package/dist/judgement/runs/verification-sync.js +4 -3
- package/dist/language/flows/change/gate.d.ts +10 -0
- package/dist/language/flows/change/gate.js +22 -0
- package/dist/language/flows/decision.js +34 -0
- package/dist/language/flows/flow-anchor.d.ts +54 -8
- package/dist/language/flows/flow-anchor.js +88 -3
- package/dist/language/flows/flow-cross-step.d.ts +15 -0
- package/dist/language/flows/flow-cross-step.js +21 -0
- package/dist/language/flows/flow-learning.js +8 -11
- package/dist/language/flows/flow-mutate-tools.js +5 -3
- package/dist/language/flows/flow-replay-run.d.ts +23 -9
- package/dist/language/flows/flow-replay-run.js +101 -94
- package/dist/language/flows/flow-replay.d.ts +23 -1
- package/dist/language/flows/flow-replay.js +92 -19
- package/dist/language/flows/flow-scope.js +5 -1
- package/dist/language/flows/flow-step-runners.d.ts +20 -2
- package/dist/language/flows/flow-step-runners.js +236 -42
- package/dist/language/flows/flow-store-for-session.d.ts +2 -2
- package/dist/language/flows/flow-store-for-session.js +2 -2
- package/dist/language/flows/flow-tools.js +58 -34
- package/dist/language/flows/flows.js +5 -2
- package/dist/language/flows/heal-run.js +6 -4
- package/dist/language/flows/recording/flake.d.ts +2 -2
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
- package/dist/language/flows/recording/tape/recordings.js +40 -8
- package/dist/language/flows/replay-from-hud.d.ts +20 -0
- package/dist/language/flows/replay-from-hud.js +51 -0
- package/dist/language/flows/replay.d.ts +9 -5
- package/dist/language/flows/replay.js +27 -9
- package/dist/language/flows/suite/suite-flakes.js +2 -0
- package/dist/language/flows/suite-selection.d.ts +2 -15
- package/dist/language/flows/suite-selection.js +4 -3
- package/dist/language/flows/verify-change-tools.js +8 -4
- package/dist/launch-chromium.d.ts +27 -5
- package/dist/launch-chromium.js +38 -8
- package/dist/memory/cloud/cloud-config.d.ts +12 -0
- package/dist/memory/cloud/cloud-config.js +15 -0
- package/dist/memory/cloud/cloud-sync.d.ts +17 -14
- package/dist/memory/cloud/cloud-sync.js +12 -1
- package/dist/memory/cloud/harness-config.d.ts +26 -1
- package/dist/memory/cloud/harness-config.js +99 -4
- package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
- package/dist/memory/cloud/hud-notices-source.js +128 -0
- package/dist/memory/cloud/memory-scope.d.ts +144 -0
- package/dist/memory/cloud/memory-scope.js +205 -0
- package/dist/memory/cloud/sync-cycle.d.ts +21 -3
- package/dist/memory/cloud/sync-cycle.js +45 -5
- package/dist/memory/cloud/sync-daemon.js +7 -5
- package/dist/memory/cloud/sync-disk.d.ts +1 -1
- package/dist/memory/cloud/sync-disk.js +5 -8
- package/dist/memory/impact/account-watch.d.ts +19 -0
- package/dist/memory/impact/account-watch.js +34 -0
- package/dist/memory/impact/impact-recorder.d.ts +8 -0
- package/dist/memory/impact/impact-recorder.js +12 -2
- package/dist/memory/impact/impact-store.d.ts +16 -0
- package/dist/memory/impact/impact-store.js +142 -26
- package/dist/memory/intent/intent-shard.d.ts +2 -2
- package/dist/memory/intent/intent-tools.js +88 -5
- package/dist/memory/journal/envelope.d.ts +16 -16
- package/dist/memory/journal/journal-query.d.ts +14 -0
- package/dist/memory/journal/journal-query.js +15 -0
- package/dist/memory/journal/journal-recorder.js +7 -1
- package/dist/memory/journal/on-disk/retention.d.ts +3 -0
- package/dist/memory/journal/on-disk/retention.js +5 -0
- package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
- package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
- package/dist/memory/journal/session-end.js +6 -4
- package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
- package/dist/memory/project/artifact-root-resolver.js +134 -26
- package/dist/memory/project/artifact-root.d.ts +42 -5
- package/dist/memory/project/artifact-root.js +87 -8
- package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
- package/dist/memory/project/dir/reticle-dir.js +3 -1
- package/dist/memory/project/project-tools.js +22 -3
- package/dist/memory/project/session-root.d.ts +24 -1
- package/dist/memory/project/session-root.js +41 -5
- package/dist/memory/project/sync-status.d.ts +39 -0
- package/dist/memory/project/sync-status.js +90 -0
- package/dist/memory/recall/memory-tools.js +2 -0
- package/dist/memory/recall/project-memory.d.ts +19 -0
- package/dist/memory/recall/project-memory.js +45 -6
- package/dist/portal/bridge/bridge.d.ts +7 -4
- package/dist/portal/bridge/bridge.js +25 -3
- package/dist/portal/bridge/pairing-token.d.ts +9 -0
- package/dist/portal/bridge/pairing-token.js +17 -0
- package/dist/portal/input/real-input.d.ts +81 -10
- package/dist/portal/input/real-input.js +207 -22
- package/dist/portal/input/viewport-tools.js +19 -9
- package/dist/portal/pool/browser-pool.d.ts +22 -9
- package/dist/portal/pool/browser-pool.js +70 -35
- package/dist/portal/pool/playwright-launcher.d.ts +13 -0
- package/dist/portal/pool/playwright-launcher.js +17 -1
- package/dist/portal/pool/pool-contract.d.ts +7 -4
- package/dist/portal/session/command-payload.d.ts +8 -0
- package/dist/portal/session/command-payload.js +19 -0
- package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
- package/dist/portal/session/dev-server/dev-command.js +61 -12
- package/dist/portal/session/facts/departure-note.d.ts +22 -0
- package/dist/portal/session/facts/departure-note.js +47 -0
- package/dist/portal/session/facts/session-replaced.d.ts +5 -0
- package/dist/portal/session/facts/session-replaced.js +5 -0
- package/dist/portal/session/first-run-wiring.d.ts +42 -0
- package/dist/portal/session/first-run-wiring.js +132 -0
- package/dist/portal/session/human/notes-ledger.d.ts +20 -0
- package/dist/portal/session/human/notes-ledger.js +55 -0
- package/dist/portal/session/human/review-store.d.ts +12 -0
- package/dist/portal/session/human/review-store.js +18 -10
- package/dist/portal/session/live-control-tools.d.ts +0 -13
- package/dist/portal/session/live-control-tools.js +28 -3
- package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
- package/dist/portal/session/no-session-diagnosis.js +15 -0
- package/dist/portal/session/no-session-next-action.d.ts +11 -0
- package/dist/portal/session/no-session-next-action.js +31 -5
- package/dist/portal/session/no-session-watch.d.ts +14 -1
- package/dist/portal/session/no-session-watch.js +63 -10
- package/dist/portal/session/presence/session-mirror.js +3 -0
- package/dist/portal/session/serving-directory.d.ts +27 -0
- package/dist/portal/session/serving-directory.js +45 -0
- package/dist/portal/session/session-health.d.ts +7 -0
- package/dist/portal/session/session-health.js +6 -0
- package/dist/portal/session/session-info.d.ts +8 -1
- package/dist/portal/session/session-info.js +6 -0
- package/dist/portal/session/session-manager.d.ts +21 -0
- package/dist/portal/session/session-manager.js +44 -8
- package/dist/portal/session/session-reaper.js +4 -1
- package/dist/portal/session/session-successor.d.ts +5 -0
- package/dist/portal/session/session-tools.js +26 -3
- package/dist/portal/session/session.d.ts +12 -10
- package/dist/portal/session/session.js +30 -31
- package/dist/start-options.d.ts +6 -0
- package/dist/status-payload.d.ts +10 -0
- package/dist/status-payload.js +23 -0
- package/dist/surface/http-server.d.ts +1 -1
- package/dist/surface/http-server.js +15 -4
- package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
- package/dist/surface/mcp/mcp-proxy.js +36 -5
- package/dist/surface/mcp/mcp.d.ts +11 -6
- package/dist/surface/mcp/mcp.js +28 -14
- package/dist/surface/mcp/proxy/proxy-log.js +3 -5
- package/dist/surface/mcp/server-instructions.d.ts +4 -2
- package/dist/surface/mcp/server-instructions.js +14 -4
- package/dist/surface/mcp/shared-params.js +2 -2
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
- package/dist/surface/tools/act/settle-in-flight.js +25 -3
- package/dist/surface/tools/act-sequence-tool.js +2 -2
- package/dist/surface/tools/act-tools.js +25 -9
- package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +17 -2
- package/dist/surface/tools/browser-tools.js +8 -1
- package/dist/surface/tools/chat-drives.d.ts +26 -0
- package/dist/surface/tools/chat-drives.js +165 -0
- package/dist/surface/tools/dynamic-tools.js +29 -2
- package/dist/surface/tools/empty-session-list.d.ts +41 -0
- package/dist/surface/tools/empty-session-list.js +80 -0
- package/dist/surface/tools/error-recovery.d.ts +6 -0
- package/dist/surface/tools/error-recovery.js +38 -6
- package/dist/surface/tools/explore-tools.d.ts +10 -1
- package/dist/surface/tools/explore-tools.js +82 -13
- package/dist/surface/tools/feedback-tools.js +14 -0
- package/dist/surface/tools/harness-explore.d.ts +96 -14
- package/dist/surface/tools/harness-explore.js +227 -202
- package/dist/surface/tools/harness-plan.d.ts +12 -0
- package/dist/surface/tools/harness-plan.js +31 -0
- package/dist/surface/tools/harness-script.d.ts +8 -0
- package/dist/surface/tools/harness-script.js +342 -0
- package/dist/surface/tools/harness-toolset.d.ts +14 -1
- package/dist/surface/tools/harness-toolset.js +77 -6
- package/dist/surface/tools/invoke-tool.js +64 -9
- package/dist/surface/tools/lease-seed.d.ts +29 -0
- package/dist/surface/tools/lease-seed.js +123 -0
- package/dist/surface/tools/lease-tools.d.ts +3 -23
- package/dist/surface/tools/lease-tools.js +30 -131
- package/dist/surface/tools/live-call-text.js +2 -2
- package/dist/surface/tools/next-step.d.ts +19 -0
- package/dist/surface/tools/next-step.js +159 -0
- package/dist/surface/tools/observe-tools.js +2 -4
- package/dist/surface/tools/platform-moment.d.ts +12 -0
- package/dist/surface/tools/platform-moment.js +168 -0
- package/dist/surface/tools/query-hint-schema.d.ts +7 -0
- package/dist/surface/tools/query-hint-schema.js +10 -0
- package/dist/surface/tools/read-tools.js +8 -2
- package/dist/surface/tools/real-input-attempt.js +96 -13
- package/dist/surface/tools/tool-kit.d.ts +25 -1
- package/dist/surface/tools/tool-kit.js +4 -6
- package/dist/surface/tools/tools.js +20 -48
- package/dist/telemetry/argument-shape.js +1 -1
- package/dist/telemetry/hud-metrics.d.ts +2 -0
- package/dist/telemetry/hud-metrics.js +17 -0
- package/dist/telemetry/onboarding-firsts.d.ts +2 -2
- package/dist/telemetry/onboarding-firsts.js +2 -2
- package/dist/telemetry/telemetry.d.ts +12 -0
- package/dist/telemetry/telemetry.js +43 -14
- package/dist/telemetry/verification-of.js +1 -0
- package/dist/wire-journal.js +4 -1
- package/docs/README.md +1 -1
- package/docs/actions.mdx +1 -2
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/autodrive.md +29 -25
- package/docs/cli/cloud.mdx +6 -4
- package/docs/cli/doctor.mdx +6 -0
- package/docs/cli/drive.mdx +8 -2
- package/docs/cli/init.mdx +11 -13
- package/docs/cli/status.mdx +5 -1
- package/docs/cli/version.mdx +3 -3
- package/docs/cli.mdx +14 -8
- package/docs/desktop.mdx +5 -1
- package/docs/environment.mdx +3 -7
- package/docs/getting-started.md +14 -2
- package/docs/human-control.md +1 -2
- package/docs/index.mdx +4 -3
- package/docs/install-agentic.mdx +1 -1
- package/docs/install-manual.mdx +2 -2
- package/docs/packages/next.mdx +3 -0
- package/docs/packages/tauri.mdx +2 -2
- package/docs/packages/vite-plugin.mdx +24 -1
- package/docs/packages.mdx +1 -1
- package/docs/quickstart.mdx +23 -11
- package/docs/telemetry.md +1 -1
- package/docs/tools/act.mdx +1 -1
- package/docs/tools/session-and-feedback.mdx +9 -9
- package/docs/usage.md +5 -3
- package/docs/what-is-recorded.md +13 -3
- package/docs/what-is-reticle.mdx +3 -1
- package/package.json +8 -12
- package/dist/features/harness/driver.d.ts +0 -50
- package/dist/features/harness/driver.js +0 -236
- package/dist/features/harness/jev-driver.d.ts +0 -188
- package/dist/features/harness/jev-driver.js +0 -678
- package/dist/features/harness/openai-driver.d.ts +0 -46
- package/dist/features/harness/openai-driver.js +0 -194
- package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
- package/dist/memory/project/dir/fill-value-store.js +0 -88
- package/docs/local-registry.md +0 -143
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { asProjectId } from '@reticlehq/core';
|
|
2
2
|
import { refusedResult } from './page-commands/undeclared-command.js';
|
|
3
3
|
import { recordImpact } from '../../memory/impact/impact-recorder.js';
|
|
4
|
+
import { recordNotes } from './human/notes-ledger.js';
|
|
5
|
+
import { commandPayload } from './command-payload.js';
|
|
4
6
|
import { LastAct } from './last-act.js';
|
|
5
7
|
import { GapLedger } from '@reticlehq/engine/evidence/gap-ledger.js';
|
|
6
8
|
import { CaptureLedger } from '../../surface/tools/feature-capture.js';
|
|
@@ -12,11 +14,12 @@ const IMPACT_PUSH_DEBOUNCE_MS = 700;
|
|
|
12
14
|
import { PendingCommands, CommandTimeoutError } from './page-commands/pending-commands.js';
|
|
13
15
|
import { span } from '../../trace.js';
|
|
14
16
|
import { keepCallerContextInDaemon, noteToDaemonLog } from './daemon-answers.js';
|
|
15
|
-
import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, HudUseDataSchema, ReticleCommand,
|
|
17
|
+
import { isKnownRealm, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, HudUseDataSchema, ReticleCommand, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
|
|
16
18
|
import { RingBuffer } from '@reticlehq/engine/window/ring-buffer.js';
|
|
17
|
-
import { readJournalWriteLoss, readQueryEvents, } from '../../memory/journal/journal-query.js';
|
|
19
|
+
import { readJournalWriteLoss, readQueryEvents, windowLost, } from '../../memory/journal/journal-query.js';
|
|
18
20
|
import {} from '@reticlehq/engine/window/ambient.js';
|
|
19
21
|
import { ObservedState, controlLabelsOf } from './facts/observed-state.js';
|
|
22
|
+
import { DepartureNote } from './facts/departure-note.js';
|
|
20
23
|
import { recordBrowserLatency, recordHudUse, recordSdkFailure, } from '../../telemetry/session-metrics.js';
|
|
21
24
|
import { LiveControl } from './human/live-control.js';
|
|
22
25
|
import { ReviewStore } from './human/review-store.js';
|
|
@@ -99,6 +102,8 @@ export class Session {
|
|
|
99
102
|
/** Which browser the page said it is (chrome/edge/arc/…). Undefined on an SDK too old to report one. */
|
|
100
103
|
#brand;
|
|
101
104
|
#focused = true;
|
|
105
|
+
/** The desktop window this is and its document's HTTP status, merged from every PAGE_HEALTH. */
|
|
106
|
+
page = {};
|
|
102
107
|
/** Liveness: wall-clock of the last AGENT command (distinct from browser chatter / lastSeen). */
|
|
103
108
|
#lastAgentActivityAt;
|
|
104
109
|
/** Server-side mirror of the agent-tuned idle window, so the reaper honors reticle_session. */
|
|
@@ -107,7 +112,7 @@ export class Session {
|
|
|
107
112
|
#autoEnded = false;
|
|
108
113
|
#live = new LiveControl();
|
|
109
114
|
/** Human review marks: mistakes the human pinned to elements, for the agent to drain and fix. */
|
|
110
|
-
#review = new ReviewStore();
|
|
115
|
+
#review = new ReviewStore((marks) => recordNotes(this.artifactRoot, marks));
|
|
111
116
|
/** Whether the session_lease has already been returned (fire-once per session). */
|
|
112
117
|
#firstCommandDone = false;
|
|
113
118
|
/** Durable causal-journal recorder; undefined when journaling is off (opt-out or not yet attached). */
|
|
@@ -129,6 +134,7 @@ export class Session {
|
|
|
129
134
|
* A hot update advances it inside the same document. An unstamped event never clears it.
|
|
130
135
|
*/
|
|
131
136
|
#editEpoch;
|
|
137
|
+
#departureNote = new DepartureNote();
|
|
132
138
|
constructor(hello, socket, clock) {
|
|
133
139
|
this.id = hello.sessionId;
|
|
134
140
|
this.projectId = hello.projectId === undefined ? undefined : asProjectId(hello.projectId);
|
|
@@ -148,6 +154,9 @@ export class Session {
|
|
|
148
154
|
elapsed() {
|
|
149
155
|
return this.#clock() - this.#startedAt;
|
|
150
156
|
}
|
|
157
|
+
departedTo() {
|
|
158
|
+
return this.#departureNote.read(this.elapsed());
|
|
159
|
+
}
|
|
151
160
|
/** Mark that the SDK was just heard from. Called on every inbound message. */
|
|
152
161
|
touch() {
|
|
153
162
|
this.#lastSeenAt = this.#clock();
|
|
@@ -226,6 +235,7 @@ export class Session {
|
|
|
226
235
|
runtime: this.#runtime,
|
|
227
236
|
versionSkew: this.versionSkew,
|
|
228
237
|
hidden: this.#hidden,
|
|
238
|
+
page: this.page,
|
|
229
239
|
health: () => this.health(),
|
|
230
240
|
staleMs: () => this.staleMs(),
|
|
231
241
|
pendingMarkCount: () => this.#review.pendingCount(),
|
|
@@ -283,6 +293,7 @@ export class Session {
|
|
|
283
293
|
this.#editEpoch = event.editEpoch;
|
|
284
294
|
if (event.type === EventType.PAGE_HEALTH) {
|
|
285
295
|
const r = readHealthEvent(event.data);
|
|
296
|
+
Object.assign(this.page, r.page);
|
|
286
297
|
this.applyHealth(r.hidden ?? this.#hidden, r.focused ?? this.#focused, r.runtime, r.engine, r.brand);
|
|
287
298
|
}
|
|
288
299
|
// The in-page half of Reticle reporting its own failure — recorded like a tool error
|
|
@@ -303,8 +314,7 @@ export class Session {
|
|
|
303
314
|
const parsed = HumanMarkDataSchema.safeParse(event.data);
|
|
304
315
|
if (parsed.success) {
|
|
305
316
|
this.#review.add(parsed.data, this.elapsed());
|
|
306
|
-
//
|
|
307
|
-
// have shown a permanent zero next to a page covered in pins.
|
|
317
|
+
// Without this the report shows a permanent zero next to a page covered in pins.
|
|
308
318
|
recordImpact({ marks: 1 }, {}, this.artifactRoot);
|
|
309
319
|
}
|
|
310
320
|
}
|
|
@@ -316,6 +326,7 @@ export class Session {
|
|
|
316
326
|
if ('string' === typeof to && to.length > 0)
|
|
317
327
|
this.url = to;
|
|
318
328
|
}
|
|
329
|
+
this.#departureNote.observe(event, this.elapsed());
|
|
319
330
|
const t = this.elapsed();
|
|
320
331
|
const stamped = { ...event, t, sessionId: this.id };
|
|
321
332
|
// The recorder attributes the event to the in-flight action (if any) and journals it durably; the
|
|
@@ -528,20 +539,16 @@ export class Session {
|
|
|
528
539
|
bufferHealth() {
|
|
529
540
|
return this.#buffer.bufferHealth();
|
|
530
541
|
}
|
|
542
|
+
/** Did the event store lose scarce evidence from a window opened at `cursor`? See `windowLost`. */
|
|
543
|
+
lostSince(cursor) {
|
|
544
|
+
return windowLost(this.#journalReader, this.#buffer, cursor);
|
|
545
|
+
}
|
|
531
546
|
/**
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* moves for the age and churn evictions that every live page produces continuously. The journal is
|
|
535
|
-
* the other half of that store, so a durable read that could not reach back to `cursor` lost
|
|
536
|
-
* exactly what an eviction would have. Both boundaries are INCLUSIVE (`t` is a millisecond many
|
|
537
|
-
* records share) and an absent `lostThroughT` impeaches every window rather than none — the
|
|
538
|
-
* conservative direction, both times.
|
|
547
|
+
* Hold events at/after `cursor` against count-cap eviction until the returned function runs, so a
|
|
548
|
+
* predicate's own match cannot be dropped inside the window it is graded on (#668).
|
|
539
549
|
*/
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
return true;
|
|
543
|
-
const lost = this.#journalReader?.readLoss?.();
|
|
544
|
-
return lost !== undefined && (lost.lostThroughT === undefined || lost.lostThroughT >= cursor);
|
|
550
|
+
protectWindow(cursor) {
|
|
551
|
+
return this.#buffer.protect(cursor);
|
|
545
552
|
}
|
|
546
553
|
onEvent(listener) {
|
|
547
554
|
this.#listeners.add(listener);
|
|
@@ -574,13 +581,7 @@ export class Session {
|
|
|
574
581
|
if (successor !== undefined)
|
|
575
582
|
return successor.command(name, args, timeoutMs);
|
|
576
583
|
const id = this.#pending.nextId(COMMAND_ID_PREFIX);
|
|
577
|
-
const payload =
|
|
578
|
-
kind: MessageKind.COMMAND,
|
|
579
|
-
id,
|
|
580
|
-
sessionId: this.id,
|
|
581
|
-
name,
|
|
582
|
-
args,
|
|
583
|
-
});
|
|
584
|
+
const payload = commandPayload(id, this.id, name, args);
|
|
584
585
|
// The timeout message is built LAZILY: health can change while a command is in flight, and the
|
|
585
586
|
// diagnosis should describe the page as it was when the command actually gave up.
|
|
586
587
|
const awaited = this.#pending.track(id, timeoutMs, () => commandTimeoutMessage(name, timeoutMs, {
|
|
@@ -792,6 +793,10 @@ export class Session {
|
|
|
792
793
|
pushNarration(text) {
|
|
793
794
|
this.#post(ReticleCommand.NARRATE, { text, level: 'info' });
|
|
794
795
|
}
|
|
796
|
+
/** A HUD replay's progress, or the Harness's drive plan: pictures the HUD redraws as they change. */
|
|
797
|
+
pushView(name, args) {
|
|
798
|
+
this.#post(name, { ...args });
|
|
799
|
+
}
|
|
795
800
|
/** The one-time lease block on the first agent command, then undefined forever after. */
|
|
796
801
|
takeSessionLease() {
|
|
797
802
|
if (this.#firstCommandDone)
|
|
@@ -866,13 +871,7 @@ export class Session {
|
|
|
866
871
|
this.recordActedRef(ref);
|
|
867
872
|
}
|
|
868
873
|
const id = this.#pending.nextId(COMMAND_ID_PREFIX);
|
|
869
|
-
const payload =
|
|
870
|
-
kind: MessageKind.COMMAND,
|
|
871
|
-
id,
|
|
872
|
-
sessionId: this.id,
|
|
873
|
-
name,
|
|
874
|
-
args,
|
|
875
|
-
});
|
|
874
|
+
const payload = commandPayload(id, this.id, name, args);
|
|
876
875
|
try {
|
|
877
876
|
this.#socket.send(payload);
|
|
878
877
|
}
|
package/dist/start-options.d.ts
CHANGED
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
import type { OwnedRealInputProvider } from './portal/input/real-input.js';
|
|
8
8
|
import type { InjectConnectOptions } from './portal/input/real-input.js';
|
|
9
9
|
export interface StartOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The panel's Sign in, supplied by the CLI door, which owns install-time code: given a repaint, it
|
|
12
|
+
* answers the handler the bridge runs on each press. An embedder of the library leaves it out, and
|
|
13
|
+
* its panel falls back to copying `reticle login`.
|
|
14
|
+
*/
|
|
15
|
+
hudSignIn?: (repaint: () => void) => () => void;
|
|
10
16
|
port?: number;
|
|
11
17
|
/** Bind address. Non-loopback hosts require a token. Defaults to RETICLE_HOST or localhost. */
|
|
12
18
|
host?: string;
|
package/dist/status-payload.d.ts
CHANGED
|
@@ -47,4 +47,14 @@ whyLead?: string): StatusPayload;
|
|
|
47
47
|
* which errs loud rather than green.
|
|
48
48
|
*/
|
|
49
49
|
export declare function verifyEndpointMismatch(status: unknown, wantedPort: number): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* What a restart is about to do to the agents attached to the daemon whose `/status` this is, or
|
|
52
|
+
* undefined when nothing.
|
|
53
|
+
*
|
|
54
|
+
* A restart puts this CLI's version on the daemon. An agent whose own MCP server is older stays
|
|
55
|
+
* attached to a daemon it no longer matches, and a running agent cannot restart its own MCP server,
|
|
56
|
+
* so the restart that was meant to converge the versions strands it instead (#812). Said before the
|
|
57
|
+
* kill, with the way to restart at the version the attached agents run.
|
|
58
|
+
*/
|
|
59
|
+
export declare function strandedClientsNote(status: unknown, next?: string): string | undefined;
|
|
50
60
|
export {};
|
package/dist/status-payload.js
CHANGED
|
@@ -39,3 +39,26 @@ export function verifyEndpointMismatch(status, wantedPort) {
|
|
|
39
39
|
? `the daemon already running serves the verify HTTP endpoint on :${String(served)}, not :${String(wantedPort)} — ${fix}`
|
|
40
40
|
: `a daemon is already running without the verify HTTP endpoint — \`--http\` cannot be applied to it; ${fix}`;
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* What a restart is about to do to the agents attached to the daemon whose `/status` this is, or
|
|
44
|
+
* undefined when nothing.
|
|
45
|
+
*
|
|
46
|
+
* A restart puts this CLI's version on the daemon. An agent whose own MCP server is older stays
|
|
47
|
+
* attached to a daemon it no longer matches, and a running agent cannot restart its own MCP server,
|
|
48
|
+
* so the restart that was meant to converge the versions strands it instead (#812). Said before the
|
|
49
|
+
* kill, with the way to restart at the version the attached agents run.
|
|
50
|
+
*/
|
|
51
|
+
export function strandedClientsNote(status, next = SERVER_VERSION) {
|
|
52
|
+
const raw = 'object' === typeof status && null !== status
|
|
53
|
+
? status.mcpPeers
|
|
54
|
+
: undefined;
|
|
55
|
+
const peers = Array.isArray(raw) ? raw.filter((v) => 'string' === typeof v) : [];
|
|
56
|
+
const behind = [...new Set(peers.filter((v) => v !== next))];
|
|
57
|
+
const pin = behind[0];
|
|
58
|
+
if (pin === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
return (`warning: ${String(peers.length)} agent MCP server(s) are attached at ${behind.join(', ')}, ` +
|
|
61
|
+
`and this restart runs the daemon at ${next}. They stay attached to a daemon they no longer ` +
|
|
62
|
+
'match until their editor restarts its MCP server. To keep them working, restart at their ' +
|
|
63
|
+
`version instead: npx @reticlehq/server@${pin} restart`);
|
|
64
|
+
}
|
|
@@ -8,7 +8,7 @@ export interface SharedServer {
|
|
|
8
8
|
* so each concurrent Claude Code client needs its own McpServer.
|
|
9
9
|
* Must be called before listen.
|
|
10
10
|
*/
|
|
11
|
-
attachMcp(factory: () => McpServer): void;
|
|
11
|
+
attachMcp(factory: (peerSkew?: string) => McpServer): void;
|
|
12
12
|
/** Register the JSON the daemon returns from GET /status (live sessions + health for `reticle status`). */
|
|
13
13
|
/** `held`: the request carried STATUS_HOLD_QUERY — a setup run is waiting on this daemon. */
|
|
14
14
|
attachStatus(provider: (held: boolean) => unknown): void;
|
|
@@ -2,7 +2,7 @@ import * as http from 'node:http';
|
|
|
2
2
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
3
3
|
import { MCP_SSE_PATH, MCP_MESSAGE_PATH, MCP_SHUTDOWN_EVENT, STATUS_PATH, STATUS_HOLD_QUERY, DRIVE_PATH, } from '@reticlehq/core';
|
|
4
4
|
import { getSessionMetrics } from '../telemetry/session-metrics.js';
|
|
5
|
-
import {
|
|
5
|
+
import { agentPeerSkew, PEER_VERSION_PARAM, PEER_CONTRACT_PARAM, } from '../command/version/peer-announce.js';
|
|
6
6
|
import { log } from '../log.js';
|
|
7
7
|
import { isLoopbackHost, isLocalWebOrigin, isLoopbackPeer, requestToken, tokensMatch, } from '../portal/bridge/token-auth.js';
|
|
8
8
|
import { LOOPBACK_IDLE_MS } from './loopback-agent.js';
|
|
@@ -27,6 +27,8 @@ export function createSharedServer(options = {}) {
|
|
|
27
27
|
* and this message is addressed to the proxy rather than to the client behind it.
|
|
28
28
|
*/
|
|
29
29
|
const sseStreams = new Map();
|
|
30
|
+
/** The version each attached agent's MCP server announced, by SSE session. */
|
|
31
|
+
const peerVersions = new Map();
|
|
30
32
|
const token = options.token;
|
|
31
33
|
// The agent control plane (MCP transport) and /status carry the same trust as the browser WS: a
|
|
32
34
|
// loopback peer is trusted (the local stdio proxy and `reticle status` always dial 127.0.0.1), but any
|
|
@@ -78,7 +80,11 @@ export function createSharedServer(options = {}) {
|
|
|
78
80
|
if ('GET' === req.method && path === STATUS_PATH) {
|
|
79
81
|
// A setup run waiting on this daemon says so on its polls; see daemon-usefulness.
|
|
80
82
|
const held = url.searchParams.has(STATUS_HOLD_QUERY);
|
|
81
|
-
const
|
|
83
|
+
const status = statusProvider?.(held) ?? { running: true };
|
|
84
|
+
// The attached agents' MCP versions, so `restart` can say whom it is about to strand (#812).
|
|
85
|
+
const body = JSON.stringify('object' === typeof status && null !== status && 0 < peerVersions.size
|
|
86
|
+
? { ...status, mcpPeers: [...peerVersions.values()] }
|
|
87
|
+
: status);
|
|
82
88
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
83
89
|
res.end(body);
|
|
84
90
|
return;
|
|
@@ -141,7 +147,7 @@ export function createSharedServer(options = {}) {
|
|
|
141
147
|
// own check (a stderr line no agent reads), a nudge queued here rides out on the agent's next
|
|
142
148
|
// tool result. That is the pair the user hits after `npm update`: a cached npx MCP package
|
|
143
149
|
// talking to a daemon from a different build.
|
|
144
|
-
|
|
150
|
+
const peerSkew = agentPeerSkew(url.searchParams.get(PEER_VERSION_PARAM), url.searchParams.get(PEER_CONTRACT_PARAM));
|
|
145
151
|
if (mcpFactory === undefined) {
|
|
146
152
|
res.writeHead(503, { 'Content-Type': 'text/plain' });
|
|
147
153
|
res.end('MCP server not ready');
|
|
@@ -150,16 +156,21 @@ export function createSharedServer(options = {}) {
|
|
|
150
156
|
// Fresh McpServer per connection: the MCP SDK's Protocol layer only supports
|
|
151
157
|
// one active transport per Server instance, so concurrent clients each need
|
|
152
158
|
// their own instance backed by the same shared ToolDeps.
|
|
153
|
-
|
|
159
|
+
// Handed to THIS connection's server only: the skew is about this agent, not the daemon (#1136).
|
|
160
|
+
const mcpServer = mcpFactory(peerSkew);
|
|
154
161
|
const transport = new SSEServerTransport(MCP_MESSAGE_PATH, res);
|
|
155
162
|
const sid = transport.sessionId;
|
|
156
163
|
transports.set(sid, transport);
|
|
157
164
|
sseStreams.set(sid, res);
|
|
165
|
+
const peerVersion = url.searchParams.get(PEER_VERSION_PARAM);
|
|
166
|
+
if (null !== peerVersion && 0 < peerVersion.length)
|
|
167
|
+
peerVersions.set(sid, peerVersion);
|
|
158
168
|
if (1 === transports.size)
|
|
159
169
|
agentPresence?.(true); // first agent attached
|
|
160
170
|
res.on('close', () => {
|
|
161
171
|
transports.delete(sid);
|
|
162
172
|
sseStreams.delete(sid);
|
|
173
|
+
peerVersions.delete(sid);
|
|
163
174
|
transport.close().catch(() => undefined);
|
|
164
175
|
mcpServer.close().catch(() => undefined);
|
|
165
176
|
log('mcp_client_disconnected', { sessionId: sid });
|
|
@@ -99,6 +99,11 @@ export declare class HandshakeReplay {
|
|
|
99
99
|
* to reconnect and how to answer what it cannot send.
|
|
100
100
|
*/
|
|
101
101
|
export declare function buildSessionUrl(rawData: string, port: number): string | null;
|
|
102
|
+
/** The briefing's facts about this directory; a live session outweighs stale memory (#1138). */
|
|
103
|
+
export declare function instructionStateAt(port: number, liveSessions?: number): {
|
|
104
|
+
previouslyConnected: boolean;
|
|
105
|
+
appHere: boolean;
|
|
106
|
+
};
|
|
102
107
|
export declare function startMcpProxy(port: number,
|
|
103
108
|
/**
|
|
104
109
|
* Bring a daemon back before reconnecting. Without it the proxy retried a DEAD port until the
|
|
@@ -9,7 +9,7 @@ export { SseFrameParser } from './sse-frame-parser.js';
|
|
|
9
9
|
export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
|
|
10
10
|
import { probeDaemon } from './proxy/proxy-daemon-probe.js';
|
|
11
11
|
import { SERVER_VERSION } from '../../command/version/identity/server-version.js';
|
|
12
|
-
import { buildServerInstructions } from './server-instructions.js';
|
|
12
|
+
import { buildServerInstructions, localizeInstructions } from './server-instructions.js';
|
|
13
13
|
import { hasAnyProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
|
|
14
14
|
import { reticleStateHome } from '../../command/daemon/daemon.js';
|
|
15
15
|
import { projectIdsAt } from '../../command/cli/ports/resolve/cli-port.js';
|
|
@@ -17,6 +17,7 @@ import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from '../../command/version/p
|
|
|
17
17
|
import { onStreamDrop, onClientRequest, onReconnectBudgetSpent, onQueueExpired, OnDrop, OnRequest, } from './proxy/proxy-lifecycle.js';
|
|
18
18
|
import { describePresence, probePresence } from '../../command/daemon/binding/port-presence.js';
|
|
19
19
|
import { flushProxySessionMetrics } from '../../telemetry/proxy-telemetry.js';
|
|
20
|
+
import { detectStack } from '../../telemetry/feedback-context.js';
|
|
20
21
|
/**
|
|
21
22
|
* The same `/status` probe `doctor`, `status` and `kill` ask with. Reused rather than re-written:
|
|
22
23
|
* the whole defect this import closes was the proxy answering a DIFFERENT question from every other
|
|
@@ -288,14 +289,43 @@ export function buildSessionUrl(rawData, port) {
|
|
|
288
289
|
*/
|
|
289
290
|
function proxyInstructions(port) {
|
|
290
291
|
try {
|
|
291
|
-
return buildServerInstructions(
|
|
292
|
-
previouslyConnected: hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())),
|
|
293
|
-
});
|
|
292
|
+
return buildServerInstructions(instructionStateAt(port));
|
|
294
293
|
}
|
|
295
294
|
catch {
|
|
296
295
|
return buildServerInstructions({ previouslyConnected: false });
|
|
297
296
|
}
|
|
298
297
|
}
|
|
298
|
+
/** The briefing's facts about this directory; a live session outweighs stale memory (#1138). */
|
|
299
|
+
export function instructionStateAt(port, liveSessions = 0) {
|
|
300
|
+
const cwd = process.cwd();
|
|
301
|
+
return {
|
|
302
|
+
previouslyConnected: liveSessions > 0 || hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(cwd)),
|
|
303
|
+
appHere: detectStack(cwd).stack !== undefined,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
/** An `initialize` reply re-briefed for the AGENT's directory; see localizeInstructions. */
|
|
307
|
+
function withLocalBriefing(line, appHere) {
|
|
308
|
+
if (!line.includes('"instructions"'))
|
|
309
|
+
return line;
|
|
310
|
+
try {
|
|
311
|
+
const msg = JSON.parse(line);
|
|
312
|
+
if (null === msg || 'object' !== typeof msg || !('result' in msg))
|
|
313
|
+
return line;
|
|
314
|
+
const result = msg.result;
|
|
315
|
+
if (null === result || 'object' !== typeof result || !('instructions' in result))
|
|
316
|
+
return line;
|
|
317
|
+
const { instructions } = result;
|
|
318
|
+
if ('string' !== typeof instructions)
|
|
319
|
+
return line;
|
|
320
|
+
const localized = localizeInstructions(instructions, appHere);
|
|
321
|
+
if (localized === instructions)
|
|
322
|
+
return line;
|
|
323
|
+
return JSON.stringify({ ...msg, result: { ...result, instructions: localized } });
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
return line;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
299
329
|
export function startMcpProxy(port,
|
|
300
330
|
/**
|
|
301
331
|
* Bring a daemon back before reconnecting. Without it the proxy retried a DEAD port until the
|
|
@@ -395,6 +425,7 @@ onUsableSession = () => undefined) {
|
|
|
395
425
|
queueTimer.unref();
|
|
396
426
|
};
|
|
397
427
|
const replay = new HandshakeReplay();
|
|
428
|
+
const appHere = detectStack(process.cwd()).stack !== undefined;
|
|
398
429
|
const pending = new PendingRequests();
|
|
399
430
|
const quit = (code) => {
|
|
400
431
|
if (stopped)
|
|
@@ -421,7 +452,7 @@ onUsableSession = () => undefined) {
|
|
|
421
452
|
const emit = (line) => {
|
|
422
453
|
idleExit.noteTraffic();
|
|
423
454
|
pending.observeInbound(line);
|
|
424
|
-
process.stdout.write(`${line}\n`);
|
|
455
|
+
process.stdout.write(`${withLocalBriefing(line, appHere)}\n`);
|
|
425
456
|
};
|
|
426
457
|
let attempts = 0;
|
|
427
458
|
/** The daemon announced a planned shutdown; the next drop is that, not a fault. */
|
|
@@ -3,15 +3,20 @@ import { z } from 'zod';
|
|
|
3
3
|
import { type ToolDeps } from '../tools/tools.js';
|
|
4
4
|
import type { ToolDef } from '../tools/tools.js';
|
|
5
5
|
import { type ToolSurface } from '../tools/tool-surface.js';
|
|
6
|
+
import { type InstructionState } from './server-instructions.js';
|
|
6
7
|
/** The JSON-RPC method the SDK registers its tool dispatcher under. */
|
|
7
8
|
export declare const CALL_TOOL_METHOD = "tools/call";
|
|
8
9
|
/**
|
|
9
|
-
* Merge the runtime-spliced envelope
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Merge the runtime-spliced envelope into a tool's declared outputSchema so structuredContent
|
|
11
|
+
* validation keeps those fields instead of rejecting the call. The tool's own keys win over the
|
|
12
|
+
* permissive envelope defaults (e.g. ACT keeps its typed `session` shape).
|
|
13
|
+
*
|
|
14
|
+
* EVERY tool with an output schema, not only the session-bound ones: `runTool` adds `next`,
|
|
15
|
+
* `update_available`, `version_skew` and the feedback keys to any tool's result, and a session-exempt
|
|
16
|
+
* tool with a strict schema (`reticle_context`) failed the whole call on a validating client the
|
|
17
|
+
* first time one of them rode on it.
|
|
13
18
|
*/
|
|
14
|
-
export declare function withSessionEnvelope(
|
|
19
|
+
export declare function withSessionEnvelope(_name: string, outputSchema: z.ZodRawShape | undefined): z.ZodRawShape | undefined;
|
|
15
20
|
/**
|
|
16
21
|
* Abbreviations whose internal period is NOT a sentence end.
|
|
17
22
|
*
|
|
@@ -77,7 +82,7 @@ export declare function createMcpServer(rawDeps: ToolDeps, profile?: ToolSurface
|
|
|
77
82
|
* told to instrument a project that is already wired loses a paragraph, while an agent not told
|
|
78
83
|
* loses the entire install — and the second is what the field overwhelmingly shows.
|
|
79
84
|
*/
|
|
80
|
-
previouslyConnected?: boolean,
|
|
85
|
+
previouslyConnected?: boolean | Omit<InstructionState, 'advertised'>,
|
|
81
86
|
/**
|
|
82
87
|
* The full tool table this server serves. Defaults to what this package ships.
|
|
83
88
|
*
|
package/dist/surface/mcp/mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
3
4
|
import { z } from 'zod';
|
|
4
5
|
import { isPredicateParam } from '@reticlehq/engine/question/predicate/predicate-eval.js';
|
|
@@ -8,7 +9,7 @@ import { filterTools, describeToolSurface, TOOL_SURFACE, } from '../tools/tool-s
|
|
|
8
9
|
import { ReticleTool } from '@reticlehq/core';
|
|
9
10
|
import { SHARED_PARAM_SHORT } from './shared-params.js';
|
|
10
11
|
import { buildDynamicTools } from '../tools/dynamic-tools.js';
|
|
11
|
-
import { runTool
|
|
12
|
+
import { runTool } from '../tools/invoke-tool.js';
|
|
12
13
|
import { sessionEnvelopeShape, newSnapshotCache } from '../tools/tool-kit.js';
|
|
13
14
|
import { buildErrorPayload } from '../tools/error-recovery.js';
|
|
14
15
|
import { takeVersionSkewOnto } from '../../command/version/version-nudge.js';
|
|
@@ -39,13 +40,17 @@ import { getSessionMetrics } from '../../telemetry/session-metrics.js';
|
|
|
39
40
|
import { reportMcpConnected } from '../../telemetry/mcp-connection.js';
|
|
40
41
|
import { parsePredicate } from '@reticlehq/engine/question/predicate/predicate-parse.js';
|
|
41
42
|
/**
|
|
42
|
-
* Merge the runtime-spliced envelope
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* Merge the runtime-spliced envelope into a tool's declared outputSchema so structuredContent
|
|
44
|
+
* validation keeps those fields instead of rejecting the call. The tool's own keys win over the
|
|
45
|
+
* permissive envelope defaults (e.g. ACT keeps its typed `session` shape).
|
|
46
|
+
*
|
|
47
|
+
* EVERY tool with an output schema, not only the session-bound ones: `runTool` adds `next`,
|
|
48
|
+
* `update_available`, `version_skew` and the feedback keys to any tool's result, and a session-exempt
|
|
49
|
+
* tool with a strict schema (`reticle_context`) failed the whole call on a validating client the
|
|
50
|
+
* first time one of them rode on it.
|
|
46
51
|
*/
|
|
47
|
-
export function withSessionEnvelope(
|
|
48
|
-
if (outputSchema === undefined
|
|
52
|
+
export function withSessionEnvelope(_name, outputSchema) {
|
|
53
|
+
if (outputSchema === undefined)
|
|
49
54
|
return outputSchema;
|
|
50
55
|
return { ...sessionEnvelopeShape, ...outputSchema };
|
|
51
56
|
}
|
|
@@ -134,7 +139,7 @@ const COMPACT_PREDICATE_DESCRIPTION = `${PREDICATE_KINDS}${PREDICATE_FIELD_GRAMM
|
|
|
134
139
|
* schema cost; the first clause keeps each param's purpose and any enum hints. Params without a
|
|
135
140
|
* description pass through unchanged. Predicate params are replaced wholesale — see above.
|
|
136
141
|
*/
|
|
137
|
-
function leanZodShape(shape, predicateAnchor) {
|
|
142
|
+
function leanZodShape(shape, advertised, predicateAnchor) {
|
|
138
143
|
const out = {};
|
|
139
144
|
// The navigation hint is said ONCE per turn, so it is anchored to one parameter — not one tool.
|
|
140
145
|
// A tool can carry two predicate parameters (a canonical name plus a neighbouring tool's alias),
|
|
@@ -160,8 +165,13 @@ function leanZodShape(shape, predicateAnchor) {
|
|
|
160
165
|
out[key] = schema.describe(shared);
|
|
161
166
|
continue;
|
|
162
167
|
}
|
|
168
|
+
// Through the same rewrite as tool descriptions: a parameter that says "a ref from
|
|
169
|
+
// reticle_snapshot" names a tool this surface does not have.
|
|
163
170
|
const desc = schema.description;
|
|
164
|
-
out[key] =
|
|
171
|
+
out[key] =
|
|
172
|
+
'string' === typeof desc
|
|
173
|
+
? schema.describe(liveCallText(firstSentence(desc), advertised))
|
|
174
|
+
: schema;
|
|
165
175
|
}
|
|
166
176
|
return out;
|
|
167
177
|
}
|
|
@@ -292,9 +302,13 @@ export function advertisedConfig(tool, advertised, profile) {
|
|
|
292
302
|
// Against the advertised set, for the same reason results are: `reticle_navigate`'s own
|
|
293
303
|
// description told the reader to confirm a reload with `reticle_sessions`, on the surface
|
|
294
304
|
// where that name is gone. A description is advice like any other.
|
|
295
|
-
|
|
305
|
+
// Not on the meta-tools: they exist to name what is reachable only through `reticle_run`,
|
|
306
|
+
// and rewriting those names to advertised ones turned `reticle_tools` into nonsense.
|
|
307
|
+
isMetaTool
|
|
308
|
+
? tool.description
|
|
309
|
+
: liveCallText(terse ? firstSentence(tool.description) : tool.description, new Set(advertised.map((each) => each.name))), tool.example),
|
|
296
310
|
inputSchema: terse
|
|
297
|
-
? leanZodShape(tool.inputSchema, tool.name === anchor ? undefined : anchor)
|
|
311
|
+
? leanZodShape(tool.inputSchema, new Set(advertised.map((each) => each.name)), tool.name === anchor ? undefined : anchor)
|
|
298
312
|
: tool.inputSchema,
|
|
299
313
|
...(outputSchema !== undefined ? { outputSchema } : {}),
|
|
300
314
|
};
|
|
@@ -491,7 +505,7 @@ tools = tableForSurface(profile)) {
|
|
|
491
505
|
* runs once per client connection, which makes it the right owner; a caller that supplies its own
|
|
492
506
|
* store keeps it.
|
|
493
507
|
*/
|
|
494
|
-
const deps = { snapshots: newSnapshotCache(), ...rawDeps };
|
|
508
|
+
const deps = { snapshots: newSnapshotCache(), attachId: randomUUID(), ...rawDeps };
|
|
495
509
|
const encoding = (process.env[ENCODING_ENV] ?? '').toLowerCase();
|
|
496
510
|
// Which surface this daemon advertises. The 18-tool default and the 48-tool full surface are
|
|
497
511
|
// different products from inside an agent's context, so outcomes are only comparable when the
|
|
@@ -499,7 +513,7 @@ tools = tableForSurface(profile)) {
|
|
|
499
513
|
getSessionMetrics().recordSurface(profile);
|
|
500
514
|
const server = new McpServer(SERVER_INFO, {
|
|
501
515
|
instructions: buildServerInstructions({
|
|
502
|
-
previouslyConnected,
|
|
516
|
+
...('boolean' === typeof previouslyConnected ? { previouslyConnected } : previouslyConnected),
|
|
503
517
|
// The surface this server is about to serve. Without it the briefing describes the tools this
|
|
504
518
|
// package SHIPS rather than the ones this daemon advertises, and on any trimmed surface that
|
|
505
519
|
// is a briefing for a different product — measured cost: an agent that stopped driving.
|
|
@@ -662,7 +676,7 @@ tools = tableForSurface(profile)) {
|
|
|
662
676
|
content: [
|
|
663
677
|
{
|
|
664
678
|
type: 'text',
|
|
665
|
-
text: JSON.stringify(liveCallValues(takeVersionSkewOnto(buildErrorPayload(message)), advertisedNames)),
|
|
679
|
+
text: JSON.stringify(liveCallValues(takeVersionSkewOnto(buildErrorPayload(message), deps.peerSkew), advertisedNames)),
|
|
666
680
|
},
|
|
667
681
|
],
|
|
668
682
|
};
|
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
* keep that file from becoming the largest thing on the user's disk.
|
|
7
7
|
*/
|
|
8
8
|
import { appendFileSync, mkdirSync, renameSync, statSync, truncateSync } from 'node:fs';
|
|
9
|
-
import { homedir } from 'node:os';
|
|
10
9
|
import { join } from 'node:path';
|
|
11
|
-
import { ReticleDir } from '@reticlehq/core';
|
|
12
10
|
import { log } from '../../../log.js';
|
|
13
|
-
import { MAX_DAEMON_LOG_BYTES, recoverOversizedLog, rotateDaemonLog, } from '../../../command/daemon/daemon.js';
|
|
11
|
+
import { MAX_DAEMON_LOG_BYTES, recoverOversizedLog, rotateDaemonLog, reticleStateHome, } from '../../../command/daemon/daemon.js';
|
|
14
12
|
/**
|
|
15
13
|
* Which port this proxy serves, for the log file name. Set once at startup.
|
|
16
14
|
*
|
|
@@ -79,7 +77,7 @@ export function setProxyLogPort(port) {
|
|
|
79
77
|
/** The proxy's own log file, so a silent drop leaves a readable trace the agent can go read. */
|
|
80
78
|
export function proxyLogPath(port = logPort) {
|
|
81
79
|
const name = port === undefined ? 'mcp-proxy.log' : `proxy-${String(port)}.log`;
|
|
82
|
-
return join(
|
|
80
|
+
return join(reticleStateHome(), name);
|
|
83
81
|
}
|
|
84
82
|
/**
|
|
85
83
|
* Log to stderr (which the agent host usually swallows) AND to ~/.reticle/proxy-<port>.log, which it
|
|
@@ -96,7 +94,7 @@ export function proxyLogPath(port = logPort) {
|
|
|
96
94
|
export function proxyLog(event, fields = {}) {
|
|
97
95
|
log(event, fields);
|
|
98
96
|
try {
|
|
99
|
-
const dir =
|
|
97
|
+
const dir = reticleStateHome();
|
|
100
98
|
mkdirSync(dir, { recursive: true });
|
|
101
99
|
// WITH A TIMESTAMP. This file is the only record of an outage that survives the session, and
|
|
102
100
|
// without one it cannot answer the two questions anyone brings to it: when did this happen, and
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/** The nudge set for the AGENT's directory: a shared daemon briefs from where IT started. */
|
|
2
|
+
export declare function localizeInstructions(instructions: string, appHere: boolean): string;
|
|
3
|
+
export interface InstructionState {
|
|
2
4
|
/**
|
|
3
5
|
* The names this surface actually advertises. Every tool the prose mentions is resolved from it.
|
|
4
6
|
*
|
|
@@ -15,6 +17,7 @@ interface InstructionState {
|
|
|
15
17
|
* is whether this install has EVER worked, not whether it is working this second.
|
|
16
18
|
*/
|
|
17
19
|
previouslyConnected: boolean;
|
|
20
|
+
appHere?: boolean;
|
|
18
21
|
}
|
|
19
22
|
/** The instructions this daemon should advertise, given what it knows about the project. */
|
|
20
23
|
/**
|
|
@@ -23,4 +26,3 @@ interface InstructionState {
|
|
|
23
26
|
* repeated across every parameter description. See shared-params.ts.
|
|
24
27
|
*/
|
|
25
28
|
export declare function buildServerInstructions(state: InstructionState): string;
|
|
26
|
-
export {};
|
|
@@ -44,7 +44,7 @@ import { surfaceVocabulary, listOf } from './surface-vocabulary.js';
|
|
|
44
44
|
*/
|
|
45
45
|
const verdictDiscipline = (v) => `Reticle verifies a running web app from the inside: go (${v.navigate}), look (${listOf(v.look, v.find)}), act and prove in one hop (${v.actAndWait}), observe (${listOf(v.observe, v.state, v.network, v.console)}), assert (${v.assert}). Verify a user-facing change against the real app before you call it done — \`npx @reticlehq/server gate --since <ref>\` fails unless a passing artifact covers every flow your edits affect — and never weaken a check to make it pass.
|
|
46
46
|
|
|
47
|
-
Only ${v.act.length > 0 ? `${v.actAndWait}, ${v.assert} and ${v.act}{steps}` : `${v.actAndWait} and ${v.assert}`} produce a verdict. Everything else
|
|
47
|
+
Only ${v.act.length > 0 ? `${v.actAndWait}, ${v.assert} and ${v.act}{steps}` : `${v.actAndWait} and ${v.assert}`} produce a verdict. Everything else proves nothing; a drive without one has no result. Prefer ${v.actAndWait}({ ref, action, until }) — it names the expected consequence BEFORE the action, which is the difference between a check and a rationalisation. Only verified:"yes" is a pass; "unknown" (could not tell) and "no-fault" (nothing declared) are not proved. When a verdict catches what reading the code missed, say Reticle found it. Do a result's \`next\`.
|
|
48
48
|
|
|
49
49
|
Done is the journey's END proved, not its first step: drive each step your change touches and give the last an \`until\` naming the end state (\`durable: true\` if it must persist). A "yes" on it over a clean capture IS the answer — stop.
|
|
50
50
|
|
|
@@ -90,9 +90,19 @@ const replayFirst = (v) => {
|
|
|
90
90
|
* common way this step half-succeeds. Telling every agent to restart sent them to kill the server
|
|
91
91
|
* `init` had just started.
|
|
92
92
|
*/
|
|
93
|
-
const firstMoveFor = (v) => `FIRST: no app has ever connected to Reticle in this project, so nothing here can be verified yet. Having these tools is not the same as being set up — the tools reach a daemon, and the daemon has nothing to look at until the app itself is instrumented.
|
|
93
|
+
const firstMoveFor = (v, appHere) => `FIRST: no app has ever connected to Reticle in this project, so nothing here can be verified yet. Having these tools is not the same as being set up — the tools reach a daemon, and the daemon has nothing to look at until the app itself is instrumented.
|
|
94
94
|
|
|
95
|
-
Fix that
|
|
95
|
+
Fix that first: call ${v.sessions}. Its first call wires the app (\`init\`, dev server, browser) and \`wired\` lists the files it changed; tell the user. If it answers \`run_init\`, run that command in the app's directory. A session listed there is the proof, and until one appears no other tool can tell you anything about this app.${appHere ? TELL_THE_USER : ''}`;
|
|
96
|
+
const UNWIRED_LEAD_END = 'can tell you anything about this app.';
|
|
97
|
+
// An agent never asked to verify anything never reads "fix that first"; this makes it raise wiring.
|
|
98
|
+
const TELL_THE_USER = ' This directory holds a web app that is not wired yet: in your first reply, even if their request is unrelated, tell the user in one line and offer to wire it (the first session call does it). Do it only if they agree; if they decline, do not raise it again.';
|
|
99
|
+
/** The nudge set for the AGENT's directory: a shared daemon briefs from where IT started. */
|
|
100
|
+
export function localizeInstructions(instructions, appHere) {
|
|
101
|
+
const without = instructions.replace(TELL_THE_USER, '');
|
|
102
|
+
if (!appHere || !without.includes(UNWIRED_LEAD_END))
|
|
103
|
+
return without;
|
|
104
|
+
return without.replace(UNWIRED_LEAD_END, `${UNWIRED_LEAD_END}${TELL_THE_USER}`);
|
|
105
|
+
}
|
|
96
106
|
/**
|
|
97
107
|
* When to reach for the tools that are advertised but never explained, and how to reach the two
|
|
98
108
|
* that are explained but not advertised. Both halves of the same defect.
|
|
@@ -146,7 +156,7 @@ export function buildServerInstructions(state) {
|
|
|
146
156
|
// passing a surface, so a stale fallback here is what every agent reaching Reticle through
|
|
147
157
|
// `reticle mcp` reads — naming tools the surface it is being served does not have.
|
|
148
158
|
const v = surfaceVocabulary(state.advertised ?? defaultAdvertisedNames());
|
|
149
|
-
const firstMove = firstMoveFor(v);
|
|
159
|
+
const firstMove = firstMoveFor(v, true === state.appHere);
|
|
150
160
|
/*
|
|
151
161
|
* Said in BOTH states, and gated only on whether the surface can reach replay.
|
|
152
162
|
*
|