@nylorun/runtime 0.36.1-beta → 0.38.0-beta
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/CHANGELOG.md +70 -0
- package/README.md +10 -0
- package/dist/api/a2a/card.d.ts +10 -4
- package/dist/api/a2a/card.js +18 -6
- package/dist/api/a2a/endpoint.d.ts +5 -2
- package/dist/api/a2a/endpoint.js +31 -9
- package/dist/api/a2a/extensions.d.ts +41 -0
- package/dist/api/a2a/extensions.js +92 -0
- package/dist/api/a2a/list.d.ts +60 -0
- package/dist/api/a2a/list.js +169 -0
- package/dist/api/a2a/protocol.d.ts +105 -9
- package/dist/api/a2a/protocol.js +169 -18
- package/dist/api/a2a/push.d.ts +76 -0
- package/dist/api/a2a/push.js +305 -0
- package/dist/api/a2a/routes.d.ts +12 -6
- package/dist/api/a2a/routes.js +409 -112
- package/dist/api/a2a/tasks.d.ts +72 -4
- package/dist/api/a2a/tasks.js +305 -27
- package/dist/api/components.d.ts +538 -0
- package/dist/api/components.js +3 -1
- package/dist/api/http/app.js +2 -0
- package/dist/api/http/routes/artifacts.js +19 -0
- package/dist/api/http/routes/turns.d.ts +18 -0
- package/dist/api/http/routes/turns.js +67 -0
- package/dist/api/inbound-files.d.ts +12 -1
- package/dist/api/inbound-files.js +44 -0
- package/dist/api/openapi.js +7 -2
- package/dist/harness/image-engine.d.ts +14 -0
- package/dist/harness/image-engine.js +16 -0
- package/dist/harness/pod.d.ts +2 -0
- package/dist/harness/pod.js +5 -0
- package/dist/harness-api/ws-server.js +7 -1
- package/dist/host/config-for.d.ts +2 -0
- package/dist/host/config-for.js +1 -0
- package/dist/host/main.js +1 -0
- package/dist/host/stack-config.d.ts +16 -0
- package/dist/host/stack-config.js +30 -0
- package/dist/management-openapi.json +37 -2
- package/dist/openapi.json +1485 -738
- package/dist/reads/types.d.ts +19 -0
- package/dist/sandbox/join.d.ts +7 -1
- package/dist/sandbox/join.js +5 -1
- package/dist/sandbox/pods/builtin.d.ts +18 -0
- package/dist/sandbox/pods/builtin.js +33 -0
- package/dist/sandbox/pods/client.d.ts +5 -4
- package/dist/sandbox/pods/spec.js +14 -1
- package/dist/store/postgres/drizzle/0039_a2a_push.sql +32 -0
- package/dist/store/postgres/drizzle/0040_turn_index.sql +23 -0
- package/dist/store/postgres/drizzle/0041_turn_index_backfill.sql +88 -0
- package/dist/store/postgres/drizzle/0042_worker_engine_image.sql +1 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/reads.js +41 -1
- package/dist/store/postgres/schema.d.ts +1314 -533
- package/dist/store/postgres/schema.js +94 -0
- package/dist/store/postgres/store.js +135 -3
- package/dist/store/postgres/turns.d.ts +62 -0
- package/dist/store/postgres/turns.js +153 -0
- package/dist/store/types.d.ts +82 -3
- package/dist/tenant/runtime.js +7 -5
- package/dist/tenant/session-model.d.ts +11 -1
- package/dist/tenant/session-model.js +34 -5
- package/dist/tenant/sessions.d.ts +2 -1
- package/dist/tenant/sessions.js +8 -2
- package/dist/tenant/sweep.d.ts +3 -0
- package/dist/tenant/sweep.js +39 -11
- package/dist/tenant/types.d.ts +23 -0
- package/dist/tuning.d.ts +9 -0
- package/dist/tuning.js +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -171,6 +171,8 @@ export const agents = nylorun.table("agents", {
|
|
|
171
171
|
volumeId: text(),
|
|
172
172
|
/** The workload image by digest, from the first ready pod on (Q14): later pods run it. */
|
|
173
173
|
imageDigest: textC(),
|
|
174
|
+
/** The engine the image carries (ADR-99): `{ version, digest? }`, from a join of its engine. */
|
|
175
|
+
engineImage: jsonText(),
|
|
174
176
|
/**
|
|
175
177
|
* The disk lease (R2d step 3): `{ kind, version?, startedAt, until }` while a snapshot, a
|
|
176
178
|
* restore or a seed copies the disk; turns are refused until it ends or `until` passes.
|
|
@@ -495,6 +497,46 @@ export const modelBudgets = nylorun.table("model_budgets", {
|
|
|
495
497
|
limitTokens: bigint({ mode: "number" }),
|
|
496
498
|
updatedAt: text().notNull(),
|
|
497
499
|
}, (t) => [primaryKey({ name: "model_budgets_pkey", columns: [t.scope, t.scopeId] })]);
|
|
500
|
+
/**
|
|
501
|
+
* The turn index (A2A design §9, Host feature `turns`): one row per turn, written by `Tx.event`
|
|
502
|
+
* in the transaction of each event that changes the turn (`store/postgres/turns.ts`), so it
|
|
503
|
+
* agrees with the session's log. The session's Agent, owner and A2A context are copied in when
|
|
504
|
+
* the turn starts. `txid` is the transaction that last changed the row: the change feed reads
|
|
505
|
+
* rows in its order, only below every transaction still running (`pg_snapshot_xmin`), as the
|
|
506
|
+
* model call export does. `firstSeq` and `lastSeq` bound the turn's lifecycle events in the log.
|
|
507
|
+
*/
|
|
508
|
+
export const turns = nylorun.table("turns", {
|
|
509
|
+
sessionId: textC().notNull(),
|
|
510
|
+
turnId: textC().notNull(),
|
|
511
|
+
agentId: textC().notNull(),
|
|
512
|
+
/** The session owner (`ownerUserId`); null for an application's own session. */
|
|
513
|
+
ownerUserId: textC(),
|
|
514
|
+
/** `info.a2a.contextId` of an A2A context's session. */
|
|
515
|
+
a2aContextId: textC(),
|
|
516
|
+
status: text({ enum: ["running", "paused", "completed", "failed", "cancelled"] }).notNull(),
|
|
517
|
+
/** ISO times: of the turn's first event, its end, and its last status change. */
|
|
518
|
+
startedAt: textC().notNull(),
|
|
519
|
+
endedAt: textC(),
|
|
520
|
+
changedAt: textC().notNull(),
|
|
521
|
+
/** The end facts' `totals` (`{steps, tokens, costUsd}`), once it ended with them. */
|
|
522
|
+
totals: jsonText(),
|
|
523
|
+
/**
|
|
524
|
+
* While `paused`: what its first open interaction asks for, as profile v2 names it
|
|
525
|
+
* (`question`, `approval` or `connection`); null otherwise.
|
|
526
|
+
*/
|
|
527
|
+
waitingFor: text({ enum: ["question", "approval", "connection"] }),
|
|
528
|
+
/** The queued messages it took (protocol 17): their A2A task ids name it too. */
|
|
529
|
+
queuedMessageIds: text().array().notNull().default(sql `'{}'`),
|
|
530
|
+
firstSeq: bigint({ mode: "number" }).notNull(),
|
|
531
|
+
lastSeq: bigint({ mode: "number" }).notNull(),
|
|
532
|
+
txid: xid8().notNull().default(sql `pg_current_xact_id()`),
|
|
533
|
+
}, (t) => [
|
|
534
|
+
primaryKey({ name: "turns_pkey", columns: [t.sessionId, t.turnId] }),
|
|
535
|
+
index("turns_turn").on(t.turnId),
|
|
536
|
+
index("turns_changes").on(t.txid, t.sessionId, t.turnId),
|
|
537
|
+
index("turns_owner_changes").on(t.ownerUserId, t.txid),
|
|
538
|
+
index("turns_owner_agent").on(t.ownerUserId, t.agentId, t.changedAt),
|
|
539
|
+
]);
|
|
498
540
|
/**
|
|
499
541
|
* Each session's queue (protocol 17, D76): messages that arrived while its turn ran or waited, in
|
|
500
542
|
* order (`seq`), until a turn takes them. A row holds the command as sent, the turn input its
|
|
@@ -700,6 +742,58 @@ export const sandboxSignals = nylorun.table("sandbox_signals", {
|
|
|
700
742
|
index("sandbox_signals_created_at").on(t.createdAt),
|
|
701
743
|
check("sandbox_signals_kind_check", sql `${t.kind} IN ('reconcile', 'arm')`),
|
|
702
744
|
]);
|
|
745
|
+
/**
|
|
746
|
+
* A2A push notification configs (Host feature `a2a-profile-v2`): where a task's updates go, one
|
|
747
|
+
* row per task and config id (`taskId`: the A2A id its caller knows, a queued message's own until
|
|
748
|
+
* a turn takes it; `turnId`: the turn it follows). The push translator (`api/a2a/push.ts`) reads
|
|
749
|
+
* the session's events from `nextSeq` on and writes each update as an `a2a_pushes` row in one
|
|
750
|
+
* transaction; `doneAt` is set once the task's turn ended, and the row is deleted after the
|
|
751
|
+
* Host tuning's `a2a.pushConfigRetentionMs`. `extensions` are the profile extensions the caller
|
|
752
|
+
* activated when it gave the config; `baseUrl` is the Runtime's URL as that caller reached it,
|
|
753
|
+
* for file URLs; `traceId` is `telemetry/v1`'s.
|
|
754
|
+
*/
|
|
755
|
+
export const a2aPushConfigs = nylorun.table("a2a_push_configs", {
|
|
756
|
+
sessionId: textC().notNull(),
|
|
757
|
+
taskId: textC().notNull(),
|
|
758
|
+
configId: textC().notNull(),
|
|
759
|
+
contextId: text().notNull(),
|
|
760
|
+
turnId: textC().notNull(),
|
|
761
|
+
url: text().notNull(),
|
|
762
|
+
token: text(),
|
|
763
|
+
authentication: jsonText(),
|
|
764
|
+
baseUrl: text().notNull(),
|
|
765
|
+
extensions: jsonText().notNull(),
|
|
766
|
+
traceId: text(),
|
|
767
|
+
nextSeq: bigint({ mode: "number" }).notNull(),
|
|
768
|
+
createdAt: timestamp({ withTimezone: true, mode: "date" })
|
|
769
|
+
.notNull()
|
|
770
|
+
.default(sql `clock_timestamp()`),
|
|
771
|
+
doneAt: timestamp({ withTimezone: true, mode: "date" }),
|
|
772
|
+
}, (t) => [
|
|
773
|
+
primaryKey({ name: "a2a_push_configs_pkey", columns: [t.sessionId, t.taskId, t.configId] }),
|
|
774
|
+
index("a2a_push_configs_done_at").on(t.doneAt),
|
|
775
|
+
]);
|
|
776
|
+
/**
|
|
777
|
+
* The push outbox (outbox kind `push`): one row per A2A update a push config is owed, its body
|
|
778
|
+
* the exact JSON sent (a `StreamResponse`), delivered after commit and by the Tenant sweep as the
|
|
779
|
+
* other outbox rows are (`attempts`, `retry_at`, `parked_at`). The row id is the Standard
|
|
780
|
+
* Webhooks `webhook-id`, the same on every attempt. A row whose config is gone, or that kept
|
|
781
|
+
* failing for the Host tuning's `a2a.pushMaxAgeMs` (24 hours), is deleted: the change feed
|
|
782
|
+
* covers what it would have said.
|
|
783
|
+
*/
|
|
784
|
+
export const a2aPushes = nylorun.table("a2a_pushes", {
|
|
785
|
+
id: textC().primaryKey(),
|
|
786
|
+
sessionId: textC().notNull(),
|
|
787
|
+
taskId: textC().notNull(),
|
|
788
|
+
configId: textC().notNull(),
|
|
789
|
+
body: text().notNull(),
|
|
790
|
+
createdAt: timestamp({ withTimezone: true, mode: "date" })
|
|
791
|
+
.notNull()
|
|
792
|
+
.default(sql `clock_timestamp()`),
|
|
793
|
+
attempts: integer().notNull().default(0),
|
|
794
|
+
retryAt: timestamp({ withTimezone: true, mode: "date" }),
|
|
795
|
+
parkedAt: timestamp({ withTimezone: true, mode: "date" }),
|
|
796
|
+
}, (t) => [index("a2a_pushes_created_at").on(t.createdAt)]);
|
|
703
797
|
// ---------------------------------------------------------------------------
|
|
704
798
|
// nylorun_streams: the record
|
|
705
799
|
/**
|
|
@@ -59,8 +59,9 @@ import { database, driverError } from "./db.js";
|
|
|
59
59
|
import { expectedSchemaVersion, readSchemaVersion } from "./migrate.js";
|
|
60
60
|
import { hostTuning } from "../../tuning.js";
|
|
61
61
|
import { createPostgresRecordReader } from "./record.js";
|
|
62
|
+
import { indexTurn, listTurns, turnOfQueuedMessage, turnRow } from "./turns.js";
|
|
62
63
|
import { postgresAgentRecordWriter, postgresRecordWriter } from "./record-writer.js";
|
|
63
|
-
import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, effects, links, modelBudgets, modelUsage, principals, sandboxes, agentEvents, agents, agentManifests, sessionEvents, sessionLogHeads, sessions, sessionQueue, agentTriggers, triggerRuns, memories, memoryVersions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, vaultAudit, vaultCredentials, vaultIdempotency, vaults, sandboxSignals, wakes, } from "./schema.js";
|
|
64
|
+
import { artifacts, artifactVersions, artifactContent, definitionFiles, definitionFileUses, commands, effects, links, modelBudgets, modelUsage, principals, sandboxes, agentEvents, agents, agentManifests, sessionEvents, sessionLogHeads, sessions, sessionQueue, a2aPushConfigs, a2aPushes, agentTriggers, triggerRuns, memories, memoryVersions, toJson, toolCrossings, signingKeys, tenant, tenantSettings, turns, vaultAudit, vaultCredentials, vaultIdempotency, vaults, sandboxSignals, wakes, } from "./schema.js";
|
|
64
65
|
export function createPostgresSessionStore(options) {
|
|
65
66
|
return new PostgresSessionStore(options);
|
|
66
67
|
}
|
|
@@ -282,6 +283,7 @@ function environmentOf(row) {
|
|
|
282
283
|
...(row.retiring !== null ? { retiring: row.retiring } : {}),
|
|
283
284
|
...(row.volumeId !== null ? { volumeId: row.volumeId } : {}),
|
|
284
285
|
...(row.imageDigest !== null ? { imageDigest: row.imageDigest } : {}),
|
|
286
|
+
...(row.engineImage ? { engineImage: row.engineImage } : {}),
|
|
285
287
|
},
|
|
286
288
|
}
|
|
287
289
|
: {}),
|
|
@@ -316,6 +318,7 @@ function podColumns(patch) {
|
|
|
316
318
|
retiring: patch.retiring,
|
|
317
319
|
volumeId: patch.volumeId,
|
|
318
320
|
imageDigest: patch.imageDigest,
|
|
321
|
+
engineImage: patch.engineImage,
|
|
319
322
|
};
|
|
320
323
|
return Object.fromEntries(Object.entries(columns).filter(([, value]) => value !== undefined));
|
|
321
324
|
}
|
|
@@ -327,7 +330,17 @@ const SIGNING_KEY_STAMP = {
|
|
|
327
330
|
};
|
|
328
331
|
/** The audit columns a row has (`ord` only orders them). */
|
|
329
332
|
const { ord: _ord, ...AUDIT } = getTableColumns(vaultAudit);
|
|
330
|
-
const SESSION_TABLES = [
|
|
333
|
+
const SESSION_TABLES = [
|
|
334
|
+
sessions,
|
|
335
|
+
commands,
|
|
336
|
+
effects,
|
|
337
|
+
links,
|
|
338
|
+
wakes,
|
|
339
|
+
sessionQueue,
|
|
340
|
+
turns,
|
|
341
|
+
a2aPushConfigs,
|
|
342
|
+
a2aPushes,
|
|
343
|
+
];
|
|
331
344
|
/**
|
|
332
345
|
* The outbox's tables, by the kind of request each holds. Their delivery columns (`id`,
|
|
333
346
|
* `created_at`, `attempts`, `retry_at`, `parked_at`) are the same.
|
|
@@ -335,7 +348,12 @@ const SESSION_TABLES = [sessions, commands, effects, links, wakes, sessionQueue]
|
|
|
335
348
|
const OUTBOX = {
|
|
336
349
|
wake: wakes,
|
|
337
350
|
sandbox: sandboxSignals,
|
|
351
|
+
push: a2aPushes,
|
|
338
352
|
};
|
|
353
|
+
/** A push outbox row as it is delivered. */
|
|
354
|
+
function outboxPush(row) {
|
|
355
|
+
return { id: row.id, sessionId: row.sessionId, taskId: row.taskId, configId: row.configId, body: row.body };
|
|
356
|
+
}
|
|
339
357
|
/** A wake outbox row as it is delivered: its own dedupe key, or one made from the row id. */
|
|
340
358
|
function outboxWake(id, reason, dedupeKey) {
|
|
341
359
|
return { reason, dedupeKey: dedupeKey ?? `wake:${id}` };
|
|
@@ -439,10 +457,25 @@ class PostgresTx {
|
|
|
439
457
|
...(writer ? { writer } : {}),
|
|
440
458
|
maxEntryBytes: await this.maxEntryBytes(),
|
|
441
459
|
});
|
|
460
|
+
// The turn index changes with the log, in this transaction (Host feature `turns`).
|
|
461
|
+
await indexTurn(db, event);
|
|
442
462
|
this.events.push(event);
|
|
443
463
|
this.generations.push(generation);
|
|
444
464
|
return structuredClone(event);
|
|
445
465
|
}
|
|
466
|
+
// --- turn index ----------------------------------------------------------
|
|
467
|
+
async turn(sessionId, turnId) {
|
|
468
|
+
this.check();
|
|
469
|
+
return turnRow(this.db, sessionId, turnId);
|
|
470
|
+
}
|
|
471
|
+
async turnOfQueuedMessage(sessionId, queuedMessageId) {
|
|
472
|
+
this.check();
|
|
473
|
+
return turnOfQueuedMessage(this.db, sessionId, queuedMessageId);
|
|
474
|
+
}
|
|
475
|
+
async listTurns(query) {
|
|
476
|
+
this.check();
|
|
477
|
+
return listTurns(this.db, query);
|
|
478
|
+
}
|
|
446
479
|
// --- wake outbox ---------------------------------------------------------
|
|
447
480
|
async wake(sessionId, wake) {
|
|
448
481
|
this.check();
|
|
@@ -514,12 +547,27 @@ class PostgresTx {
|
|
|
514
547
|
due: row.dueSeconds,
|
|
515
548
|
})),
|
|
516
549
|
];
|
|
550
|
+
const push = DUE(OUTBOX.push, graceMs);
|
|
551
|
+
const pushRows = await this.db
|
|
552
|
+
.select({ ...getTableColumns(a2aPushes), dueSeconds: push.dueSeconds })
|
|
553
|
+
.from(a2aPushes)
|
|
554
|
+
.where(and(isNull(a2aPushes.parkedAt), lte(push.due, now)))
|
|
555
|
+
.orderBy(push.due, a2aPushes.id)
|
|
556
|
+
.limit(limit);
|
|
557
|
+
rows.push(...pushRows.map((row) => ({
|
|
558
|
+
kind: "push",
|
|
559
|
+
id: row.id,
|
|
560
|
+
request: { kind: "push", push: outboxPush(row) },
|
|
561
|
+
createdAt: row.createdAt.toISOString(),
|
|
562
|
+
attempts: row.attempts,
|
|
563
|
+
due: row.dueSeconds,
|
|
564
|
+
})));
|
|
517
565
|
rows.sort((a, b) => a.due - b.due || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
518
566
|
return rows.slice(0, limit).map(({ due: _due, ...row }) => row);
|
|
519
567
|
}
|
|
520
568
|
async deleteOutbox(rows) {
|
|
521
569
|
this.check();
|
|
522
|
-
for (const kind of ["wake", "sandbox"]) {
|
|
570
|
+
for (const kind of ["wake", "sandbox", "push"]) {
|
|
523
571
|
const ids = rows.filter((row) => row.kind === kind).map((row) => row.id);
|
|
524
572
|
if (ids.length > 0)
|
|
525
573
|
await this.db.delete(OUTBOX[kind]).where(inArray(OUTBOX[kind].id, ids));
|
|
@@ -538,6 +586,90 @@ class PostgresTx {
|
|
|
538
586
|
})
|
|
539
587
|
.where(eq(table.id, row.id));
|
|
540
588
|
}
|
|
589
|
+
// --- A2A push (Host feature `a2a-profile-v2`) ---------------------------------
|
|
590
|
+
async putPushConfig(row) {
|
|
591
|
+
this.check();
|
|
592
|
+
const { sessionId: _s, taskId: _t, configId: _c, ...set } = row;
|
|
593
|
+
await this.db
|
|
594
|
+
.insert(a2aPushConfigs)
|
|
595
|
+
.values({ ...row, doneAt: row.doneAt ?? null })
|
|
596
|
+
.onConflictDoUpdate({
|
|
597
|
+
target: [a2aPushConfigs.sessionId, a2aPushConfigs.taskId, a2aPushConfigs.configId],
|
|
598
|
+
set: { ...set, doneAt: row.doneAt ?? null, createdAt: sql `clock_timestamp()` },
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
async pushConfig(sessionId, taskId, configId) {
|
|
602
|
+
this.check();
|
|
603
|
+
const [row] = await this.db
|
|
604
|
+
.select()
|
|
605
|
+
.from(a2aPushConfigs)
|
|
606
|
+
.where(and(eq(a2aPushConfigs.sessionId, sessionId), eq(a2aPushConfigs.taskId, taskId), eq(a2aPushConfigs.configId, configId)));
|
|
607
|
+
return row;
|
|
608
|
+
}
|
|
609
|
+
async pushConfigs(sessionId, taskId, options = {}) {
|
|
610
|
+
this.check();
|
|
611
|
+
const query = this.db
|
|
612
|
+
.select()
|
|
613
|
+
.from(a2aPushConfigs)
|
|
614
|
+
.where(and(eq(a2aPushConfigs.sessionId, sessionId), eq(a2aPushConfigs.taskId, taskId), options.after === undefined ? undefined : gt(a2aPushConfigs.configId, options.after)))
|
|
615
|
+
.orderBy(a2aPushConfigs.configId)
|
|
616
|
+
.$dynamic();
|
|
617
|
+
return options.limit === undefined ? query : query.limit(options.limit);
|
|
618
|
+
}
|
|
619
|
+
async deletePushConfig(sessionId, taskId, configId) {
|
|
620
|
+
this.check();
|
|
621
|
+
const deleted = await this.db
|
|
622
|
+
.delete(a2aPushConfigs)
|
|
623
|
+
.where(and(eq(a2aPushConfigs.sessionId, sessionId), eq(a2aPushConfigs.taskId, taskId), eq(a2aPushConfigs.configId, configId)))
|
|
624
|
+
.returning({ id: a2aPushConfigs.configId });
|
|
625
|
+
return deleted.length > 0;
|
|
626
|
+
}
|
|
627
|
+
async activePushConfigs(sessionId, options = {}) {
|
|
628
|
+
this.check();
|
|
629
|
+
const query = this.db
|
|
630
|
+
.select()
|
|
631
|
+
.from(a2aPushConfigs)
|
|
632
|
+
.where(and(eq(a2aPushConfigs.sessionId, sessionId), isNull(a2aPushConfigs.doneAt)))
|
|
633
|
+
.orderBy(a2aPushConfigs.taskId, a2aPushConfigs.configId)
|
|
634
|
+
.$dynamic();
|
|
635
|
+
return options.lock ? query.for("update") : query;
|
|
636
|
+
}
|
|
637
|
+
async pushSessions(limit, after) {
|
|
638
|
+
this.check();
|
|
639
|
+
const rows = await this.db
|
|
640
|
+
.selectDistinct({ sessionId: a2aPushConfigs.sessionId })
|
|
641
|
+
.from(a2aPushConfigs)
|
|
642
|
+
.where(and(isNull(a2aPushConfigs.doneAt), after === undefined ? undefined : gt(a2aPushConfigs.sessionId, after)))
|
|
643
|
+
.orderBy(a2aPushConfigs.sessionId)
|
|
644
|
+
.limit(limit);
|
|
645
|
+
return rows.map((row) => row.sessionId);
|
|
646
|
+
}
|
|
647
|
+
async advancePushConfig(key, change) {
|
|
648
|
+
this.check();
|
|
649
|
+
await this.db
|
|
650
|
+
.update(a2aPushConfigs)
|
|
651
|
+
.set({
|
|
652
|
+
turnId: change.turnId,
|
|
653
|
+
nextSeq: change.nextSeq,
|
|
654
|
+
traceId: change.traceId,
|
|
655
|
+
...(change.done ? { doneAt: sql `clock_timestamp()` } : {}),
|
|
656
|
+
})
|
|
657
|
+
.where(and(eq(a2aPushConfigs.sessionId, key.sessionId), eq(a2aPushConfigs.taskId, key.taskId), eq(a2aPushConfigs.configId, key.configId)));
|
|
658
|
+
}
|
|
659
|
+
async prunePushConfigs(before) {
|
|
660
|
+
this.check();
|
|
661
|
+
const deleted = await this.db
|
|
662
|
+
.delete(a2aPushConfigs)
|
|
663
|
+
.where(lt(a2aPushConfigs.doneAt, before))
|
|
664
|
+
.returning({ id: a2aPushConfigs.configId });
|
|
665
|
+
return deleted.length;
|
|
666
|
+
}
|
|
667
|
+
async enqueuePush(push) {
|
|
668
|
+
this.check();
|
|
669
|
+
const id = `push_${randomUUID()}`;
|
|
670
|
+
await this.db.insert(a2aPushes).values({ id, ...push });
|
|
671
|
+
this.outbox.push({ kind: "push", id, request: { kind: "push", push: { id, ...push } } });
|
|
672
|
+
}
|
|
541
673
|
// --- ownership -----------------------------------------------------------
|
|
542
674
|
async takeOwnership(sessionId, claim) {
|
|
543
675
|
this.check();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { EventType, TurnStatus } from "@nylorun/core/contracts";
|
|
2
|
+
import type { Queryable } from "./db.js";
|
|
3
|
+
import { type TurnRow } from "./schema.js";
|
|
4
|
+
/** The events that change a turn's row, and the status each leaves it in. */
|
|
5
|
+
export declare const TURN_EVENT_STATUS: Readonly<Partial<Record<EventType, TurnStatus>>>;
|
|
6
|
+
/** The statuses a turn ends in. */
|
|
7
|
+
export declare const ENDED_TURN_STATUSES: ReadonlySet<TurnStatus>;
|
|
8
|
+
/** What `indexTurn` reads of an event: the envelope fields, as `appendEvent` built them. */
|
|
9
|
+
export interface IndexedEvent {
|
|
10
|
+
readonly sessionId: string;
|
|
11
|
+
readonly turnId: string | null;
|
|
12
|
+
readonly seq: number;
|
|
13
|
+
readonly time: string;
|
|
14
|
+
readonly type: string;
|
|
15
|
+
readonly payload: unknown;
|
|
16
|
+
}
|
|
17
|
+
/** What an interaction asks for, as profile v2 names it (`api/a2a/tasks.ts` `interactionOf`). */
|
|
18
|
+
export type WaitingFor = "question" | "approval" | "connection";
|
|
19
|
+
/** What a `turn.paused` payload's first open interaction asks for, or null. */
|
|
20
|
+
export declare function waitingForOf(payload: unknown): WaitingFor | null;
|
|
21
|
+
/**
|
|
22
|
+
* Writes the change `event` makes to its turn's row, in the event's transaction (the session
|
|
23
|
+
* row is locked). An event without a turn, or of a type that changes none, writes nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function indexTurn(db: Queryable, event: IndexedEvent): Promise<void>;
|
|
26
|
+
/** One turn's row, or undefined when the session never had that turn. */
|
|
27
|
+
export declare function turnRow(db: Queryable, sessionId: string, turnId: string): Promise<TurnRow | undefined>;
|
|
28
|
+
/** The turn that took queued message `queuedMessageId` of a session, if one did. */
|
|
29
|
+
export declare function turnOfQueuedMessage(db: Queryable, sessionId: string, queuedMessageId: string): Promise<TurnRow | undefined>;
|
|
30
|
+
/** A status a listed turn may have: `paused` narrowed by what it waits for. */
|
|
31
|
+
export interface TurnState {
|
|
32
|
+
readonly status: TurnStatus;
|
|
33
|
+
readonly waitingFor?: readonly WaitingFor[];
|
|
34
|
+
}
|
|
35
|
+
/** Which turns `listTurns` reads: one owner's A2A tasks with one Agent (`ListTasks`). */
|
|
36
|
+
export interface TurnListQuery {
|
|
37
|
+
readonly ownerUserId: string;
|
|
38
|
+
readonly agentId: string;
|
|
39
|
+
/** Only turns of A2A contexts. */
|
|
40
|
+
readonly a2a: true;
|
|
41
|
+
/** Only this session (one A2A context's). */
|
|
42
|
+
readonly sessionId?: string;
|
|
43
|
+
/** Only turns in one of these states; empty matches none. */
|
|
44
|
+
readonly states?: readonly TurnState[];
|
|
45
|
+
/** Only turns whose status changed at or after this ISO time. */
|
|
46
|
+
readonly changedSince?: string;
|
|
47
|
+
/** Only turns after this one in the order: the last row of the previous page. */
|
|
48
|
+
readonly after?: {
|
|
49
|
+
readonly changedAt: string;
|
|
50
|
+
readonly sessionId: string;
|
|
51
|
+
readonly turnId: string;
|
|
52
|
+
};
|
|
53
|
+
readonly limit: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The turns `query` names, latest status change first (then by session and turn id, descending),
|
|
57
|
+
* at most `limit`, and how many match without `after` and `limit`.
|
|
58
|
+
*/
|
|
59
|
+
export declare function listTurns(db: Queryable, query: TurnListQuery): Promise<{
|
|
60
|
+
turns: TurnRow[];
|
|
61
|
+
total: number;
|
|
62
|
+
}>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The turn index (A2A design §9, Host feature `turns`): the `turns` table's writes and the typed
|
|
3
|
+
* queries on it. `Tx.event` calls `indexTurn` for every event it writes, in the same
|
|
4
|
+
* transaction, so a turn's row changes exactly when its log does:
|
|
5
|
+
*
|
|
6
|
+
* - `command.message` starts the turn (`running`), copying in its session's Agent, owner and A2A
|
|
7
|
+
* context (`info.a2a.contextId`, `api/a2a/routes.ts`) and the queued message it took, if any;
|
|
8
|
+
* a further message of the same turn only adds its queued message;
|
|
9
|
+
* - `command.approve` and `command.respond` set it `running` again, `turn.paused` `paused` with
|
|
10
|
+
* what its first open interaction asks for (`waitingFor`, profile v2's kinds);
|
|
11
|
+
* - `turn.completed`, `turn.failed` and `turn.cancelled` end it, with their end facts' `totals`.
|
|
12
|
+
*
|
|
13
|
+
* Each change stamps the row with its transaction (`txid`), which orders the change feed
|
|
14
|
+
* (`reads.ts`). Rows for the turns recorded before the index are backfilled by migration
|
|
15
|
+
* `0041_turn_index_backfill`.
|
|
16
|
+
*/
|
|
17
|
+
import { and, desc, eq, isNotNull, gte, inArray, or, sql } from "drizzle-orm";
|
|
18
|
+
import { sessions, toJson, turns } from "./schema.js";
|
|
19
|
+
/** The events that change a turn's row, and the status each leaves it in. */
|
|
20
|
+
export const TURN_EVENT_STATUS = {
|
|
21
|
+
"command.message": "running",
|
|
22
|
+
"command.approve": "running",
|
|
23
|
+
"command.respond": "running",
|
|
24
|
+
"turn.paused": "paused",
|
|
25
|
+
"turn.completed": "completed",
|
|
26
|
+
"turn.failed": "failed",
|
|
27
|
+
"turn.cancelled": "cancelled",
|
|
28
|
+
};
|
|
29
|
+
/** The statuses a turn ends in. */
|
|
30
|
+
export const ENDED_TURN_STATUSES = new Set([
|
|
31
|
+
"completed",
|
|
32
|
+
"failed",
|
|
33
|
+
"cancelled",
|
|
34
|
+
]);
|
|
35
|
+
/** An ended turn's `totals`, from its end facts, or null when it has none. */
|
|
36
|
+
function totalsOf(payload) {
|
|
37
|
+
const totals = payload?.totals;
|
|
38
|
+
if (typeof totals?.steps !== "number" ||
|
|
39
|
+
typeof totals.tokens !== "number" ||
|
|
40
|
+
typeof totals.costUsd !== "number")
|
|
41
|
+
return null;
|
|
42
|
+
return { steps: totals.steps, tokens: totals.tokens, costUsd: totals.costUsd };
|
|
43
|
+
}
|
|
44
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
/** What a `turn.paused` payload's first open interaction asks for, or null. */
|
|
46
|
+
export function waitingForOf(payload) {
|
|
47
|
+
const entries = isRecord(payload) && Array.isArray(payload.interactions) ? payload.interactions : [];
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!isRecord(entry) || (entry.status !== undefined && entry.status !== "interaction"))
|
|
50
|
+
continue;
|
|
51
|
+
const interaction = entry.interaction;
|
|
52
|
+
if (!isRecord(interaction) || typeof interaction.id !== "string")
|
|
53
|
+
continue;
|
|
54
|
+
const metadata = isRecord(interaction.metadata) ? interaction.metadata : undefined;
|
|
55
|
+
if (interaction.kind === "connection" || metadata?.kind === "connection")
|
|
56
|
+
return "connection";
|
|
57
|
+
return interaction.kind === "approval" ? "approval" : "question";
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Writes the change `event` makes to its turn's row, in the event's transaction (the session
|
|
63
|
+
* row is locked). An event without a turn, or of a type that changes none, writes nothing.
|
|
64
|
+
*/
|
|
65
|
+
export async function indexTurn(db, event) {
|
|
66
|
+
const status = TURN_EVENT_STATUS[event.type];
|
|
67
|
+
if (status === undefined || event.turnId === null)
|
|
68
|
+
return;
|
|
69
|
+
const ended = ENDED_TURN_STATUSES.has(status);
|
|
70
|
+
const totals = ended ? totalsOf(event.payload) : null;
|
|
71
|
+
const waitingFor = event.type === "turn.paused" ? waitingForOf(event.payload) : null;
|
|
72
|
+
const queuedMessageId = event.type === "command.message" && isRecord(event.payload) && typeof event.payload.queuedMessageId === "string"
|
|
73
|
+
? event.payload.queuedMessageId
|
|
74
|
+
: undefined;
|
|
75
|
+
const insert = sql `
|
|
76
|
+
INSERT INTO ${turns} (
|
|
77
|
+
"session_id", "turn_id", "agent_id", "owner_user_id", "a2a_context_id", "status",
|
|
78
|
+
"started_at", "ended_at", "changed_at", "totals", "waiting_for", "queued_message_ids",
|
|
79
|
+
"first_seq", "last_seq"
|
|
80
|
+
)
|
|
81
|
+
SELECT
|
|
82
|
+
${sessions.id}, ${event.turnId}, coalesce(${sessions.agentId}, ''), ${sessions.ownerUserId},
|
|
83
|
+
nylorun.doc(${sessions.body})->'info'->'a2a'->>'contextId', ${status},
|
|
84
|
+
${event.time}, ${ended ? event.time : null}, ${event.time},
|
|
85
|
+
${totals === null ? null : sql `${toJson(totals)}::text::json`}, ${waitingFor},
|
|
86
|
+
${queuedMessageId === undefined ? sql `'{}'::text[]` : sql `ARRAY[${queuedMessageId}]::text[]`},
|
|
87
|
+
${event.seq}, ${event.seq}
|
|
88
|
+
FROM ${sessions}
|
|
89
|
+
WHERE ${sessions.id} = ${event.sessionId}`;
|
|
90
|
+
// A turn's further messages (a queued batch) add their queued ids and change nothing else.
|
|
91
|
+
await db.execute(event.type === "command.message"
|
|
92
|
+
? sql `${insert} ON CONFLICT ("session_id", "turn_id") DO UPDATE SET
|
|
93
|
+
"queued_message_ids" = ${turns}."queued_message_ids" || excluded."queued_message_ids"`
|
|
94
|
+
: sql `${insert} ON CONFLICT ("session_id", "turn_id") DO UPDATE SET
|
|
95
|
+
"status" = excluded."status",
|
|
96
|
+
"ended_at" = excluded."ended_at",
|
|
97
|
+
"changed_at" = excluded."changed_at",
|
|
98
|
+
"totals" = excluded."totals",
|
|
99
|
+
"waiting_for" = excluded."waiting_for",
|
|
100
|
+
"last_seq" = excluded."last_seq",
|
|
101
|
+
"txid" = pg_current_xact_id()`);
|
|
102
|
+
}
|
|
103
|
+
/** One turn's row, or undefined when the session never had that turn. */
|
|
104
|
+
export async function turnRow(db, sessionId, turnId) {
|
|
105
|
+
const [row] = await db
|
|
106
|
+
.select()
|
|
107
|
+
.from(turns)
|
|
108
|
+
.where(and(eq(turns.sessionId, sessionId), eq(turns.turnId, turnId)));
|
|
109
|
+
return row;
|
|
110
|
+
}
|
|
111
|
+
/** The turn that took queued message `queuedMessageId` of a session, if one did. */
|
|
112
|
+
export async function turnOfQueuedMessage(db, sessionId, queuedMessageId) {
|
|
113
|
+
const [row] = await db
|
|
114
|
+
.select()
|
|
115
|
+
.from(turns)
|
|
116
|
+
.where(and(eq(turns.sessionId, sessionId), sql `${queuedMessageId} = ANY(${turns.queuedMessageIds})`))
|
|
117
|
+
.limit(1);
|
|
118
|
+
return row;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The turns `query` names, latest status change first (then by session and turn id, descending),
|
|
122
|
+
* at most `limit`, and how many match without `after` and `limit`.
|
|
123
|
+
*/
|
|
124
|
+
export async function listTurns(db, query) {
|
|
125
|
+
if (query.states?.length === 0)
|
|
126
|
+
return { turns: [], total: 0 };
|
|
127
|
+
const where = [
|
|
128
|
+
eq(turns.ownerUserId, query.ownerUserId),
|
|
129
|
+
eq(turns.agentId, query.agentId),
|
|
130
|
+
isNotNull(turns.a2aContextId),
|
|
131
|
+
];
|
|
132
|
+
if (query.sessionId !== undefined)
|
|
133
|
+
where.push(eq(turns.sessionId, query.sessionId));
|
|
134
|
+
if (query.states !== undefined)
|
|
135
|
+
where.push(or(...query.states.map((state) => and(eq(turns.status, state.status), state.waitingFor === undefined ? undefined : inArray(turns.waitingFor, [...state.waitingFor])))));
|
|
136
|
+
if (query.changedSince !== undefined)
|
|
137
|
+
where.push(gte(turns.changedAt, query.changedSince));
|
|
138
|
+
const after = query.after;
|
|
139
|
+
// One statement at a time: a transaction runs on one connection.
|
|
140
|
+
const rows = await db
|
|
141
|
+
.select()
|
|
142
|
+
.from(turns)
|
|
143
|
+
.where(and(...where, after
|
|
144
|
+
? sql `(${turns.changedAt}, ${turns.sessionId}, ${turns.turnId}) < (${after.changedAt}, ${after.sessionId}, ${after.turnId})`
|
|
145
|
+
: undefined))
|
|
146
|
+
.orderBy(desc(turns.changedAt), desc(turns.sessionId), desc(turns.turnId))
|
|
147
|
+
.limit(query.limit);
|
|
148
|
+
const [counted] = await db
|
|
149
|
+
.select({ total: sql `count(*)`.mapWith(Number) })
|
|
150
|
+
.from(turns)
|
|
151
|
+
.where(and(...where));
|
|
152
|
+
return { turns: rows, total: counted?.total ?? 0 };
|
|
153
|
+
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -65,7 +65,9 @@ import type { RecordReader } from "../streams/relay/types.js";
|
|
|
65
65
|
import type { SandboxSignal, Wake } from "../execution/types.js";
|
|
66
66
|
import type { EventPayload, EventType, EventWriter, LiveEvent, AgentEvent, AgentEventPayload, AgentEventType, AgentManifestV8, EnvironmentKind, EnvironmentRecipe, PodDesired, PodObserved, SessionEventOf } from "@nylorun/core/contracts";
|
|
67
67
|
import type { HostEffect } from "@nylorun/harness/run";
|
|
68
|
-
import type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, PrincipalRow, ToolCrossingRow, QueuedMessageRow, AgentTriggerRow, TriggerRunRow, MemoryRow, MemoryVersionRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
|
|
68
|
+
import type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, PrincipalRow, ToolCrossingRow, QueuedMessageRow, TurnRow, A2aPushConfigRow, AgentTriggerRow, TriggerRunRow, MemoryRow, MemoryVersionRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow } from "./postgres/schema.js";
|
|
69
|
+
import type { TurnListQuery } from "./postgres/turns.js";
|
|
70
|
+
export type { TurnListQuery };
|
|
69
71
|
/** Tables holding JSON documents keyed by `id`. */
|
|
70
72
|
export type DocTable = "sessions" | "commands" | "effects" | "sandboxes" | "links";
|
|
71
73
|
export declare const DOC_TABLES: readonly DocTable[];
|
|
@@ -258,6 +260,15 @@ export interface SandboxPodState {
|
|
|
258
260
|
* Environment is ready (Q14): every later pod, after a stop, a relaunch or a reset, runs it.
|
|
259
261
|
*/
|
|
260
262
|
imageDigest?: string;
|
|
263
|
+
/**
|
|
264
|
+
* The engine the workload image carries (ADR-99, `sandbox/pods/builtin.ts`): its Runtime
|
|
265
|
+
* version, as its engine reported at join, and the digest that pod ran. It counts only while
|
|
266
|
+
* that digest is `imageDigest`.
|
|
267
|
+
*/
|
|
268
|
+
engineImage?: {
|
|
269
|
+
version: string;
|
|
270
|
+
digest?: string;
|
|
271
|
+
};
|
|
261
272
|
}
|
|
262
273
|
/** A change to a pod's columns: `null` clears an optional one. */
|
|
263
274
|
export type SandboxPodPatch = {
|
|
@@ -295,7 +306,7 @@ export type CommitListener = (commit: Commit) => void;
|
|
|
295
306
|
* `bytea` columns, never inside a JSON body), the vault's audit and idempotency records, the
|
|
296
307
|
* Tenant's signing keys, a model call in the usage ledger and a model budget.
|
|
297
308
|
*/
|
|
298
|
-
export type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, ToolCrossingRow, QueuedMessageRow, AgentTriggerRow, TriggerRunRow, MemoryRow, MemoryVersionRow, PrincipalRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, };
|
|
309
|
+
export type { ArtifactRow, ArtifactVersionRow, DefinitionFileRow, ModelBudgetRow, ModelUsageRow, ModelUsageWrite, ToolCrossingRow, QueuedMessageRow, TurnRow, A2aPushConfigRow, AgentTriggerRow, TriggerRunRow, MemoryRow, MemoryVersionRow, PrincipalRow, SigningKeyRow, VaultAuditRow, VaultCredentialRow, VaultIdempotencyRow, VaultRow, };
|
|
299
310
|
/** Which rows of the usage ledger a total covers. */
|
|
300
311
|
export interface ModelUsageQuery {
|
|
301
312
|
scope: "tenant" | "manifest" | "turn";
|
|
@@ -383,10 +394,22 @@ export interface SessionStore {
|
|
|
383
394
|
export type OutboxWake = Wake & {
|
|
384
395
|
dedupeKey: string;
|
|
385
396
|
};
|
|
397
|
+
/**
|
|
398
|
+
* One A2A push as the outbox delivers it (`Tx.enqueuePush`): the config it goes to (by session,
|
|
399
|
+
* task and config id) and the exact body. `id` is its Standard Webhooks `webhook-id`.
|
|
400
|
+
*/
|
|
401
|
+
export interface OutboxPush {
|
|
402
|
+
id: string;
|
|
403
|
+
sessionId: string;
|
|
404
|
+
taskId: string;
|
|
405
|
+
configId: string;
|
|
406
|
+
body: string;
|
|
407
|
+
}
|
|
386
408
|
/**
|
|
387
409
|
* What a committed transaction asked of Durable Session Execution, as the outbox delivers it:
|
|
388
410
|
* a wake (`Tx.wake`), or a pod sandbox's reconcile or timer (`Tx.signalSandbox`; a reconcile
|
|
389
|
-
* carries its idempotency key, `signal:<row id>`).
|
|
411
|
+
* carries its idempotency key, `signal:<row id>`). Or an A2A push to a caller's URL (`push`,
|
|
412
|
+
* `Tx.enqueuePush`), which the Tenant sends itself (`api/a2a/push.ts`).
|
|
390
413
|
*/
|
|
391
414
|
export type OutboxRequest = {
|
|
392
415
|
kind: "wake";
|
|
@@ -396,6 +419,9 @@ export type OutboxRequest = {
|
|
|
396
419
|
kind: "sandbox";
|
|
397
420
|
sandboxId: string;
|
|
398
421
|
signal: SandboxSignal;
|
|
422
|
+
} | {
|
|
423
|
+
kind: "push";
|
|
424
|
+
push: OutboxPush;
|
|
399
425
|
};
|
|
400
426
|
/**
|
|
401
427
|
* Hands an outbox request to Durable Session Execution (`SessionStore.deliverTo`). Resolving
|
|
@@ -502,6 +528,22 @@ export interface Tx {
|
|
|
502
528
|
event<T extends EventType>(sessionId: string, turnId: string | null, type: T, payload: EventPayload<T>,
|
|
503
529
|
/** Who wrote it (R4.9): absent, core, observed. */
|
|
504
530
|
writer?: EventWriter): Promise<SessionEventOf<T>>;
|
|
531
|
+
/**
|
|
532
|
+
* One turn's row of the turn index, or undefined when the session never had that turn. `event`
|
|
533
|
+
* keeps the index: each event that changes a turn (`command.message`, `command.approve`,
|
|
534
|
+
* `command.respond`, `turn.*`) writes its row in the same transaction.
|
|
535
|
+
*/
|
|
536
|
+
turn(sessionId: string, turnId: string): Promise<TurnRow | undefined>;
|
|
537
|
+
/** The turn that took queued message `queuedMessageId` of the session, if one did (protocol 17). */
|
|
538
|
+
turnOfQueuedMessage(sessionId: string, queuedMessageId: string): Promise<TurnRow | undefined>;
|
|
539
|
+
/**
|
|
540
|
+
* One owner's A2A tasks with one Agent from the turn index (`ListTasks`), latest status change
|
|
541
|
+
* first, with how many match before paging.
|
|
542
|
+
*/
|
|
543
|
+
listTurns(query: TurnListQuery): Promise<{
|
|
544
|
+
turns: TurnRow[];
|
|
545
|
+
total: number;
|
|
546
|
+
}>;
|
|
505
547
|
/**
|
|
506
548
|
* Asks for an advance of `sessionId` once this transaction commits. The request is a row
|
|
507
549
|
* of the wake outbox written in this transaction, so it commits, or rolls back, with what
|
|
@@ -539,6 +581,43 @@ export interface Tx {
|
|
|
539
581
|
retryInMs: number;
|
|
540
582
|
park: boolean;
|
|
541
583
|
}): Promise<void>;
|
|
584
|
+
/** Adds or replaces a task's push config (its `createdAt` and `doneAt` start again). */
|
|
585
|
+
putPushConfig(row: Omit<A2aPushConfigRow, "createdAt" | "doneAt"> & {
|
|
586
|
+
doneAt?: Date | null;
|
|
587
|
+
}): Promise<void>;
|
|
588
|
+
/** One push config of a task. */
|
|
589
|
+
pushConfig(sessionId: string, taskId: string, configId: string): Promise<A2aPushConfigRow | undefined>;
|
|
590
|
+
/** A task's push configs, by config id, after `after`, at most `limit`. */
|
|
591
|
+
pushConfigs(sessionId: string, taskId: string, options?: {
|
|
592
|
+
after?: string;
|
|
593
|
+
limit?: number;
|
|
594
|
+
}): Promise<A2aPushConfigRow[]>;
|
|
595
|
+
/** Deletes a task's push config; whether it was there. */
|
|
596
|
+
deletePushConfig(sessionId: string, taskId: string, configId: string): Promise<boolean>;
|
|
597
|
+
/** A session's push configs still following their task (no `doneAt`); with `lock`, locked. */
|
|
598
|
+
activePushConfigs(sessionId: string, options?: {
|
|
599
|
+
lock?: boolean;
|
|
600
|
+
}): Promise<A2aPushConfigRow[]>;
|
|
601
|
+
/** Sessions with a push config still following its task, by id, after `after`, at most `limit`. */
|
|
602
|
+
pushSessions(limit: number, after?: string): Promise<string[]>;
|
|
603
|
+
/** Moves a push config on: the turn it follows, the next event it reads, and whether it is done. */
|
|
604
|
+
advancePushConfig(key: {
|
|
605
|
+
sessionId: string;
|
|
606
|
+
taskId: string;
|
|
607
|
+
configId: string;
|
|
608
|
+
}, change: {
|
|
609
|
+
turnId: string;
|
|
610
|
+
nextSeq: number;
|
|
611
|
+
traceId: string | null;
|
|
612
|
+
done: boolean;
|
|
613
|
+
}): Promise<void>;
|
|
614
|
+
/** Deletes push configs done before `before`; how many. */
|
|
615
|
+
prunePushConfigs(before: Date): Promise<number>;
|
|
616
|
+
/**
|
|
617
|
+
* Writes an A2A push to the outbox (kind `push`), delivered after commit as a wake is, and by
|
|
618
|
+
* the sweep with backoff until it is delivered or too old.
|
|
619
|
+
*/
|
|
620
|
+
enqueuePush(push: Omit<OutboxPush, "id">): Promise<void>;
|
|
542
621
|
/**
|
|
543
622
|
* Locks the session and takes ownership when it has no owner or the owner's
|
|
544
623
|
* lease ended at or before `now`; then increments `epoch` and sets the lease
|