@reticlehq/server 3.8.1 → 3.9.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 +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-verdict.js +34 -7
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +45 -4
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -17
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +46 -3
- package/dist/command/cli/cli-parse.d.ts +5 -2
- package/dist/command/cli/cli-parse.js +10 -5
- package/dist/command/cli/cli-usage.js +3 -2
- package/dist/command/cli/cli-verify.d.ts +6 -1
- package/dist/command/cli/cli-verify.js +32 -13
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +6 -5
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +2 -0
- package/dist/command/cli/ports/port-holder.js +11 -3
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +1 -0
- package/dist/command/cli/status/status-lines.js +50 -17
- package/dist/command/cli/status-fields.js +17 -3
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli.js +70 -33
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon.d.ts +10 -3
- package/dist/command/daemon/daemon.js +17 -6
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +130 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +4 -13
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +57 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/setup-command.js +30 -12
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +13 -2
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +2 -81
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +14 -5
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/flow-store-for-session.js +7 -5
- package/dist/language/flows/recording/tape/recordings.d.ts +2 -13
- package/dist/language/flows/recording/tape/recordings.js +41 -44
- package/dist/language/flows/replay.d.ts +17 -7
- package/dist/language/flows/replay.js +95 -44
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/drive-error.d.ts +6 -0
- package/dist/portal/input/drive-error.js +9 -0
- package/dist/portal/input/network-mock.js +3 -2
- package/dist/portal/input/real-input-press.d.ts +34 -0
- package/dist/portal/input/real-input-press.js +150 -0
- package/dist/portal/input/real-input.d.ts +21 -23
- package/dist/portal/input/real-input.js +153 -194
- package/dist/portal/input/viewport-tools.js +6 -3
- package/dist/portal/pool/browser-pool.d.ts +5 -1
- package/dist/portal/pool/browser-pool.js +32 -38
- package/dist/portal/pool/context-permissions.d.ts +20 -17
- package/dist/portal/pool/context-permissions.js +63 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/lease-page-error.d.ts +29 -0
- package/dist/portal/pool/lease-page-error.js +18 -0
- package/dist/portal/pool/page-error-journal.d.ts +57 -0
- package/dist/portal/pool/page-error-journal.js +181 -0
- package/dist/portal/pool/playwright-launcher.js +21 -8
- package/dist/portal/pool/pool-contract.d.ts +22 -0
- package/dist/portal/pool/pool-lease.d.ts +35 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.js +69 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +8 -0
- package/dist/portal/session/dev-server/dev-command.js +56 -8
- package/dist/portal/session/no-session-diagnosis.js +10 -3
- package/dist/portal/session/no-session-next-action.d.ts +12 -0
- package/dist/portal/session/no-session-next-action.js +44 -4
- package/dist/portal/session/no-session-watch.js +3 -1
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/session-manager.js +40 -21
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +29 -2
- package/dist/start-wiring.js +73 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +5 -3
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/tools/act/act-preflight.js +37 -17
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +7 -0
- package/dist/surface/tools/act/after-match.js +33 -0
- package/dist/surface/tools/act/resolve-target.d.ts +2 -0
- package/dist/surface/tools/act/resolve-target.js +35 -8
- package/dist/surface/tools/act/sequence-grade.d.ts +3 -1
- package/dist/surface/tools/act/sequence-grade.js +8 -1
- package/dist/surface/tools/act-sequence-tool.js +3 -0
- package/dist/surface/tools/act-tools.js +12 -12
- package/dist/surface/tools/assert/assert-verdict.js +28 -1
- package/dist/surface/tools/browser-tools.js +75 -7
- package/dist/surface/tools/dynamic-tools.js +2 -1
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.js +43 -22
- package/dist/surface/tools/invoke-tool.js +27 -20
- package/dist/surface/tools/lease-hint.d.ts +7 -0
- package/dist/surface/tools/lease-hint.js +10 -3
- package/dist/surface/tools/lease-readiness.d.ts +21 -0
- package/dist/surface/tools/lease-readiness.js +47 -0
- package/dist/surface/tools/lease-seed.d.ts +2 -0
- package/dist/surface/tools/lease-seed.js +2 -1
- package/dist/surface/tools/lease-tools.js +43 -15
- package/dist/surface/tools/real-input-attempt.js +70 -31
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/tool-kit.d.ts +12 -0
- package/dist/surface/tools/tool-kit.js +7 -0
- package/dist/surface/tools/tools.js +26 -13
- package/dist/telemetry/argument-shape.js +16 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +2 -2
- package/dist/telemetry/tool-refused.js +9 -2
- package/docs/architecture.md +3 -6
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +4 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli.mdx +1 -1
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +15 -20
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/react.mdx +14 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages/vite-plugin.mdx +1 -0
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/skill.md +1 -1
- package/docs/telemetry.md +4 -2
- package/package.json +14 -10
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { asRecord, ElementState, NearestTargetSchema } from '@reticlehq/core';
|
|
2
3
|
/**
|
|
3
4
|
* Roles that usually name the control an agent meant, ahead of a wrapping `generic` that
|
|
4
5
|
* happened to inherit the same accessible name (header CTA vs empty-state CTA).
|
|
@@ -17,8 +18,9 @@ const ROLE_RANK = {
|
|
|
17
18
|
searchbox: 10,
|
|
18
19
|
};
|
|
19
20
|
function roleRank(role) {
|
|
20
|
-
if ('string' !== typeof role)
|
|
21
|
+
if ('string' !== typeof role) {
|
|
21
22
|
return 100;
|
|
23
|
+
}
|
|
22
24
|
return ROLE_RANK[role] ?? 50;
|
|
23
25
|
}
|
|
24
26
|
function statesOf(c) {
|
|
@@ -35,8 +37,9 @@ function inViewport(c) {
|
|
|
35
37
|
*/
|
|
36
38
|
function compareCandidates(a, b) {
|
|
37
39
|
const viewport = Number(inViewport(b)) - Number(inViewport(a));
|
|
38
|
-
if (0 !== viewport)
|
|
40
|
+
if (0 !== viewport) {
|
|
39
41
|
return viewport;
|
|
42
|
+
}
|
|
40
43
|
return roleRank(a.role) - roleRank(b.role);
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
@@ -50,14 +53,18 @@ function formatCandidate(c, anyInViewport) {
|
|
|
50
53
|
const ref = 'string' === typeof c.ref ? c.ref : '?';
|
|
51
54
|
const visible = false !== c.visible;
|
|
52
55
|
let where;
|
|
53
|
-
if (!visible)
|
|
56
|
+
if (!visible) {
|
|
54
57
|
where = 'hidden';
|
|
55
|
-
|
|
58
|
+
}
|
|
59
|
+
else if (inViewport(c)) {
|
|
56
60
|
where = 'in-viewport';
|
|
57
|
-
|
|
61
|
+
}
|
|
62
|
+
else if (anyInViewport) {
|
|
58
63
|
where = 'off-screen';
|
|
59
|
-
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
60
66
|
where = 'visible';
|
|
67
|
+
}
|
|
61
68
|
const head = name.length > 0 ? `${ref} (${role} "${name}"` : `${ref} (${role}`;
|
|
62
69
|
return `${head}; ${where})`;
|
|
63
70
|
}
|
|
@@ -76,6 +83,25 @@ function nearNamesClause(hint) {
|
|
|
76
83
|
'Use that name, or — when the name carries a part that changes, like a time — target it by ' +
|
|
77
84
|
'`testid` or by `text` (a contained match).');
|
|
78
85
|
}
|
|
86
|
+
const NearestTargetsSchema = z.array(NearestTargetSchema).min(1);
|
|
87
|
+
/** The words before the nearest-target list in a no_match refusal. The repeat hint reads after it. */
|
|
88
|
+
export const NEAREST_TARGETS_LEAD = ' The nearest targets on the page, most similar first: ';
|
|
89
|
+
/**
|
|
90
|
+
* The browser's `nearest` hint as a sentence: the targets most like the one asked for, with refs,
|
|
91
|
+
* so the retry is one call and not a snapshot loop. Empty when there is no list. It is advice
|
|
92
|
+
* only: the action stays refused, and nothing here picks a target.
|
|
93
|
+
*/
|
|
94
|
+
function nearestClause(hint) {
|
|
95
|
+
const parsed = NearestTargetsSchema.safeParse(asRecord(hint)['nearest']);
|
|
96
|
+
if (!parsed.success) {
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
const listed = parsed.data
|
|
100
|
+
.map((t) => `${t.ref} (${t.role} "${t.name}${true === t.nameTruncated ? '…' : ''}")`)
|
|
101
|
+
.join(', ');
|
|
102
|
+
return (`${NEAREST_TARGETS_LEAD}${listed}. If one of them is the ` +
|
|
103
|
+
'element you meant, pass its ref as `ref`.');
|
|
104
|
+
}
|
|
79
105
|
/**
|
|
80
106
|
* Turn a target QUERY into the single ref an action can be performed on.
|
|
81
107
|
*
|
|
@@ -104,7 +130,8 @@ export function resolveTargetRef(candidates, hint) {
|
|
|
104
130
|
return {
|
|
105
131
|
kind: 'error',
|
|
106
132
|
message: 'target matched no element. Nothing was acted on and no verdict is possible — ' +
|
|
107
|
-
nearNamesClause(hint)
|
|
133
|
+
nearNamesClause(hint) +
|
|
134
|
+
nearestClause(hint),
|
|
108
135
|
};
|
|
109
136
|
}
|
|
110
137
|
if (usable.length > 1) {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* is verified FOR THOSE THREE and silent about nine, and `because` says so — reporting it as "25%
|
|
15
15
|
* verified" would be the arithmetic that hides the nine.
|
|
16
16
|
*/
|
|
17
|
-
import { Verified } from '@reticlehq/core';
|
|
17
|
+
import { Verified, VerifiedReason } from '@reticlehq/core';
|
|
18
18
|
/**
|
|
19
19
|
* What to do when a step's declared consequence does not hold.
|
|
20
20
|
*
|
|
@@ -49,6 +49,8 @@ export interface StepExpectation {
|
|
|
49
49
|
}
|
|
50
50
|
export interface SequenceGrade {
|
|
51
51
|
verified: Verified;
|
|
52
|
+
/** The deciding clause, from core's list, so a sequence verdict is counted by reason like any other. */
|
|
53
|
+
reason: VerifiedReason;
|
|
52
54
|
/** Steps that declared a consequence. */
|
|
53
55
|
declared: number;
|
|
54
56
|
total: number;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* is verified FOR THOSE THREE and silent about nine, and `because` says so — reporting it as "25%
|
|
15
15
|
* verified" would be the arithmetic that hides the nine.
|
|
16
16
|
*/
|
|
17
|
-
import { Verified } from '@reticlehq/core';
|
|
17
|
+
import { Verified, VerifiedReason } from '@reticlehq/core';
|
|
18
18
|
/**
|
|
19
19
|
* What to do when a step's declared consequence does not hold.
|
|
20
20
|
*
|
|
@@ -36,6 +36,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
36
36
|
const observed = missed.observed ?? 'nothing that matched';
|
|
37
37
|
return {
|
|
38
38
|
verified: Verified.NO,
|
|
39
|
+
reason: VerifiedReason.ASSERTION_FAILED,
|
|
39
40
|
declared,
|
|
40
41
|
total,
|
|
41
42
|
because: `a declared consequence did not hold — observed ${observed}`,
|
|
@@ -47,6 +48,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
47
48
|
const kinds = (steps[contradictedAt]?.contradicted ?? []).join(', ');
|
|
48
49
|
return {
|
|
49
50
|
verified: Verified.NO,
|
|
51
|
+
reason: VerifiedReason.CONTRADICTED,
|
|
50
52
|
declared,
|
|
51
53
|
total,
|
|
52
54
|
because: `step ${String(contradictedAt + 1)}: channels disagree about this action (${kinds}) even though it went on`,
|
|
@@ -57,6 +59,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
57
59
|
if (unjudged !== undefined)
|
|
58
60
|
return {
|
|
59
61
|
verified: Verified.UNKNOWN,
|
|
62
|
+
reason: VerifiedReason.INCONCLUSIVE,
|
|
60
63
|
declared,
|
|
61
64
|
total,
|
|
62
65
|
because: `a step could not be judged — ${unjudged.undecided ?? ''}`,
|
|
@@ -77,6 +80,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
77
80
|
if (0 === declared) {
|
|
78
81
|
return {
|
|
79
82
|
verified: Verified.UNKNOWN,
|
|
83
|
+
reason: VerifiedReason.NOTHING_DECLARED,
|
|
80
84
|
declared,
|
|
81
85
|
total,
|
|
82
86
|
because: 0 === total
|
|
@@ -95,6 +99,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
95
99
|
if (steps.length < total) {
|
|
96
100
|
return {
|
|
97
101
|
verified: Verified.UNKNOWN,
|
|
102
|
+
reason: VerifiedReason.EVIDENCE_INCOMPLETE,
|
|
98
103
|
declared,
|
|
99
104
|
total,
|
|
100
105
|
because: `only ${String(steps.length)} of ${String(total)} steps ran, so the journey never reached its end`,
|
|
@@ -103,6 +108,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
103
108
|
if (true !== steps.at(-1)?.declared) {
|
|
104
109
|
return {
|
|
105
110
|
verified: Verified.UNKNOWN,
|
|
111
|
+
reason: VerifiedReason.EVIDENCE_INCOMPLETE,
|
|
106
112
|
declared,
|
|
107
113
|
total,
|
|
108
114
|
because: `the last step, where the journey ends, declared nothing — give it an \`expect\` naming the end state (${String(declared)} of ${String(total)} steps declared one)`,
|
|
@@ -110,6 +116,7 @@ export function gradeSequence(steps, plan = { planned: steps.length, dispatched:
|
|
|
110
116
|
}
|
|
111
117
|
return {
|
|
112
118
|
verified: Verified.YES,
|
|
119
|
+
reason: VerifiedReason.PROVED,
|
|
113
120
|
declared,
|
|
114
121
|
total,
|
|
115
122
|
because: declared === total
|
|
@@ -153,6 +153,8 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
153
153
|
stopped_at: z.number().optional(),
|
|
154
154
|
/** THE field to gate on: "yes" | "no" | "unknown" — see `because`. */
|
|
155
155
|
verified: z.string().optional(),
|
|
156
|
+
/** The deciding clause of `verified`, from the same list as every other verdict. */
|
|
157
|
+
verifiedReason: z.string().optional(),
|
|
156
158
|
because: z.string().optional(),
|
|
157
159
|
/**
|
|
158
160
|
* Present only when the plan PROVED something — see `offerToKeep`. A plan that declared nothing
|
|
@@ -464,6 +466,7 @@ export const ACT_SEQUENCE_TOOL = {
|
|
|
464
466
|
...(stalledAt !== undefined ? { stalled_at: stalledAt } : {}),
|
|
465
467
|
...(stoppedAt !== undefined ? { stopped_at: stoppedAt } : {}),
|
|
466
468
|
verified: grade.verified,
|
|
469
|
+
verifiedReason: grade.reason,
|
|
467
470
|
because: grade.because,
|
|
468
471
|
// Offered at the one moment the compiled program is in hand and free to keep.
|
|
469
472
|
...(() => {
|
|
@@ -34,7 +34,7 @@ import { declaresState } from '@reticlehq/engine/question/predicate/predicate-as
|
|
|
34
34
|
import { isStateUnwatched } from '@reticlehq/engine/evidence/blind-spots.js';
|
|
35
35
|
import { hiddenMatchNote } from '@reticlehq/engine/evidence/already-true.js';
|
|
36
36
|
import { inFlightRequestLabels, repeatedRequestLabels } from './act/settle-in-flight.js';
|
|
37
|
-
import { finishAfterMatch } from './act/after-match.js';
|
|
37
|
+
import { awaitShownMatch, finishAfterMatch } from './act/after-match.js';
|
|
38
38
|
import { noteProved } from './act/proved-controls.js';
|
|
39
39
|
import { decideVerified } from '@reticlehq/engine/evidence/verified.js';
|
|
40
40
|
import { buildHonestyBlock, honestyForVerdict } from '@reticlehq/engine/evidence/honesty.js';
|
|
@@ -123,7 +123,7 @@ export const ACT_TOOLS = [
|
|
|
123
123
|
args: z
|
|
124
124
|
.record(z.string(), z.unknown())
|
|
125
125
|
.optional()
|
|
126
|
-
.describe('Action-specific arguments: { value } for fill/select, { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
|
|
126
|
+
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt — a Cmd+K) or a click/dblclick (Shift+click range select), { button: "right" | "middle" } for a click (right sends contextmenu), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { native: true } to force a trusted native click, { holdMs: N } to keep the pointer or the key DOWN for N ms (hold-to-confirm controls; effect.heldMs reports what was achieved), { confirmDangerous: true } to allow a potentially destructive control — a permission gate, NOT a duration. For press: { keys: ["Control","k"] } holds several keys down TOGETHER (pressed in order, released in reverse) and { code } names the physical key — both stay on the in-page dispatcher, the only path that can express them. For upload: { path } is a path on disk (absolute or relative to the project root; the daemon reads the file and delivers real bytes to the file picker — this is the way to verify document-ingestion flows); or { name, content?, type? } to supply inline bytes directly.'),
|
|
127
127
|
refuseWhenThrottled: z
|
|
128
128
|
.boolean()
|
|
129
129
|
.optional()
|
|
@@ -271,7 +271,7 @@ export const ACT_TOOLS = [
|
|
|
271
271
|
args: z
|
|
272
272
|
.record(z.string(), z.unknown())
|
|
273
273
|
.optional()
|
|
274
|
-
.describe('Action-specific arguments: { value } for fill/select, { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut (Meta/Control/Shift/Alt), { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { confirmDangerous: true } for a potentially destructive control. For upload: { path } is a path on disk (absolute or relative to project root; daemon reads real bytes) or { name, content?, type? } for inline bytes.'),
|
|
274
|
+
.describe('Action-specific arguments: { value } for fill/select (select also drives an ARIA combobox: it opens it and clicks the option by its label), { text } for type/press (the key NAME, e.g. Escape or Tab), { modifiers: ["Meta","Shift"] } for a press shortcut or a click (Meta/Control/Shift/Alt), { button: "right" | "middle" } for a click, { toRef } for drag (the ref to drop ON — without it the drag lands nowhere), { confirmDangerous: true } for a potentially destructive control. For upload: { path } is a path on disk (absolute or relative to project root; daemon reads real bytes) or { name, content?, type? } for inline bytes.'),
|
|
275
275
|
predicate: PredicateSchema.optional().describe('Alias for `until`.'),
|
|
276
276
|
until: PredicateSchema.optional().describe('Predicate to wait for after the action completes (same shape as reticle_assert). OMIT to wait for the page to SETTLE — network + DOM idle — the deterministic default instead of a sleep. To assert a consequence AND settle, allOf them: { kind: "allOf", predicates: [<your predicate>, { kind: "settled" }] }.'),
|
|
277
277
|
timeout_ms: timeoutMsSchema
|
|
@@ -432,9 +432,10 @@ export const ACT_TOOLS = [
|
|
|
432
432
|
const { alreadyTrue, alreadyTrueHiddenMatch, alreadyTrueEvidence, baselines } = await readAlreadyTrue(session, until, since);
|
|
433
433
|
// Same as the ACT handler: the route this step RAN on, before the action can move the app.
|
|
434
434
|
const routeBeforeWait = pathOf(session.url);
|
|
435
|
-
// Recorded in `finally`, once the verdict is known — see captureVerdictedAct.
|
|
435
|
+
// Recorded in `finally`, once the verdict is known — see captureVerdictedAct. `held`: the
|
|
436
|
+
// action made the declared claim hold.
|
|
436
437
|
let dispatched;
|
|
437
|
-
let
|
|
438
|
+
let held = false;
|
|
438
439
|
try {
|
|
439
440
|
// actCommand is the single interception point for upload+path rewrite.
|
|
440
441
|
//
|
|
@@ -484,6 +485,7 @@ export const ACT_TOOLS = [
|
|
|
484
485
|
const reverted = await finishAfterMatch(session, until, since, baselines, left);
|
|
485
486
|
if (reverted !== undefined)
|
|
486
487
|
verdict = { pass: false, failureReason: reverted, observed: reverted };
|
|
488
|
+
verdict = await awaitShownMatch(session, until, since, baselines, verdict, left);
|
|
487
489
|
}
|
|
488
490
|
const r = asRecord(actResult?.result);
|
|
489
491
|
if ('boolean' === typeof r['settled'])
|
|
@@ -580,10 +582,9 @@ export const ACT_TOOLS = [
|
|
|
580
582
|
...(0 === impeachingNotes.length ? {} : { blindSpots: impeachingNotes }),
|
|
581
583
|
losses,
|
|
582
584
|
});
|
|
583
|
-
// A refuted
|
|
584
|
-
//
|
|
585
|
-
|
|
586
|
-
deps.recordings.unassertLast();
|
|
585
|
+
// A refuted or already-true claim stays off the ambient tape (the capsule below keeps a
|
|
586
|
+
// refuted one). See captureVerdictedAct.
|
|
587
|
+
held = verdict.pass && !alreadyTrue;
|
|
587
588
|
const capsuleSaved = await saveFailedAssertCapsule({
|
|
588
589
|
deps,
|
|
589
590
|
verdict,
|
|
@@ -690,8 +691,7 @@ export const ACT_TOOLS = [
|
|
|
690
691
|
},
|
|
691
692
|
...(namedNetIsInFlight(until, stillInFlight) ? { namedRequestInFlight: true } : {}),
|
|
692
693
|
}), (part) => reloadAndRecheck(deps, session, part, timeout));
|
|
693
|
-
|
|
694
|
-
noteProved(session, recordedVerdict, actResult?.result);
|
|
694
|
+
noteProved(session, String(decision.verified), actResult?.result);
|
|
695
695
|
// Computed once: the verdict block reports it, and the instrumentation gaps are a second
|
|
696
696
|
// reading of the same evidence rather than a new observation.
|
|
697
697
|
const actionSummary = causalSummary(windowEvents, {
|
|
@@ -834,7 +834,7 @@ export const ACT_TOOLS = [
|
|
|
834
834
|
}
|
|
835
835
|
finally {
|
|
836
836
|
if (dispatched !== undefined)
|
|
837
|
-
captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait,
|
|
837
|
+
captureVerdictedAct(deps.recordings, args, dispatched.result, routeBeforeWait, held, session.id, wroteSince(acted, actedSince));
|
|
838
838
|
deps.recordings.markEnded(pathOf(currentOf(deps.sessions, session).url), session.id);
|
|
839
839
|
acted.finishAction(verdictEffect, settledOutcome, true === settledOutcome ? acted.elapsed() - actedSince : undefined);
|
|
840
840
|
}
|
|
@@ -20,6 +20,28 @@ import { gradeOfPredicate } from './assert-grade.js';
|
|
|
20
20
|
import { assertSource, stampedSourceIn } from './assert-source.js';
|
|
21
21
|
import { VerdictAttribution, verdictAttributionOf } from '@reticlehq/core';
|
|
22
22
|
import { noteSourceSeen } from '../../../portal/session/recorded-gaps.js';
|
|
23
|
+
import { sdkBehindNote } from '../../../portal/session/session-info.js';
|
|
24
|
+
// Unanchored: the engine prefixes the reason with "the page did not answer:".
|
|
25
|
+
const COMMAND_TIMEOUT = /command '[^']*' timed out after (\d+)ms/;
|
|
26
|
+
/**
|
|
27
|
+
* The cause and the one fix for a read that came back `unknown`, in place of a bare fact (#1990).
|
|
28
|
+
* A page whose SDK is behind this daemon may not serve `match` at all, so the fix is the same
|
|
29
|
+
* SDK-behind sentence the listing shows. On a current page the read ran out of the assertion's own
|
|
30
|
+
* budget, and the fix is more of it.
|
|
31
|
+
*/
|
|
32
|
+
function diagnoseTimeout(reason, session) {
|
|
33
|
+
const behind = sdkBehindNote(session);
|
|
34
|
+
if (behind !== undefined) {
|
|
35
|
+
return `${reason} ${behind}`;
|
|
36
|
+
}
|
|
37
|
+
const granted = COMMAND_TIMEOUT.exec(reason)?.[1];
|
|
38
|
+
if (granted === undefined) {
|
|
39
|
+
return reason;
|
|
40
|
+
}
|
|
41
|
+
return (`${reason} This read was bounded by what the assertion's budget had left ` +
|
|
42
|
+
`(${granted}ms): the budget ran out while the page was still working. ` +
|
|
43
|
+
'Give the assertion more time with `timeout_ms`.');
|
|
44
|
+
}
|
|
23
45
|
/**
|
|
24
46
|
* The honesty verdict for a plain `reticle_assert`.
|
|
25
47
|
*
|
|
@@ -138,7 +160,12 @@ changeUndeclared) {
|
|
|
138
160
|
});
|
|
139
161
|
const outcomeUnread = unreadWriteLabels(windowEvents);
|
|
140
162
|
const stillInFlight = inFlightRequestLabels(windowEvents, session.url, session.background);
|
|
141
|
-
const
|
|
163
|
+
const rawInconclusive = inconclusive ?? (!pass ? session.preconditionFailure?.() : undefined);
|
|
164
|
+
// A timed-out page read is the one `unknown` whose cause and fix are knowable here (#1990).
|
|
165
|
+
let effectiveInconclusive = rawInconclusive;
|
|
166
|
+
if (rawInconclusive !== undefined && COMMAND_TIMEOUT.test(rawInconclusive)) {
|
|
167
|
+
effectiveInconclusive = diagnoseTimeout(rawInconclusive, session);
|
|
168
|
+
}
|
|
142
169
|
/**
|
|
143
170
|
* Does a green here rest on the window having been COMPLETE?
|
|
144
171
|
*
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { carryReticleIdentity } from './lease-tools.js';
|
|
1
|
+
import { carryReticleIdentity, LEASE_ACQUIRE_TOOL } from './lease-tools.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { navigateResult } from './act/navigation/navigate-result.js';
|
|
4
4
|
import { absoluteTarget, awaitArrival, idsOpenAtDispatch, ARRIVAL_TIMEOUT_MS, } from './act/navigation/navigate-arrival.js';
|
|
5
5
|
import { reloadResult } from './act/navigation/reload-result.js';
|
|
6
6
|
import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS, } from '../../portal/session/session-reconnect.js';
|
|
7
|
-
import { ReticleCommand } from '@reticlehq/core';
|
|
7
|
+
import { NO_SESSION_CONNECTED_ERROR, ReticleCommand, SESSION_STARTED_NOTE, SessionStartedVia, relativeUrlNoSession, } from '@reticlehq/core';
|
|
8
8
|
import { ReticleTool } from '@reticlehq/core';
|
|
9
9
|
import { asNumber, asString } from '@reticlehq/core';
|
|
10
10
|
import { sessionIdShape, commandOrThrow } from './tool-kit.js';
|
|
@@ -17,8 +17,9 @@ const MSG_LEASE_WITHOUT_SDK = 'This page has no Reticle SDK, so only browser-lev
|
|
|
17
17
|
async function navigateLease(deps, sessionId, url, args) {
|
|
18
18
|
const timeoutMs = asNumber(args['timeout_ms']) ?? ARRIVAL_TIMEOUT_MS;
|
|
19
19
|
const went = (await deps.pool?.gotoLease(sessionId, url, timeoutMs)) ?? false;
|
|
20
|
-
if (!went)
|
|
20
|
+
if (!went) {
|
|
21
21
|
return { ok: false, url, reason: 'the leased page did not navigate' };
|
|
22
|
+
}
|
|
22
23
|
const back = await waitForSession(deps, sessionId, timeoutMs);
|
|
23
24
|
return back
|
|
24
25
|
? { ok: true, url, confirmed: true, sessionId }
|
|
@@ -28,19 +29,74 @@ async function navigateLease(deps, sessionId, url, args) {
|
|
|
28
29
|
async function waitForSession(deps, sessionId, timeoutMs) {
|
|
29
30
|
const start = deps.now();
|
|
30
31
|
for (;;) {
|
|
31
|
-
if (deps.sessions.get(sessionId) !== undefined)
|
|
32
|
+
if (deps.sessions.get(sessionId) !== undefined) {
|
|
32
33
|
return true;
|
|
33
|
-
|
|
34
|
+
}
|
|
35
|
+
if (deps.now() - start >= timeoutMs) {
|
|
34
36
|
return false;
|
|
37
|
+
}
|
|
35
38
|
await new Promise((resolve) => setTimeout(resolve, LEASE_POLL_MS));
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
const LEASE_POLL_MS = 100;
|
|
42
|
+
/** `sessionStarted` on the result. See `SessionStarted` in core. */
|
|
43
|
+
const SessionStartedSchema = z.object({
|
|
44
|
+
sessionId: z.string(),
|
|
45
|
+
via: z.nativeEnum(SessionStartedVia),
|
|
46
|
+
zeroInstall: z.boolean().optional(),
|
|
47
|
+
});
|
|
48
|
+
/** The origin a relative-URL refusal names when the project app cannot be found. */
|
|
49
|
+
const EXAMPLE_ORIGIN = 'http://localhost:3000';
|
|
50
|
+
function isHttpUrl(url) {
|
|
51
|
+
try {
|
|
52
|
+
const { protocol } = new URL(url);
|
|
53
|
+
return 'http:' === protocol || 'https:' === protocol;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* No session, and an absolute URL: open a lease on it through the lease tool itself, so the same
|
|
61
|
+
* owner, browser preflight and production rules apply as on an explicit `reticle_lease` acquire.
|
|
62
|
+
*/
|
|
63
|
+
async function startSessionOn(deps, url) {
|
|
64
|
+
const leased = (await LEASE_ACQUIRE_TOOL.handler(deps, { url }));
|
|
65
|
+
const sessionStarted = {
|
|
66
|
+
sessionId: leased.sessionId,
|
|
67
|
+
via: SessionStartedVia.LEASE,
|
|
68
|
+
...(true === leased.zeroInstall ? { zeroInstall: true } : {}),
|
|
69
|
+
};
|
|
70
|
+
const note = leased.hint === undefined ? SESSION_STARTED_NOTE : `${SESSION_STARTED_NOTE} ${leased.hint}`;
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
url,
|
|
74
|
+
confirmed: leased.ready,
|
|
75
|
+
sessionId: leased.sessionId,
|
|
76
|
+
sessionStarted,
|
|
77
|
+
note,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* No session, and a relative URL: nothing names the app. Keep the no-session refusal (and its
|
|
82
|
+
* diagnosis), and add the absolute form of the same path.
|
|
83
|
+
*/
|
|
84
|
+
async function refuseRelativeWithoutSession(deps, path) {
|
|
85
|
+
let message = NO_SESSION_CONNECTED_ERROR;
|
|
86
|
+
try {
|
|
87
|
+
deps.sessions.resolve();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
message = err instanceof Error ? err.message : String(err);
|
|
91
|
+
}
|
|
92
|
+
const origin = (await deps.projectAppUrl?.().catch(() => undefined)) ?? EXAMPLE_ORIGIN;
|
|
93
|
+
throw new Error(`${message}${relativeUrlNoSession(path, origin)}`);
|
|
94
|
+
}
|
|
39
95
|
export const BROWSER_TOOLS = [
|
|
40
96
|
{
|
|
41
97
|
name: ReticleTool.NAVIGATE,
|
|
42
98
|
example: { url: '/settings' },
|
|
43
|
-
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the old page cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect. `confirmed:true` with a `sessionId` means the requested page arrived and you can act immediately. `confirmed:false` with `landedOn` means the navigation arrived but the app landed at another URL, such as an authentication or route-guard redirect; use `landedOn` to understand where the browser actually ended up. `confirmed:false` without `landedOn` means the target was not observed within the wait window; a slow app may still be on its way, so increase `timeout_ms` when appropriate.',
|
|
99
|
+
description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the old page cannot report on it. The daemon then waits up to `timeout_ms` (default 5000) for the SDK to reconnect. `confirmed:true` with a `sessionId` means the requested page arrived and you can act immediately. `confirmed:false` with `landedOn` means the navigation arrived but the app landed at another URL, such as an authentication or route-guard redirect; use `landedOn` to understand where the browser actually ended up. `confirmed:false` without `landedOn` means the target was not observed within the wait window; a slow app may still be on its way, so increase `timeout_ms` when appropriate. With no session connected, an absolute URL opens a leased tab on it and the result carries `sessionStarted`.',
|
|
44
100
|
inputSchema: {
|
|
45
101
|
url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
|
|
46
102
|
reload: z
|
|
@@ -74,6 +130,8 @@ export const BROWSER_TOOLS = [
|
|
|
74
130
|
* of "Reticle stopped waiting" and "the page never came back" you are looking at.
|
|
75
131
|
*/
|
|
76
132
|
waitedMs: z.number().optional(),
|
|
133
|
+
/** Present only when no session was connected and this call opened one on the URL. */
|
|
134
|
+
sessionStarted: SessionStartedSchema.optional(),
|
|
77
135
|
note: z.string().optional(),
|
|
78
136
|
},
|
|
79
137
|
handler: async (deps, args) => {
|
|
@@ -110,8 +168,9 @@ export const BROWSER_TOOLS = [
|
|
|
110
168
|
return reloadResult(back, timeoutMs);
|
|
111
169
|
}
|
|
112
170
|
const requested = asString(args['url']);
|
|
113
|
-
if (requested === undefined || 0 === requested.length)
|
|
171
|
+
if (requested === undefined || 0 === requested.length) {
|
|
114
172
|
return { ok: false, reason: 'url required' };
|
|
173
|
+
}
|
|
115
174
|
assertNotPaymentNavigation(requested);
|
|
116
175
|
const named = asString(args['sessionId']);
|
|
117
176
|
// A lease whose page has no SDK right now (it was navigated to an image, a PDF, another site):
|
|
@@ -123,6 +182,15 @@ export const BROWSER_TOOLS = [
|
|
|
123
182
|
return navigateLease(deps, named, carryReticleIdentity(leaseUrl, requested), args);
|
|
124
183
|
}
|
|
125
184
|
}
|
|
185
|
+
// Nothing is connected at all, and the call named no tab. An absolute URL names the app, so
|
|
186
|
+
// open a lease on it instead of refusing. Several sessions, or one in another project, keep
|
|
187
|
+
// the refusal that resolve gives below: a lease there would guess.
|
|
188
|
+
if (named === undefined && 0 === deps.sessions.count()) {
|
|
189
|
+
if (isHttpUrl(requested)) {
|
|
190
|
+
return startSessionOn(deps, requested);
|
|
191
|
+
}
|
|
192
|
+
return refuseRelativeWithoutSession(deps, requested);
|
|
193
|
+
}
|
|
126
194
|
// Record navigate as an action. Its window is usually empty (the page unloads and the SDK
|
|
127
195
|
// reconnects), but the action record itself — "navigated to X" — is the causal fact worth keeping.
|
|
128
196
|
const session = deps.sessions.resolve(asString(args['sessionId']));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { runTool } from './invoke-tool.js';
|
|
3
3
|
import { buildErrorPayload, codedRefusalOf } from './error-recovery.js';
|
|
4
|
+
import { withRepeatHint } from './repeat-hint.js';
|
|
4
5
|
import { mergedNameRedirect, mergedNameMessage, retiredToolNames } from './merged-name-redirect.js';
|
|
5
6
|
import { CATALOGUE_SUMMARY_LENGTH, firstSentence } from './first-sentence.js';
|
|
6
7
|
import { takeVersionSkewOnto } from '../../command/version/version-nudge.js';
|
|
@@ -381,7 +382,7 @@ callable) {
|
|
|
381
382
|
// is reached through here: a stale ref, a paused session, a missing pairing token all came
|
|
382
383
|
// back as the agent's arguments being wrong, which is advice that spends the retry.
|
|
383
384
|
const message = error instanceof Error ? error.message : String(error);
|
|
384
|
-
const payload = buildErrorPayload(message, codedRefusalOf(error));
|
|
385
|
+
const payload = withRepeatHint(buildErrorPayload(message, codedRefusalOf(error)), error);
|
|
385
386
|
return { ...takeVersionSkewOnto(payload, deps.peerSkew), tool: name };
|
|
386
387
|
}
|
|
387
388
|
},
|
|
@@ -2,6 +2,8 @@ import { z } from 'zod';
|
|
|
2
2
|
import { wiringConsent } from '../../portal/session/no-session-next-action.js';
|
|
3
3
|
export { wiringConsent };
|
|
4
4
|
import type { ToolDeps } from './tool-kit.js';
|
|
5
|
+
/** The field that carries the next step for an unwired project. */
|
|
6
|
+
export declare const NEXT_STEP_FIELD = "next_step";
|
|
5
7
|
/** The fields only an empty list carries, declared so a schema-aware client does not strip them. */
|
|
6
8
|
export declare const EMPTY_SESSION_LIST_OUTPUT: {
|
|
7
9
|
why: z.ZodOptional<z.ZodString>;
|
|
@@ -32,6 +32,8 @@ const LEASABLE_ACTIONS = new Set([
|
|
|
32
32
|
NoSessionAction.OPEN_APP,
|
|
33
33
|
]);
|
|
34
34
|
const LOCALHOST = 'http://localhost';
|
|
35
|
+
/** The field that carries the next step for an unwired project. */
|
|
36
|
+
export const NEXT_STEP_FIELD = 'next_step';
|
|
35
37
|
/** The fields only an empty list carries, declared so a schema-aware client does not strip them. */
|
|
36
38
|
export const EMPTY_SESSION_LIST_OUTPUT = {
|
|
37
39
|
why: z
|
|
@@ -51,7 +53,7 @@ export const EMPTY_SESSION_LIST_OUTPUT = {
|
|
|
51
53
|
.string()
|
|
52
54
|
.optional()
|
|
53
55
|
.describe('Present ONLY when `sessions` is empty: a call with the founder for the person you are working with. Offer it; never act on it.'),
|
|
54
|
-
|
|
56
|
+
[NEXT_STEP_FIELD]: z
|
|
55
57
|
.object({
|
|
56
58
|
offered: z.nativeEnum(NextStepOffered),
|
|
57
59
|
lease: z.string().optional(),
|
|
@@ -141,7 +143,7 @@ export function emptySessionList(deps, sessions) {
|
|
|
141
143
|
sessions,
|
|
142
144
|
...(why === undefined ? {} : { why }),
|
|
143
145
|
...(next === undefined ? {} : { next_action: next }),
|
|
144
|
-
...(nextStep === undefined ? {} : {
|
|
146
|
+
...(nextStep === undefined ? {} : { [NEXT_STEP_FIELD]: nextStep }),
|
|
145
147
|
...(undefined === lastKnown ? {} : { lastKnown }),
|
|
146
148
|
// Setup that never connects is where most people give up; it is the talk worth having.
|
|
147
149
|
talk_to_us: DiscoveryInvite.AGENT,
|