@synapsor/client 0.1.4 → 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 +25 -0
- package/bin/synapsor.mjs +33 -9
- package/package.json +1 -1
- package/synapsor.mjs +72 -0
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
|
@@ -172,6 +172,10 @@ function csvFlag(args, name) {
|
|
|
172
172
|
return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : [];
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
function boolFlag(args, name) {
|
|
176
|
+
return args.includes(name);
|
|
177
|
+
}
|
|
178
|
+
|
|
175
179
|
function resolveSecretRef(value) {
|
|
176
180
|
const text = String(value || "").trim();
|
|
177
181
|
if (text.startsWith("env:")) {
|
|
@@ -282,24 +286,35 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
282
286
|
const envHint = kind === "mysql" ? "APP_MYSQL_URL" : "APP_POSTGRES_URL";
|
|
283
287
|
const name = flagValue(rest, "--name", defaultName);
|
|
284
288
|
const url = resolveSecretRef(flagValue(rest, "--url", ""));
|
|
289
|
+
const cdcUrl = resolveSecretRef(flagValue(rest, "--cdc-url", ""));
|
|
285
290
|
const ssl = flagValue(rest, "--ssl", "require");
|
|
286
|
-
const mode = flagValue(rest, "--mode", "
|
|
291
|
+
const mode = flagValue(rest, "--mode", "live_read");
|
|
287
292
|
const projectId = scopedProject(globals);
|
|
288
293
|
const databaseId = scopedDatabase(globals);
|
|
289
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");
|
|
290
295
|
if (!url) throw new Error(`${kind.toUpperCase()}_URL_REQUIRED: usage \`synapsor sources create ${kind} --name ${defaultName} --url env:${envHint} --project <project> --db <database>\``);
|
|
291
|
-
|
|
296
|
+
const body = {
|
|
292
297
|
project_id: projectId,
|
|
293
298
|
database_id: databaseId,
|
|
294
299
|
kind,
|
|
295
300
|
name,
|
|
296
301
|
connection: { url, ssl_mode: ssl },
|
|
297
302
|
mode,
|
|
298
|
-
}
|
|
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);
|
|
299
307
|
}
|
|
300
|
-
if (["test", "inspect", "generate", "doctor", "disable"].includes(sub)) {
|
|
308
|
+
if (["test", "inspect", "generate", "doctor", "disable", "cdc-status", "cdc-snapshot", "cdc-poll"].includes(sub)) {
|
|
301
309
|
const sourceId = await resolveSourceId(globals, third);
|
|
302
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
|
+
}
|
|
303
318
|
if (sub === "inspect") {
|
|
304
319
|
const schema = flagValue(rest, "--schema", "public");
|
|
305
320
|
const database = flagValue(rest, "--database", "");
|
|
@@ -340,10 +355,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
340
355
|
const database = flagValue(rest, "--database", "");
|
|
341
356
|
const tables = csvFlag(rest, "--tables");
|
|
342
357
|
const tenantColumn = flagValue(rest, "--tenant-column", "");
|
|
343
|
-
const mode = flagValue(rest, "--mode", "
|
|
358
|
+
const mode = flagValue(rest, "--mode", "live_read");
|
|
344
359
|
if (!tables.length) throw new Error("TABLES_REQUIRED: pass --tables tickets,customers,policy_chunks");
|
|
345
360
|
if (!tenantColumn && !rest.includes("--single-tenant")) throw new Error("TENANT_COLUMN_REQUIRED: pass --tenant-column tenant_id or --single-tenant");
|
|
346
|
-
|
|
361
|
+
const body = {
|
|
347
362
|
tables: tables.map((table) => ({
|
|
348
363
|
...(database ? { database } : {}),
|
|
349
364
|
schema,
|
|
@@ -354,7 +369,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
354
369
|
single_tenant: rest.includes("--single-tenant"),
|
|
355
370
|
mode,
|
|
356
371
|
})),
|
|
357
|
-
}
|
|
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);
|
|
358
375
|
}
|
|
359
376
|
}
|
|
360
377
|
|
|
@@ -408,8 +425,8 @@ Usage:
|
|
|
408
425
|
synapsor projects create <name>
|
|
409
426
|
synapsor db list --project <project>
|
|
410
427
|
synapsor db create <name> --project <project>
|
|
411
|
-
synapsor sources create postgres --name app_postgres --url env:APP_POSTGRES_URL --ssl require --mode
|
|
412
|
-
synapsor sources create mysql --name app_mysql --url env:APP_MYSQL_URL --ssl require --mode
|
|
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>
|
|
413
430
|
synapsor sources test app_postgres --project <project>
|
|
414
431
|
synapsor sources inspect app_postgres --schema public --project <project>
|
|
415
432
|
synapsor sources inspect app_mysql --database shopdb --project <project>
|
|
@@ -430,6 +447,13 @@ Usage:
|
|
|
430
447
|
synapsor proposal reject <proposal-handle> --db <database> --yes
|
|
431
448
|
synapsor replay <run-id> --db <database>
|
|
432
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
|
+
|
|
433
457
|
Environment:
|
|
434
458
|
SYNAPSOR_API_KEY, SYNAPSOR_BASE_URL
|
|
435
459
|
|
package/package.json
CHANGED
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
|
}
|