@signetai/connector-hermes-agent 0.193.1 → 0.193.3

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/index.js CHANGED
@@ -6918,6 +6918,99 @@ var PIPELINE_PROVIDER_CHOICES = [
6918
6918
  var SYNTHESIS_PROVIDER_CHOICES = PIPELINE_PROVIDER_CHOICES.filter((provider) => provider !== "command");
6919
6919
  var PIPELINE_PROVIDER_SET = new Set(PIPELINE_PROVIDER_CHOICES);
6920
6920
  var SYNTHESIS_PROVIDER_SET = new Set(SYNTHESIS_PROVIDER_CHOICES);
6921
+ var MEMORY_CONTENT_SAFETY_POLICY_VERSION = "memory-content-safety-v1";
6922
+ var MEMORY_CONTENT_SAFETY_REASONS = [
6923
+ "prompt_injection",
6924
+ "exfiltration",
6925
+ "credential_harvesting",
6926
+ "malicious_shell",
6927
+ "tool_directive",
6928
+ "invisible_unicode"
6929
+ ];
6930
+ var INVISIBLE_UNICODE_RE = /(?:\u034f|[\u00ad\u061c\u070f\u180e\u200b\u200c\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\u206a-\u206f\ufeff]|[\u{e0000}-\u{e007f}])/u;
6931
+ var STRONG_DEFENSIVE_CONTEXT_RE = /\b(?:security\s+(?:guidance|discussion|analysis)|threat\s+model|defensive)\b/i;
6932
+ var REPORTING_CONTEXT_RE = /\b(?:example|illustrat\w*|sample|quote|quoted|detector|scanner|classif\w*)\b/i;
6933
+ var REPORTING_BEFORE_RE = /\b(?:example|illustrat\w*|sample|quote|quoted|detector|scanner|classif\w*)\b[\s\S]{0,80}\b(?:say\w*|read\w*|show\w*|flag\w*|detect\w*|describ\w*|demonstrat\w*|contain\w*|match\w*|pattern)\b/i;
6934
+ var REPORTING_AFTER_RE = /\b(?:detector|scanner|classif\w*|flag\w*|pattern|dangerous|unsafe|malicious|hostile|should|would|must|never|do not|don't|avoid|quoted)\b/i;
6935
+ var NEGATED_DIRECTIVE_RE = /\b(?:never|do not|don't|should not|must not|cannot|can't|avoid|prevent|detect|mitigat\w*)\b[\s\S]{0,80}$/i;
6936
+ var PROMPT_INJECTION_RES = [
6937
+ /\b(?:ignore|disregard|override|forget|bypass)\b[\s\S]{0,100}\b(?:previous|prior|above|earlier|system|developer|assistant|safety|security)?\s*(?:instructions?|rules?|prompt|message)\b/i,
6938
+ /\b(?:new|following|these)\s+(?:(?:system|developer|assistant|hidden)\s+)?instructions?\b/i,
6939
+ /(?:^|\n)\s*(?:system|developer|instruction|prompt)\s*:/im,
6940
+ /<\s*(?:system|developer|assistant|instruction|prompt)\b[^>]*>/i,
6941
+ /\b(?:you are now|act as|roleplay as|pretend to be)\b[\s\S]{0,80}\b(?:system|admin|developer|unrestricted|jailbreak|different agent)\b/i
6942
+ ];
6943
+ var TOOL_DIRECTIVE_RES = [
6944
+ /<\s*(?:tool[_-]?call|function[_-]?call|invoke|tool)\b/i,
6945
+ /\b(?:assistant|system)\s+to\s*=\s*[a-z0-9_.-]+/i,
6946
+ /\b(?:call|invoke|use|run|execute)\s+(?:the\s+)?[a-z0-9_.-]+\s+tool\b/i
6947
+ ];
6948
+ var EXFILTRATION_RE = /\b(?:reveal|show|print|send|upload|exfiltrat\w*|dump|forward|leak|transmit|export)\b[\s\S]{0,120}(?:\b(?:system\s+prompt|hidden\s+instructions?|secret(?:s)?|credential(?:s)?|password(?:s)?|api\s*keys?|tokens?|private\s+keys?|environment\s+variables?)\b|\.env\b|~\/(?:\.ssh)\/\S+|\/etc\/(?:shadow|passwd)\b)/i;
6949
+ var EXFILTRATION_REVERSE = /(?:\b(?:system\s+prompt|hidden\s+instructions?|secret(?:s)?|credential(?:s)?|password(?:s)?|api\s*keys?|tokens?|private\s+keys?|environment\s+variables?)\b|\.env\b|~\/(?:\.ssh)\/\S+|\/etc\/(?:shadow|passwd)\b)[\s\S]{0,120}\b(?:reveal|show|print|send|upload|exfiltrat\w*|dump|forward|leak|transmit|export)\b/i;
6950
+ var CREDENTIAL_HARVESTING_RE = /\b(?:enter|paste|provide|share|send|give|submit|type|hand over)\b[\s\S]{0,80}\b(?:password|api\s*key|token|secret|credential|private\s+key)\b/i;
6951
+ var DANGEROUS_SHELL_RE = /\b(?:curl|wget)\b[^\n]{0,240}\|\s*(?:ba|z|fi)?sh\b|\brm\s+-rf\s+(?:\/|~|\.ssh)[^\n]{0,240}|\b(?:cat|head|tail)\s+~\/?\.ssh\/(?:id_[a-z]+|authorized_keys)\b|\b(?:printenv|env)\b[^\n]{0,120}\b(?:curl|wget|send|upload|post)\b/i;
6952
+ function matchHasDefensiveContext(content, match) {
6953
+ if (!match || !match[0])
6954
+ return false;
6955
+ const start = match.index ?? 0;
6956
+ const before = content.slice(Math.max(0, start - 120), start);
6957
+ const after = content.slice(start + match[0].length, start + match[0].length + 160);
6958
+ return STRONG_DEFENSIVE_CONTEXT_RE.test(match[0]) || NEGATED_DIRECTIVE_RE.test(before) || STRONG_DEFENSIVE_CONTEXT_RE.test(before) || STRONG_DEFENSIVE_CONTEXT_RE.test(after) || REPORTING_BEFORE_RE.test(before) || REPORTING_CONTEXT_RE.test(before) && REPORTING_AFTER_RE.test(after);
6959
+ }
6960
+ function hasActionableMatch(content, patterns) {
6961
+ return patterns.some((pattern) => {
6962
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
6963
+ const searchable = new RegExp(pattern.source, flags);
6964
+ let match;
6965
+ while ((match = searchable.exec(content)) !== null) {
6966
+ if (!matchHasDefensiveContext(content, match))
6967
+ return true;
6968
+ if (match[0].length === 0)
6969
+ searchable.lastIndex += 1;
6970
+ }
6971
+ return false;
6972
+ });
6973
+ }
6974
+ function hasDangerousShell(content) {
6975
+ const searchable = new RegExp(DANGEROUS_SHELL_RE.source, `${DANGEROUS_SHELL_RE.flags}g`);
6976
+ let match;
6977
+ while ((match = searchable.exec(content)) !== null) {
6978
+ if (matchHasDefensiveContext(content, match))
6979
+ continue;
6980
+ const end = (match.index ?? 0) + match[0].length;
6981
+ const after = content.slice(end, end + 160);
6982
+ if (!STRONG_DEFENSIVE_CONTEXT_RE.test(after) && !REPORTING_AFTER_RE.test(after))
6983
+ return true;
6984
+ if (match[0].length === 0)
6985
+ searchable.lastIndex += 1;
6986
+ }
6987
+ return false;
6988
+ }
6989
+ function scanMemoryContent(content) {
6990
+ const raw = typeof content === "string" ? content : String(content ?? "");
6991
+ const normalized = raw.normalize("NFKC");
6992
+ const reasons = new Set;
6993
+ if (INVISIBLE_UNICODE_RE.test(raw))
6994
+ reasons.add("invisible_unicode");
6995
+ if (hasActionableMatch(normalized, PROMPT_INJECTION_RES))
6996
+ reasons.add("prompt_injection");
6997
+ if (hasActionableMatch(normalized, TOOL_DIRECTIVE_RES))
6998
+ reasons.add("tool_directive");
6999
+ if (hasActionableMatch(normalized, [EXFILTRATION_RE, EXFILTRATION_REVERSE]))
7000
+ reasons.add("exfiltration");
7001
+ if (hasActionableMatch(normalized, [CREDENTIAL_HARVESTING_RE]))
7002
+ reasons.add("credential_harvesting");
7003
+ if (hasDangerousShell(normalized))
7004
+ reasons.add("malicious_shell");
7005
+ const orderedReasons = MEMORY_CONTENT_SAFETY_REASONS.filter((reason) => reasons.has(reason));
7006
+ const status = orderedReasons.length === 0 ? "clean" : orderedReasons.every((reason) => reason === "invisible_unicode") ? "tainted" : "blocked";
7007
+ return {
7008
+ status,
7009
+ contextEligible: status === "clean",
7010
+ reasons: orderedReasons,
7011
+ policyVersion: MEMORY_CONTENT_SAFETY_POLICY_VERSION
7012
+ };
7013
+ }
6921
7014
  var DAEMON_DERIVED_MEMORY_SOURCE_TYPES = ["extract", "aggregate-recall", "session_end", "checkpoint", "dreaming"];
6922
7015
  var MEMORIES_FTS_TOKENIZER = "unicode61";
6923
7016
  function normalizeSql(sql) {
@@ -10896,6 +10989,95 @@ function up124(db) {
10896
10989
  ON imported_source_lifecycle(agent_id, status, updated_at DESC);
10897
10990
  `);
10898
10991
  }
10992
+ function tableExists2(db, table) {
10993
+ return db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) != null;
10994
+ }
10995
+ function hasColumn24(db, table, column) {
10996
+ return db.prepare(`PRAGMA table_info("${table}")`).all().some((row) => row.name === column);
10997
+ }
10998
+ function backfill(db, rows, sourceKind, scannedAt) {
10999
+ const statement = db.prepare(`INSERT INTO memory_content_safety
11000
+ (agent_id, source_kind, source_id, status, context_eligible, reasons_json, policy_version, scanned_at)
11001
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
11002
+ ON CONFLICT(agent_id, source_kind, source_id) DO UPDATE SET
11003
+ status = excluded.status,
11004
+ context_eligible = excluded.context_eligible,
11005
+ reasons_json = excluded.reasons_json,
11006
+ policy_version = excluded.policy_version,
11007
+ scanned_at = excluded.scanned_at`);
11008
+ for (const row of rows) {
11009
+ const sourceId = row.source_id?.trim();
11010
+ if (!sourceId)
11011
+ continue;
11012
+ const assessment = scanMemoryContent(row.content ?? "");
11013
+ statement.run(row.agent_id?.trim() || "default", sourceKind, sourceId, assessment.status, assessment.contextEligible ? 1 : 0, JSON.stringify(assessment.reasons), MEMORY_CONTENT_SAFETY_POLICY_VERSION, scannedAt);
11014
+ }
11015
+ }
11016
+ function backfillTable(db, params) {
11017
+ if (!tableExists2(db, params.table) || !hasColumn24(db, params.table, params.sourceIdColumn) || !hasColumn24(db, params.table, params.contentColumn))
11018
+ return;
11019
+ const agentColumn = hasColumn24(db, params.table, "agent_id") ? "COALESCE(NULLIF(TRIM(agent_id), ''), 'default')" : "'default'";
11020
+ const rows = db.prepare(`SELECT ${agentColumn} AS agent_id, ${params.sourceIdColumn} AS source_id, ${params.contentColumn} AS content
11021
+ FROM ${params.table}${params.where ? ` WHERE ${params.where}` : ""}`).all();
11022
+ backfill(db, rows, params.sourceKind, params.scannedAt);
11023
+ }
11024
+ function up125(db) {
11025
+ db.exec(`
11026
+ CREATE TABLE IF NOT EXISTS memory_content_safety (
11027
+ agent_id TEXT NOT NULL,
11028
+ source_kind TEXT NOT NULL CHECK (source_kind IN ('memory', 'artifact', 'transcript', 'summary', 'source_chunk')),
11029
+ source_id TEXT NOT NULL,
11030
+ status TEXT NOT NULL CHECK (status IN ('clean', 'tainted', 'blocked')),
11031
+ context_eligible INTEGER NOT NULL CHECK (context_eligible IN (0, 1)),
11032
+ reasons_json TEXT NOT NULL DEFAULT '[]',
11033
+ policy_version TEXT NOT NULL,
11034
+ scanned_at TEXT NOT NULL,
11035
+ PRIMARY KEY (agent_id, source_kind, source_id)
11036
+ );
11037
+
11038
+ CREATE INDEX IF NOT EXISTS idx_memory_content_safety_status
11039
+ ON memory_content_safety(agent_id, status, source_kind);
11040
+ CREATE INDEX IF NOT EXISTS idx_memory_content_safety_eligibility
11041
+ ON memory_content_safety(agent_id, source_kind, context_eligible);
11042
+ `);
11043
+ const scannedAt = new Date().toISOString();
11044
+ backfillTable(db, {
11045
+ table: "memories",
11046
+ sourceKind: "memory",
11047
+ sourceIdColumn: "id",
11048
+ contentColumn: "content",
11049
+ scannedAt
11050
+ });
11051
+ backfillTable(db, {
11052
+ table: "memory_artifacts",
11053
+ sourceKind: "artifact",
11054
+ sourceIdColumn: "source_path",
11055
+ contentColumn: "content",
11056
+ scannedAt
11057
+ });
11058
+ backfillTable(db, {
11059
+ table: "session_transcripts",
11060
+ sourceKind: "transcript",
11061
+ sourceIdColumn: "session_key",
11062
+ contentColumn: "content",
11063
+ scannedAt
11064
+ });
11065
+ backfillTable(db, {
11066
+ table: "session_summaries",
11067
+ sourceKind: "summary",
11068
+ sourceIdColumn: "id",
11069
+ contentColumn: "content",
11070
+ scannedAt
11071
+ });
11072
+ backfillTable(db, {
11073
+ table: "embeddings",
11074
+ sourceKind: "source_chunk",
11075
+ sourceIdColumn: "id",
11076
+ contentColumn: "chunk_text",
11077
+ where: "source_type IN ('source_chunk', 'source_obsidian_chunk')",
11078
+ scannedAt
11079
+ });
11080
+ }
10899
11081
  var MIGRATIONS = [
10900
11082
  {
10901
11083
  version: 1,
@@ -11904,6 +12086,12 @@ var MIGRATIONS = [
11904
12086
  artifacts: {
11905
12087
  tables: ["imported_source_lifecycle"]
11906
12088
  }
12089
+ },
12090
+ {
12091
+ version: 125,
12092
+ name: "memory-content-safety",
12093
+ up: up125,
12094
+ artifacts: { tables: ["memory_content_safety"] }
11907
12095
  }
11908
12096
  ];
11909
12097
  var LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
@@ -19121,6 +19309,99 @@ var PIPELINE_PROVIDER_CHOICES2 = [
19121
19309
  var SYNTHESIS_PROVIDER_CHOICES2 = PIPELINE_PROVIDER_CHOICES2.filter((provider) => provider !== "command");
19122
19310
  var PIPELINE_PROVIDER_SET2 = new Set(PIPELINE_PROVIDER_CHOICES2);
19123
19311
  var SYNTHESIS_PROVIDER_SET2 = new Set(SYNTHESIS_PROVIDER_CHOICES2);
19312
+ var MEMORY_CONTENT_SAFETY_POLICY_VERSION2 = "memory-content-safety-v1";
19313
+ var MEMORY_CONTENT_SAFETY_REASONS2 = [
19314
+ "prompt_injection",
19315
+ "exfiltration",
19316
+ "credential_harvesting",
19317
+ "malicious_shell",
19318
+ "tool_directive",
19319
+ "invisible_unicode"
19320
+ ];
19321
+ var INVISIBLE_UNICODE_RE2 = /(?:\u034f|[\u00ad\u061c\u070f\u180e\u200b\u200c\u200e\u200f\u202a-\u202e\u2060\u2066-\u2069\u206a-\u206f\ufeff]|[\u{e0000}-\u{e007f}])/u;
19322
+ var STRONG_DEFENSIVE_CONTEXT_RE2 = /\b(?:security\s+(?:guidance|discussion|analysis)|threat\s+model|defensive)\b/i;
19323
+ var REPORTING_CONTEXT_RE2 = /\b(?:example|illustrat\w*|sample|quote|quoted|detector|scanner|classif\w*)\b/i;
19324
+ var REPORTING_BEFORE_RE2 = /\b(?:example|illustrat\w*|sample|quote|quoted|detector|scanner|classif\w*)\b[\s\S]{0,80}\b(?:say\w*|read\w*|show\w*|flag\w*|detect\w*|describ\w*|demonstrat\w*|contain\w*|match\w*|pattern)\b/i;
19325
+ var REPORTING_AFTER_RE2 = /\b(?:detector|scanner|classif\w*|flag\w*|pattern|dangerous|unsafe|malicious|hostile|should|would|must|never|do not|don't|avoid|quoted)\b/i;
19326
+ var NEGATED_DIRECTIVE_RE2 = /\b(?:never|do not|don't|should not|must not|cannot|can't|avoid|prevent|detect|mitigat\w*)\b[\s\S]{0,80}$/i;
19327
+ var PROMPT_INJECTION_RES2 = [
19328
+ /\b(?:ignore|disregard|override|forget|bypass)\b[\s\S]{0,100}\b(?:previous|prior|above|earlier|system|developer|assistant|safety|security)?\s*(?:instructions?|rules?|prompt|message)\b/i,
19329
+ /\b(?:new|following|these)\s+(?:(?:system|developer|assistant|hidden)\s+)?instructions?\b/i,
19330
+ /(?:^|\n)\s*(?:system|developer|instruction|prompt)\s*:/im,
19331
+ /<\s*(?:system|developer|assistant|instruction|prompt)\b[^>]*>/i,
19332
+ /\b(?:you are now|act as|roleplay as|pretend to be)\b[\s\S]{0,80}\b(?:system|admin|developer|unrestricted|jailbreak|different agent)\b/i
19333
+ ];
19334
+ var TOOL_DIRECTIVE_RES2 = [
19335
+ /<\s*(?:tool[_-]?call|function[_-]?call|invoke|tool)\b/i,
19336
+ /\b(?:assistant|system)\s+to\s*=\s*[a-z0-9_.-]+/i,
19337
+ /\b(?:call|invoke|use|run|execute)\s+(?:the\s+)?[a-z0-9_.-]+\s+tool\b/i
19338
+ ];
19339
+ var EXFILTRATION_RE2 = /\b(?:reveal|show|print|send|upload|exfiltrat\w*|dump|forward|leak|transmit|export)\b[\s\S]{0,120}(?:\b(?:system\s+prompt|hidden\s+instructions?|secret(?:s)?|credential(?:s)?|password(?:s)?|api\s*keys?|tokens?|private\s+keys?|environment\s+variables?)\b|\.env\b|~\/(?:\.ssh)\/\S+|\/etc\/(?:shadow|passwd)\b)/i;
19340
+ var EXFILTRATION_REVERSE2 = /(?:\b(?:system\s+prompt|hidden\s+instructions?|secret(?:s)?|credential(?:s)?|password(?:s)?|api\s*keys?|tokens?|private\s+keys?|environment\s+variables?)\b|\.env\b|~\/(?:\.ssh)\/\S+|\/etc\/(?:shadow|passwd)\b)[\s\S]{0,120}\b(?:reveal|show|print|send|upload|exfiltrat\w*|dump|forward|leak|transmit|export)\b/i;
19341
+ var CREDENTIAL_HARVESTING_RE2 = /\b(?:enter|paste|provide|share|send|give|submit|type|hand over)\b[\s\S]{0,80}\b(?:password|api\s*key|token|secret|credential|private\s+key)\b/i;
19342
+ var DANGEROUS_SHELL_RE2 = /\b(?:curl|wget)\b[^\n]{0,240}\|\s*(?:ba|z|fi)?sh\b|\brm\s+-rf\s+(?:\/|~|\.ssh)[^\n]{0,240}|\b(?:cat|head|tail)\s+~\/?\.ssh\/(?:id_[a-z]+|authorized_keys)\b|\b(?:printenv|env)\b[^\n]{0,120}\b(?:curl|wget|send|upload|post)\b/i;
19343
+ function matchHasDefensiveContext2(content, match) {
19344
+ if (!match || !match[0])
19345
+ return false;
19346
+ const start = match.index ?? 0;
19347
+ const before = content.slice(Math.max(0, start - 120), start);
19348
+ const after = content.slice(start + match[0].length, start + match[0].length + 160);
19349
+ return STRONG_DEFENSIVE_CONTEXT_RE2.test(match[0]) || NEGATED_DIRECTIVE_RE2.test(before) || STRONG_DEFENSIVE_CONTEXT_RE2.test(before) || STRONG_DEFENSIVE_CONTEXT_RE2.test(after) || REPORTING_BEFORE_RE2.test(before) || REPORTING_CONTEXT_RE2.test(before) && REPORTING_AFTER_RE2.test(after);
19350
+ }
19351
+ function hasActionableMatch2(content, patterns) {
19352
+ return patterns.some((pattern) => {
19353
+ const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
19354
+ const searchable = new RegExp(pattern.source, flags);
19355
+ let match;
19356
+ while ((match = searchable.exec(content)) !== null) {
19357
+ if (!matchHasDefensiveContext2(content, match))
19358
+ return true;
19359
+ if (match[0].length === 0)
19360
+ searchable.lastIndex += 1;
19361
+ }
19362
+ return false;
19363
+ });
19364
+ }
19365
+ function hasDangerousShell2(content) {
19366
+ const searchable = new RegExp(DANGEROUS_SHELL_RE2.source, `${DANGEROUS_SHELL_RE2.flags}g`);
19367
+ let match;
19368
+ while ((match = searchable.exec(content)) !== null) {
19369
+ if (matchHasDefensiveContext2(content, match))
19370
+ continue;
19371
+ const end = (match.index ?? 0) + match[0].length;
19372
+ const after = content.slice(end, end + 160);
19373
+ if (!STRONG_DEFENSIVE_CONTEXT_RE2.test(after) && !REPORTING_AFTER_RE2.test(after))
19374
+ return true;
19375
+ if (match[0].length === 0)
19376
+ searchable.lastIndex += 1;
19377
+ }
19378
+ return false;
19379
+ }
19380
+ function scanMemoryContent2(content) {
19381
+ const raw = typeof content === "string" ? content : String(content ?? "");
19382
+ const normalized = raw.normalize("NFKC");
19383
+ const reasons = new Set;
19384
+ if (INVISIBLE_UNICODE_RE2.test(raw))
19385
+ reasons.add("invisible_unicode");
19386
+ if (hasActionableMatch2(normalized, PROMPT_INJECTION_RES2))
19387
+ reasons.add("prompt_injection");
19388
+ if (hasActionableMatch2(normalized, TOOL_DIRECTIVE_RES2))
19389
+ reasons.add("tool_directive");
19390
+ if (hasActionableMatch2(normalized, [EXFILTRATION_RE2, EXFILTRATION_REVERSE2]))
19391
+ reasons.add("exfiltration");
19392
+ if (hasActionableMatch2(normalized, [CREDENTIAL_HARVESTING_RE2]))
19393
+ reasons.add("credential_harvesting");
19394
+ if (hasDangerousShell2(normalized))
19395
+ reasons.add("malicious_shell");
19396
+ const orderedReasons = MEMORY_CONTENT_SAFETY_REASONS2.filter((reason) => reasons.has(reason));
19397
+ const status = orderedReasons.length === 0 ? "clean" : orderedReasons.every((reason) => reason === "invisible_unicode") ? "tainted" : "blocked";
19398
+ return {
19399
+ status,
19400
+ contextEligible: status === "clean",
19401
+ reasons: orderedReasons,
19402
+ policyVersion: MEMORY_CONTENT_SAFETY_POLICY_VERSION2
19403
+ };
19404
+ }
19124
19405
  var DAEMON_DERIVED_MEMORY_SOURCE_TYPES2 = ["extract", "aggregate-recall", "session_end", "checkpoint", "dreaming"];
19125
19406
  var MEMORIES_FTS_TOKENIZER2 = "unicode61";
19126
19407
  function normalizeSql2(sql) {
@@ -19172,7 +19453,7 @@ function memoriesFtsNeedsTokenizerRepair2(sql) {
19172
19453
  return true;
19173
19454
  return !normalized.includes(`tokenize='${MEMORIES_FTS_TOKENIZER2}'`);
19174
19455
  }
19175
- function up125(db) {
19456
+ function up126(db) {
19176
19457
  db.exec(`
19177
19458
  CREATE TABLE IF NOT EXISTS schema_migrations (
19178
19459
  version INTEGER PRIMARY KEY,
@@ -19261,12 +19542,12 @@ function up125(db) {
19261
19542
  } catch {}
19262
19543
  createMemoriesFts2(db);
19263
19544
  }
19264
- function hasColumn24(db, table, column) {
19545
+ function hasColumn25(db, table, column) {
19265
19546
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
19266
19547
  return rows.some((r) => r.name === column);
19267
19548
  }
19268
19549
  function addColumnIfMissing27(db, table, column, definition) {
19269
- if (!hasColumn24(db, table, column)) {
19550
+ if (!hasColumn25(db, table, column)) {
19270
19551
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
19271
19552
  }
19272
19553
  }
@@ -19416,12 +19697,12 @@ function up310(db) {
19416
19697
  WHERE content_hash IS NOT NULL AND is_deleted = 0
19417
19698
  `);
19418
19699
  }
19419
- function hasColumn25(db, table, column) {
19700
+ function hasColumn26(db, table, column) {
19420
19701
  const rows = db.prepare(`PRAGMA table_info(${table})`).all();
19421
19702
  return rows.some((r) => r.name === column);
19422
19703
  }
19423
19704
  function addColumnIfMissing32(db, table, column, definition) {
19424
- if (!hasColumn25(db, table, column)) {
19705
+ if (!hasColumn26(db, table, column)) {
19425
19706
  db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
19426
19707
  }
19427
19708
  }
@@ -19615,7 +19896,7 @@ function up1110(db) {
19615
19896
  ON session_scores(session_key);
19616
19897
  `);
19617
19898
  }
19618
- function up126(db) {
19899
+ function up127(db) {
19619
19900
  db.exec(`
19620
19901
  CREATE TABLE IF NOT EXISTS scheduled_tasks (
19621
19902
  id TEXT PRIMARY KEY,
@@ -22407,7 +22688,7 @@ function up1032(db) {
22407
22688
  )
22408
22689
  `);
22409
22690
  }
22410
- function tableExists2(db, table) {
22691
+ function tableExists3(db, table) {
22411
22692
  return db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) !== undefined;
22412
22693
  }
22413
22694
  function hasColumn162(db, table, column) {
@@ -22429,7 +22710,7 @@ function up1042(db) {
22429
22710
  CREATE INDEX IF NOT EXISTS idx_derived_memory_sources_source
22430
22711
  ON derived_memory_sources(agent_id, source_kind, source_id);
22431
22712
  `);
22432
- if (tableExists2(db, "aggregate_evidence_sources")) {
22713
+ if (tableExists3(db, "aggregate_evidence_sources")) {
22433
22714
  db.exec(`
22434
22715
  INSERT OR IGNORE INTO derived_memory_sources
22435
22716
  (derived_memory_id, source_kind, source_id, source_path, agent_id, created_at)
@@ -22437,7 +22718,7 @@ function up1042(db) {
22437
22718
  FROM aggregate_evidence_sources
22438
22719
  `);
22439
22720
  }
22440
- if (tableExists2(db, "aggregate_memory_sources")) {
22721
+ if (tableExists3(db, "aggregate_memory_sources")) {
22441
22722
  db.exec(`
22442
22723
  INSERT OR IGNORE INTO derived_memory_sources
22443
22724
  (derived_memory_id, source_kind, source_id, source_path, agent_id, created_at)
@@ -22445,10 +22726,10 @@ function up1042(db) {
22445
22726
  FROM aggregate_memory_sources
22446
22727
  `);
22447
22728
  }
22448
- if (tableExists2(db, "memories") && !hasColumn162(db, "memories", "stale_at")) {
22729
+ if (tableExists3(db, "memories") && !hasColumn162(db, "memories", "stale_at")) {
22449
22730
  db.exec("ALTER TABLE memories ADD COLUMN stale_at TEXT");
22450
22731
  }
22451
- if (tableExists2(db, "memories")) {
22732
+ if (tableExists3(db, "memories")) {
22452
22733
  db.exec(`
22453
22734
  CREATE INDEX IF NOT EXISTS idx_memories_stale_derived
22454
22735
  ON memories(agent_id, stale_at)
@@ -23099,11 +23380,100 @@ function up1242(db) {
23099
23380
  ON imported_source_lifecycle(agent_id, status, updated_at DESC);
23100
23381
  `);
23101
23382
  }
23383
+ function tableExists22(db, table) {
23384
+ return db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) != null;
23385
+ }
23386
+ function hasColumn242(db, table, column) {
23387
+ return db.prepare(`PRAGMA table_info("${table}")`).all().some((row) => row.name === column);
23388
+ }
23389
+ function backfill2(db, rows, sourceKind, scannedAt) {
23390
+ const statement = db.prepare(`INSERT INTO memory_content_safety
23391
+ (agent_id, source_kind, source_id, status, context_eligible, reasons_json, policy_version, scanned_at)
23392
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23393
+ ON CONFLICT(agent_id, source_kind, source_id) DO UPDATE SET
23394
+ status = excluded.status,
23395
+ context_eligible = excluded.context_eligible,
23396
+ reasons_json = excluded.reasons_json,
23397
+ policy_version = excluded.policy_version,
23398
+ scanned_at = excluded.scanned_at`);
23399
+ for (const row of rows) {
23400
+ const sourceId = row.source_id?.trim();
23401
+ if (!sourceId)
23402
+ continue;
23403
+ const assessment = scanMemoryContent2(row.content ?? "");
23404
+ statement.run(row.agent_id?.trim() || "default", sourceKind, sourceId, assessment.status, assessment.contextEligible ? 1 : 0, JSON.stringify(assessment.reasons), MEMORY_CONTENT_SAFETY_POLICY_VERSION2, scannedAt);
23405
+ }
23406
+ }
23407
+ function backfillTable2(db, params) {
23408
+ if (!tableExists22(db, params.table) || !hasColumn242(db, params.table, params.sourceIdColumn) || !hasColumn242(db, params.table, params.contentColumn))
23409
+ return;
23410
+ const agentColumn = hasColumn242(db, params.table, "agent_id") ? "COALESCE(NULLIF(TRIM(agent_id), ''), 'default')" : "'default'";
23411
+ const rows = db.prepare(`SELECT ${agentColumn} AS agent_id, ${params.sourceIdColumn} AS source_id, ${params.contentColumn} AS content
23412
+ FROM ${params.table}${params.where ? ` WHERE ${params.where}` : ""}`).all();
23413
+ backfill2(db, rows, params.sourceKind, params.scannedAt);
23414
+ }
23415
+ function up1252(db) {
23416
+ db.exec(`
23417
+ CREATE TABLE IF NOT EXISTS memory_content_safety (
23418
+ agent_id TEXT NOT NULL,
23419
+ source_kind TEXT NOT NULL CHECK (source_kind IN ('memory', 'artifact', 'transcript', 'summary', 'source_chunk')),
23420
+ source_id TEXT NOT NULL,
23421
+ status TEXT NOT NULL CHECK (status IN ('clean', 'tainted', 'blocked')),
23422
+ context_eligible INTEGER NOT NULL CHECK (context_eligible IN (0, 1)),
23423
+ reasons_json TEXT NOT NULL DEFAULT '[]',
23424
+ policy_version TEXT NOT NULL,
23425
+ scanned_at TEXT NOT NULL,
23426
+ PRIMARY KEY (agent_id, source_kind, source_id)
23427
+ );
23428
+
23429
+ CREATE INDEX IF NOT EXISTS idx_memory_content_safety_status
23430
+ ON memory_content_safety(agent_id, status, source_kind);
23431
+ CREATE INDEX IF NOT EXISTS idx_memory_content_safety_eligibility
23432
+ ON memory_content_safety(agent_id, source_kind, context_eligible);
23433
+ `);
23434
+ const scannedAt = new Date().toISOString();
23435
+ backfillTable2(db, {
23436
+ table: "memories",
23437
+ sourceKind: "memory",
23438
+ sourceIdColumn: "id",
23439
+ contentColumn: "content",
23440
+ scannedAt
23441
+ });
23442
+ backfillTable2(db, {
23443
+ table: "memory_artifacts",
23444
+ sourceKind: "artifact",
23445
+ sourceIdColumn: "source_path",
23446
+ contentColumn: "content",
23447
+ scannedAt
23448
+ });
23449
+ backfillTable2(db, {
23450
+ table: "session_transcripts",
23451
+ sourceKind: "transcript",
23452
+ sourceIdColumn: "session_key",
23453
+ contentColumn: "content",
23454
+ scannedAt
23455
+ });
23456
+ backfillTable2(db, {
23457
+ table: "session_summaries",
23458
+ sourceKind: "summary",
23459
+ sourceIdColumn: "id",
23460
+ contentColumn: "content",
23461
+ scannedAt
23462
+ });
23463
+ backfillTable2(db, {
23464
+ table: "embeddings",
23465
+ sourceKind: "source_chunk",
23466
+ sourceIdColumn: "id",
23467
+ contentColumn: "chunk_text",
23468
+ where: "source_type IN ('source_chunk', 'source_obsidian_chunk')",
23469
+ scannedAt
23470
+ });
23471
+ }
23102
23472
  var MIGRATIONS2 = [
23103
23473
  {
23104
23474
  version: 1,
23105
23475
  name: "baseline",
23106
- up: up125,
23476
+ up: up126,
23107
23477
  artifacts: { tables: ["memories", "conversations", "embeddings"] }
23108
23478
  },
23109
23479
  {
@@ -23175,7 +23545,7 @@ var MIGRATIONS2 = [
23175
23545
  {
23176
23546
  version: 12,
23177
23547
  name: "scheduled-tasks",
23178
- up: up126,
23548
+ up: up127,
23179
23549
  artifacts: { tables: ["scheduled_tasks", "task_runs"] }
23180
23550
  },
23181
23551
  {
@@ -24107,6 +24477,12 @@ var MIGRATIONS2 = [
24107
24477
  artifacts: {
24108
24478
  tables: ["imported_source_lifecycle"]
24109
24479
  }
24480
+ },
24481
+ {
24482
+ version: 125,
24483
+ name: "memory-content-safety",
24484
+ up: up1252,
24485
+ artifacts: { tables: ["memory_content_safety"] }
24110
24486
  }
24111
24487
  ];
24112
24488
  var LATEST_SCHEMA_VERSION2 = MIGRATIONS2[MIGRATIONS2.length - 1]?.version ?? 0;
@@ -56,10 +56,10 @@ Environment variables:
56
56
 
57
57
  The plugin bridges Hermes Agent's memory lifecycle to the Signet daemon:
58
58
 
59
- 1. **Session start** — Calls Signet's session-start hook, which returns identity files (AGENTS.md, SOUL.md, USER.md, MEMORY.md), scored memories, and knowledge graph constraints. Injected into the system prompt.
59
+ 1. **Session start** — Calls Signet's session-start hook, which returns identity files (AGENTS.md, SOUL.md, USER.md, MEMORY.md), scored memories, and knowledge graph constraints. The deterministic `stableSystemPrompt` is returned by `system_prompt_block()`; state-dependent `dynamicContext` is staged for Hermes' API-only prefetch path rather than being added to the canonical transcript.
60
60
 
61
61
  2. **Per-turn recall** — On each user message, calls the user-prompt-submit hook. Signet runs hybrid search (BM25 + vector similarity + knowledge graph traversal + predictive scoring) and returns the most relevant memories.
62
62
 
63
- 3. **Session end** — Sends the conversation transcript to Signet's session-end hook, which queues it for the memory pipeline: extraction, knowledge graph updates, retention decay, and MEMORY.md synthesis.
63
+ 3. **Session end** — Sends a transcript with internal Signet memory delimiters removed to Signet's session-end hook, which queues it for the memory pipeline: extraction, knowledge graph updates, retention decay, and MEMORY.md synthesis.
64
64
 
65
65
  4. **Explicit tools** — The agent can call canonical Signet tools such as `memory_search` and `memory_store` directly during conversation for on-demand memory operations. Legacy `signet_*` names are handled for compatibility but are not advertised to the model.
@@ -23,6 +23,7 @@ from __future__ import annotations
23
23
  import json
24
24
  import logging
25
25
  import os
26
+ import re
26
27
  import threading
27
28
  from pathlib import Path
28
29
  from typing import Any, Dict, List, Optional
@@ -39,6 +40,20 @@ except ImportError: # pragma: no cover — only missing during Hermes bootstrap
39
40
 
40
41
  logger = logging.getLogger(__name__)
41
42
 
43
+ _INTERNAL_MEMORY_BLOCK_RE = re.compile(
44
+ r"<\\?\s*(?:signet-memory-context|signet-memory|memory-context)(?=[\s/>])(?:[^>\"']|\"[^\"]*\"|'[^']*')*>.*?(?:<\\?\s*/\s*(?:signet-memory-context|signet-memory|memory-context)\s*>|$)",
45
+ re.IGNORECASE | re.DOTALL,
46
+ )
47
+ _INTERNAL_MEMORY_CLOSE_RE = re.compile(
48
+ r"<\\?\s*/\s*(?:signet-memory-context|signet-memory|memory-context)\s*>",
49
+ re.IGNORECASE,
50
+ )
51
+
52
+
53
+ def _strip_internal_memory_context(value: str) -> str:
54
+ """Keep provider-only memory wrappers out of Hermes transcript state."""
55
+ return _INTERNAL_MEMORY_CLOSE_RE.sub("", _INTERNAL_MEMORY_BLOCK_RE.sub("", value))
56
+
42
57
 
43
58
  # ---------------------------------------------------------------------------
44
59
  # Tool schemas
@@ -326,6 +341,10 @@ class SignetMemoryProvider(MemoryProvider):
326
341
  self._project = ""
327
342
  self._inject_cache = ""
328
343
  self._inject_lock = threading.Lock()
344
+ # Session-start dynamic context is kept separate from the ordinary
345
+ # per-turn result. queue_prefetch() clears the latter before starting
346
+ # a new recall, but must not erase the first API-only context block.
347
+ self._session_prefetch_result = ""
329
348
  self._prefetch_result = ""
330
349
  self._notification_result = ""
331
350
  self._prefetch_lock = threading.Lock()
@@ -390,8 +409,9 @@ class SignetMemoryProvider(MemoryProvider):
390
409
  def initialize(self, session_id: str, **kwargs) -> None:
391
410
  """Connect to the Signet daemon and call session-start hook.
392
411
 
393
- Retrieves identity, memories, and system prompt injection from
394
- the daemon. Caches the inject text for system_prompt_block().
412
+ Retrieves identity, memories, and the cache-stable prompt contract
413
+ from the daemon. The stable prefix is cached for system_prompt_block;
414
+ dynamic session context is staged for Hermes' API-only prefetch path.
395
415
  """
396
416
  if SignetClient is None:
397
417
  logger.warning("Signet plugin: SignetClient not importable — skipping initialization")
@@ -429,23 +449,30 @@ class SignetMemoryProvider(MemoryProvider):
429
449
  self._session_key = session_id or "hermes-default"
430
450
  self._project = _resolve_agent_workspace(agent_id, kwargs)
431
451
 
432
- # Call session-start hook — get identity + memories + inject
452
+ # Call session-start hook — get identity + memories + split context
433
453
  result = self._client.session_start(
434
454
  self._session_key,
435
455
  project=self._project,
436
456
  )
437
457
  if result:
438
- inject = result.get("inject", "")
439
- if inject:
458
+ raw_stable_prompt = result.get("stableSystemPrompt") or result.get("inject", "")
459
+ stable_prompt = raw_stable_prompt if isinstance(raw_stable_prompt, str) else ""
460
+ dynamic_context = result.get("dynamicContext", "")
461
+ if stable_prompt:
440
462
  with self._inject_lock:
441
- self._inject_cache = inject
463
+ self._inject_cache = stable_prompt
464
+ with self._prefetch_lock:
465
+ self._prefetch_generation += 1
466
+ self._session_prefetch_result = dynamic_context if isinstance(dynamic_context, str) else ""
467
+ self._prefetch_result = ""
468
+ self._notification_result = ""
442
469
  # Capture identity and warnings for downstream consumers
443
470
  self._identity = result.get("identity")
444
471
  self._warnings = result.get("warnings", [])
445
472
  self._session_initialized = True
446
473
  logger.debug(
447
474
  "Signet session-start: %d chars inject, %d memories",
448
- len(inject),
475
+ len(stable_prompt) + (len(dynamic_context) if isinstance(dynamic_context, str) else 0),
449
476
  len(result.get("memories", [])),
450
477
  )
451
478
  else:
@@ -454,16 +481,17 @@ class SignetMemoryProvider(MemoryProvider):
454
481
  def system_prompt_block(self) -> str:
455
482
  """Return the Signet system prompt injection.
456
483
 
457
- On the first call, returns the full session-start inject
458
- (identity, memories, context). Subsequent calls return a
459
- minimal header since per-turn recall is handled by prefetch().
484
+ On the first call, returns only the deterministic session-start
485
+ prefix. Dynamic context is returned by prefetch(), where Hermes can
486
+ attach it to its API-only copy of the user message. Subsequent calls
487
+ return a minimal header.
460
488
  """
461
489
  if not self._client:
462
490
  return ""
463
491
 
464
492
  with self._inject_lock:
465
493
  if self._inject_cache:
466
- # First call — return full inject and clear cache
494
+ # First call — return the stable prefix and clear the cache.
467
495
  block = self._inject_cache
468
496
  self._inject_cache = ""
469
497
  return block
@@ -502,7 +530,8 @@ class SignetMemoryProvider(MemoryProvider):
502
530
  logger.debug("Signet notification prefetch failed: %s", e)
503
531
 
504
532
  with self._prefetch_lock:
505
- parts = [self._prefetch_result, self._notification_result]
533
+ parts = [self._session_prefetch_result, self._prefetch_result, self._notification_result]
534
+ self._session_prefetch_result = ""
506
535
  self._prefetch_result = ""
507
536
  self._notification_result = ""
508
537
 
@@ -520,7 +549,7 @@ class SignetMemoryProvider(MemoryProvider):
520
549
 
521
550
  # Accumulate transcript for checkpoint/session-end
522
551
  with self._transcript_lock:
523
- self._transcript_lines.append(f"user: {query}")
552
+ self._transcript_lines.append(f"user: {_strip_internal_memory_context(query)}")
524
553
 
525
554
  # Capture mutable state before spawning the thread to avoid
526
555
  # data races: sync_turn() can update _last_assistant_message
@@ -550,6 +579,10 @@ class SignetMemoryProvider(MemoryProvider):
550
579
  # the cached prefix mid-conversation.
551
580
  if not result.get("sessionKnown", True) and self._session_initialized:
552
581
  logger.debug("Signet daemon restarted mid-session, restoring session claim")
582
+ with self._prefetch_lock:
583
+ # Do not replay a pre-restart session-start block
584
+ # into an already-running Hermes conversation.
585
+ self._session_prefetch_result = ""
553
586
  reinit = client.session_start(
554
587
  session_key, project=project, claim_only=True,
555
588
  )
@@ -559,7 +592,7 @@ class SignetMemoryProvider(MemoryProvider):
559
592
  "the next prompt may be treated as a new session"
560
593
  )
561
594
  return
562
- inject = result.get("inject", "")
595
+ inject = result.get("dynamicContext") or result.get("inject", "")
563
596
  notification = result.get("notifications")
564
597
  notification_inject = notification.get("inject", "") if isinstance(notification, dict) else ""
565
598
  recall_inject = inject
@@ -607,7 +640,7 @@ class SignetMemoryProvider(MemoryProvider):
607
640
  # Accumulate assistant side of transcript
608
641
  if assistant_content:
609
642
  with self._transcript_lock:
610
- self._transcript_lines.append(f"assistant: {assistant_content}")
643
+ self._transcript_lines.append(f"assistant: {_strip_internal_memory_context(assistant_content)}")
611
644
  self._queue_notification_refresh("sync_turn")
612
645
 
613
646
  def _queue_notification_refresh(self, hook: str) -> None:
@@ -661,6 +694,7 @@ class SignetMemoryProvider(MemoryProvider):
661
694
  self._inject_cache = ""
662
695
  with self._prefetch_lock:
663
696
  self._prefetch_generation += 1
697
+ self._session_prefetch_result = ""
664
698
  self._prefetch_result = ""
665
699
  self._notification_result = ""
666
700
 
@@ -676,10 +710,13 @@ class SignetMemoryProvider(MemoryProvider):
676
710
  project=self._project,
677
711
  )
678
712
  if result:
679
- inject = result.get("inject", "")
680
- if inject and inject.strip():
713
+ stable_prompt = result.get("stableSystemPrompt") or result.get("inject", "")
714
+ dynamic_context = result.get("dynamicContext", "")
715
+ if stable_prompt and isinstance(stable_prompt, str) and stable_prompt.strip():
681
716
  with self._inject_lock:
682
- self._inject_cache = inject
717
+ self._inject_cache = stable_prompt
718
+ with self._prefetch_lock:
719
+ self._session_prefetch_result = dynamic_context if isinstance(dynamic_context, str) else ""
683
720
  self._identity = result.get("identity")
684
721
  self._warnings = result.get("warnings", [])
685
722
  self._session_initialized = True
@@ -732,6 +769,10 @@ class SignetMemoryProvider(MemoryProvider):
732
769
 
733
770
  def on_session_end(self, messages: List[Dict[str, Any]]) -> None:
734
771
  """Call session-end hook to trigger memory extraction from transcript."""
772
+ with self._prefetch_lock:
773
+ self._session_prefetch_result = ""
774
+ self._prefetch_result = ""
775
+ self._notification_result = ""
735
776
  if not self._client:
736
777
  return
737
778
 
@@ -746,7 +787,7 @@ class SignetMemoryProvider(MemoryProvider):
746
787
  role = msg.get("role", "unknown")
747
788
  content = msg.get("content", "")
748
789
  if content:
749
- transcript_lines.append(f"{role}: {content}")
790
+ transcript_lines.append(f"{role}: {_strip_internal_memory_context(str(content))}")
750
791
  transcript = "\n\n".join(transcript_lines)
751
792
 
752
793
  if not transcript:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signetai/connector-hermes-agent",
3
- "version": "0.193.1",
3
+ "version": "0.193.3",
4
4
  "description": "Signet connector for Hermes Agent — installs Signet as a pluggable memory provider",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,8 +25,8 @@
25
25
  "typecheck": "tsc --noEmit"
26
26
  },
27
27
  "dependencies": {
28
- "@signetai/connector-base": "0.193.1",
29
- "@signetai/core": "0.193.1"
28
+ "@signetai/connector-base": "0.193.3",
29
+ "@signetai/core": "0.193.3"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.0.0",