@mindstudio-ai/remy 0.1.307 → 0.1.308

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
@@ -8444,6 +8444,155 @@ function friendlyError(raw) {
8444
8444
  return `Something went wrong: ${raw}`;
8445
8445
  }
8446
8446
 
8447
+ // src/suggestions.ts
8448
+ var SUGGEST_MARKER = "](suggest:";
8449
+ var SEPARATORS = "\\s\xB7|,\\-\u2013\u2014";
8450
+ var SEPARATORS_ONLY = new RegExp(`^[${SEPARATORS}]*$`);
8451
+ var TRAILING_SEPARATORS = new RegExp(`[${SEPARATORS}]+$`);
8452
+ var LIST_MARKER = /^\s*(?:[-*+]|\d+[.)])\s+/;
8453
+ var FENCE = /^ {0,3}(?:```|~~~)/;
8454
+ function findLinks(line) {
8455
+ const found = [];
8456
+ let i = 0;
8457
+ while (i < line.length) {
8458
+ const ch = line[i];
8459
+ if (ch === "\\") {
8460
+ i += 2;
8461
+ continue;
8462
+ }
8463
+ if (ch === "`") {
8464
+ let runEnd = i;
8465
+ while (runEnd < line.length && line[runEnd] === "`") {
8466
+ runEnd++;
8467
+ }
8468
+ const fence = line.slice(i, runEnd);
8469
+ const close = line.indexOf(fence, runEnd);
8470
+ if (close === -1) {
8471
+ break;
8472
+ }
8473
+ i = close + fence.length;
8474
+ continue;
8475
+ }
8476
+ if (ch !== "[") {
8477
+ i++;
8478
+ continue;
8479
+ }
8480
+ const labelEnd = line.indexOf("]", i + 1);
8481
+ if (labelEnd === -1) {
8482
+ break;
8483
+ }
8484
+ if (!line.startsWith(SUGGEST_MARKER, labelEnd)) {
8485
+ i = labelEnd + 1;
8486
+ continue;
8487
+ }
8488
+ let depth = 1;
8489
+ let p = labelEnd + SUGGEST_MARKER.length;
8490
+ while (p < line.length && depth > 0) {
8491
+ if (line[p] === "(") {
8492
+ depth++;
8493
+ } else if (line[p] === ")") {
8494
+ depth--;
8495
+ }
8496
+ p++;
8497
+ }
8498
+ if (depth > 0) {
8499
+ break;
8500
+ }
8501
+ found.push({
8502
+ start: i,
8503
+ end: p,
8504
+ label: line.slice(i + 1, labelEnd).trim(),
8505
+ message: line.slice(labelEnd + SUGGEST_MARKER.length, p - 1).trim(),
8506
+ standalone: false
8507
+ });
8508
+ i = p;
8509
+ }
8510
+ return found;
8511
+ }
8512
+ function classify(line, matches) {
8513
+ let tailClean = SEPARATORS_ONLY.test(
8514
+ line.slice(matches[matches.length - 1].end)
8515
+ );
8516
+ for (let i = matches.length - 1; i >= 0; i--) {
8517
+ matches[i].standalone = tailClean;
8518
+ if (!tailClean) {
8519
+ continue;
8520
+ }
8521
+ const gap = i > 0 ? line.slice(matches[i - 1].end, matches[i].start) : "";
8522
+ tailClean = SEPARATORS_ONLY.test(gap);
8523
+ }
8524
+ }
8525
+ function parseSuggestions(raw) {
8526
+ if (!raw.includes(SUGGEST_MARKER)) {
8527
+ return { text: raw, suggestions: [] };
8528
+ }
8529
+ const suggestions = [];
8530
+ const seen = /* @__PURE__ */ new Set();
8531
+ const outLines = [];
8532
+ let inFence = false;
8533
+ let droppedLine = false;
8534
+ for (const line of raw.split("\n")) {
8535
+ if (FENCE.test(line)) {
8536
+ inFence = !inFence;
8537
+ outLines.push(line);
8538
+ continue;
8539
+ }
8540
+ if (inFence) {
8541
+ outLines.push(line);
8542
+ continue;
8543
+ }
8544
+ const matches = findLinks(line);
8545
+ if (matches.length === 0) {
8546
+ outLines.push(line);
8547
+ continue;
8548
+ }
8549
+ classify(line, matches);
8550
+ for (const m of matches) {
8551
+ const key = `${m.label}::${m.message}`;
8552
+ if (!seen.has(key)) {
8553
+ seen.add(key);
8554
+ suggestions.push({ label: m.label, message: m.message });
8555
+ }
8556
+ }
8557
+ let rebuilt = "";
8558
+ let cursor = 0;
8559
+ for (const m of matches) {
8560
+ rebuilt += line.slice(cursor, m.start);
8561
+ if (!m.standalone) {
8562
+ rebuilt += m.label;
8563
+ }
8564
+ cursor = m.end;
8565
+ }
8566
+ rebuilt += line.slice(cursor);
8567
+ if (matches.some((m) => m.standalone)) {
8568
+ if (SEPARATORS_ONLY.test(rebuilt.replace(LIST_MARKER, ""))) {
8569
+ droppedLine = true;
8570
+ continue;
8571
+ }
8572
+ rebuilt = rebuilt.replace(TRAILING_SEPARATORS, "");
8573
+ }
8574
+ outLines.push(rebuilt);
8575
+ }
8576
+ let text = outLines.join("\n").replace(/\s+$/, "");
8577
+ if (droppedLine) {
8578
+ text = text.replace(/\n{3,}/g, "\n\n");
8579
+ }
8580
+ return { text, suggestions };
8581
+ }
8582
+ function annotateSuggestions(blocks) {
8583
+ for (const block of blocks) {
8584
+ if (block.type !== "text" || !block.text.includes(SUGGEST_MARKER)) {
8585
+ continue;
8586
+ }
8587
+ const { text, suggestions } = parseSuggestions(block.text);
8588
+ if (suggestions.length === 0) {
8589
+ continue;
8590
+ }
8591
+ block.displayText = text;
8592
+ block.suggestions = suggestions;
8593
+ }
8594
+ }
8595
+
8447
8596
  // src/agent.ts
8448
8597
  var log15 = createLogger("agent");
8449
8598
  var BRAND_TRIGGERING_TOOLS = /* @__PURE__ */ new Set([
@@ -8638,8 +8787,27 @@ async function runTurn(params) {
8638
8787
  toolInputAccumulators.set(id, acc);
8639
8788
  }
8640
8789
  return acc;
8790
+ }, emitTextBlockSnapshot2 = function(closing) {
8791
+ let block;
8792
+ for (let i = contentBlocks.length - 1; i >= 0; i--) {
8793
+ const candidate = contentBlocks[i];
8794
+ if (candidate.type === "text") {
8795
+ block = candidate;
8796
+ break;
8797
+ }
8798
+ }
8799
+ if (!block || !block.text.includes(SUGGEST_MARKER)) {
8800
+ return;
8801
+ }
8802
+ const { text, suggestions } = parseSuggestions(block.text);
8803
+ const worthSending = closing ? suggestions.length > 0 : suggestions.length > emittedSuggestionCount;
8804
+ if (!worthSending) {
8805
+ return;
8806
+ }
8807
+ emittedSuggestionCount = suggestions.length;
8808
+ onEvent({ type: "text_block", text, suggestions });
8641
8809
  };
8642
- var getOrCreateAccumulator = getOrCreateAccumulator2;
8810
+ var getOrCreateAccumulator = getOrCreateAccumulator2, emitTextBlockSnapshot = emitTextBlockSnapshot2;
8643
8811
  if (signal?.aborted) {
8644
8812
  onEvent({ type: "turn_cancelled" });
8645
8813
  saveSession(state);
@@ -8651,6 +8819,7 @@ async function runTurn(params) {
8651
8819
  let thinkingCompleteCount = 0;
8652
8820
  let lastThinkingRelatedStartedAt;
8653
8821
  let textBlockOpen = false;
8822
+ let emittedSuggestionCount = 0;
8654
8823
  const toolInputAccumulators = /* @__PURE__ */ new Map();
8655
8824
  let stopReason = "end_turn";
8656
8825
  let turnProviderMetadata;
@@ -8739,14 +8908,19 @@ async function runTurn(params) {
8739
8908
  text: event.text,
8740
8909
  startedAt: event.ts
8741
8910
  });
8911
+ emittedSuggestionCount = 0;
8742
8912
  }
8743
8913
  textBlockOpen = true;
8744
8914
  onEvent({ type: "text", text: event.text });
8915
+ emitTextBlockSnapshot2(false);
8745
8916
  break;
8746
8917
  }
8747
8918
  case "thinking":
8748
8919
  if (event.text === "") {
8749
8920
  thinkingBlockStartTimes.push(event.ts);
8921
+ if (textBlockOpen) {
8922
+ emitTextBlockSnapshot2(true);
8923
+ }
8750
8924
  textBlockOpen = false;
8751
8925
  }
8752
8926
  onEvent({ type: "thinking", text: event.text });
@@ -8872,6 +9046,11 @@ async function runTurn(params) {
8872
9046
  throw err;
8873
9047
  }
8874
9048
  }
9049
+ if (textBlockOpen) {
9050
+ emitTextBlockSnapshot2(true);
9051
+ textBlockOpen = false;
9052
+ }
9053
+ annotateSuggestions(contentBlocks);
8875
9054
  if (signal?.aborted) {
8876
9055
  statusWatcher.stop();
8877
9056
  if (contentBlocks.length > 0) {
@@ -10002,6 +10181,13 @@ var HeadlessSession = class {
10002
10181
  rid
10003
10182
  );
10004
10183
  return;
10184
+ case "text_block":
10185
+ this.emit(
10186
+ "text_block",
10187
+ { text: e.text, suggestions: e.suggestions },
10188
+ rid
10189
+ );
10190
+ return;
10005
10191
  case "thinking":
10006
10192
  this.emit(
10007
10193
  "thinking",
package/dist/index.js CHANGED
@@ -8752,6 +8752,161 @@ var init_errors = __esm({
8752
8752
  }
8753
8753
  });
8754
8754
 
8755
+ // src/suggestions.ts
8756
+ function findLinks(line) {
8757
+ const found = [];
8758
+ let i = 0;
8759
+ while (i < line.length) {
8760
+ const ch = line[i];
8761
+ if (ch === "\\") {
8762
+ i += 2;
8763
+ continue;
8764
+ }
8765
+ if (ch === "`") {
8766
+ let runEnd = i;
8767
+ while (runEnd < line.length && line[runEnd] === "`") {
8768
+ runEnd++;
8769
+ }
8770
+ const fence = line.slice(i, runEnd);
8771
+ const close = line.indexOf(fence, runEnd);
8772
+ if (close === -1) {
8773
+ break;
8774
+ }
8775
+ i = close + fence.length;
8776
+ continue;
8777
+ }
8778
+ if (ch !== "[") {
8779
+ i++;
8780
+ continue;
8781
+ }
8782
+ const labelEnd = line.indexOf("]", i + 1);
8783
+ if (labelEnd === -1) {
8784
+ break;
8785
+ }
8786
+ if (!line.startsWith(SUGGEST_MARKER, labelEnd)) {
8787
+ i = labelEnd + 1;
8788
+ continue;
8789
+ }
8790
+ let depth = 1;
8791
+ let p = labelEnd + SUGGEST_MARKER.length;
8792
+ while (p < line.length && depth > 0) {
8793
+ if (line[p] === "(") {
8794
+ depth++;
8795
+ } else if (line[p] === ")") {
8796
+ depth--;
8797
+ }
8798
+ p++;
8799
+ }
8800
+ if (depth > 0) {
8801
+ break;
8802
+ }
8803
+ found.push({
8804
+ start: i,
8805
+ end: p,
8806
+ label: line.slice(i + 1, labelEnd).trim(),
8807
+ message: line.slice(labelEnd + SUGGEST_MARKER.length, p - 1).trim(),
8808
+ standalone: false
8809
+ });
8810
+ i = p;
8811
+ }
8812
+ return found;
8813
+ }
8814
+ function classify(line, matches) {
8815
+ let tailClean = SEPARATORS_ONLY.test(
8816
+ line.slice(matches[matches.length - 1].end)
8817
+ );
8818
+ for (let i = matches.length - 1; i >= 0; i--) {
8819
+ matches[i].standalone = tailClean;
8820
+ if (!tailClean) {
8821
+ continue;
8822
+ }
8823
+ const gap = i > 0 ? line.slice(matches[i - 1].end, matches[i].start) : "";
8824
+ tailClean = SEPARATORS_ONLY.test(gap);
8825
+ }
8826
+ }
8827
+ function parseSuggestions(raw) {
8828
+ if (!raw.includes(SUGGEST_MARKER)) {
8829
+ return { text: raw, suggestions: [] };
8830
+ }
8831
+ const suggestions = [];
8832
+ const seen = /* @__PURE__ */ new Set();
8833
+ const outLines = [];
8834
+ let inFence = false;
8835
+ let droppedLine = false;
8836
+ for (const line of raw.split("\n")) {
8837
+ if (FENCE.test(line)) {
8838
+ inFence = !inFence;
8839
+ outLines.push(line);
8840
+ continue;
8841
+ }
8842
+ if (inFence) {
8843
+ outLines.push(line);
8844
+ continue;
8845
+ }
8846
+ const matches = findLinks(line);
8847
+ if (matches.length === 0) {
8848
+ outLines.push(line);
8849
+ continue;
8850
+ }
8851
+ classify(line, matches);
8852
+ for (const m of matches) {
8853
+ const key = `${m.label}::${m.message}`;
8854
+ if (!seen.has(key)) {
8855
+ seen.add(key);
8856
+ suggestions.push({ label: m.label, message: m.message });
8857
+ }
8858
+ }
8859
+ let rebuilt = "";
8860
+ let cursor = 0;
8861
+ for (const m of matches) {
8862
+ rebuilt += line.slice(cursor, m.start);
8863
+ if (!m.standalone) {
8864
+ rebuilt += m.label;
8865
+ }
8866
+ cursor = m.end;
8867
+ }
8868
+ rebuilt += line.slice(cursor);
8869
+ if (matches.some((m) => m.standalone)) {
8870
+ if (SEPARATORS_ONLY.test(rebuilt.replace(LIST_MARKER, ""))) {
8871
+ droppedLine = true;
8872
+ continue;
8873
+ }
8874
+ rebuilt = rebuilt.replace(TRAILING_SEPARATORS, "");
8875
+ }
8876
+ outLines.push(rebuilt);
8877
+ }
8878
+ let text = outLines.join("\n").replace(/\s+$/, "");
8879
+ if (droppedLine) {
8880
+ text = text.replace(/\n{3,}/g, "\n\n");
8881
+ }
8882
+ return { text, suggestions };
8883
+ }
8884
+ function annotateSuggestions(blocks) {
8885
+ for (const block of blocks) {
8886
+ if (block.type !== "text" || !block.text.includes(SUGGEST_MARKER)) {
8887
+ continue;
8888
+ }
8889
+ const { text, suggestions } = parseSuggestions(block.text);
8890
+ if (suggestions.length === 0) {
8891
+ continue;
8892
+ }
8893
+ block.displayText = text;
8894
+ block.suggestions = suggestions;
8895
+ }
8896
+ }
8897
+ var SUGGEST_MARKER, SEPARATORS, SEPARATORS_ONLY, TRAILING_SEPARATORS, LIST_MARKER, FENCE;
8898
+ var init_suggestions = __esm({
8899
+ "src/suggestions.ts"() {
8900
+ "use strict";
8901
+ SUGGEST_MARKER = "](suggest:";
8902
+ SEPARATORS = "\\s\xB7|,\\-\u2013\u2014";
8903
+ SEPARATORS_ONLY = new RegExp(`^[${SEPARATORS}]*$`);
8904
+ TRAILING_SEPARATORS = new RegExp(`[${SEPARATORS}]+$`);
8905
+ LIST_MARKER = /^\s*(?:[-*+]|\d+[.)])\s+/;
8906
+ FENCE = /^ {0,3}(?:```|~~~)/;
8907
+ }
8908
+ });
8909
+
8755
8910
  // src/brandExtraction/index.ts
8756
8911
  import fs20 from "fs";
8757
8912
  import path11 from "path";
@@ -9285,8 +9440,27 @@ async function runTurn(params) {
9285
9440
  toolInputAccumulators.set(id, acc);
9286
9441
  }
9287
9442
  return acc;
9443
+ }, emitTextBlockSnapshot2 = function(closing) {
9444
+ let block;
9445
+ for (let i = contentBlocks.length - 1; i >= 0; i--) {
9446
+ const candidate = contentBlocks[i];
9447
+ if (candidate.type === "text") {
9448
+ block = candidate;
9449
+ break;
9450
+ }
9451
+ }
9452
+ if (!block || !block.text.includes(SUGGEST_MARKER)) {
9453
+ return;
9454
+ }
9455
+ const { text, suggestions } = parseSuggestions(block.text);
9456
+ const worthSending = closing ? suggestions.length > 0 : suggestions.length > emittedSuggestionCount;
9457
+ if (!worthSending) {
9458
+ return;
9459
+ }
9460
+ emittedSuggestionCount = suggestions.length;
9461
+ onEvent({ type: "text_block", text, suggestions });
9288
9462
  };
9289
- var getOrCreateAccumulator = getOrCreateAccumulator2;
9463
+ var getOrCreateAccumulator = getOrCreateAccumulator2, emitTextBlockSnapshot = emitTextBlockSnapshot2;
9290
9464
  if (signal?.aborted) {
9291
9465
  onEvent({ type: "turn_cancelled" });
9292
9466
  saveSession(state);
@@ -9298,6 +9472,7 @@ async function runTurn(params) {
9298
9472
  let thinkingCompleteCount = 0;
9299
9473
  let lastThinkingRelatedStartedAt;
9300
9474
  let textBlockOpen = false;
9475
+ let emittedSuggestionCount = 0;
9301
9476
  const toolInputAccumulators = /* @__PURE__ */ new Map();
9302
9477
  let stopReason = "end_turn";
9303
9478
  let turnProviderMetadata;
@@ -9386,14 +9561,19 @@ async function runTurn(params) {
9386
9561
  text: event.text,
9387
9562
  startedAt: event.ts
9388
9563
  });
9564
+ emittedSuggestionCount = 0;
9389
9565
  }
9390
9566
  textBlockOpen = true;
9391
9567
  onEvent({ type: "text", text: event.text });
9568
+ emitTextBlockSnapshot2(false);
9392
9569
  break;
9393
9570
  }
9394
9571
  case "thinking":
9395
9572
  if (event.text === "") {
9396
9573
  thinkingBlockStartTimes.push(event.ts);
9574
+ if (textBlockOpen) {
9575
+ emitTextBlockSnapshot2(true);
9576
+ }
9397
9577
  textBlockOpen = false;
9398
9578
  }
9399
9579
  onEvent({ type: "thinking", text: event.text });
@@ -9519,6 +9699,11 @@ async function runTurn(params) {
9519
9699
  throw err;
9520
9700
  }
9521
9701
  }
9702
+ if (textBlockOpen) {
9703
+ emitTextBlockSnapshot2(true);
9704
+ textBlockOpen = false;
9705
+ }
9706
+ annotateSuggestions(contentBlocks);
9522
9707
  if (signal?.aborted) {
9523
9708
  statusWatcher.stop();
9524
9709
  if (contentBlocks.length > 0) {
@@ -9802,6 +9987,7 @@ var init_agent = __esm({
9802
9987
  init_resolve();
9803
9988
  init_errors();
9804
9989
  init_cleanMessages();
9990
+ init_suggestions();
9805
9991
  init_sentinel();
9806
9992
  init_trigger2();
9807
9993
  init_surfaces();
@@ -10992,6 +11178,13 @@ var init_headless = __esm({
10992
11178
  rid
10993
11179
  );
10994
11180
  return;
11181
+ case "text_block":
11182
+ this.emit(
11183
+ "text_block",
11184
+ { text: e.text, suggestions: e.suggestions },
11185
+ rid
11186
+ );
11187
+ return;
10995
11188
  case "thinking":
10996
11189
  this.emit(
10997
11190
  "thinking",
@@ -38,7 +38,7 @@ You will occasionally receive automated messages prefixed with `@@automated_mess
38
38
 
39
39
  ## Style
40
40
  - Your messages are rendered as markdown. Use formatting (headers, bold, lists, code blocks) when it helps readability. You can include images using `![alt](url)` — use this to show the user screenshots, generated images, or other visual references inline in your messages.
41
- - When offering suggestions or options the user might want to quickly select in a conversation, format them as clickable suggestion links: `[label](suggest:message sent on click)`. The label renders as a tappable chip and should be a few words — chip-sized, not sentence-sized. The `suggest:` payload can be longer; that's what gets sent as the user's next message when clicked. Use these liberally: when brainstorming, offering directions, listing options, or any time you're asking a question the user could answer with a quick tap. When explicitly gathering information from the user, however, always use the `promptUser` tool instead.
41
+ - When offering suggestions or options the user might want to quickly select in a conversation, format them as clickable suggestion links: `[label](suggest:message sent on click)`. The label renders as a tappable chip and should be a few words — chip-sized, not sentence-sized. The `suggest:` payload can be longer; that's what gets sent as the user's next message when clicked. A run of links on its own line, or at the end of a line, is lifted out of your message and rendered as chips beneath it; a link written mid-sentence keeps its label in the sentence and offers the chip as well. Use these liberally: when brainstorming, offering directions, listing options, or any time you're asking a question the user could answer with a quick tap. When explicitly gathering information from the user, however, always use the `promptUser` tool instead.
42
42
  - When pointing the user to a specific page in their running app, format the link as `[label](preview:/path)` — clicking it navigates the live preview there. The payload is a path-relative URL (just `/...`, with optional query/hash); for external URLs, use a plain markdown link.
43
43
  - Keep language accessible. Describe what the app *does*, not how it's implemented, unless the user demonstrates technical fluency.
44
44
  - Always use full paths relative to the project root when mentioning files (`dist/interfaces/web/src/App.tsx`, not `App.tsx`). Paths will be rendered as clickable links for the user.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindstudio-ai/remy",
3
- "version": "0.1.307",
3
+ "version": "0.1.308",
4
4
  "description": "Remy coding agent",
5
5
  "repository": {
6
6
  "type": "git",