robrain 2.3.9 → 2.4.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 (51) hide show
  1. package/dist/commands/doctor.d.ts.map +1 -1
  2. package/dist/commands/doctor.js +42 -27
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/commands/export-memory.d.ts.map +1 -1
  5. package/dist/commands/export-memory.js +7 -4
  6. package/dist/commands/export-memory.js.map +1 -1
  7. package/dist/commands/init-project.js +2 -2
  8. package/dist/commands/init-project.js.map +1 -1
  9. package/dist/commands/install.d.ts +11 -0
  10. package/dist/commands/install.d.ts.map +1 -1
  11. package/dist/commands/install.js +45 -60
  12. package/dist/commands/install.js.map +1 -1
  13. package/dist/commands/install.test.d.ts +2 -0
  14. package/dist/commands/install.test.d.ts.map +1 -0
  15. package/dist/commands/install.test.js +107 -0
  16. package/dist/commands/install.test.js.map +1 -0
  17. package/dist/commands/mcp.d.ts +1 -0
  18. package/dist/commands/mcp.d.ts.map +1 -1
  19. package/dist/commands/mcp.js +3 -0
  20. package/dist/commands/mcp.js.map +1 -1
  21. package/dist/commands/mcp.test.js +8 -0
  22. package/dist/commands/mcp.test.js.map +1 -1
  23. package/dist/commands/outcomes.js +4 -4
  24. package/dist/commands/outcomes.js.map +1 -1
  25. package/dist/commands/review.d.ts.map +1 -1
  26. package/dist/commands/review.js +53 -11
  27. package/dist/commands/review.js.map +1 -1
  28. package/dist/commands/status.d.ts.map +1 -1
  29. package/dist/commands/status.js +7 -5
  30. package/dist/commands/status.js.map +1 -1
  31. package/dist/commands/up.d.ts +3 -2
  32. package/dist/commands/up.d.ts.map +1 -1
  33. package/dist/commands/up.js +9 -7
  34. package/dist/commands/up.js.map +1 -1
  35. package/dist/commands/up.test.js +14 -2
  36. package/dist/commands/up.test.js.map +1 -1
  37. package/dist/index.js +2 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/lib/config.d.ts +2 -0
  40. package/dist/lib/config.d.ts.map +1 -1
  41. package/dist/lib/config.js.map +1 -1
  42. package/dist/lib/editor.codex.test.js +35 -0
  43. package/dist/lib/editor.codex.test.js.map +1 -1
  44. package/dist/lib/editor.d.ts +14 -0
  45. package/dist/lib/editor.d.ts.map +1 -1
  46. package/dist/lib/editor.js +30 -4
  47. package/dist/lib/editor.js.map +1 -1
  48. package/dist/lib/mcp-bundle.test.js +2 -2
  49. package/package.json +2 -2
  50. package/vendor/sensing-mcp/dist/index.js +234 -2
  51. package/vendor/sensing-mcp/package.json +1 -1
@@ -6935,6 +6935,222 @@ var require_veto_match = __commonJS({
6935
6935
  }
6936
6936
  });
6937
6937
 
6938
+ // ../shared/dist/trust.js
6939
+ var require_trust = __commonJS({
6940
+ "../shared/dist/trust.js"(exports) {
6941
+ "use strict";
6942
+ Object.defineProperty(exports, "__esModule", { value: true });
6943
+ exports.ANOMALOUS_FIELD_LENGTH = exports.QUARANTINE_THRESHOLD = void 0;
6944
+ exports.scoreMemoryTrust = scoreMemoryTrust;
6945
+ exports.QUARANTINE_THRESHOLD = 0.6;
6946
+ exports.ANOMALOUS_FIELD_LENGTH = 8e3;
6947
+ var DAMPEN_DISCUSSED = 0.25;
6948
+ function regexDetector(id, type, weight, dampenable, regex) {
6949
+ return {
6950
+ id,
6951
+ type,
6952
+ weight,
6953
+ dampenable,
6954
+ find(text) {
6955
+ const out = [];
6956
+ regex.lastIndex = 0;
6957
+ for (const m of text.matchAll(regex)) {
6958
+ out.push({ index: m.index ?? 0, match: m[0] });
6959
+ }
6960
+ return out;
6961
+ }
6962
+ };
6963
+ }
6964
+ var ZERO_WIDTH_RE = /[\u200B\u200C\u200D\u2060\uFEFF]/g;
6965
+ var BIDI_RE = /[\u202A-\u202E\u2066-\u2069]/g;
6966
+ var MARKDOWN_LINK_RE = /\[([^\]\n]+)\]\(\s*([^)\s]+)\s*\)/g;
6967
+ var HOST_IN_TEXT_RE = /(?:https?:\/\/)?([a-z0-9-]+(?:\.[a-z0-9-]+)+)/i;
6968
+ function normalizeHost(host) {
6969
+ return host.toLowerCase().replace(/^www\./, "");
6970
+ }
6971
+ function linkTargetHost(url) {
6972
+ const m = url.match(/^https?:\/\/([^/?#]+)/i);
6973
+ return m?.[1] ? normalizeHost(m[1].replace(/:\d+$/, "")) : null;
6974
+ }
6975
+ var DETECTORS = [
6976
+ // ── instruction_injection — imperatives aimed at the agent ──
6977
+ regexDetector("ii_ignore_instructions", "instruction_injection", 0.45, true, /ignore\s+(?:all|previous|prior)\s+(?:instructions|context)/gi),
6978
+ regexDetector("ii_you_must", "instruction_injection", 0.3, true, /you must (?:always|never)\b/gi),
6979
+ regexDetector("ii_hide_from_user", "instruction_injection", 0.45, true, /do not tell the user/gi),
6980
+ regexDetector("ii_without_asking", "instruction_injection", 0.3, true, /without asking(?: the user)?\b/gi),
6981
+ regexDetector("ii_system_prompt", "instruction_injection", 0.25, true, /system prompt/gi),
6982
+ // Role-prefix smuggling — a stored memory has no business containing
6983
+ // chat-transcript role markers at line starts. HARD: quarantines alone.
6984
+ regexDetector("ii_role_smuggling", "instruction_injection", 0.7, false, /(?:^|\n)\s{0,8}(?:system|assistant|user)\s*:/gi),
6985
+ // <system>/<important> pseudo-tags — prompt-boundary forgery. HARD.
6986
+ regexDetector("ii_pseudo_tag", "instruction_injection", 0.7, false, /<\/?\s*(?:system|important)\s*>/gi),
6987
+ // ── tool_coercion — trying to make the agent run things ─────
6988
+ regexDetector("tc_run_command", "tool_coercion", 0.4, true, /run the following command/gi),
6989
+ regexDetector("tc_execute_this", "tool_coercion", 0.35, true, /execute this\b/gi),
6990
+ // curl … | sh pipeline — a live remote-code-execution payload. HARD when
6991
+ // undiscussed; dampened when it's quoted in a rejection ("rejected:
6992
+ // curl | bash installers").
6993
+ regexDetector("tc_curl_pipe_sh", "tool_coercion", 0.7, true, /\bcurl\b[^\n]{0,200}?\|\s*(?:ba|z|da)?sh\b/gi),
6994
+ regexDetector("tc_rm_rf", "tool_coercion", 0.5, true, /\brm\s+-rf\b/g),
6995
+ regexDetector("tc_bashrc", "tool_coercion", 0.5, true, /add[^\n]{0,40}\.(?:bashrc|zshrc|profile)\b/gi),
6996
+ // ── link_smuggling — URI schemes + text/host mismatch ───────
6997
+ regexDetector("ls_javascript_uri", "link_smuggling", 0.7, true, /\bjavascript:\s*\S/gi),
6998
+ regexDetector("ls_data_uri", "link_smuggling", 0.7, true, /\bdata:[a-z]+\/[a-z0-9.+-]+[;,]/gi),
6999
+ {
7000
+ id: "ls_text_host_mismatch",
7001
+ type: "link_smuggling",
7002
+ weight: 0.5,
7003
+ dampenable: true,
7004
+ find(text) {
7005
+ const out = [];
7006
+ MARKDOWN_LINK_RE.lastIndex = 0;
7007
+ for (const m of text.matchAll(MARKDOWN_LINK_RE)) {
7008
+ const [, linkText, target] = m;
7009
+ if (linkText === void 0 || target === void 0)
7010
+ continue;
7011
+ const targetHost = linkTargetHost(target);
7012
+ if (!targetHost)
7013
+ continue;
7014
+ const textHost = linkText.match(HOST_IN_TEXT_RE)?.[1];
7015
+ if (!textHost)
7016
+ continue;
7017
+ const shown = normalizeHost(textHost);
7018
+ if (targetHost === shown || targetHost.endsWith(`.${shown}`) || shown.endsWith(`.${targetHost}`))
7019
+ continue;
7020
+ out.push({ index: m.index ?? 0, match: m[0] });
7021
+ }
7022
+ return out;
7023
+ }
7024
+ },
7025
+ // ── invisible_unicode — HARD, never dampened ────────────────
7026
+ {
7027
+ id: "iu_zero_width",
7028
+ type: "invisible_unicode",
7029
+ weight: 0.9,
7030
+ dampenable: false,
7031
+ find: (text) => invisibleOccurrences(text, ZERO_WIDTH_RE, "zero-width")
7032
+ },
7033
+ {
7034
+ id: "iu_bidi",
7035
+ type: "invisible_unicode",
7036
+ weight: 0.9,
7037
+ dampenable: false,
7038
+ find: (text) => invisibleOccurrences(text, BIDI_RE, "bidi-override")
7039
+ },
7040
+ // ── encoded_blob — opaque payloads have no place in a decision ──
7041
+ {
7042
+ id: "eb_base64",
7043
+ type: "encoded_blob",
7044
+ weight: 0.7,
7045
+ dampenable: false,
7046
+ find(text) {
7047
+ const out = [];
7048
+ for (const m of text.matchAll(/[A-Za-z0-9+/]{120,}={0,2}/g)) {
7049
+ const run = m[0];
7050
+ if (!/\d/.test(run) || !/[a-z]/.test(run) || !/[A-Z]/.test(run))
7051
+ continue;
7052
+ out.push({ index: m.index ?? 0, match: run, evidence: `base64-like run of ${run.length} chars: ${run.slice(0, 40)}\u2026` });
7053
+ }
7054
+ return out;
7055
+ }
7056
+ },
7057
+ {
7058
+ id: "eb_hex",
7059
+ type: "encoded_blob",
7060
+ weight: 0.7,
7061
+ dampenable: false,
7062
+ find(text) {
7063
+ const out = [];
7064
+ for (const m of text.matchAll(/[0-9a-fA-F]{160,}/g)) {
7065
+ if (!/\d/.test(m[0]))
7066
+ continue;
7067
+ out.push({ index: m.index ?? 0, match: m[0], evidence: `hex run of ${m[0].length} chars: ${m[0].slice(0, 40)}\u2026` });
7068
+ }
7069
+ return out;
7070
+ }
7071
+ }
7072
+ ];
7073
+ function invisibleOccurrences(text, re, label) {
7074
+ re.lastIndex = 0;
7075
+ const matches = [...text.matchAll(re)];
7076
+ const first = matches[0];
7077
+ if (!first)
7078
+ return [];
7079
+ const codepoints = [...new Set(matches.map((m) => `U+${(m[0].codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}`))];
7080
+ return [{
7081
+ index: first.index ?? 0,
7082
+ match: first[0],
7083
+ evidence: `${matches.length}\xD7 ${label} char${matches.length === 1 ? "" : "s"} (${codepoints.join(", ")}) near "${excerptAround(text, first.index ?? 0)}"`
7084
+ }];
7085
+ }
7086
+ var DISCUSSION_MARKER_RE = /(?:reject(?:ed|ing)?|rule[sd]?\s+out|sanitiz\w*|redact\w*|filter\w*|detect\w*|scan\w*|block\w*|flag\w*|strip\w*|escap\w*|guard\w*|forbid\w*|ban(?:ned|ning)?|avoid\w*|never\s+(?:use|run|allow)|do(?:n't| not)\s+(?:use|run|allow)|disallow\w*|prevent\w*|against|instead\s+of|pattern[s]?|attack[s]?|inject\w*|poison\w*|phish\w*|malicious|risk[sy]?|vulnerab\w*|supply.chain|e\.g\.|such\s+as|example[s]?|like\s+["'`])/i;
7087
+ var QUOTE_CHARS = /* @__PURE__ */ new Set(['"', "'", "`", "\u2018", "\u2019", "\u201C", "\u201D"]);
7088
+ function inDiscussedContext(text, index) {
7089
+ const before = text.slice(Math.max(0, index - 100), index);
7090
+ if (DISCUSSION_MARKER_RE.test(before))
7091
+ return true;
7092
+ const lineStart = text.lastIndexOf("\n", index) + 1;
7093
+ const linePrefix = text.slice(lineStart, index);
7094
+ let quotes = 0;
7095
+ for (const ch of linePrefix)
7096
+ if (QUOTE_CHARS.has(ch))
7097
+ quotes += 1;
7098
+ return quotes % 2 === 1;
7099
+ }
7100
+ var EVIDENCE_MAX = 80;
7101
+ function escapeInvisible(s) {
7102
+ return s.replace(/[\u200B-\u200D\u2060\uFEFF\u202A-\u202E\u2066-\u2069]/g, (ch) => `U+${(ch.codePointAt(0) ?? 0).toString(16).toUpperCase().padStart(4, "0")}`);
7103
+ }
7104
+ function excerpt(s) {
7105
+ const oneLine = escapeInvisible(s).replace(/\s+/g, " ").trim();
7106
+ return oneLine.length <= EVIDENCE_MAX ? oneLine : `${oneLine.slice(0, EVIDENCE_MAX - 1)}\u2026`;
7107
+ }
7108
+ function excerptAround(text, index) {
7109
+ return excerpt(text.slice(Math.max(0, index - 20), index + 20));
7110
+ }
7111
+ var MAX_FLAGS = 20;
7112
+ function scoreMemoryTrust(texts) {
7113
+ const hits = /* @__PURE__ */ new Map();
7114
+ for (const text of texts) {
7115
+ if (!text)
7116
+ continue;
7117
+ for (const det of DETECTORS) {
7118
+ for (const occ of det.find(text)) {
7119
+ const dampened = det.dampenable && inDiscussedContext(text, occ.index);
7120
+ const weight = dampened ? det.weight * DAMPEN_DISCUSSED : det.weight;
7121
+ const prev = hits.get(det.id);
7122
+ if (prev && prev.weight >= weight)
7123
+ continue;
7124
+ hits.set(det.id, {
7125
+ type: det.type,
7126
+ weight,
7127
+ evidence: occ.evidence ?? excerpt(occ.match)
7128
+ });
7129
+ }
7130
+ }
7131
+ if (text.length > exports.ANOMALOUS_FIELD_LENGTH) {
7132
+ const prev = hits.get("anomalous_length");
7133
+ if (!prev) {
7134
+ hits.set("anomalous_length", {
7135
+ type: "anomalous_length",
7136
+ weight: 0.3,
7137
+ evidence: `field length ${text.length} > ${exports.ANOMALOUS_FIELD_LENGTH}`
7138
+ });
7139
+ }
7140
+ }
7141
+ }
7142
+ let survival = 1;
7143
+ const flags = [];
7144
+ for (const hit of [...hits.values()].sort((a, b) => b.weight - a.weight)) {
7145
+ survival *= 1 - hit.weight;
7146
+ if (flags.length < MAX_FLAGS)
7147
+ flags.push({ type: hit.type, evidence: hit.evidence });
7148
+ }
7149
+ return { score: Math.round((1 - survival) * 100) / 100, flags };
7150
+ }
7151
+ }
7152
+ });
7153
+
6938
7154
  // ../shared/dist/extract-decision.js
6939
7155
  var require_extract_decision = __commonJS({
6940
7156
  "../shared/dist/extract-decision.js"(exports) {
@@ -7070,6 +7286,7 @@ var require_dist = __commonJS({
7070
7286
  __exportStar(require_embeddings(), exports);
7071
7287
  __exportStar(require_redact(), exports);
7072
7288
  __exportStar(require_veto_match(), exports);
7289
+ __exportStar(require_trust(), exports);
7073
7290
  __exportStar(require_extract_decision(), exports);
7074
7291
  __exportStar(require_db(), exports);
7075
7292
  }
@@ -22491,6 +22708,9 @@ var init_mcp = __esm({
22491
22708
  // src/config.ts
22492
22709
  import { homedir } from "node:os";
22493
22710
  import { join } from "node:path";
22711
+ function isThinMode(env = process.env) {
22712
+ return env.ROBRAIN_MODE?.trim().toLowerCase() === "cloud";
22713
+ }
22494
22714
  var import_shared, config2;
22495
22715
  var init_config = __esm({
22496
22716
  "src/config.ts"() {
@@ -22699,7 +22919,7 @@ function inferScope(turn, extracted) {
22699
22919
  return "team";
22700
22920
  }
22701
22921
  async function classifyTopicShift(turn, signal) {
22702
- if (config2.topicShiftDisableEmbedding) return null;
22922
+ if (config2.topicShiftDisableEmbedding || isThinMode()) return null;
22703
22923
  const sessionId = turn.session_id;
22704
22924
  const window = embeddingWindows.get(sessionId) ?? [];
22705
22925
  const currentEmbedding = await embed(turn.user_message, signal);
@@ -23062,7 +23282,7 @@ function resolveSessionId(raw) {
23062
23282
  function buildServer() {
23063
23283
  const server = new McpServer({
23064
23284
  name: "sensing-mcp",
23065
- version: "2.3.9"
23285
+ version: "2.4.2"
23066
23286
  });
23067
23287
  server.tool(
23068
23288
  "sensing_start_session",
@@ -23159,6 +23379,16 @@ function buildServer() {
23159
23379
  const projectId = session.project_id;
23160
23380
  setImmediate(async () => {
23161
23381
  try {
23382
+ if (isThinMode()) {
23383
+ const errors = await routeFlushTurns([turn], projectId);
23384
+ if (errors.length === 0) {
23385
+ lastDecisionShipFailure = null;
23386
+ streamBuffer.markClassified(session_id, sequence);
23387
+ } else {
23388
+ lastDecisionShipFailure = `${session_id} seq ${sequence}: ${errors.join("; ")}`;
23389
+ }
23390
+ return;
23391
+ }
23162
23392
  const decisionSignal = await classifyDecision(turn, projectId);
23163
23393
  if (decisionSignal) {
23164
23394
  const outcome = await routeDecisionSignal(decisionSignal, projectId);
@@ -23259,6 +23489,8 @@ function buildServer() {
23259
23489
  content: [{
23260
23490
  type: "text",
23261
23491
  text: JSON.stringify({
23492
+ // Only surfaced in thin mode so full-mode status output stays byte-identical.
23493
+ ...isThinMode() ? { mode: "cloud-thin" } : {},
23262
23494
  session_found: !!session,
23263
23495
  project_id: session?.project_id,
23264
23496
  started_at: session?.started_at,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robrain/sensing-mcp",
3
- "version": "2.3.9",
3
+ "version": "2.4.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js"