cbrowser 20.1.0 → 20.1.2

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.
Files changed (34) hide show
  1. package/dist/analysis/persona-comparison.d.ts.map +1 -1
  2. package/dist/analysis/persona-comparison.js +3 -2
  3. package/dist/analysis/persona-comparison.js.map +1 -1
  4. package/dist/cli-args.d.ts +2 -0
  5. package/dist/cli-args.d.ts.map +1 -1
  6. package/dist/cli-args.js +39 -4
  7. package/dist/cli-args.js.map +1 -1
  8. package/dist/cli.js +23 -11
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cognitive/index.d.ts +42 -0
  11. package/dist/cognitive/index.d.ts.map +1 -1
  12. package/dist/cognitive/index.js +122 -25
  13. package/dist/cognitive/index.js.map +1 -1
  14. package/dist/cognitive/journey-dialog.d.ts +7 -0
  15. package/dist/cognitive/journey-dialog.d.ts.map +1 -1
  16. package/dist/cognitive/journey-dialog.js +14 -1
  17. package/dist/cognitive/journey-dialog.js.map +1 -1
  18. package/dist/cognitive/journey-trace.d.ts +2 -0
  19. package/dist/cognitive/journey-trace.d.ts.map +1 -1
  20. package/dist/cognitive/journey-trace.js.map +1 -1
  21. package/dist/mcp-tools/base/browser-state-tools.d.ts.map +1 -1
  22. package/dist/mcp-tools/base/browser-state-tools.js +10 -1
  23. package/dist/mcp-tools/base/browser-state-tools.js.map +1 -1
  24. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  25. package/dist/mcp-tools/base/cognitive-tools.js +28 -2
  26. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  27. package/dist/mcp-tools/journey-sessions.d.ts +28 -0
  28. package/dist/mcp-tools/journey-sessions.d.ts.map +1 -0
  29. package/dist/mcp-tools/journey-sessions.js +38 -0
  30. package/dist/mcp-tools/journey-sessions.js.map +1 -0
  31. package/dist/types.d.ts +2 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/package.json +1 -1
@@ -3,6 +3,25 @@
3
3
  * Copyright 2026 Alexandria Eden alexandria.shai.eden@gmail.com
4
4
  * Learn more at https://cbrowser.ai - MIT License
5
5
  */
6
+ /**
7
+ * Cognitive Journey API Module
8
+ *
9
+ * Provides cognitive user simulation with two modes:
10
+ *
11
+ * ## Mode 1: Claude Code Session (Recommended when inside Claude Code)
12
+ * When running via MCP inside Claude Code, use the step-by-step tools:
13
+ * - `cognitive_journey_init` - Initialize journey with persona
14
+ * - `cognitive_journey_update_state` - Track cognitive state after each action
15
+ * No API key needed - Claude Code itself is the reasoning engine.
16
+ *
17
+ * ## Mode 2: Standalone (API Key Required)
18
+ * When running standalone (CLI, CI/CD, scripts), uses Anthropic API directly:
19
+ * - `npx cbrowser cognitive-journey --persona X --start URL --goal "..."
20
+ * Requires: `npx cbrowser config set-api-key YOUR_KEY`
21
+ *
22
+ * @module cognitive
23
+ */
24
+ import Anthropic from "@anthropic-ai/sdk";
6
25
  import { type AutoCaptureSetting } from "../recording/auto-capture.js";
7
26
  import type { CognitiveState, CognitiveJourneyResult, CognitiveTraits } from "../types.js";
8
27
  import type { DecisionProvider } from "./journey-trace.js";
@@ -39,6 +58,8 @@ export declare function isCognitiveAvailable(): boolean;
39
58
  export interface CognitiveJourneyOptions {
40
59
  /** Persona name or description */
41
60
  persona: string;
61
+ /** A label for this run (the CLI's first word). Names its trace and capture files and is returned on the result. */
62
+ name?: string;
42
63
  /** Goal statement for the journey */
43
64
  goal: string;
44
65
  /** Starting URL */
@@ -177,6 +198,8 @@ export interface CognitiveStep {
177
198
  * not anything the model said.
178
199
  */
179
200
  replyReadable: boolean;
201
+ /** Why the reply could not be read, when replyReadable is false. */
202
+ replyProblem?: string;
180
203
  state: CognitiveState;
181
204
  /** Step-aligned screenshot. Captured only on initial page load + scroll>100. */
182
205
  screenshotPath?: string;
@@ -193,6 +216,25 @@ export interface CognitiveStep {
193
216
  url?: string;
194
217
  }
195
218
  export declare function runCognitiveJourney(options: CognitiveJourneyOptions): Promise<CognitiveJourneyResult>;
219
+ /**
220
+ * The live per-step decision: one messages.create call, its text blocks read
221
+ * together, and one more ask for the JSON alone when the reply does not hold
222
+ * a JSON object. describeLastReply says why the last reply was unreadable.
223
+ * Exported for tests, which pass a fake client.
224
+ */
225
+ export declare function createLiveDecisionProvider(anthropic: Pick<Anthropic, "messages"> | null, model: string): DecisionProvider;
226
+ /** A journey or persona name as a file-name part: letters, digits, - and _ only. */
227
+ export declare function journeySlug(name: string): string;
228
+ /**
229
+ * The JSON object in a model reply, or why there is none. Tried in order: the
230
+ * reply with any code fences removed, each balanced {...} in it (string-aware,
231
+ * so a reply with prose after its JSON still reads), then the old greedy
232
+ * first-"{"-to-last-"}" match; the first that parses to an object wins.
233
+ */
234
+ export declare function readReplyJson(reply: string): {
235
+ value?: Record<string, unknown>;
236
+ problem?: string;
237
+ };
196
238
  export { createInitialEmotionalState, createEmotionalConfig, applyEmotionalTrigger, decayEmotions, calculateAbandonmentModifier, calculateExplorationTendency, calculateDecisionSpeedModifier, describeEmotionalState, shouldConsiderAbandonment, } from "./emotions.js";
197
239
  export { GoalDecomposer } from "./goal-decomposer.js";
198
240
  export type { ParsedGoal, GoalPlan, SubGoal, Strategy, PlannedAction, GoalResult, ExecutionOptions, } from "./goal-types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cognitive/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cognitive/index.ts"],"names":[],"mappings":"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"}
@@ -693,25 +693,16 @@ export async function runCognitiveJourney(options) {
693
693
  // Decision-provider seam (v18.68.0): the per-step LLM call sits behind a
694
694
  // DecisionProvider so a recorded trace (or an injected fake) can stand in
695
695
  // for the live API. The default path is the same anthropic.messages.create
696
- // call as before — params and text extraction unchanged. The post-journey
696
+ // call as before, reading every text block (20.1.2). The post-journey
697
697
  // goal evaluation goes through the same provider (FINAL_EVAL_STEP), so a
698
698
  // replay reproduces the recorded verdict with no model call.
699
- const liveLLMProvider = {
700
- decide: async (ctx) => {
701
- if (!anthropic)
702
- throw new Error("Anthropic API key not configured.");
703
- const response = await anthropic.messages.create({
704
- model,
705
- max_tokens: 2000,
706
- ...(ctx.systemPrompt ? { system: ctx.systemPrompt } : {}),
707
- messages: ctx.messages.map((m) => ({ role: m.role, content: m.content })),
708
- });
709
- return response.content[0].type === "text" ? response.content[0].text : "";
710
- },
711
- };
699
+ const liveLLMProvider = createLiveDecisionProvider(anthropic, model);
712
700
  const decisionProvider = options.decisionProvider ??
713
701
  (options.replayTrace ? new TraceReplayProvider(options.replayTrace) : liveLLMProvider);
714
- const traceWriter = options.trace ? new JourneyTraceWriter() : null;
702
+ const nameSlug = options.name ? journeySlug(options.name) : undefined;
703
+ const traceWriter = options.trace
704
+ ? new JourneyTraceWriter(nameSlug ? { journeyId: `journey-${nameSlug}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` } : undefined)
705
+ : null;
715
706
  // Get or create persona
716
707
  const existingPersona = getPersona(options.persona);
717
708
  let personaObj;
@@ -990,7 +981,7 @@ export async function runCognitiveJourney(options) {
990
981
  // Auto-capture (v18.70.0): recording starts once the journey's start URL is
991
982
  // loaded, so frame 0 is the page the persona actually opens on, and stops
992
983
  // just before the browser closes. Capture never fails the journey.
993
- const captureSession = await startAutoCapture(await browser.getPage(), options.capture, `journey-${options.persona.replace(/[^a-zA-Z0-9-_]+/g, "-").toLowerCase()}-${Date.now()}`);
984
+ const captureSession = await startAutoCapture(await browser.getPage(), options.capture, `journey-${nameSlug ?? journeySlug(options.persona)}-${Date.now()}`);
994
985
  // Apply geolocation at runtime as well (ensures permission is granted after context exists)
995
986
  if (effectiveLocation.geolocation) {
996
987
  await browser.setGeolocationRuntime(effectiveLocation.geolocation);
@@ -1326,6 +1317,9 @@ Right now it's ${temporalState.description}.`;
1326
1317
  // action); say so rather than let it pass as an "evaluate" step.
1327
1318
  const parsed = parseCognitiveResponse(assistantMessage);
1328
1319
  const replyReadable = Object.keys(parsed).length > 0;
1320
+ const replyProblem = replyReadable
1321
+ ? undefined
1322
+ : decisionProvider.describeLastReply?.() ?? readReplyJson(assistantMessage).problem;
1329
1323
  // Capture URL BEFORE we apply any action. We use the previous URL +
1330
1324
  // the post-action URL to gate goal progress on real evidence — Claude's
1331
1325
  // self-reported progress is taken as a *signal*, but only allowed to
@@ -1549,6 +1543,7 @@ Right now it's ${temporalState.description}.`;
1549
1543
  pageTitle: pageTitle,
1550
1544
  phase: parsed.phase || "evaluate",
1551
1545
  replyReadable,
1546
+ ...(replyProblem ? { replyProblem } : {}),
1552
1547
  action: parsed.action || null,
1553
1548
  actionTarget: parsed.actionTarget || null,
1554
1549
  focusedElement: parsed.focusedElement || null,
@@ -1586,6 +1581,7 @@ Right now it's ${temporalState.description}.`;
1586
1581
  action: parsed.action,
1587
1582
  actionTarget: parsed.actionTarget || undefined,
1588
1583
  replyReadable,
1584
+ ...(replyProblem ? { replyProblem } : {}),
1589
1585
  state: { ...state },
1590
1586
  screenshotPath,
1591
1587
  attentionHeatmapBase64,
@@ -1926,7 +1922,7 @@ Answer with JSON:
1926
1922
  });
1927
1923
  }
1928
1924
  try {
1929
- const evalJson = JSON.parse(evalText.replace(/```json?\n?/g, '').replace(/```/g, '').trim());
1925
+ const evalJson = (readReplyJson(evalText).value ?? {});
1930
1926
  if (evalJson.achieved) {
1931
1927
  goalAchieved = true;
1932
1928
  goalEvidence = `[FINAL EVAL] ${evalJson.evidence || finalPageText.substring(0, 200)}`;
@@ -1960,6 +1956,7 @@ Answer with JSON:
1960
1956
  : state.confusionLevel;
1961
1957
  // Build result
1962
1958
  return {
1959
+ ...(options.name ? { name: options.name } : {}),
1963
1960
  persona: personaObj.name,
1964
1961
  goal: options.goal,
1965
1962
  goalAchieved,
@@ -2230,18 +2227,118 @@ STRATEGY:
2230
2227
 
2231
2228
  Respond in JSON format.`;
2232
2229
  }
2230
+ /**
2231
+ * The live per-step decision: one messages.create call, its text blocks read
2232
+ * together, and one more ask for the JSON alone when the reply does not hold
2233
+ * a JSON object. describeLastReply says why the last reply was unreadable.
2234
+ * Exported for tests, which pass a fake client.
2235
+ */
2236
+ export function createLiveDecisionProvider(anthropic, model) {
2237
+ // Why the last live reply could not be read; the step reports it as
2238
+ // replyProblem. Undefined after a readable reply.
2239
+ let lastLiveProblem;
2240
+ return {
2241
+ decide: async (ctx) => {
2242
+ if (!anthropic)
2243
+ throw new Error("Anthropic API key not configured.");
2244
+ const ask = (messages, maxTokens = 2000) => anthropic.messages.create({
2245
+ model,
2246
+ max_tokens: maxTokens,
2247
+ ...(ctx.systemPrompt ? { system: ctx.systemPrompt } : {}),
2248
+ messages: messages.map((m) => ({ role: m.role, content: m.content })),
2249
+ });
2250
+ const textOf = (r) => r.content.map((b) => (b.type === "text" ? b.text : "")).join("");
2251
+ const whyUnreadable = (r, text) => text.trim()
2252
+ ? readReplyJson(text).problem
2253
+ : `the model returned no text (stop_reason ${r.stop_reason ?? "none"}; content blocks: ${r.content.map((b) => b.type).join(", ") || "none"})`;
2254
+ lastLiveProblem = undefined;
2255
+ const first = await ask(ctx.messages);
2256
+ const firstText = textOf(first);
2257
+ if (readReplyJson(firstText).value)
2258
+ return firstText;
2259
+ // Asked once more, for the JSON alone. Inside the provider, so a trace
2260
+ // records one decision per step and still replays.
2261
+ // Thinking counts against max_tokens, so a reply cut off there may hold
2262
+ // no text at all; the second ask gets room for both.
2263
+ const firstProblem = whyUnreadable(first, firstText);
2264
+ const retry = await ask([
2265
+ ...ctx.messages,
2266
+ { role: "assistant", content: firstText.trim() || "(no text)" },
2267
+ { role: "user", content: "Your reply could not be read as the JSON object the response format asks for. Reply again with only that JSON object, nothing before or after it." },
2268
+ ], first.stop_reason === "max_tokens" ? 8000 : 2000);
2269
+ const retryText = textOf(retry);
2270
+ if (readReplyJson(retryText).value)
2271
+ return retryText;
2272
+ lastLiveProblem = `${firstProblem}; asked once more, and ${whyUnreadable(retry, retryText)}`;
2273
+ return retryText;
2274
+ },
2275
+ describeLastReply: () => lastLiveProblem,
2276
+ };
2277
+ }
2278
+ /** A journey or persona name as a file-name part: letters, digits, - and _ only. */
2279
+ export function journeySlug(name) {
2280
+ return name.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "journey";
2281
+ }
2233
2282
  function parseCognitiveResponse(response) {
2234
- try {
2235
- // Extract JSON from response (may have markdown code blocks)
2236
- const jsonMatch = response.match(/\{[\s\S]*\}/);
2237
- if (jsonMatch) {
2238
- return JSON.parse(jsonMatch[0]);
2283
+ return (readReplyJson(response).value ?? {});
2284
+ }
2285
+ /**
2286
+ * The JSON object in a model reply, or why there is none. Tried in order: the
2287
+ * reply with any code fences removed, each balanced {...} in it (string-aware,
2288
+ * so a reply with prose after its JSON still reads), then the old greedy
2289
+ * first-"{"-to-last-"}" match; the first that parses to an object wins.
2290
+ */
2291
+ export function readReplyJson(reply) {
2292
+ const text = reply.trim();
2293
+ if (!text)
2294
+ return { problem: "the model returned no text" };
2295
+ const unfenced = text.replace(/```(?:json)?/gi, "").trim();
2296
+ const candidates = [unfenced];
2297
+ for (let i = 0; i < unfenced.length; i++) {
2298
+ if (unfenced[i] !== "{")
2299
+ continue;
2300
+ let depth = 0;
2301
+ let inString = false;
2302
+ let escaped = false;
2303
+ for (let j = i; j < unfenced.length; j++) {
2304
+ const c = unfenced[j];
2305
+ if (inString) {
2306
+ if (escaped)
2307
+ escaped = false;
2308
+ else if (c === "\\")
2309
+ escaped = true;
2310
+ else if (c === '"')
2311
+ inString = false;
2312
+ continue;
2313
+ }
2314
+ if (c === '"')
2315
+ inString = true;
2316
+ else if (c === "{")
2317
+ depth++;
2318
+ else if (c === "}" && --depth === 0) {
2319
+ candidates.push(unfenced.slice(i, j + 1));
2320
+ i = j;
2321
+ break;
2322
+ }
2239
2323
  }
2240
2324
  }
2241
- catch {
2242
- // Parsing failed
2325
+ const greedy = unfenced.match(/\{[\s\S]*\}/);
2326
+ if (greedy)
2327
+ candidates.push(greedy[0]);
2328
+ let firstError = "";
2329
+ for (const candidate of candidates) {
2330
+ try {
2331
+ const value = JSON.parse(candidate);
2332
+ if (value && typeof value === "object" && !Array.isArray(value))
2333
+ return { value };
2334
+ }
2335
+ catch (e) {
2336
+ if (!firstError)
2337
+ firstError = e.message;
2338
+ }
2243
2339
  }
2244
- return {};
2340
+ const start = text.slice(0, 80).replace(/\s+/g, " ");
2341
+ return { problem: `the reply was not a JSON object (${firstError || "no {...} in it"}); it began: ${start}${text.length > 80 ? "..." : ""}` };
2245
2342
  }
2246
2343
  function checkAbandonmentTriggers(state, thresholds) {
2247
2344
  if (state.patienceRemaining < thresholds.patienceMin) {