@signetai/connector-base 0.185.4 → 0.185.6

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 (2) hide show
  1. package/dist/index.js +251 -0
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -40,6 +40,7 @@ import { dirname as dirname5, isAbsolute, join as join3, relative, sep } from "n
40
40
  import { createRequire } from "node:module";
41
41
  import { dirname, join } from "node:path";
42
42
  import { fileURLToPath } from "node:url";
43
+ import { createHash } from "node:crypto";
43
44
  import { homedir as homedir2 } from "os";
44
45
  import { join as join2 } from "path";
45
46
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
@@ -10576,6 +10577,240 @@ function up116(db) {
10576
10577
  ON cross_agent_messages(delivery_path, delivery_state, delivery_lease_expires_at, delivery_updated_at);
10577
10578
  `);
10578
10579
  }
10580
+ function hasTable4(db, table) {
10581
+ return db.prepare("SELECT 1 AS present FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) != null;
10582
+ }
10583
+ function addColumnIfMissing25(db, table, column, definition) {
10584
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
10585
+ if (columns.some((row) => row.name === column))
10586
+ return;
10587
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
10588
+ }
10589
+ function tableColumns(db, table) {
10590
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
10591
+ return new Set(rows.map((row) => typeof row.name === "string" ? row.name : ""));
10592
+ }
10593
+ function hashTranscript(content) {
10594
+ return createHash("sha256").update(content, "utf8").digest("hex");
10595
+ }
10596
+ function backfillTranscriptHashes(db) {
10597
+ const columns = tableColumns(db, "session_transcripts");
10598
+ if (!columns.has("content_hash") || !columns.has("content"))
10599
+ return;
10600
+ const rows = db.prepare("SELECT session_key, agent_id, content FROM session_transcripts WHERE content_hash IS NULL").all();
10601
+ const update = db.prepare("UPDATE session_transcripts SET content_hash = ? WHERE agent_id = ? AND session_key = ? AND content_hash IS NULL");
10602
+ for (const row of rows) {
10603
+ if (typeof row.content !== "string" || typeof row.session_key !== "string")
10604
+ continue;
10605
+ update.run(hashTranscript(row.content), typeof row.agent_id === "string" ? row.agent_id : "default", row.session_key);
10606
+ }
10607
+ }
10608
+ var COMPLETION_BOUNDARY_REASONS = new Set(["session_closed", "session_clear", "stale_session", "ttl_expired"]);
10609
+ function summaryJobTimestamp(job) {
10610
+ return job.completed_at ?? job.ended_at ?? job.captured_at ?? job.created_at ?? new Date(0).toISOString();
10611
+ }
10612
+ function laterTimestamp(current, candidate) {
10613
+ if (current === null)
10614
+ return candidate;
10615
+ if (candidate === null)
10616
+ return current;
10617
+ const currentMillis = Date.parse(current);
10618
+ const candidateMillis = Date.parse(candidate);
10619
+ if (Number.isFinite(currentMillis) && Number.isFinite(candidateMillis)) {
10620
+ return candidateMillis > currentMillis ? candidate : current;
10621
+ }
10622
+ return candidate > current ? candidate : current;
10623
+ }
10624
+ function isCompletionBoundary(job, columns) {
10625
+ return columns.has("trigger") && job.trigger === "session_end" || columns.has("boundary_reason") && COMPLETION_BOUNDARY_REASONS.has(job.boundary_reason ?? "");
10626
+ }
10627
+ function mergeTranscriptContent(current, next) {
10628
+ if (current.length === 0)
10629
+ return next;
10630
+ if (next.length === 0 || current === next || current.includes(next))
10631
+ return current;
10632
+ if (next.includes(current))
10633
+ return next;
10634
+ return `${current}
10635
+ ${next}`;
10636
+ }
10637
+ function backfillTranscriptsFromSummaryJobs(db) {
10638
+ if (!hasTable4(db, "summary_jobs"))
10639
+ return;
10640
+ const summaryColumns = tableColumns(db, "summary_jobs");
10641
+ if (!summaryColumns.has("transcript"))
10642
+ return;
10643
+ const select = (name) => summaryColumns.has(name) ? `"${name}" AS "${name}"` : `NULL AS "${name}"`;
10644
+ const jobs = db.prepare(`SELECT ${[
10645
+ "id",
10646
+ "session_key",
10647
+ "transcript",
10648
+ "harness",
10649
+ "project",
10650
+ "agent_id",
10651
+ "trigger",
10652
+ "boundary_reason",
10653
+ "captured_at",
10654
+ "ended_at",
10655
+ "completed_at",
10656
+ "created_at"
10657
+ ].map(select).join(", ")} FROM summary_jobs`).all();
10658
+ const candidates = new Map;
10659
+ for (const job of jobs) {
10660
+ if (typeof job.transcript !== "string" || job.transcript.trim().length === 0)
10661
+ continue;
10662
+ const agentId = typeof job.agent_id === "string" && job.agent_id.length > 0 ? job.agent_id : "default";
10663
+ const sessionKey = typeof job.session_key === "string" && job.session_key.trim().length > 0 ? job.session_key : `legacy-summary-job:${job.id ?? hashTranscript(`${summaryJobTimestamp(job)}\x00${job.transcript}`)}`;
10664
+ const key = `${agentId}\x00${sessionKey}`;
10665
+ const current = candidates.get(key);
10666
+ const timestamp = summaryJobTimestamp(job);
10667
+ const boundary = isCompletionBoundary(job, summaryColumns);
10668
+ const createdAt = job.captured_at ?? job.created_at ?? job.ended_at ?? timestamp;
10669
+ if (!current) {
10670
+ candidates.set(key, {
10671
+ sessionKey,
10672
+ job: { ...job, agent_id: agentId },
10673
+ content: job.transcript,
10674
+ createdAt,
10675
+ completedAt: boundary ? timestamp : null
10676
+ });
10677
+ continue;
10678
+ }
10679
+ const currentTimestamp = summaryJobTimestamp(current.job);
10680
+ const preferred = timestamp > currentTimestamp || timestamp === currentTimestamp && job.transcript.length > current.content.length ? { ...job, agent_id: agentId } : current.job;
10681
+ candidates.set(key, {
10682
+ sessionKey,
10683
+ job: preferred,
10684
+ content: mergeTranscriptContent(current.content, job.transcript),
10685
+ createdAt: current.createdAt < createdAt ? current.createdAt : createdAt,
10686
+ completedAt: laterTimestamp(current.completedAt, boundary ? timestamp : null)
10687
+ });
10688
+ }
10689
+ const transcriptColumns = tableColumns(db, "session_transcripts");
10690
+ const hasUpdated = transcriptColumns.has("updated_at");
10691
+ const hasCompleted = transcriptColumns.has("completed_at");
10692
+ const hasHash = transcriptColumns.has("content_hash");
10693
+ const insertColumns = ["session_key", "content", "harness", "project", "agent_id", "created_at"];
10694
+ if (hasUpdated)
10695
+ insertColumns.push("updated_at");
10696
+ if (hasCompleted)
10697
+ insertColumns.push("completed_at");
10698
+ if (hasHash)
10699
+ insertColumns.push("content_hash");
10700
+ const insert = db.prepare(`INSERT OR IGNORE INTO session_transcripts (${insertColumns.join(", ")}) VALUES (${insertColumns.map(() => "?").join(", ")})`);
10701
+ const existing = db.prepare("SELECT content, completed_at FROM session_transcripts WHERE agent_id = ? AND session_key = ?");
10702
+ for (const candidate of candidates.values()) {
10703
+ const job = candidate.job;
10704
+ const agentId = job.agent_id ?? "default";
10705
+ const existingRow = existing.get(agentId, candidate.sessionKey);
10706
+ if (existingRow != null) {
10707
+ const previousContent = typeof existingRow.content === "string" ? existingRow.content : "";
10708
+ const mergedContent = mergeTranscriptContent(previousContent, candidate.content);
10709
+ const completedAt = typeof existingRow.completed_at === "string" && existingRow.completed_at.length > 0 ? existingRow.completed_at : candidate.completedAt;
10710
+ const assignments = ["content = ?"];
10711
+ const values2 = [mergedContent];
10712
+ if (hasUpdated && mergedContent !== previousContent) {
10713
+ assignments.push("updated_at = ?");
10714
+ values2.push(candidate.createdAt);
10715
+ }
10716
+ if (hasCompleted && completedAt && !existingRow.completed_at) {
10717
+ assignments.push("completed_at = ?");
10718
+ values2.push(completedAt);
10719
+ }
10720
+ if (hasHash) {
10721
+ assignments.push("content_hash = ?");
10722
+ values2.push(hashTranscript(mergedContent));
10723
+ }
10724
+ values2.push(agentId, candidate.sessionKey);
10725
+ db.prepare(`UPDATE session_transcripts SET ${assignments.join(", ")} WHERE agent_id = ? AND session_key = ?`).run(...values2);
10726
+ continue;
10727
+ }
10728
+ const values = [
10729
+ candidate.sessionKey,
10730
+ candidate.content,
10731
+ job.harness ?? null,
10732
+ job.project ?? null,
10733
+ agentId,
10734
+ candidate.createdAt
10735
+ ];
10736
+ if (hasUpdated)
10737
+ values.push(candidate.createdAt);
10738
+ if (hasCompleted)
10739
+ values.push(candidate.completedAt);
10740
+ if (hasHash)
10741
+ values.push(hashTranscript(candidate.content));
10742
+ insert.run(...values);
10743
+ }
10744
+ }
10745
+ function up117(db) {
10746
+ if (!hasTable4(db, "session_transcripts"))
10747
+ return;
10748
+ addColumnIfMissing25(db, "session_transcripts", "completed_at", "TEXT");
10749
+ addColumnIfMissing25(db, "session_transcripts", "content_hash", "TEXT");
10750
+ backfillTranscriptHashes(db);
10751
+ if (hasTable4(db, "transcript_capture_jobs")) {
10752
+ const captureColumns = db.prepare("PRAGMA table_info(transcript_capture_jobs)").all();
10753
+ if (captureColumns.some((row) => row.name === "summary_status")) {
10754
+ db.exec("UPDATE transcript_capture_jobs SET summary_status = 'not_requested' WHERE summary_status IN ('pending', 'failed', 'skipped')");
10755
+ }
10756
+ }
10757
+ if (hasTable4(db, "summary_jobs")) {
10758
+ backfillTranscriptsFromSummaryJobs(db);
10759
+ backfillTranscriptHashes(db);
10760
+ const summaryColumns = tableColumns(db, "summary_jobs");
10761
+ const timestampParts = ["completed_at", "ended_at", "captured_at", "created_at"].filter((column) => summaryColumns.has(column)).map((column) => `sj.${column}`);
10762
+ const timestampValue = timestampParts.length === 1 ? timestampParts[0] : `COALESCE(${timestampParts.join(", ")})`;
10763
+ const completionTimestamp = timestampValue ? `MAX(${timestampValue})` : "NULL";
10764
+ const boundaryParts = [
10765
+ summaryColumns.has("trigger") ? "sj.trigger = 'session_end'" : null,
10766
+ summaryColumns.has("boundary_reason") ? "sj.boundary_reason IN ('session_closed', 'session_clear', 'stale_session', 'ttl_expired')" : null
10767
+ ].filter((part) => part !== null);
10768
+ const boundaryPredicate = boundaryParts.length > 0 ? `(${boundaryParts.join(" OR ")})` : "1=1";
10769
+ const agentPredicate = summaryColumns.has("agent_id") ? "sj.agent_id = session_transcripts.agent_id" : "session_transcripts.agent_id = 'default'";
10770
+ if (completionTimestamp !== "NULL") {
10771
+ db.exec(`
10772
+ UPDATE session_transcripts
10773
+ SET completed_at = COALESCE(
10774
+ completed_at,
10775
+ (
10776
+ SELECT ${completionTimestamp}
10777
+ FROM summary_jobs AS sj
10778
+ WHERE ${agentPredicate}
10779
+ AND sj.session_key = session_transcripts.session_key
10780
+ AND ${boundaryPredicate}
10781
+ )
10782
+ )
10783
+ WHERE completed_at IS NULL;
10784
+ `);
10785
+ }
10786
+ db.exec("DELETE FROM summary_jobs");
10787
+ }
10788
+ const completionIndexColumns = ["agent_id", "completed_at"];
10789
+ if (tableColumns(db, "session_transcripts").has("updated_at"))
10790
+ completionIndexColumns.push("updated_at");
10791
+ db.exec(`
10792
+ CREATE INDEX IF NOT EXISTS idx_st_agent_completed
10793
+ ON session_transcripts(${completionIndexColumns.join(", ")});
10794
+ CREATE INDEX IF NOT EXISTS idx_st_agent_hash
10795
+ ON session_transcripts(agent_id, content_hash);
10796
+ `);
10797
+ }
10798
+ function up118(db) {
10799
+ db.exec(`
10800
+ CREATE INDEX IF NOT EXISTS idx_memory_jobs_pressure_status
10801
+ ON memory_jobs(status)
10802
+ WHERE status IN ('pending', 'leased') AND job_type <> 'extract';
10803
+ CREATE INDEX IF NOT EXISTS idx_memory_jobs_pressure_created_at
10804
+ ON memory_jobs(created_at)
10805
+ WHERE status IN ('pending', 'leased') AND job_type <> 'extract';
10806
+ CREATE INDEX IF NOT EXISTS idx_summary_jobs_pressure_status
10807
+ ON summary_jobs(status)
10808
+ WHERE status IN ('pending', 'leased');
10809
+ CREATE INDEX IF NOT EXISTS idx_summary_jobs_pressure_created_at
10810
+ ON summary_jobs(created_at)
10811
+ WHERE status IN ('pending', 'leased');
10812
+ `);
10813
+ }
10579
10814
  var MIGRATIONS = [
10580
10815
  {
10581
10816
  version: 1,
@@ -11515,6 +11750,22 @@ var MIGRATIONS = [
11515
11750
  { table: "cross_agent_messages", column: "acp_target_agent_name" }
11516
11751
  ]
11517
11752
  }
11753
+ },
11754
+ {
11755
+ version: 117,
11756
+ name: "retire-summary-worker",
11757
+ up: up117,
11758
+ artifacts: {
11759
+ columns: [
11760
+ { table: "session_transcripts", column: "completed_at" },
11761
+ { table: "session_transcripts", column: "content_hash" }
11762
+ ]
11763
+ }
11764
+ },
11765
+ {
11766
+ version: 118,
11767
+ name: "queue-pressure-indices",
11768
+ up: up118
11518
11769
  }
11519
11770
  ];
11520
11771
  var LATEST_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1]?.version ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signetai/connector-base",
3
- "version": "0.185.4",
3
+ "version": "0.185.6",
4
4
  "description": "Base class for Signet harness connectors",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,7 +26,7 @@
26
26
  "typecheck": "tsc --noEmit"
27
27
  },
28
28
  "dependencies": {
29
- "@signetai/core": "0.185.4",
29
+ "@signetai/core": "0.185.6",
30
30
  "json5": "^2.2.3",
31
31
  "jsonc-parser": "3.3.1"
32
32
  },