@synapsor/client 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -101,6 +101,30 @@ await run.complete({ decision: "waiver_proposed" }, { status: "waiting_approval"
101
101
  const graph = await run.explain();
102
102
  ```
103
103
 
104
+ Find the persisted run/evidence/proposal later by business object, workflow,
105
+ tenant, query fingerprint, or time window:
106
+
107
+ ```js
108
+ const activity = await db.agentActivity.search({
109
+ tenantId: "acme",
110
+ businessObjectId: "T-1042",
111
+ workflow: "support.ticket_refund_flow",
112
+ timeRange: { from: 123, to: 456 },
113
+ limit: 50,
114
+ });
115
+ ```
116
+
117
+ This helper sends the native lookup form:
118
+
119
+ ```sql
120
+ SELECT *
121
+ FROM AGENT ACTIVITY
122
+ WHERE tenant_id = 'acme'
123
+ AND business_object_id = 'T-1042'
124
+ ORDER BY created_at DESC
125
+ LIMIT 50;
126
+ ```
127
+
104
128
  Replay a stored capability run by using the numeric `agent_run_id` returned by
105
129
  Synapsor. Deterministic replay returns the captured persisted run; comparison
106
130
  modes can inspect the original snapshot, current state, a commit version, a
@@ -163,6 +187,7 @@ and reports `applied`, `conflict`, or `failed` back to Synapsor idempotently.
163
187
  - `setSession({...})`
164
188
  - `invokeAgentCapability(name, args, options)`
165
189
  - `agentRuns.start(...)`, `run.invokeCapability(...)`, `run.checkpoint(...)`, `run.complete(...)`, `run.explain(...)`
190
+ - `agentActivity.search({ tenantId, businessObjectId, workflow, timeRange, limit })`
166
191
  - `replayAgentRun(id, { mode, version, timestamp, branchName })`
167
192
  - `externalActions.claim(...)` and `externalActions.confirm(...)`
168
193
  - `createAgentEval(...)` / `evals.create(...)` for run-history or `sourceTable` dataset evals
package/bin/synapsor.mjs CHANGED
@@ -2,12 +2,14 @@
2
2
  import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
4
  import { homedir } from "node:os";
5
+ import { createRequire } from "node:module";
5
6
  import { dirname, join } from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { createInterface } from "node:readline/promises";
8
9
  import { stdin as input, stdout as output } from "node:process";
9
10
 
10
- const PACKAGE_VERSION = "0.1.2";
11
+ const require = createRequire(import.meta.url);
12
+ const { version: PACKAGE_VERSION } = require("../package.json");
11
13
  const DEFAULT_BASE_URL = "https://synapsor.ai";
12
14
  const ERROR_HINTS = new Map([
13
15
  ["AUTH_REQUIRED", "Set SYNAPSOR_API_KEY or run `synapsor config set api-key`."],
@@ -170,6 +172,10 @@ function csvFlag(args, name) {
170
172
  return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : [];
171
173
  }
172
174
 
175
+ function boolFlag(args, name) {
176
+ return args.includes(name);
177
+ }
178
+
173
179
  function resolveSecretRef(value) {
174
180
  const text = String(value || "").trim();
175
181
  if (text.startsWith("env:")) {
@@ -280,24 +286,35 @@ async function main(argv = process.argv.slice(2)) {
280
286
  const envHint = kind === "mysql" ? "APP_MYSQL_URL" : "APP_POSTGRES_URL";
281
287
  const name = flagValue(rest, "--name", defaultName);
282
288
  const url = resolveSecretRef(flagValue(rest, "--url", ""));
289
+ const cdcUrl = resolveSecretRef(flagValue(rest, "--cdc-url", ""));
283
290
  const ssl = flagValue(rest, "--ssl", "require");
284
- const mode = flagValue(rest, "--mode", "read-only");
291
+ const mode = flagValue(rest, "--mode", "live_read");
285
292
  const projectId = scopedProject(globals);
286
293
  const databaseId = scopedDatabase(globals);
287
294
  if (!projectId || !databaseId) throw new Error("PROJECT_AND_DATABASE_REQUIRED: use --project <project_id> --db <database_id> or SYNAPSOR_PROJECT_ID/SYNAPSOR_DATABASE_ID");
288
295
  if (!url) throw new Error(`${kind.toUpperCase()}_URL_REQUIRED: usage \`synapsor sources create ${kind} --name ${defaultName} --url env:${envHint} --project <project> --db <database>\``);
289
- return print(await request(globals, "POST", "/v1/control/external-sources", {
296
+ const body = {
290
297
  project_id: projectId,
291
298
  database_id: databaseId,
292
299
  kind,
293
300
  name,
294
301
  connection: { url, ssl_mode: ssl },
295
302
  mode,
296
- }), globals);
303
+ };
304
+ if (cdcUrl) body.cdc_connection = { url: cdcUrl, ssl_mode: ssl };
305
+ if (mode === "cdc_mirror" || boolFlag(rest, "--cdc-ack")) body.cdc_acknowledged = true;
306
+ return print(await request(globals, "POST", "/v1/control/external-sources", body), globals);
297
307
  }
298
- if (["test", "inspect", "generate", "doctor", "disable"].includes(sub)) {
308
+ if (["test", "inspect", "generate", "doctor", "disable", "cdc-status", "cdc-snapshot", "cdc-poll"].includes(sub)) {
299
309
  const sourceId = await resolveSourceId(globals, third);
300
310
  if (sub === "test") return print(await request(globals, "POST", `/v1/control/external-sources/${encodeURIComponent(sourceId)}/test`, {}), globals);
311
+ if (sub === "cdc-status") return print(await request(globals, "GET", `/v1/control/external-sources/${encodeURIComponent(sourceId)}/cdc/status`), globals);
312
+ if (sub === "cdc-snapshot" || sub === "cdc-poll") {
313
+ const tables = csvFlag(rest, "--tables");
314
+ const body = { cdc_acknowledged: true };
315
+ if (tables.length) body.tables = tables;
316
+ return print(await request(globals, "POST", `/v1/control/external-sources/${encodeURIComponent(sourceId)}/cdc/${sub === "cdc-snapshot" ? "snapshot" : "poll"}`, body), globals);
317
+ }
301
318
  if (sub === "inspect") {
302
319
  const schema = flagValue(rest, "--schema", "public");
303
320
  const database = flagValue(rest, "--database", "");
@@ -338,10 +355,10 @@ async function main(argv = process.argv.slice(2)) {
338
355
  const database = flagValue(rest, "--database", "");
339
356
  const tables = csvFlag(rest, "--tables");
340
357
  const tenantColumn = flagValue(rest, "--tenant-column", "");
341
- const mode = flagValue(rest, "--mode", "live-read");
358
+ const mode = flagValue(rest, "--mode", "live_read");
342
359
  if (!tables.length) throw new Error("TABLES_REQUIRED: pass --tables tickets,customers,policy_chunks");
343
360
  if (!tenantColumn && !rest.includes("--single-tenant")) throw new Error("TENANT_COLUMN_REQUIRED: pass --tenant-column tenant_id or --single-tenant");
344
- return print(await request(globals, "POST", `/v1/control/external-sources/${encodeURIComponent(sourceId)}/import`, {
361
+ const body = {
345
362
  tables: tables.map((table) => ({
346
363
  ...(database ? { database } : {}),
347
364
  schema,
@@ -352,7 +369,9 @@ async function main(argv = process.argv.slice(2)) {
352
369
  single_tenant: rest.includes("--single-tenant"),
353
370
  mode,
354
371
  })),
355
- }), globals);
372
+ };
373
+ if (mode === "cdc_mirror" || boolFlag(rest, "--cdc-ack")) body.cdc_acknowledged = true;
374
+ return print(await request(globals, "POST", `/v1/control/external-sources/${encodeURIComponent(sourceId)}/import`, body), globals);
356
375
  }
357
376
  }
358
377
 
@@ -406,8 +425,8 @@ Usage:
406
425
  synapsor projects create <name>
407
426
  synapsor db list --project <project>
408
427
  synapsor db create <name> --project <project>
409
- synapsor sources create postgres --name app_postgres --url env:APP_POSTGRES_URL --ssl require --mode read-only --project <project> --db <database>
410
- synapsor sources create mysql --name app_mysql --url env:APP_MYSQL_URL --ssl require --mode read-only --project <project> --db <database>
428
+ synapsor sources create postgres --name app_postgres --url env:APP_POSTGRES_URL --ssl require --mode live_read --project <project> --db <database>
429
+ synapsor sources create mysql --name app_mysql --url env:APP_MYSQL_URL --ssl require --mode live_read --project <project> --db <database>
411
430
  synapsor sources test app_postgres --project <project>
412
431
  synapsor sources inspect app_postgres --schema public --project <project>
413
432
  synapsor sources inspect app_mysql --database shopdb --project <project>
@@ -428,6 +447,13 @@ Usage:
428
447
  synapsor proposal reject <proposal-handle> --db <database> --yes
429
448
  synapsor replay <run-id> --db <database>
430
449
 
450
+ Preview/dev CDC commands:
451
+ These are not the current public customer connector path. Use live_read unless you are running a reviewed dev/test CDC smoke.
452
+ synapsor sources import app_postgres --schema public --tables tickets --tenant-column tenant_id --mode cdc_mirror --cdc-ack --project <project>
453
+ synapsor sources cdc-snapshot app_postgres --tables external_support.tickets --project <project>
454
+ synapsor sources cdc-poll app_postgres --tables external_support.tickets --project <project>
455
+ synapsor sources cdc-status app_postgres --project <project>
456
+
431
457
  Environment:
432
458
  SYNAPSOR_API_KEY, SYNAPSOR_BASE_URL
433
459
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synapsor/client",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Node.js SDK for Synapsor, the agent-native database for auditable AI applications",
5
5
  "type": "module",
6
6
  "main": "./synapsor.mjs",
package/synapsor.mjs CHANGED
@@ -80,6 +80,7 @@ export class Synapsor {
80
80
  this.apiKey = apiKey;
81
81
  this.process = process;
82
82
  this.timeoutMs = timeoutMs;
83
+ this.agentActivity = new AgentActivityNamespace(this);
83
84
  this.agentRuns = new AgentRunsNamespace(this);
84
85
  this.externalActions = new ExternalActionsNamespace(this);
85
86
  this.evals = new AgentEvalsNamespace(this);
@@ -268,6 +269,13 @@ export class Synapsor {
268
269
  return results.at(-1).result?.rows ?? [];
269
270
  }
270
271
 
272
+ async searchAgentActivity(filters = {}, options = {}) {
273
+ return this.query(buildAgentActivitySearchSql(filters, options), {
274
+ session: options.session,
275
+ asOf: options.asOf ?? options.as_of,
276
+ });
277
+ }
278
+
271
279
  async invokeAgentCapability(capability, args = {}, {
272
280
  session = undefined,
273
281
  traceId = undefined,
@@ -1131,6 +1139,16 @@ export class Synapsor {
1131
1139
  }
1132
1140
  }
1133
1141
 
1142
+ class AgentActivityNamespace {
1143
+ constructor(client) {
1144
+ this.client = client;
1145
+ }
1146
+
1147
+ async search(filters = {}, options = {}) {
1148
+ return this.client.searchAgentActivity(filters, options);
1149
+ }
1150
+ }
1151
+
1134
1152
  class AgentRunsNamespace {
1135
1153
  constructor(client) {
1136
1154
  this.client = client;
@@ -1396,6 +1414,60 @@ function typedSql(value) {
1396
1414
  return `${identifier(ref.type)}(${sqlLiteral(String(ref.id ?? ""))})`;
1397
1415
  }
1398
1416
 
1417
+ function buildAgentActivitySearchSql(filters = {}, options = {}) {
1418
+ const where = [];
1419
+ const addFilter = (column, ...keys) => {
1420
+ for (const key of keys) {
1421
+ if (filters[key] !== undefined && filters[key] !== null && filters[key] !== "") {
1422
+ where.push(`${column} = ${sqlScalar(filters[key])}`);
1423
+ return;
1424
+ }
1425
+ }
1426
+ };
1427
+ addFilter("activity_kind", "activityKind", "activity_kind");
1428
+ addFilter("tenant_id", "tenantId", "tenant_id");
1429
+ addFilter("principal", "principal");
1430
+ addFilter("workflow", "workflow");
1431
+ addFilter("capability", "capability");
1432
+ addFilter("run_id", "runId", "run_id");
1433
+ addFilter("evidence_bundle_id", "evidenceBundleId", "evidence_bundle_id");
1434
+ addFilter("proposal_id", "proposalId", "proposal_id");
1435
+ addFilter("replay_id", "replayId", "replay_id");
1436
+ addFilter("business_object_type", "businessObjectType", "business_object_type");
1437
+ addFilter("business_object_id", "businessObjectId", "business_object_id");
1438
+ addFilter("query_fingerprint", "queryFingerprint", "query_fingerprint");
1439
+
1440
+ const timeRange = filters.timeRange ?? filters.time_range ?? {};
1441
+ const from = timeRange.from ?? filters.createdFrom ?? filters.created_from;
1442
+ const to = timeRange.to ?? filters.createdTo ?? filters.created_to;
1443
+ if (from !== undefined && from !== null && from !== "") {
1444
+ where.push(`created_at >= ${sqlScalar(from)}`);
1445
+ }
1446
+ if (to !== undefined && to !== null && to !== "") {
1447
+ where.push(`created_at <= ${sqlScalar(to)}`);
1448
+ }
1449
+
1450
+ const requestedLimit = options.limit ?? filters.limit ?? 50;
1451
+ const limit = Number.isInteger(Number(requestedLimit)) && Number(requestedLimit) > 0
1452
+ ? Math.min(Number(requestedLimit), 500)
1453
+ : 50;
1454
+ const whereSql = where.length > 0 ? ` WHERE ${where.join(" AND ")}` : "";
1455
+ return `SELECT * FROM AGENT ACTIVITY${whereSql} ORDER BY created_at DESC LIMIT ${limit};`;
1456
+ }
1457
+
1458
+ function sqlScalar(value) {
1459
+ if (typeof value === "number" && Number.isFinite(value)) {
1460
+ return String(Math.trunc(value));
1461
+ }
1462
+ if (typeof value === "bigint") {
1463
+ return String(value);
1464
+ }
1465
+ if (typeof value === "boolean") {
1466
+ return value ? "TRUE" : "FALSE";
1467
+ }
1468
+ return sqlLiteral(String(value));
1469
+ }
1470
+
1399
1471
  function sqlLiteral(value) {
1400
1472
  return `'${String(value).replaceAll("'", "''")}'`;
1401
1473
  }