@foldspace_npm/harness 0.1.4 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/bin/attach.mjs +17 -15
- package/package.json +1 -1
- package/src/attach-helpers.mjs +15 -0
- package/src/cli-registry.mjs +4 -1
- package/templates/agent-starter/CLAUDE.md +13 -5
- package/templates/agent-starter/README.md +2 -1
package/README.md
CHANGED
|
@@ -142,9 +142,10 @@ Use `foldspace help attach` for mode requirements, effects, and safety options.
|
|
|
142
142
|
### Verify actions through the agent
|
|
143
143
|
|
|
144
144
|
`foldspace attach` does not invoke action handlers directly. It loads the local
|
|
145
|
-
artifact, verifies that
|
|
146
|
-
and then observes action callbacks while
|
|
147
|
-
experience.
|
|
145
|
+
artifact, verifies that the captured local registry matches the configured SDK
|
|
146
|
+
agent — including an empty registry — and then observes action callbacks while
|
|
147
|
+
you exercise the normal agent experience. Named actions are not required to
|
|
148
|
+
attach.
|
|
148
149
|
|
|
149
150
|
Action output identifies SDK callbacks and local `execute`/`render` phases:
|
|
150
151
|
|
package/bin/attach.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
hostMatches,
|
|
31
31
|
hostPatternsFromTarget,
|
|
32
32
|
parseAgentId,
|
|
33
|
+
registrationVerified,
|
|
33
34
|
} from "../src/attach-helpers.mjs";
|
|
34
35
|
import {
|
|
35
36
|
buildReplacePrelude,
|
|
@@ -294,8 +295,10 @@ const sessionCollector = createSessionCollector();
|
|
|
294
295
|
// sessionId -> preparation state used for deterministic detach cleanup.
|
|
295
296
|
const prepared = new Map();
|
|
296
297
|
const preparing = new Set();
|
|
297
|
-
// Rejections are scoped to the
|
|
298
|
-
|
|
298
|
+
// Rejections are scoped to the CDP session. SPA URL changes must not retry
|
|
299
|
+
// prepare() on the same tab. Off-host navigation still clears rejected in
|
|
300
|
+
// targetInfoChanged so a later return to the app can retry.
|
|
301
|
+
const rejected = new Set();
|
|
299
302
|
// targetId -> sessionId, so a target that navigates INTO a matching host can be
|
|
300
303
|
// prepared later. Without this, opening Chrome on a new tab and then browsing
|
|
301
304
|
// to the app never arms the swap.
|
|
@@ -451,13 +454,7 @@ async function verifyPreparedPage(ws, sessionId) {
|
|
|
451
454
|
},
|
|
452
455
|
);
|
|
453
456
|
latest.registration = registration;
|
|
454
|
-
if (
|
|
455
|
-
observation?.captureCount > 0 &&
|
|
456
|
-
expectedActionNames.length > 0 &&
|
|
457
|
-
registration?.agentFound &&
|
|
458
|
-
registration.missingActionNames?.length === 0 &&
|
|
459
|
-
registration.unexpectedActionNames?.length === 0
|
|
460
|
-
) {
|
|
457
|
+
if (registrationVerified(observation, registration)) {
|
|
461
458
|
return {
|
|
462
459
|
ok: true,
|
|
463
460
|
state,
|
|
@@ -493,9 +490,6 @@ async function verifyPreparedPage(ws, sessionId) {
|
|
|
493
490
|
}
|
|
494
491
|
|
|
495
492
|
async function prepare(ws, sessionId, url) {
|
|
496
|
-
if (rejected.has(sessionId) && rejected.get(sessionId) !== url) {
|
|
497
|
-
rejected.delete(sessionId);
|
|
498
|
-
}
|
|
499
493
|
if (
|
|
500
494
|
prepared.has(sessionId) ||
|
|
501
495
|
preparing.has(sessionId) ||
|
|
@@ -527,7 +521,7 @@ async function prepare(ws, sessionId, url) {
|
|
|
527
521
|
pageProductIds,
|
|
528
522
|
});
|
|
529
523
|
if (!guard.ok) {
|
|
530
|
-
rejected.
|
|
524
|
+
rejected.add(sessionId);
|
|
531
525
|
logLifecycle(
|
|
532
526
|
createLifecycleResult({
|
|
533
527
|
operation: "prepare_page",
|
|
@@ -697,6 +691,11 @@ async function prepare(ws, sessionId, url) {
|
|
|
697
691
|
verification.registration?.actionNames || [],
|
|
698
692
|
expectedActionNames:
|
|
699
693
|
verification.observation?.expectedActionNames || [],
|
|
694
|
+
expectedActionCount: Array.isArray(
|
|
695
|
+
verification.observation?.expectedActionNames,
|
|
696
|
+
)
|
|
697
|
+
? verification.observation.expectedActionNames.length
|
|
698
|
+
: 0,
|
|
700
699
|
missingActionNames:
|
|
701
700
|
verification.registration?.missingActionNames || [],
|
|
702
701
|
unexpectedActionNames:
|
|
@@ -715,7 +714,7 @@ async function prepare(ws, sessionId, url) {
|
|
|
715
714
|
await cleanupPreparedSession(ws, sessionId, state);
|
|
716
715
|
prepared.delete(sessionId);
|
|
717
716
|
}
|
|
718
|
-
rejected.
|
|
717
|
+
rejected.add(sessionId);
|
|
719
718
|
}
|
|
720
719
|
}
|
|
721
720
|
} finally {
|
|
@@ -920,7 +919,10 @@ console.log(`Mode: ${attachMode}`);
|
|
|
920
919
|
console.log(`Agent: ${agentApiName} (product ${productId}, ${agentMode})`);
|
|
921
920
|
console.log(`Test: ${noTestMode ? "OFF — conversations WILL appear in the dashboard" : "on"}`);
|
|
922
921
|
console.log(`Hosts: ${hostPatterns.join(", ")}`);
|
|
923
|
-
console.log(`Serving: ${path.relative(root, bundlePath)}
|
|
922
|
+
console.log(`Serving: ${path.relative(root, bundlePath)}`);
|
|
923
|
+
console.log(
|
|
924
|
+
`CDP: this process owns the debug port. Detach before using chrome-devtools MCP against the same Chrome.\n`,
|
|
925
|
+
);
|
|
924
926
|
|
|
925
927
|
function printSessionSummary() {
|
|
926
928
|
if (summaryPrinted) return;
|
package/package.json
CHANGED
package/src/attach-helpers.mjs
CHANGED
|
@@ -140,6 +140,21 @@ export function actionRequestMatchesTarget(request, target) {
|
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
export function registrationVerified(observation, registration) {
|
|
144
|
+
return Boolean(
|
|
145
|
+
observation?.installed === true &&
|
|
146
|
+
observation?.ownerMatches === true &&
|
|
147
|
+
observation?.actionNameLimitExceeded !== true &&
|
|
148
|
+
observation?.captureCount > 0 &&
|
|
149
|
+
Array.isArray(observation.expectedActionNames) &&
|
|
150
|
+
registration?.agentFound === true &&
|
|
151
|
+
Array.isArray(registration.missingActionNames) &&
|
|
152
|
+
registration.missingActionNames.length === 0 &&
|
|
153
|
+
Array.isArray(registration.unexpectedActionNames) &&
|
|
154
|
+
registration.unexpectedActionNames.length === 0,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
143
158
|
export function summarizeConsoleMessages(messages, limit = 50) {
|
|
144
159
|
const summaries = new Map();
|
|
145
160
|
for (const message of messages) {
|
package/src/cli-registry.mjs
CHANGED
|
@@ -189,12 +189,14 @@ export const CLI_COMMANDS = Object.freeze([
|
|
|
189
189
|
"May reload and instrument matching target pages",
|
|
190
190
|
"Test mode is enabled unless --no-test-mode is passed",
|
|
191
191
|
"Never directly invokes an action handler",
|
|
192
|
+
"An empty local action registry is valid; named actions are not required",
|
|
192
193
|
"Restores prepared pages when detached cleanly",
|
|
193
194
|
],
|
|
194
195
|
next: [
|
|
195
196
|
"Exercise the visible agent normally",
|
|
196
197
|
"Confirm [lifecycle] inspect_registration:registration_ok",
|
|
197
|
-
"
|
|
198
|
+
"On mismatch, read foldspace help attach --json diagnostics and the lifecycle details",
|
|
199
|
+
"Confirm execute/render observations when verifying a named action",
|
|
198
200
|
"Detach with Ctrl-C",
|
|
199
201
|
],
|
|
200
202
|
}),
|
|
@@ -305,6 +307,7 @@ export function createCliRegistry({ packageName, packageVersion }) {
|
|
|
305
307
|
"Capability metadata describes current behavior; it is not hosted-environment enforcement.",
|
|
306
308
|
"Action verification is passive and agent-driven.",
|
|
307
309
|
"The harness bin alias is equivalent to foldspace.",
|
|
310
|
+
"attach diagnostics are attach-internal; interpret them from the lifecycle log, not as CLI commands.",
|
|
308
311
|
],
|
|
309
312
|
};
|
|
310
313
|
}
|
|
@@ -55,7 +55,9 @@ npm run attach
|
|
|
55
55
|
|
|
56
56
|
`inject` launches an isolated Chrome profile and records its debug port.
|
|
57
57
|
It does not generate or load an application extension. `attach` prepares the
|
|
58
|
-
page and loads the local `dist/index.js` bundle through CDP.
|
|
58
|
+
page and loads the local `dist/index.js` bundle through CDP. An empty local
|
|
59
|
+
registry is valid: attach before implementing handlers to see how the agent
|
|
60
|
+
works. `npm run build` is still required so `dist/index.js` exists.
|
|
59
61
|
|
|
60
62
|
Use the default swap only when the page already has the configured product and
|
|
61
63
|
agent. Use `--bootstrap` only when the page has no Foldspace SDK, and
|
|
@@ -63,9 +65,14 @@ agent. Use `--bootstrap` only when the page has no Foldspace SDK, and
|
|
|
63
65
|
present without the configured agent.
|
|
64
66
|
|
|
65
67
|
The attach log must report `inspect_registration:registration_ok` before
|
|
66
|
-
treating the page as registered.
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
treating the page as registered. Zero captured actions is success when the
|
|
69
|
+
local registry is empty. On `registration_mismatch`, read
|
|
70
|
+
`npx foldspace help attach --json` diagnostics and map those names onto the
|
|
71
|
+
lifecycle details (`missingActionNames`, `unexpectedActionNames`,
|
|
72
|
+
`diagnosticError`).
|
|
73
|
+
|
|
74
|
+
Prove a named action through the visible agent and require both an SDK action
|
|
75
|
+
callback and local execute/render evidence; do not invoke the handler directly.
|
|
69
76
|
|
|
70
77
|
## Verification gates
|
|
71
78
|
|
|
@@ -73,7 +80,8 @@ Do not report success without all six:
|
|
|
73
80
|
|
|
74
81
|
1. TypeScript compiles with `npx tsc --noEmit -p tsconfig.json`.
|
|
75
82
|
2. The expected handler appears in `dist/index.js`.
|
|
76
|
-
3. The browser reports
|
|
83
|
+
3. The browser reports `inspect_registration:registration_ok`. For a named
|
|
84
|
+
action, the captured registry includes that handler.
|
|
77
85
|
4. The action behaves correctly against the real target workflow.
|
|
78
86
|
5. Existing neighbouring action fixtures still pass when fixtures exist.
|
|
79
87
|
6. Browser evidence came from the live target, not from hand-authored examples.
|
|
@@ -54,7 +54,8 @@ npm run attach # load local actions and observe the agent over CDP
|
|
|
54
54
|
|
|
55
55
|
Run `inject` before `attach`. Sign in to the product in the Chrome window that
|
|
56
56
|
`inject` opens. `inject` does not generate an application extension; `attach`
|
|
57
|
-
loads `dist/index.js` directly through CDP.
|
|
57
|
+
loads `dist/index.js` directly through CDP. An empty local registry is valid,
|
|
58
|
+
so you can attach before implementing handlers.
|
|
58
59
|
|
|
59
60
|
Choose the attach mode from the state of the target page:
|
|
60
61
|
|