@mindstudio-ai/remy 0.1.338 → 0.1.340

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/dist/headless.js CHANGED
@@ -3651,6 +3651,16 @@ function collectRecordings(messages) {
3651
3651
  }
3652
3652
  return recordings;
3653
3653
  }
3654
+ function replayWindow(recordings) {
3655
+ if (recordings.length === 0) {
3656
+ return null;
3657
+ }
3658
+ return {
3659
+ sessionId: recordings[recordings.length - 1].sessionId,
3660
+ startTs: Math.min(...recordings.map((r) => r.startTs)),
3661
+ endTs: Math.max(...recordings.map((r) => r.endTs))
3662
+ };
3663
+ }
3654
3664
  function liftRecording(result) {
3655
3665
  if (!result.includes('"recording"')) {
3656
3666
  return { result };
@@ -3675,6 +3685,8 @@ function liftRecording(result) {
3675
3685
  }
3676
3686
  const recording = {
3677
3687
  path: ref.path,
3688
+ ...typeof ref.store === "string" ? { store: ref.store } : {},
3689
+ ...typeof ref.key === "string" ? { key: ref.key } : {},
3678
3690
  sessionId: ref.sessionId,
3679
3691
  runId: ref.runId,
3680
3692
  seq: ref.seq,
@@ -3687,7 +3699,16 @@ function liftRecording(result) {
3687
3699
  const rest = { ...obj };
3688
3700
  delete rest.recording;
3689
3701
  return {
3690
- result: JSON.stringify({ ...rest, recorded: true }),
3702
+ result: JSON.stringify({
3703
+ ...rest,
3704
+ recorded: true,
3705
+ replay: {
3706
+ sessionId: recording.sessionId,
3707
+ runId: recording.runId,
3708
+ ...recording.store ? { store: recording.store } : {},
3709
+ ...recording.key ? { key: recording.key } : {}
3710
+ }
3711
+ }),
3691
3712
  recording
3692
3713
  };
3693
3714
  }
@@ -4861,10 +4882,12 @@ async function runBrowserAutomation(task, context, opts) {
4861
4882
  dropToolResultMessages(result.messages)
4862
4883
  );
4863
4884
  const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
4864
- const recorded = collectRecordings(result.messages).length > 0;
4885
+ const recordings = collectRecordings(result.messages);
4886
+ const replay = replayWindow(recordings);
4865
4887
  return {
4866
4888
  text: result.text,
4867
- recorded,
4889
+ recorded: recordings.length > 0,
4890
+ ...replay ? { replay } : {},
4868
4891
  ...preferred?.url ? { screenshot: preferred } : {}
4869
4892
  };
4870
4893
  } finally {
@@ -4901,6 +4924,12 @@ var browserAutomationTool = {
4901
4924
  text += `
4902
4925
 
4903
4926
  Replay of this run: ![Browser test replay](replay:${context.toolCallId})`;
4927
+ }
4928
+ if (result.replay) {
4929
+ const { sessionId, startTs, endTs } = result.replay;
4930
+ text += `
4931
+
4932
+ To render this replay as an mp4 (returns a public URL): \`remy-admin recordings export --session ${sessionId} --from ${startTs} --to ${endTs}\``;
4904
4933
  }
4905
4934
  return text;
4906
4935
  }
package/dist/index.js CHANGED
@@ -2528,6 +2528,16 @@ function collectRecordings(messages) {
2528
2528
  }
2529
2529
  return recordings;
2530
2530
  }
2531
+ function replayWindow(recordings) {
2532
+ if (recordings.length === 0) {
2533
+ return null;
2534
+ }
2535
+ return {
2536
+ sessionId: recordings[recordings.length - 1].sessionId,
2537
+ startTs: Math.min(...recordings.map((r) => r.startTs)),
2538
+ endTs: Math.max(...recordings.map((r) => r.endTs))
2539
+ };
2540
+ }
2531
2541
  function liftRecording(result) {
2532
2542
  if (!result.includes('"recording"')) {
2533
2543
  return { result };
@@ -2552,6 +2562,8 @@ function liftRecording(result) {
2552
2562
  }
2553
2563
  const recording = {
2554
2564
  path: ref.path,
2565
+ ...typeof ref.store === "string" ? { store: ref.store } : {},
2566
+ ...typeof ref.key === "string" ? { key: ref.key } : {},
2555
2567
  sessionId: ref.sessionId,
2556
2568
  runId: ref.runId,
2557
2569
  seq: ref.seq,
@@ -2564,7 +2576,16 @@ function liftRecording(result) {
2564
2576
  const rest = { ...obj };
2565
2577
  delete rest.recording;
2566
2578
  return {
2567
- result: JSON.stringify({ ...rest, recorded: true }),
2579
+ result: JSON.stringify({
2580
+ ...rest,
2581
+ recorded: true,
2582
+ replay: {
2583
+ sessionId: recording.sessionId,
2584
+ runId: recording.runId,
2585
+ ...recording.store ? { store: recording.store } : {},
2586
+ ...recording.key ? { key: recording.key } : {}
2587
+ }
2588
+ }),
2568
2589
  recording
2569
2590
  };
2570
2591
  }
@@ -5920,10 +5941,12 @@ async function runBrowserAutomation(task, context, opts) {
5920
5941
  dropToolResultMessages(result.messages)
5921
5942
  );
5922
5943
  const preferred = opts?.capture === "viewport" ? lastCapture.viewport ?? lastCapture.fullPage : lastCapture.fullPage ?? lastCapture.viewport;
5923
- const recorded = collectRecordings(result.messages).length > 0;
5944
+ const recordings = collectRecordings(result.messages);
5945
+ const replay = replayWindow(recordings);
5924
5946
  return {
5925
5947
  text: result.text,
5926
- recorded,
5948
+ recorded: recordings.length > 0,
5949
+ ...replay ? { replay } : {},
5927
5950
  ...preferred?.url ? { screenshot: preferred } : {}
5928
5951
  };
5929
5952
  } finally {
@@ -5980,6 +6003,12 @@ var init_browserAutomation = __esm({
5980
6003
  text += `
5981
6004
 
5982
6005
  Replay of this run: ![Browser test replay](replay:${context.toolCallId})`;
6006
+ }
6007
+ if (result.replay) {
6008
+ const { sessionId, startTs, endTs } = result.replay;
6009
+ text += `
6010
+
6011
+ To render this replay as an mp4 (returns a public URL): \`remy-admin recordings export --session ${sessionId} --from ${startTs} --to ${endTs}\``;
5983
6012
  }
5984
6013
  return text;
5985
6014
  }
@@ -43,12 +43,23 @@ All fields are nested under the `"web"` key.
43
43
  | `devPort` | `number` | `5173` | Port for the dev server |
44
44
  | `devCommand` | `string` | `"npm run dev"` | Command to start the dev server |
45
45
  | `defaultPreviewMode` | `"desktop"` \| `"mobile"` | `"desktop"` | Default preview viewport in the editor. Set to `"mobile"` for mobile-first apps. |
46
+ | `previewLinks` | `array` | — | A few places worth opening directly, as chips under the editor's preview address bar. See "Preview links" below. |
46
47
  | `prerender` | `object` | — | Opt into prerendering the listed routes/patterns for crawlers/unfurlers. See "Prerendering" below. |
47
48
  | `mounts` | `array` | — | Serve other same-workspace apps under path prefixes of this app's hosts. See "Mounting other apps" below. |
48
49
  | `redirects` | `array` | — | Path-level redirects. See "Redirects" below. |
49
50
  | `rewrites` | `array` | — | Serve different content at the same URL. See "Rewrites" below. |
50
51
  | `trailingSlash` | `"strip"` \| `"append"` | — | Enforce a canonical trailing-slash form with a 308. Off by default. |
51
52
 
53
+ ### Preview links
54
+
55
+ `{ "web": { "previewLinks": [{ "label": "Admin console", "path": "/console" }] } }` — each entry a short human `label` and a `/`-prefixed `path` on this app. The editor shows them as chips under the preview's address bar so the user can reach a place in one click. Order is preserved; a chip for an agent interface's `webInterfacePath` is appended automatically unless an entry already points there.
56
+
57
+ Curate these with the same judgement as scenarios — a few places worth visiting, not a chip per route. Four good ones beat fifteen. Highest value first: surfaces nothing links to (an admin console behind its own sign-in), pages needing a token or id in the URL (point them at a record the scenarios seed, using its known id/slug, so the link lands on real content), and role-gated routes where hitting the sign-in screen is itself worth seeing. Then two or three ordinary pages carrying the most of the app's character — a populated list, a detail view with real data, the screen where the main workflow happens.
58
+
59
+ Leave the root out. The address bar directly above these chips already has back, forward, refresh and home buttons.
60
+
61
+ Write them after the scenarios, when you know which ids and slugs exist. Paths aren't validated — revisit the list when you rename a route, or the chip 404s.
62
+
52
63
  ### Frontend SDK
53
64
 
54
65
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.338",
3
+ "version": "0.1.340",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,10 +21,12 @@
21
21
  "scripts": {
22
22
  "build": "tsup",
23
23
  "dev": "tsup --watch src",
24
- "typecheck": "tsc --noEmit",
24
+ "typecheck": "npm run tsc -- --noEmit",
25
25
  "lint:fix": "prettier --write ./src",
26
26
  "local-update": "npm run build && npm link",
27
- "prepare": "husky"
27
+ "prepare": "husky",
28
+ "tsc": "node node_modules/@typescript/native/bin/tsc",
29
+ "//tsc": "The native TypeScript 7 compiler by explicit path. It (typescript@7 under the @typescript/native alias) and the JS compiler API behind the `typescript` alias — which tsup's dts step needs — both declare a `tsc` bin, so node_modules/.bin/tsc is whichever npm linked last."
28
30
  },
29
31
  "files": [
30
32
  "dist"
@@ -33,22 +35,23 @@
33
35
  "node": ">=18.0.0"
34
36
  },
35
37
  "dependencies": {
38
+ "chalk": "^5.4.1",
39
+ "fast-glob": "^3.3.3",
36
40
  "ink": "^6.6.0",
37
- "react": "^19.1.0",
38
41
  "ink-spinner": "^5.0.0",
39
42
  "ink-text-input": "^6.0.0",
40
- "chalk": "^5.4.1",
41
- "fast-glob": "^3.3.3"
43
+ "react": "^19.1.0"
42
44
  },
43
45
  "devDependencies": {
44
- "typescript": "^5.8.3",
45
- "tsup": "^8.5.0",
46
46
  "@types/node": "^22.16.0",
47
47
  "@types/react": "^19.1.8",
48
+ "@typescript/native": "npm:typescript@^7.0.2",
48
49
  "husky": "^9.1.7",
49
50
  "lint-staged": "^16.3.2",
50
51
  "prettier": "^3.8.1",
51
- "prettier-plugin-curly": "^0.4.1"
52
+ "prettier-plugin-curly": "^0.4.1",
53
+ "tsup": "^8.5.0",
54
+ "typescript": "npm:@typescript/typescript6@^6.0.2"
52
55
  },
53
56
  "lint-staged": {
54
57
  "**/*": "prettier --write --ignore-unknown"