@reticlehq/server 3.3.0 → 3.4.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/LICENSE-ENTERPRISE +1 -1
- package/README.md +3 -3
- package/SKILL.md +12 -14
- package/dist/chromium-launch-options.d.ts +4 -3
- package/dist/chromium-launch-options.js +6 -2
- package/dist/command/cli/adhoc-suite.d.ts +22 -0
- package/dist/command/cli/adhoc-suite.js +90 -19
- package/dist/command/cli/adhoc-verdict.d.ts +35 -0
- package/dist/command/cli/adhoc-verdict.js +90 -3
- package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
- package/dist/command/cli/answers/gate-hook-message.js +6 -0
- package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
- package/dist/command/cli/auth/cloud-keystore.js +0 -3
- package/dist/command/cli/auth/cloud-session.d.ts +4 -0
- package/dist/command/cli/auth/cloud-session.js +2 -0
- package/dist/command/cli/cli-doctor.js +7 -5
- package/dist/command/cli/cli-flow-commands.d.ts +3 -6
- package/dist/command/cli/cli-flow-commands.js +67 -4
- package/dist/command/cli/cli-parse.d.ts +8 -1
- package/dist/command/cli/cli-parse.js +21 -3
- package/dist/command/cli/cli-update-commands.js +8 -1
- package/dist/command/cli/cli-verify.d.ts +24 -3
- package/dist/command/cli/cli-verify.js +54 -11
- package/dist/command/cli/cloud-cli.js +5 -2
- package/dist/command/cli/cloud-kit.js +3 -5
- package/dist/command/cli/cloud-login.js +21 -3
- package/dist/command/cli/demo/demo-page.js +6 -3
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
- package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
- package/dist/command/cli/launch/cli-launch.d.ts +8 -0
- package/dist/command/cli/launch/cli-launch.js +21 -9
- package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
- package/dist/command/cli/launch/sdk-version-match.js +206 -0
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
- package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
- package/dist/command/cli/ports/resolve/cli-port.js +125 -15
- package/dist/command/cli/setup-mcp-cli.js +13 -13
- package/dist/command/cli/tutorial.d.ts +2 -2
- package/dist/command/cli/tutorial.js +21 -7
- package/dist/command/cli.js +62 -10
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
- package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
- package/dist/command/daemon/daemon-resolve.d.ts +9 -0
- package/dist/command/daemon/daemon-resolve.js +1 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
- package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
- package/dist/command/dev/stale-issue-guard.d.ts +3 -4
- package/dist/command/dev/stale-issue-guard.js +8 -8
- package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
- package/dist/command/setup/bringup/connect-lease.js +38 -0
- package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
- package/dist/command/setup/bringup/dev-server-files.js +131 -0
- package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
- package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
- package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
- package/dist/command/setup/bringup/ensure-daemon.js +135 -16
- package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
- package/dist/command/setup/bringup/static-page-server.js +151 -0
- package/dist/command/setup/desktop-shape.d.ts +4 -0
- package/dist/command/setup/desktop-shape.js +18 -2
- package/dist/command/setup/init/init-port.d.ts +15 -0
- package/dist/command/setup/init/init-port.js +44 -0
- package/dist/command/setup/init/init-runtime.d.ts +1 -1
- package/dist/command/setup/init/init-runtime.js +39 -2
- package/dist/command/setup/node-effects.d.ts +27 -14
- package/dist/command/setup/node-effects.js +80 -44
- package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
- package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
- package/dist/command/setup/probe/page-probe.d.ts +13 -2
- package/dist/command/setup/probe/page-probe.js +18 -3
- package/dist/command/setup/run-setup.d.ts +26 -1
- package/dist/command/setup/run-setup.js +96 -13
- package/dist/command/setup/session-pick.d.ts +13 -0
- package/dist/command/setup/session-pick.js +20 -0
- package/dist/command/setup/setup-command.d.ts +2 -14
- package/dist/command/setup/setup-command.js +105 -31
- package/dist/command/setup/setup-mcp.js +3 -3
- package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
- package/dist/command/setup/terminal/wait-progress.js +17 -0
- package/dist/features/crawl/crawl-empty.js +1 -1
- package/dist/features/crawl/crawl-tools.d.ts +6 -0
- package/dist/features/crawl/crawl-tools.js +57 -0
- package/dist/features/crawl/crawl.js +171 -157
- package/dist/features/exhaust/app-coverage.d.ts +33 -0
- package/dist/features/exhaust/app-coverage.js +31 -0
- package/dist/features/exhaust/code-coverage.d.ts +47 -0
- package/dist/features/exhaust/code-coverage.js +77 -0
- package/dist/features/exhaust/exhaust-run.d.ts +33 -0
- package/dist/features/exhaust/exhaust-run.js +82 -0
- package/dist/features/exhaust/explorer.d.ts +103 -0
- package/dist/features/exhaust/explorer.js +195 -0
- package/dist/features/exhaust/failure-branches.d.ts +33 -0
- package/dist/features/exhaust/failure-branches.js +92 -0
- package/dist/features/exhaust/ledger.d.ts +202 -0
- package/dist/features/exhaust/ledger.js +252 -0
- package/dist/features/exhaust/session-fold.d.ts +31 -0
- package/dist/features/exhaust/session-fold.js +79 -0
- package/dist/features/exhaust/session-port.d.ts +36 -0
- package/dist/features/exhaust/session-port.js +102 -0
- package/dist/features/harness/drive-report.d.ts +0 -9
- package/dist/features/harness/drive-report.js +18 -0
- package/dist/features/harness/driver.js +4 -7
- package/dist/features/harness/fill-values.d.ts +6 -0
- package/dist/features/harness/fill-values.js +3 -0
- package/dist/features/harness/jev-driver.js +30 -13
- package/dist/features/harness/openai-driver.js +4 -7
- package/dist/features/harness/platform-config.js +5 -6
- package/dist/index.d.ts +9 -0
- package/dist/index.js +40 -11
- package/dist/judgement/domain/domain-model.js +3 -1
- package/dist/judgement/oracles/self-instrument.js +4 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
- package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
- package/dist/judgement/runs/verification-sync.d.ts +3 -1
- package/dist/judgement/runs/verification-sync.js +1 -1
- package/dist/language/flows/change/affected.d.ts +9 -0
- package/dist/language/flows/change/affected.js +35 -0
- package/dist/language/flows/change/gate.d.ts +20 -0
- package/dist/language/flows/change/gate.js +12 -1
- package/dist/language/flows/flow-author.d.ts +7 -0
- package/dist/language/flows/flow-author.js +19 -0
- package/dist/language/flows/flow-expect-grammar.js +1 -1
- package/dist/language/flows/flow-journey.d.ts +54 -0
- package/dist/language/flows/flow-journey.js +119 -0
- package/dist/language/flows/flow-replay-run.d.ts +2 -3
- package/dist/language/flows/flow-replay-run.js +13 -47
- package/dist/language/flows/flow-replay.d.ts +22 -8
- package/dist/language/flows/flow-replay.js +55 -9
- package/dist/language/flows/flow-result.d.ts +2 -0
- package/dist/language/flows/flow-result.js +25 -0
- package/dist/language/flows/flow-tools.js +17 -5
- package/dist/language/flows/flows.d.ts +10 -1
- package/dist/language/flows/flows.js +90 -14
- package/dist/language/flows/recording/replay-halt.js +3 -1
- package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
- package/dist/language/flows/recording/tape/recordings.js +55 -1
- package/dist/language/flows/replay.d.ts +19 -0
- package/dist/language/flows/replay.js +42 -6
- package/dist/language/flows/verify-change-tools.js +5 -2
- package/dist/launch-chromium.d.ts +80 -0
- package/dist/launch-chromium.js +218 -0
- package/dist/memory/cloud/cloud-config.d.ts +11 -2
- package/dist/memory/cloud/cloud-config.js +47 -7
- package/dist/memory/cloud/cloud-sync.d.ts +4 -1
- package/dist/memory/cloud/cloud-sync.js +6 -9
- package/dist/memory/cloud/harness-offer.js +5 -6
- package/dist/memory/cloud/harness-switch.js +5 -6
- package/dist/memory/cloud/sync-cycle.d.ts +68 -3
- package/dist/memory/cloud/sync-cycle.js +298 -55
- package/dist/memory/cloud/sync-daemon.js +24 -20
- package/dist/memory/cloud/sync-disk.js +16 -2
- package/dist/memory/intent/intent-shard.d.ts +10 -32
- package/dist/memory/intent/intent-shard.js +0 -10
- package/dist/memory/journal/drive-flow.d.ts +18 -13
- package/dist/memory/journal/drive-flow.js +45 -37
- package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
- package/dist/memory/journal/session-end.d.ts +9 -1
- package/dist/memory/journal/session-end.js +24 -12
- package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
- package/dist/memory/project/dir/reticle-dir.js +1 -0
- package/dist/memory/project/project-tools.js +3 -1
- package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
- package/dist/portal/bridge/auth-failure-reason.js +4 -2
- package/dist/portal/bridge/bridge.d.ts +8 -0
- package/dist/portal/bridge/bridge.js +43 -2
- package/dist/portal/input/js-coverage.d.ts +25 -0
- package/dist/portal/input/js-coverage.js +30 -0
- package/dist/portal/input/real-input.d.ts +11 -0
- package/dist/portal/input/real-input.js +29 -4
- package/dist/portal/pool/playwright-launcher.d.ts +6 -0
- package/dist/portal/pool/playwright-launcher.js +20 -13
- package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
- package/dist/portal/session/dev-server/dev-command.js +30 -12
- package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
- package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
- package/dist/portal/session/dev-server/route-status-probe.js +57 -0
- package/dist/portal/session/facts/observed-state.d.ts +11 -0
- package/dist/portal/session/facts/observed-state.js +34 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
- package/dist/portal/session/no-session-diagnosis.js +62 -5
- package/dist/portal/session/no-session-next-action.d.ts +13 -0
- package/dist/portal/session/no-session-next-action.js +48 -9
- package/dist/portal/session/no-session-watch.d.ts +18 -0
- package/dist/portal/session/no-session-watch.js +88 -2
- package/dist/portal/session/session-health.js +3 -2
- package/dist/portal/session/session-manager.d.ts +20 -6
- package/dist/portal/session/session-manager.js +19 -3
- package/dist/portal/session/session-reaper.d.ts +28 -0
- package/dist/portal/session/session-reaper.js +49 -1
- package/dist/portal/session/session.d.ts +5 -0
- package/dist/portal/session/session.js +15 -19
- package/dist/surface/http-server.d.ts +2 -1
- package/dist/surface/http-server.js +4 -2
- package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
- package/dist/surface/mcp/mcp-proxy.js +2 -2
- package/dist/surface/mcp/mcp.js +4 -2
- package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
- package/dist/surface/mcp/server-instructions.js +9 -7
- package/dist/surface/tools/act/after-match.d.ts +17 -0
- package/dist/surface/tools/act/after-match.js +30 -0
- package/dist/surface/tools/act/durable.d.ts +69 -0
- package/dist/surface/tools/act/durable.js +139 -0
- package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
- package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
- package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
- package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
- package/dist/surface/tools/act/proved-controls.d.ts +9 -0
- package/dist/surface/tools/act/proved-controls.js +11 -0
- package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
- package/dist/surface/tools/act/sequence-grade.js +32 -1
- package/dist/surface/tools/act-capsule.d.ts +7 -0
- package/dist/surface/tools/act-capsule.js +46 -1
- package/dist/surface/tools/act-sequence-tool.js +25 -8
- package/dist/surface/tools/act-tools.js +26 -50
- package/dist/surface/tools/assert/assert-grade.js +12 -1
- package/dist/surface/tools/assert/assert-verdict.js +2 -2
- package/dist/surface/tools/browser-tools.js +14 -2
- package/dist/surface/tools/coverage-tools.js +27 -1
- package/dist/surface/tools/error-recovery.js +4 -4
- package/dist/surface/tools/feedback-invite.js +6 -4
- package/dist/surface/tools/harness-explore.d.ts +3 -2
- package/dist/surface/tools/harness-explore.js +7 -6
- package/dist/surface/tools/lease-tools.js +15 -6
- package/dist/surface/tools/live-call-text.js +4 -1
- package/dist/surface/tools/observe-tools.js +9 -1
- package/dist/surface/tools/tools.js +4 -0
- package/dist/telemetry/argument-shape.d.ts +0 -2
- package/dist/telemetry/argument-shape.js +0 -4
- package/dist/telemetry/error-class.d.ts +2 -3
- package/dist/telemetry/error-class.js +2 -3
- package/dist/telemetry/harness-drive.d.ts +0 -2
- package/dist/telemetry/harness-drive.js +0 -4
- package/dist/wire-journal.d.ts +3 -0
- package/dist/wire-journal.js +4 -0
- package/dist/workspace-packages.d.ts +0 -2
- package/dist/workspace-packages.js +1 -9
- package/docs/README.md +4 -2
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +4 -4
- package/docs/architecture.md +6 -6
- package/docs/best-practices.mdx +1 -1
- package/docs/capabilities.mdx +32 -1
- package/docs/cli/cloud.mdx +14 -4
- package/docs/cli/doctor.mdx +4 -0
- package/docs/cli/feedback.mdx +2 -2
- package/docs/cli/gate.mdx +9 -4
- package/docs/cli/update.mdx +10 -0
- package/docs/cli.mdx +9 -0
- package/docs/desktop.mdx +14 -10
- package/docs/environment.mdx +87 -0
- package/docs/faq.mdx +18 -16
- package/docs/first-drive.md +5 -13
- package/docs/flows.md +21 -2
- package/docs/frameworks.mdx +29 -10
- package/docs/getting-started.md +3 -3
- package/docs/http-transport.md +6 -3
- package/docs/human-control.md +2 -2
- package/docs/index.mdx +16 -6
- package/docs/install-agentic.mdx +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/integration-patterns.md +3 -3
- package/docs/multi-agent-testing.md +1 -1
- package/docs/packages/babel-plugin.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/react.mdx +2 -2
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +2 -2
- package/docs/packages.mdx +9 -9
- package/docs/predicates.mdx +50 -5
- package/docs/quickstart.mdx +2 -2
- package/docs/recipes.mdx +2 -2
- package/docs/state-management.mdx +2 -2
- package/docs/system-map.md +1 -1
- package/docs/telemetry.md +1 -1
- package/docs/token-efficiency.md +3 -3
- package/docs/tools/act-and-wait.mdx +1 -0
- package/docs/tools/act-sequence.mdx +1 -1
- package/docs/tools/console.mdx +1 -1
- package/docs/tools/inspect.mdx +1 -1
- package/docs/tools/network.mdx +1 -1
- package/docs/tools/query.mdx +1 -1
- package/docs/tools/sessions.mdx +1 -1
- package/docs/tools/snapshot.mdx +1 -1
- package/docs/tools/state.mdx +1 -1
- package/docs/tools/wait-for.mdx +1 -1
- package/docs/usage.md +7 -6
- package/docs/use-cases.mdx +157 -0
- package/docs/vs-screenshots.mdx +3 -3
- package/docs/what-is-recorded.md +2 -2
- package/docs/what-is-reticle.mdx +17 -20
- package/docs/why-reticle.mdx +14 -21
- package/package.json +10 -9
- package/dist/judgement/capsule/minimize.d.ts +0 -12
- package/dist/judgement/capsule/minimize.js +0 -22
- package/dist/judgement/phenomena/phenomena.d.ts +0 -27
- package/dist/judgement/phenomena/phenomena.js +0 -101
- package/dist/language/flows/flow-report.d.ts +0 -22
- package/dist/language/flows/flow-report.js +0 -106
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A live session, as the explorer's port.
|
|
3
|
+
*
|
|
4
|
+
* The one hard part is that a full page load REPLACES the session: the SDK reconnects under the
|
|
5
|
+
* same id (sessionStorage keeps it) but as a new object, and a command in flight on the old one is
|
|
6
|
+
* rejected. So every call reads the CURRENT session, and anything that may have navigated waits for
|
|
7
|
+
* the successor before it answers — the same wait `navigate { reload }` does.
|
|
8
|
+
*/
|
|
9
|
+
import { ActionType, ReticleCommand, ReticleTool, SnapshotMode, asRecord, asString, } from '@reticlehq/core';
|
|
10
|
+
import { RELOAD_RECONNECT_TIMEOUT_MS, waitForReconnect, } from '../../portal/session/session-reconnect.js';
|
|
11
|
+
import { isSessionReplacedError } from '../../portal/session/facts/session-replaced.js';
|
|
12
|
+
export function sessionPort(input) {
|
|
13
|
+
let current = input.start;
|
|
14
|
+
/** Wait for the document that replaced `before`. False when none arrived in time. */
|
|
15
|
+
const successor = async (before) => {
|
|
16
|
+
const back = await waitForReconnect({
|
|
17
|
+
current: () => input.sessions.get(before.id),
|
|
18
|
+
previous: before,
|
|
19
|
+
timeoutMs: RELOAD_RECONNECT_TIMEOUT_MS,
|
|
20
|
+
now: input.now,
|
|
21
|
+
sleep: input.sleep,
|
|
22
|
+
});
|
|
23
|
+
const next = input.sessions.get(before.id);
|
|
24
|
+
if (!back || next === undefined)
|
|
25
|
+
return false;
|
|
26
|
+
current = next;
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
const look = async () => {
|
|
30
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
31
|
+
try {
|
|
32
|
+
const res = await current.command(ReticleCommand.SNAPSHOT, {
|
|
33
|
+
mode: SnapshotMode.INTERACTIVE,
|
|
34
|
+
});
|
|
35
|
+
const body = asRecord(res.result);
|
|
36
|
+
const route = asString(asRecord(body['status'])['route']);
|
|
37
|
+
return { tree: asString(body['tree']) ?? '', ...(route === undefined ? {} : { route }) };
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// A click that followed a real link leaves the next read on a dead document.
|
|
41
|
+
if (!isSessionReplacedError(error) || !(await successor(current)))
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { tree: '' };
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
current: () => current,
|
|
49
|
+
look,
|
|
50
|
+
async reset() {
|
|
51
|
+
const before = current;
|
|
52
|
+
try {
|
|
53
|
+
const res = await before.command(ReticleCommand.NAVIGATE, { url: input.startUrl });
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (!isSessionReplacedError(error))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (!(await successor(before)))
|
|
62
|
+
return false;
|
|
63
|
+
await input.sleep(input.settleMs);
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
async act(ref, action, value) {
|
|
67
|
+
const session = current;
|
|
68
|
+
const since = session.elapsed();
|
|
69
|
+
session.beginAction?.(ReticleTool.CRAWL, { ref, action });
|
|
70
|
+
let ok = false;
|
|
71
|
+
let replaced = false;
|
|
72
|
+
try {
|
|
73
|
+
const res = await session.command(ReticleCommand.ACT, {
|
|
74
|
+
ref,
|
|
75
|
+
action: 'fill' === action ? ActionType.FILL : ActionType.CLICK,
|
|
76
|
+
args: value === undefined ? {} : { value },
|
|
77
|
+
});
|
|
78
|
+
ok = res.ok && false !== asRecord(res.result)['dispatched'];
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Following a real link replaces the document mid-command: the click WORKED.
|
|
82
|
+
if (!isSessionReplacedError(error))
|
|
83
|
+
throw error;
|
|
84
|
+
replaced = true;
|
|
85
|
+
ok = true;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
session.finishAction?.();
|
|
89
|
+
}
|
|
90
|
+
await input.sleep(input.settleMs);
|
|
91
|
+
const events = session.eventsSince(since);
|
|
92
|
+
if (replaced && (await successor(session)))
|
|
93
|
+
return { ok, events: [...events, ...current.eventsSince(0)] };
|
|
94
|
+
return { ok, events };
|
|
95
|
+
},
|
|
96
|
+
...(input.setMocks === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: {
|
|
99
|
+
mock: (rules) => input.setMocks?.(current.url, rules) ?? Promise.resolve(false),
|
|
100
|
+
}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
@@ -61,13 +61,4 @@ export declare function replayedFlows(toolCalls: readonly ToolOutcome[]): {
|
|
|
61
61
|
}[];
|
|
62
62
|
/** Reduce the raw call log to the actions that actually drove the app. */
|
|
63
63
|
export declare function drivenSteps(toolCalls: readonly ToolOutcome[]): DrivenStep[];
|
|
64
|
-
/**
|
|
65
|
-
* The account, as lines.
|
|
66
|
-
*
|
|
67
|
-
* Proved and not-proved are counted separately and both are stated, because the failure mode this
|
|
68
|
-
* whole product exists to prevent is a reader taking "we did twelve things" for "twelve things
|
|
69
|
-
* work". `unknown` is deliberately grouped under NOT proved and labelled as undecided evidence
|
|
70
|
-
* rather than as a failure — it calls for a better check, not a code change, and collapsing the two
|
|
71
|
-
* would send an agent to rewrite working code.
|
|
72
|
-
*/
|
|
73
64
|
export declare function describeDrive(toolCalls: readonly ToolOutcome[], savedFlows: readonly string[]): string;
|
|
@@ -134,6 +134,18 @@ export function drivenSteps(toolCalls) {
|
|
|
134
134
|
* rather than as a failure — it calls for a better check, not a code change, and collapsing the two
|
|
135
135
|
* would send an agent to rewrite working code.
|
|
136
136
|
*/
|
|
137
|
+
/** The pages the drive's own snapshots reported, in order, each distinct page once. */
|
|
138
|
+
function pagesReached(toolCalls) {
|
|
139
|
+
const pages = [];
|
|
140
|
+
for (const call of toolCalls) {
|
|
141
|
+
if (ReticleTool.SNAPSHOT !== call.name || call.isError)
|
|
142
|
+
continue;
|
|
143
|
+
const route = asString(asRecord(asRecord(call.result)['status'])['route']);
|
|
144
|
+
if (route !== undefined && !pages.includes(route))
|
|
145
|
+
pages.push(route);
|
|
146
|
+
}
|
|
147
|
+
return pages;
|
|
148
|
+
}
|
|
137
149
|
export function describeDrive(toolCalls, savedFlows) {
|
|
138
150
|
const steps = drivenSteps(toolCalls);
|
|
139
151
|
const replays = replayedFlows(toolCalls);
|
|
@@ -166,10 +178,16 @@ export function describeDrive(toolCalls, savedFlows) {
|
|
|
166
178
|
const proved = steps.filter((step) => Verified.YES === step.verified);
|
|
167
179
|
const failed = steps.filter((step) => Verified.NO === step.verified);
|
|
168
180
|
const undecided = steps.filter((step) => Verified.YES !== step.verified && Verified.NO !== step.verified);
|
|
181
|
+
const pages = pagesReached(toolCalls);
|
|
169
182
|
const lines = [
|
|
170
183
|
...(replayLine === undefined ? [] : [replayLine]),
|
|
171
184
|
`Drove ${String(steps.length)} action(s): ${String(proved.length)} proved, ` +
|
|
172
185
|
`${String(failed.length)} failed, ${String(undecided.length)} not decided.`,
|
|
186
|
+
// How DEEP it got. An action count reads the same for a checkout walked to its receipt and for
|
|
187
|
+
// eleven clicks on the home page; the pages reached are what tell them apart.
|
|
188
|
+
...(0 === pages.length
|
|
189
|
+
? []
|
|
190
|
+
: [`Reached ${String(pages.length)} page(s): ${pages.join(' → ')}.`]),
|
|
173
191
|
];
|
|
174
192
|
for (const step of steps.slice(0, MAX_LISTED)) {
|
|
175
193
|
const verdict = step.verified ?? 'nothing declared';
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* purpose.
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
|
-
import { ReticleEnv,
|
|
14
|
+
import { ReticleEnv, platformCredentialFrom } from '@reticlehq/core';
|
|
15
15
|
/**
|
|
16
16
|
* The default driver model.
|
|
17
17
|
*
|
|
@@ -91,13 +91,10 @@ export function harnessOptionsFromEnv(env) {
|
|
|
91
91
|
*
|
|
92
92
|
* An explicit `ANTHROPIC_API_KEY` still wins: someone who exported one meant it.
|
|
93
93
|
*/
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
if (cloudKey === undefined || 0 === cloudKey.length)
|
|
94
|
+
const cloud = platformCredentialFrom(env);
|
|
95
|
+
if (cloud === undefined)
|
|
97
96
|
return undefined;
|
|
98
|
-
|
|
99
|
-
return undefined;
|
|
100
|
-
return withOptions(cloudKey, cloudUrl);
|
|
97
|
+
return withOptions(cloud.apiKey, cloud.url);
|
|
101
98
|
}
|
|
102
99
|
/** Build a driver backed by the Messages API. */
|
|
103
100
|
export function harnessDriver(options) {
|
|
@@ -64,4 +64,10 @@ export declare function fillValues(deps: {
|
|
|
64
64
|
generator?: HarnessDriverOptions;
|
|
65
65
|
/** One line about where the drive is, so a value suits the page it is typed on. */
|
|
66
66
|
context?: () => string;
|
|
67
|
+
/**
|
|
68
|
+
* A value the user SUPPLIED for this field — the same `RETICLE_SECRET_<FIELD>` replay reads for
|
|
69
|
+
* a redacted one. Checked first so a drive gets past a login, and never cached: the cache is a
|
|
70
|
+
* file on disk.
|
|
71
|
+
*/
|
|
72
|
+
secret?: (label: string) => string | undefined;
|
|
67
73
|
}): (label: string) => Promise<string>;
|
|
@@ -135,6 +135,9 @@ async function generateOne(options, label, context) {
|
|
|
135
135
|
export function fillValues(deps) {
|
|
136
136
|
let spent = 0;
|
|
137
137
|
return async (label) => {
|
|
138
|
+
const supplied = deps.secret?.(label);
|
|
139
|
+
if (supplied !== undefined && supplied.length > 0)
|
|
140
|
+
return supplied;
|
|
138
141
|
const cached = deps.cache?.get(label);
|
|
139
142
|
if (cached !== undefined)
|
|
140
143
|
return cached;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* exist because this product is the verdict, and a verdict sourced from a model's confidence is the
|
|
22
22
|
* thing Reticle sells the absence of.
|
|
23
23
|
*/
|
|
24
|
-
import { RETICLE_URL_PARAM, ReticleEnv, ReticleTool,
|
|
24
|
+
import { RETICLE_URL_PARAM, ReticleEnv, ReticleTool, platformCredentialFrom, asRecord, parseInteractive, } from '@reticlehq/core';
|
|
25
25
|
import { FINISH_TOOL } from './harness.js';
|
|
26
26
|
import { heuristicFillValue } from './fill-values.js';
|
|
27
27
|
export const DEFAULT_JEV_MODEL = 'jev-latest';
|
|
@@ -55,12 +55,15 @@ const Tool = {
|
|
|
55
55
|
RECORD: ReticleTool.RECORD,
|
|
56
56
|
FLOW_SAVE: ReticleTool.FLOW_SAVE,
|
|
57
57
|
FLOW_REPLAY: ReticleTool.FLOW_REPLAY,
|
|
58
|
+
NAVIGATE: ReticleTool.NAVIGATE,
|
|
58
59
|
FINISH: FINISH_TOOL.name,
|
|
59
60
|
};
|
|
60
61
|
/** The synthetic option meaning "stop driving". Not a tool arg — a key in the choice set. */
|
|
61
62
|
const FINISH_OPTION = 'finish';
|
|
62
63
|
/** The synthetic option meaning "I cannot tell from this page; look again". */
|
|
63
64
|
const RELOOK_OPTION = 'look_again';
|
|
65
|
+
/** Back to the page before this one — a dead end used to end the whole drive where it stood. */
|
|
66
|
+
const BACK_OPTION = 'go_back';
|
|
64
67
|
/**
|
|
65
68
|
* Tools the model may route to, beyond acting on a control.
|
|
66
69
|
*
|
|
@@ -244,14 +247,11 @@ export function jevOptionsFromEnv(env) {
|
|
|
244
247
|
};
|
|
245
248
|
}
|
|
246
249
|
// The ordinary path: the key the user minted on the platform, against the platform's own proxy.
|
|
247
|
-
// Reticle never ships a Jev key
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
if (cloudKey === undefined || 0 === cloudKey.length)
|
|
250
|
+
// Reticle never ships a Jev key; the URL is the configured platform, else the hosted service.
|
|
251
|
+
const cloud = platformCredentialFrom(env);
|
|
252
|
+
if (cloud === undefined)
|
|
251
253
|
return undefined;
|
|
252
|
-
|
|
253
|
-
return undefined;
|
|
254
|
-
return { apiKey: cloudKey, baseUrl: cloudUrl };
|
|
254
|
+
return { apiKey: cloud.apiKey, baseUrl: cloud.url };
|
|
255
255
|
}
|
|
256
256
|
/** Strip the snapshot's list marker, so a description reads as a phrase rather than a bullet. */
|
|
257
257
|
function cleanDesc(desc) {
|
|
@@ -287,6 +287,8 @@ function readState(history) {
|
|
|
287
287
|
saved: [],
|
|
288
288
|
tree: undefined,
|
|
289
289
|
route: undefined,
|
|
290
|
+
pages: [],
|
|
291
|
+
proved: [],
|
|
290
292
|
replayed: [],
|
|
291
293
|
acted: [],
|
|
292
294
|
blocked: [],
|
|
@@ -333,13 +335,22 @@ function readState(history) {
|
|
|
333
335
|
if ('string' === typeof tree)
|
|
334
336
|
state.tree = tree;
|
|
335
337
|
const route = asRecord(result['status'])['route'];
|
|
336
|
-
if ('string' === typeof route)
|
|
338
|
+
if ('string' === typeof route) {
|
|
337
339
|
state.route = appRoute(route);
|
|
340
|
+
if (state.pages.at(-1) !== route)
|
|
341
|
+
state.pages.push(route);
|
|
342
|
+
}
|
|
338
343
|
}
|
|
344
|
+
if (Tool.NAVIGATE === outcome.name)
|
|
345
|
+
state.tree = undefined;
|
|
339
346
|
if (Tool.ACT_AND_WAIT === outcome.name) {
|
|
340
347
|
const ref = asRecord(outcome.args)['ref'];
|
|
341
348
|
const failure = asRecord(outcome.result)['error'];
|
|
342
349
|
const refused = outcome.isError && 'string' === typeof failure && failure.includes(DANGEROUS_ARG);
|
|
350
|
+
const until = asRecord(asRecord(outcome.args)['until']);
|
|
351
|
+
const signal = until['name'];
|
|
352
|
+
if ('yes' === asRecord(outcome.result)['verified'] && 'string' === typeof signal)
|
|
353
|
+
state.proved.push(signal);
|
|
343
354
|
if ('string' === typeof ref) {
|
|
344
355
|
// A refused act never happened, so it is not something this drive has driven. Recording
|
|
345
356
|
// it as driven would retire the control after an attempt that never reached the app.
|
|
@@ -550,7 +561,7 @@ export function jevDriver(options) {
|
|
|
550
561
|
// ── the one real decision ────────────────────────────────────────────────────────────────
|
|
551
562
|
// What the drive is currently for: the first plan step that is not a replay. Replays are all
|
|
552
563
|
// done by the time we reach here, so whatever is left is the intent nobody has proved.
|
|
553
|
-
const objective = plan.find((step) => PLAN_REPLAY !== step.kind);
|
|
564
|
+
const objective = plan.find((step) => PLAN_REPLAY !== step.kind && !drive.proved.includes(step.target));
|
|
554
565
|
const candidates = candidatesFrom(drive.tree);
|
|
555
566
|
/**
|
|
556
567
|
* A page with nothing to act on ends the DRIVE, not just the journey.
|
|
@@ -567,11 +578,15 @@ export function jevDriver(options) {
|
|
|
567
578
|
return only(request(Tool.RECORD, { action: 'stop', recordingName: drive.open.name }), 'nothing interactive on this page; wind up');
|
|
568
579
|
}
|
|
569
580
|
const criteria = {
|
|
570
|
-
[FINISH_OPTION]: 'Stop driving:
|
|
581
|
+
[FINISH_OPTION]: 'Stop driving: every journey started here reached its end state, or nothing leads anywhere new.',
|
|
571
582
|
[RELOOK_OPTION]: 'Look at the page again without acting, because this reading looks incomplete.',
|
|
572
583
|
[OBSERVE_OPTION]: 'Read what the application DID since the last action — the requests it made and what it logged. Choose this when something was driven and it is not yet clear whether it worked.',
|
|
573
584
|
[STATE_OPTION]: "Read the application's own state, which is the strongest evidence there is and the one thing a screen cannot fake.",
|
|
574
585
|
};
|
|
586
|
+
const previous = drive.pages.at(-2);
|
|
587
|
+
if (previous !== undefined)
|
|
588
|
+
criteria[BACK_OPTION] =
|
|
589
|
+
`Go back to ${previous}: this page is a dead end, or a journey started there is not finished.`;
|
|
575
590
|
for (const candidate of candidates) {
|
|
576
591
|
const verb = actionFor(candidate.role);
|
|
577
592
|
const already = drive.acted.includes(candidate.ref) ? ' (already driven once)' : '';
|
|
@@ -580,12 +595,12 @@ export function jevDriver(options) {
|
|
|
580
595
|
const response = await callJev({ apiKey: options.apiKey, model, baseUrl, path, doFetch }, buildState(input.system, input.history, drive, objective), {
|
|
581
596
|
next_action: {
|
|
582
597
|
type: 'choice',
|
|
583
|
-
instructions: 'Which single action
|
|
598
|
+
instructions: 'Which single action carries the current user journey forward toward its END state — a submitted form, a saved record, a confirmation? Finish the journey this page started before opening a new one; prefer something not already driven only once it is finished.',
|
|
584
599
|
criteria,
|
|
585
600
|
},
|
|
586
601
|
journey_complete: {
|
|
587
602
|
type: 'noul',
|
|
588
|
-
instructions: '
|
|
603
|
+
instructions: 'Every journey started so far has been carried to its end state and checked there, and further driving would add nothing.',
|
|
589
604
|
},
|
|
590
605
|
});
|
|
591
606
|
const usage = {
|
|
@@ -602,6 +617,8 @@ export function jevDriver(options) {
|
|
|
602
617
|
}
|
|
603
618
|
if (RELOOK_OPTION === chosen)
|
|
604
619
|
return only(request(Tool.SNAPSHOT, { mode: 'interactive' }), 'look again', usage);
|
|
620
|
+
if (BACK_OPTION === chosen && previous !== undefined)
|
|
621
|
+
return only(request(Tool.NAVIGATE, { url: previous }), `back to ${previous}`, usage);
|
|
605
622
|
// Routing to a READ rather than an action. Neither changes the app, so neither is recorded as
|
|
606
623
|
// something driven — they are how the drive finds out whether what it drove worked.
|
|
607
624
|
if (OBSERVE_OPTION === chosen)
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* it is reported below so the number is visible rather than assumed.
|
|
18
18
|
*/
|
|
19
19
|
import { z } from 'zod';
|
|
20
|
-
import { ReticleEnv,
|
|
20
|
+
import { ReticleEnv, platformCredentialFrom } from '@reticlehq/core';
|
|
21
21
|
/**
|
|
22
22
|
* The default model.
|
|
23
23
|
*
|
|
@@ -71,13 +71,10 @@ export function openAiOptionsFromEnv(env) {
|
|
|
71
71
|
const direct = env[ReticleEnv.HARNESS_OPENAI_KEY];
|
|
72
72
|
if (direct !== undefined && 0 < direct.length)
|
|
73
73
|
return withModel({ apiKey: direct });
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
if (cloudKey === undefined || 0 === cloudKey.length)
|
|
74
|
+
const cloud = platformCredentialFrom(env);
|
|
75
|
+
if (cloud === undefined)
|
|
77
76
|
return undefined;
|
|
78
|
-
|
|
79
|
-
return undefined;
|
|
80
|
-
return withModel({ apiKey: cloudKey, baseUrl: cloudUrl });
|
|
77
|
+
return withModel({ apiKey: cloud.apiKey, baseUrl: cloud.url });
|
|
81
78
|
}
|
|
82
79
|
function toWireTool(tool) {
|
|
83
80
|
return {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - it never overrides someone who was EXPLICIT. A driver named in the call, or set in the
|
|
17
17
|
* environment, wins over the stored preference. Precedence lives in the caller.
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { platformCredentialFrom } from '@reticlehq/core';
|
|
20
20
|
const CONFIG_PATH = '/v1/model/config';
|
|
21
21
|
/**
|
|
22
22
|
* How long the daemon will wait for a preference before driving without it.
|
|
@@ -68,12 +68,11 @@ function parse(body) {
|
|
|
68
68
|
* warning printed on a path that then works correctly is how people learn to ignore stderr.
|
|
69
69
|
*/
|
|
70
70
|
export async function fetchPlatformConfig(env, doFetch = (url, init) => fetch(url, init), timeoutMs = TIMEOUT_MS) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
if (key === undefined || 0 === key.length)
|
|
74
|
-
return undefined;
|
|
75
|
-
if (host === undefined || 0 === host.length)
|
|
71
|
+
const cloud = platformCredentialFrom(env);
|
|
72
|
+
if (cloud === undefined)
|
|
76
73
|
return undefined;
|
|
74
|
+
const key = cloud.apiKey;
|
|
75
|
+
const host = cloud.url;
|
|
77
76
|
const controller = new AbortController();
|
|
78
77
|
const timer = setTimeout(() => {
|
|
79
78
|
controller.abort();
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,15 @@ export type { ToolDeps, ToolDef } from './surface/tools/tools.js';
|
|
|
15
15
|
export { createToolInvoker, UNKNOWN_TOOL_ERROR } from './surface/tools/tool-invoker.js';
|
|
16
16
|
export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/tools/invoke-tool.js';
|
|
17
17
|
export type { ToolInvoker } from './surface/tools/tool-invoker.js';
|
|
18
|
+
/**
|
|
19
|
+
* The port-presence check the CLI commands (`drive`, `verify`, `status`, `doctor`, `kill`) all share,
|
|
20
|
+
* re-exported so a consumer embedding `start()` in its own process can refuse a taken port the same
|
|
21
|
+
* honest way instead of letting a raw `EADDRINUSE` stack out — the gap `@reticlehq/test`'s
|
|
22
|
+
* `bootSession()` had (reticlehq/reticle#1141): it called `start()` directly with no pre-flight probe.
|
|
23
|
+
*/
|
|
24
|
+
export { probePresence, describePresence, PortPresence, } from './command/daemon/binding/port-presence.js';
|
|
25
|
+
export { probeDaemon } from './surface/mcp/proxy/proxy-daemon-probe.js';
|
|
26
|
+
export { fetchStatus } from './command/daemon/binding/daemon-status-probe.js';
|
|
18
27
|
export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
|
|
19
28
|
export { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
20
29
|
export type { RecordedStep, CompiledProgram } from './language/flows/recording/tape/recordings.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { fetchPlatformConfig } from './features/harness/platform-config.js';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { existsSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { homedir } from 'node:os';
|
|
7
|
-
import { linkedCloudPort } from './memory/cloud/cloud-config.js';
|
|
7
|
+
import { linkedCloudPort, platformEnvPort } from './memory/cloud/cloud-config.js';
|
|
8
8
|
import { attachCloudSync } from './memory/cloud/sync-daemon.js';
|
|
9
9
|
import { wireHooks } from './hooks/hook-commands.js';
|
|
10
10
|
import { PROJECT_REGISTRY_FILE, parseProjectRegistry, projectCandidates, } from '@reticlehq/core/artifacts';
|
|
@@ -25,6 +25,7 @@ import { SERVER_VERSION } from './command/version/identity/server-version.js';
|
|
|
25
25
|
import { BaselineStore } from './memory/project/baselines.js';
|
|
26
26
|
import { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
27
27
|
import { initImpact } from './memory/impact/impact-recorder.js';
|
|
28
|
+
import { flowAuthor } from './language/flows/flow-author.js';
|
|
28
29
|
import { FlowStore } from './language/flows/flows.js';
|
|
29
30
|
import { buildFlowChips } from './language/flows/flow-scope.js';
|
|
30
31
|
import { ProjectStore } from './memory/project/project-store.js';
|
|
@@ -42,7 +43,7 @@ import { LEASE_ACQUIRE_TOOL } from './surface/tools/lease-tools.js';
|
|
|
42
43
|
import { runTool } from './surface/tools/invoke-tool.js';
|
|
43
44
|
import { SessionReaper, endAllSessions, MCP_DISCONNECT_SUMMARY, } from './portal/session/session-reaper.js';
|
|
44
45
|
import { wireSessionScope } from './portal/session/no-session-watch.js';
|
|
45
|
-
import { buildIdlePredicate } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
46
|
+
import { buildIdlePredicate, noteSetupHold } from './command/daemon/lifetime/daemon-usefulness.js';
|
|
46
47
|
import { resolveToolSurface } from './surface/tools/tool-surface.js';
|
|
47
48
|
import { statusPayload } from './status-payload.js';
|
|
48
49
|
import { CdpRealInputProvider, LaunchedRealInputProvider } from './portal/input/real-input.js';
|
|
@@ -54,7 +55,7 @@ import { LeaseReaper } from './portal/pool/lease-reaper.js';
|
|
|
54
55
|
import { findProjectConfig, projectIdsAt, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
|
|
55
56
|
import { hasAnyProjectConnectedBefore } from './memory/recall/prior/connection-memory.js';
|
|
56
57
|
import { reticleStateHome } from './command/daemon/daemon.js';
|
|
57
|
-
import {
|
|
58
|
+
import { probeLaunchableChromium } from './launch-chromium.js';
|
|
58
59
|
import { attachJournal } from './wire-journal.js';
|
|
59
60
|
import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
|
|
60
61
|
import { AMBIENT_RECORDING } from './language/flows/recording/tape/recordings.js';
|
|
@@ -80,6 +81,15 @@ export { buildSessionRecommendation } from './portal/session/presence/session-re
|
|
|
80
81
|
export { TOOLS } from './surface/tools/tools.js';
|
|
81
82
|
export { createToolInvoker, UNKNOWN_TOOL_ERROR } from './surface/tools/tool-invoker.js';
|
|
82
83
|
export { runTool, SESSION_BOUND_TOOLS, SESSION_EXEMPT_TOOLS } from './surface/tools/invoke-tool.js';
|
|
84
|
+
/**
|
|
85
|
+
* The port-presence check the CLI commands (`drive`, `verify`, `status`, `doctor`, `kill`) all share,
|
|
86
|
+
* re-exported so a consumer embedding `start()` in its own process can refuse a taken port the same
|
|
87
|
+
* honest way instead of letting a raw `EADDRINUSE` stack out — the gap `@reticlehq/test`'s
|
|
88
|
+
* `bootSession()` had (reticlehq/reticle#1141): it called `start()` directly with no pre-flight probe.
|
|
89
|
+
*/
|
|
90
|
+
export { probePresence, describePresence, PortPresence, } from './command/daemon/binding/port-presence.js';
|
|
91
|
+
export { probeDaemon } from './surface/mcp/proxy/proxy-daemon-probe.js';
|
|
92
|
+
export { fetchStatus } from './command/daemon/binding/daemon-status-probe.js';
|
|
83
93
|
export { BaselineStore, normalizeLines, diffLines } from './memory/project/baselines.js';
|
|
84
94
|
export { RecordingStore } from './language/flows/recording/tape/recordings.js';
|
|
85
95
|
export { FlowStore, recordedStepToFlowStep } from './language/flows/flows.js';
|
|
@@ -263,6 +273,8 @@ function knownProjectRoots() {
|
|
|
263
273
|
}
|
|
264
274
|
return [...roots];
|
|
265
275
|
}
|
|
276
|
+
/** This project's platform credential (stored, else the env key), as the env the platform readers take. */
|
|
277
|
+
const platformEnvFor = (root) => platformEnvPort(createNodeFileSystem(), root ?? join(process.cwd(), ReticleDir.ROOT), homedir(), process.env);
|
|
266
278
|
export async function start(options = {}) {
|
|
267
279
|
const port = options.port ?? RETICLE_DEFAULT_PORT;
|
|
268
280
|
// Open the user's impact record before anything can connect. Not inside the MCP branch: a daemon
|
|
@@ -273,7 +285,7 @@ export async function start(options = {}) {
|
|
|
273
285
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
274
286
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
275
287
|
// to reach for the other.
|
|
276
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
288
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
277
289
|
});
|
|
278
290
|
const uninstallHooks = wireHooks(options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT), readProjectId(process.cwd()));
|
|
279
291
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
@@ -316,6 +328,7 @@ export async function start(options = {}) {
|
|
|
316
328
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
317
329
|
reportStep: reportOnboardingStep,
|
|
318
330
|
flows,
|
|
331
|
+
author: flowAuthor,
|
|
319
332
|
});
|
|
320
333
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
321
334
|
attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
|
|
@@ -352,16 +365,23 @@ export async function start(options = {}) {
|
|
|
352
365
|
project,
|
|
353
366
|
fs,
|
|
354
367
|
reticleRoot,
|
|
368
|
+
linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
|
|
355
369
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
356
370
|
now,
|
|
357
371
|
bridgePort: port,
|
|
358
|
-
browserProbe:
|
|
372
|
+
browserProbe: probeLaunchableChromium,
|
|
359
373
|
// The daemon's OWN project, so a tool can tell "this session is mine" from "this session
|
|
360
374
|
// belongs to a sibling app under the same daemon". contract_save refuses on the second.
|
|
361
375
|
...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
|
|
362
376
|
};
|
|
363
377
|
const profile = resolveToolSurface(options.toolProfile);
|
|
364
|
-
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
378
|
+
const server = createMcpServer(realInput !== undefined ? { ...deps, realInput } : deps, profile,
|
|
379
|
+
// A session live RIGHT NOW is stronger evidence than the durable memory, which can be empty or
|
|
380
|
+
// stale for a project the plugin wired without writing `.reticle.json` (see connection-memory.ts's
|
|
381
|
+
// KNOWN LIMIT). Without this OR, that project's `initialize` led with the first-install steps
|
|
382
|
+
// while a real session was already connected — see #1138.
|
|
383
|
+
bridge.sessions.count() > 0 ||
|
|
384
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd())));
|
|
365
385
|
// When the agent (the MCP client) disconnects cleanly, end every active session at once so the
|
|
366
386
|
// HUD doesn't linger. (If the agent instead KILLS this process, the WS dies and the browser
|
|
367
387
|
// self-ends via SESSION_LIFECYCLE.BRIDGE_LOST_MS — see transport.ts.)
|
|
@@ -404,7 +424,7 @@ export async function startDaemon(options = {}) {
|
|
|
404
424
|
// The daemon owns both sides of this seam, so it is the layer that may join them: the cache
|
|
405
425
|
// lives in cloud memory, the platform read lives in the harness feature, and neither is allowed
|
|
406
426
|
// to reach for the other.
|
|
407
|
-
config: harnessConfigSource(() => fetchPlatformConfig(
|
|
427
|
+
config: harnessConfigSource(async () => fetchPlatformConfig(await platformEnvFor(options.reticleRoot)())),
|
|
408
428
|
});
|
|
409
429
|
const security = await resolveBridgeSecurityWithAutoToken(options);
|
|
410
430
|
const shared = createSharedServer(security.token === undefined ? {} : { token: security.token });
|
|
@@ -420,7 +440,12 @@ export async function startDaemon(options = {}) {
|
|
|
420
440
|
// answers WHY rather than just "sessionCount: 0".
|
|
421
441
|
// `verifyPort` rides along so a later `serve --http` can tell whether this daemon already honours
|
|
422
442
|
// the requested `--http-port` instead of silently ignoring the flag (#687).
|
|
423
|
-
shared.attachStatus(() =>
|
|
443
|
+
shared.attachStatus((held) => {
|
|
444
|
+
// `init` holds the daemon it is waiting on, so the idle rule cannot shut it down mid-wait.
|
|
445
|
+
if (held)
|
|
446
|
+
noteSetupHold(Date.now());
|
|
447
|
+
return statusPayload(bridge.sessions.count(), bridge.sessions.list(), bridge.sessions.noSessionHint(), verifyHttp?.port, bridge.sessions.noSessionLead());
|
|
448
|
+
});
|
|
424
449
|
// Agent-independent presence: the daemon outlives any single agent, so when the LAST agent's MCP
|
|
425
450
|
// connection drops (it stopped, or is waiting on the human), end every session and push a clear
|
|
426
451
|
// "go to your terminal" notice to the panel — the human is on the browser and must not lose a typed
|
|
@@ -462,6 +487,7 @@ export async function startDaemon(options = {}) {
|
|
|
462
487
|
takeAmbientTape: () => recordings.stop(AMBIENT_RECORDING),
|
|
463
488
|
reportStep: reportOnboardingStep,
|
|
464
489
|
flows,
|
|
490
|
+
author: flowAuthor,
|
|
465
491
|
onRunPersisted: () => syncNudge.run?.(),
|
|
466
492
|
});
|
|
467
493
|
const project = new ProjectStore(fs, reticleRoot, { now });
|
|
@@ -501,7 +527,7 @@ export async function startDaemon(options = {}) {
|
|
|
501
527
|
// console and the panel cannot disagree about a setting they both offer. Nothing is awaited and a
|
|
502
528
|
// failure is not surfaced: the next snapshot re-reads the platform, so a lost write shows up as
|
|
503
529
|
// the switch springing back, which is the truthful outcome.
|
|
504
|
-
bridge.attachHarnessRequest((enabled) => void
|
|
530
|
+
bridge.attachHarnessRequest((enabled) => void platformEnvFor(reticleRoot)().then((env) => writeHarnessSwitch(env, enabled)));
|
|
505
531
|
// Scope auto-selection to the active project (from .reticle.json) so a stray tab from another app is
|
|
506
532
|
// never picked when the agent omits a sessionId. Explicit per-call scope/sessionId still overrides.
|
|
507
533
|
// Scope + the no-session diagnosis: "no browser session connected" is the error that ends most
|
|
@@ -533,7 +559,7 @@ export async function startDaemon(options = {}) {
|
|
|
533
559
|
artifactRootFor: artifactRootResolver(reticleRoot),
|
|
534
560
|
now,
|
|
535
561
|
bridgePort: port,
|
|
536
|
-
browserProbe:
|
|
562
|
+
browserProbe: probeLaunchableChromium,
|
|
537
563
|
// A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
|
|
538
564
|
onRunPersisted: () => cloudSync.nudge(),
|
|
539
565
|
};
|
|
@@ -541,7 +567,10 @@ export async function startDaemon(options = {}) {
|
|
|
541
567
|
const effectiveDeps = realInput !== undefined ? { ...deps, realInput } : deps;
|
|
542
568
|
// Read per attach, not once: a project that gets wired while this daemon is alive should stop
|
|
543
569
|
// being told to wire itself on the next agent that connects.
|
|
544
|
-
shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
|
|
570
|
+
shared.attachMcp(() => createMcpServer(effectiveDeps, profile,
|
|
571
|
+
// See the sibling call in `start`: a live session outweighs empty/stale durable memory (#1138).
|
|
572
|
+
bridge.sessions.count() > 0 ||
|
|
573
|
+
hasAnyProjectConnectedBefore(reticleStateHome(), port, projectIdsAt(process.cwd()))));
|
|
545
574
|
// `reticle drive <url>` when this daemon already owns the port: it asks HERE instead of trying to
|
|
546
575
|
// bind a port we are holding, and gets the same pooled context an agent's reticle_lease returns —
|
|
547
576
|
// through runTool, so it is counted and reported like any other call rather than being a second,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Pure: no IO, no clock. The tool layer loads flows + contract and calls this.
|
|
14
14
|
*/
|
|
15
|
-
import { AnchorKind, PredicateKind, } from '@reticlehq/core';
|
|
15
|
+
import { AnchorKind, PredicateKind, compareSourceClauses, } from '@reticlehq/core';
|
|
16
16
|
import { classifyFlowAssertions, FlowAssertionGrade } from '../../language/flows/flow-classify.js';
|
|
17
17
|
import { successLabel } from '../../language/flows/flow-success.js';
|
|
18
18
|
import { flowRisk, latestRun, rankByRisk, RiskLevel } from './flow-risk.js';
|
|
@@ -41,6 +41,8 @@ function walkClauses(predicate) {
|
|
|
41
41
|
}
|
|
42
42
|
if (PredicateKind.NOT === predicate.kind)
|
|
43
43
|
return walkClauses(predicate.predicate);
|
|
44
|
+
if (PredicateKind.COMPARE === predicate.kind)
|
|
45
|
+
return compareSourceClauses(predicate);
|
|
44
46
|
return [predicate];
|
|
45
47
|
}
|
|
46
48
|
function signalNamesIn(predicate) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `missing-signal` while honesty said `no-signal-on-mutation` for the same absence.
|
|
10
10
|
*/
|
|
11
11
|
import { InstrumentationGapKind } from '@reticlehq/core/artifacts';
|
|
12
|
-
/** PascalCase a store name for a `useX
|
|
12
|
+
/** PascalCase a store name for a `useX` hint (cart → Cart). */
|
|
13
13
|
function pascal(name) {
|
|
14
14
|
return 0 === name.length ? name : name[0]?.toUpperCase() + name.slice(1);
|
|
15
15
|
}
|
|
@@ -18,7 +18,9 @@ function insertFor(gap) {
|
|
|
18
18
|
case InstrumentationGapKind.NO_SIGNAL_ON_MUTATION:
|
|
19
19
|
return `reticle.signal('${gap.name}');`;
|
|
20
20
|
case InstrumentationGapKind.NO_STORE_REGISTERED:
|
|
21
|
-
|
|
21
|
+
// The store itself, so its subscribe is wired too. A `() => useX.getState()` getter is
|
|
22
|
+
// readable but never reports a change, which is the state this gap is about (#1146).
|
|
23
|
+
return `registerStore('${gap.name}', use${pascal(gap.name)});`;
|
|
22
24
|
case InstrumentationGapKind.MISSING_TESTID:
|
|
23
25
|
return `data-testid="${gap.name}"`;
|
|
24
26
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* perspective a flow that drifted no longer behaves, so it must not read as green. The actionable
|
|
5
5
|
* "why" is lifted from the replay's decision envelope (or its error) into failureReason.
|
|
6
6
|
*/
|
|
7
|
-
import { ReplayStatus, RunFlowStatus, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
7
|
+
import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
|
|
8
8
|
/** OK → PASS; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
|
|
9
9
|
export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
|
|
10
10
|
/** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
|
|
11
|
-
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number): RunFlowResult;
|
|
11
|
+
export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;
|