Slice 4 control-plane repairs, lint/catalog cleanup, Flue adapter fixes

Convex control plane (Slices 1-4 prerequisites for Slice 5):
- Versioned Definition/Design persistence: revisions preserve history
  instead of deleting prior slices/approvals
- Fenced conversation turn queue: attempt-numbered leases prevent stale
  worker overwrites; expired turns reconciled by cron
- Fenced Run/Attempt claiming: only queued attempts from running Runs can
  be claimed; lease expiry checks on every finish/checkpoint
- Multi-slice progression: successful slice marks next slice ready instead
  of completing the whole Work; completed Runs blocked from retry
- Typed AttemptClassification in schema and resolver decisions table
- Durable resolver decisions for normal outcomes, cancellation, and
  lease-expiry recovery
- Persistent artifacts and delivery metadata with provenance, source
  revision, verification status, and controlled delivery transitions
- High-impact open questions block definition approval
- Question submission gated to defining/awaiting-approval states only
- Delivery status updates validate JSON metadata before persisting
- Planner failures recorded as durable blocked events
- Approval identity uses canonical tokenIdentifier

Primitives:
- decodeDefinition separates decode from validate
- Design validation enforces 1-4 slices with unique IDs and valid deps
- Artifact and delivery draft schemas with verified-revision binding
- Delivery status transition table

Package management:
- Consolidated shared deps into single catalog (hono, valibot, streamdown,
  @types/react, @types/node, @tailwindcss/*, react-native)
- Removed cross-version Hono boundary: flue() exported as Fetchable
- Deleted bun.lock and regenerated from clean catalog resolution

Lint/format:
- Removed .eslintignore; oxc uses native ignorePatterns in oxlint.config.ts
- Deleted redundant packages/backend/.oxlintrc.json
- Added repos/** and scripts/** to ignore patterns
- Convex-specific rule overrides for ES2022 target constraints
- Fixed all oxc errors in fluePersistence.ts and agents/src/db.ts
- Fixed all formatting across changed files
This commit is contained in:
-Puter
2026-07-28 02:53:05 +05:30
parent 0e32c35515
commit a8d946b6a9
43 changed files with 3066 additions and 1424 deletions

View File

@@ -17,8 +17,8 @@
"@code/env": "workspace:*",
"@flue/runtime": "latest",
"convex": "catalog:",
"hono": "4.12.31",
"valibot": "^1.4.2"
"hono": "catalog:",
"valibot": "catalog:"
},
"devDependencies": {
"@code/config": "workspace:*",

View File

@@ -1,7 +1,7 @@
import { parseAgentEnv } from "@code/env/agent";
import { registerProvider } from "@flue/runtime";
import type { Fetchable } from "@flue/runtime/routing";
import { flue } from "@flue/runtime/routing";
import { Hono } from "hono";
const agentEnv = parseAgentEnv(process.env);
@@ -24,7 +24,5 @@ registerProvider(agentEnv.AGENT_MODEL_PROVIDER, {
},
});
const app = new Hono();
app.route("/", flue() as unknown as Hono);
export default app;
// flue() returns a complete Hono app; the Fetchable contract accepts it.
export default flue() satisfies Fetchable;

View File

@@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file, @typescript-eslint/no-invalid-void-type, class-methods-use-this, @typescript-eslint/parameter-properties */
/**
* Convex-backed Flue {@link PersistenceAdapter} for the agents package.
*
@@ -25,70 +26,11 @@
*/
import { env } from "@code/env/server";
import {
type AgentAttemptMarker,
type AgentDispatchAdmission,
type AgentDispatchReceipt,
type AgentExecutionStore,
type AgentSubmission,
type AgentSubmissionStore,
AttachmentConflictError,
type AttachmentRef,
type AttachmentStore,
type ConversationProducerClaim,
type ConversationRecord,
type ConversationStreamBatch,
type ConversationStreamIdentity,
type ConversationStreamMeta,
type ConversationStreamReadResult,
type ConversationStreamStore,
type CreateRunInput,
DEFAULT_LIST_LIMIT,
DEFAULT_READ_LIMIT,
type DirectAgentSubmissionInput,
type DispatchAgentSubmissionInput,
type DispatchInput,
type EndRunInput,
type EventStreamMeta,
type EventStreamReadResult,
type EventStreamStore,
type GetAttachmentInput,
MAX_LIST_LIMIT,
MAX_READ_LIMIT,
type PersistedChunkRow,
type PersistenceAdapter,
type PersistenceStores,
type PutAttachmentInput,
type RunPointer,
type RunRecord,
type RunStore,
type RunStatus,
StreamListenerRegistry,
type StoredAttachment,
type SubmissionAttemptRef,
type SubmissionClaimRef,
type SubmissionDurability,
type SubmissionSettlementObligation,
type SubmissionSettledRecord,
assertSupportedFlueSchemaVersion,
clampLimit,
copyAttachmentBytes,
createSessionStorageKey,
decodeRunCursor,
encodeRunCursor,
formatOffset,
hydratePersistedDirectSubmission,
parseOffset,
prepareDirectSubmission,
verifyAttachmentBytes,
} from "@flue/runtime/adapter";
import { AttachmentConflictError, DEFAULT_LIST_LIMIT, DEFAULT_READ_LIMIT, MAX_LIST_LIMIT, MAX_READ_LIMIT, StreamListenerRegistry, assertSupportedFlueSchemaVersion, clampLimit, copyAttachmentBytes, createSessionStorageKey, decodeRunCursor, encodeRunCursor, formatOffset, hydratePersistedDirectSubmission, parseOffset, prepareDirectSubmission, verifyAttachmentBytes } from '@flue/runtime/adapter';
import type { AgentAttemptMarker, AgentDispatchAdmission, AgentDispatchReceipt, AgentExecutionStore, AgentSubmission, AgentSubmissionStore, AttachmentRef, AttachmentStore, ConversationProducerClaim, ConversationRecord, ConversationStreamBatch, ConversationStreamIdentity, ConversationStreamMeta, ConversationStreamReadResult, ConversationStreamStore, CreateRunInput, DirectAgentSubmissionInput, DispatchAgentSubmissionInput, DispatchInput, EndRunInput, EventStreamMeta, EventStreamReadResult, EventStreamStore, GetAttachmentInput, PersistedChunkRow, PersistenceAdapter, PersistenceStores, PutAttachmentInput, RunPointer, RunRecord, RunStore, RunStatus, StoredAttachment, SubmissionAttemptRef, SubmissionClaimRef, SubmissionDurability, SubmissionSettlementObligation, SubmissionSettledRecord } from '@flue/runtime/adapter';
import { ConvexHttpClient } from "convex/browser";
import {
type FunctionArgs,
type FunctionReference,
type FunctionReturnType,
makeFunctionReference,
} from "convex/server";
import { makeFunctionReference } from 'convex/server';
import type { FunctionArgs, FunctionReference, FunctionReturnType } from 'convex/server';
// ---------------------------------------------------------------------------
// Token + arg types
@@ -100,7 +42,7 @@ import {
// ---------------------------------------------------------------------------
/** Auth token present on every Convex function call. */
type TokenArgs = { readonly token: string; readonly [key: string]: unknown };
interface TokenArgs { readonly token: string; readonly [key: string]: unknown }
const TOKEN = (): string => env.FLUE_DB_TOKEN;
@@ -112,7 +54,7 @@ type TraceCarrier = NonNullable<DirectAgentSubmissionInput["traceCarrier"]>;
// ---------------------------------------------------------------------------
/** Common fields stored for every submission row. */
type SubmissionRow = {
interface SubmissionRow {
readonly sequence: number;
readonly submissionId: string;
readonly sessionKey: string;
@@ -134,42 +76,42 @@ type SubmissionRow = {
readonly ownerId: string | null;
readonly leaseExpiresAt: number;
readonly traceCarrierJson: string | null;
};
}
type SettlementObligationRow = {
interface SettlementObligationRow {
readonly submissionId: string;
readonly sessionKey: string;
readonly attemptId: string;
readonly recordId: string;
readonly recordJson: string;
};
}
type AttemptMarkerRow = {
interface AttemptMarkerRow {
readonly submissionId: string;
readonly attemptId: string;
readonly createdAt: number;
};
}
type ConversationStreamRow = {
interface ConversationStreamRow {
readonly identity: ConversationStreamIdentity;
readonly incarnation: string;
readonly nextOffset: number;
readonly producerId: string | null;
readonly producerEpoch: number;
readonly nextProducerSequence: number;
};
}
type ConversationBatchRow = {
interface ConversationBatchRow {
readonly offset: number;
readonly recordsJson: string;
};
}
type EventEntryRow = {
interface EventEntryRow {
readonly offset: number;
readonly dataJson: string;
};
}
type RunRow = {
interface RunRow {
readonly runId: string;
readonly workflowName: string;
readonly status: RunStatus;
@@ -181,9 +123,9 @@ type RunRow = {
readonly resultJson: string | null;
readonly errorJson: string | null;
readonly traceCarrierJson: string | null;
};
}
type RunPointerRow = {
interface RunPointerRow {
readonly runId: string;
readonly workflowName: string;
readonly status: RunStatus;
@@ -191,15 +133,15 @@ type RunPointerRow = {
readonly endedAt: string | null;
readonly durationMs: number | null;
readonly isError: boolean | null;
};
}
type AttachmentRefWire = {
interface AttachmentRefWire {
readonly id: string;
readonly mimeType: string;
readonly size: number;
readonly digest: string;
readonly filename?: string;
};
}
// ---------------------------------------------------------------------------
// Typed function references
@@ -263,7 +205,7 @@ const replaceSubmissionAttempt = makeFunctionReference<
>("fluePersistence:replaceSubmissionAttempt");
/** Unified admission envelope — `kind` discriminates dispatch vs direct. */
type AdmitSubmissionEnvelope = {
interface AdmitSubmissionEnvelope {
readonly kind: "dispatch" | "direct";
readonly submissionId: string;
readonly sessionKey: string;
@@ -271,12 +213,12 @@ type AdmitSubmissionEnvelope = {
readonly inputJson: string;
readonly chunksJson?: string;
readonly traceCarrierJson?: string;
};
type AdmitSubmissionResponse = {
}
interface AdmitSubmissionResponse {
readonly kind: "submission" | "retained_receipt" | "conflict";
readonly submission?: SubmissionRow;
readonly receipt?: AgentDispatchReceipt;
};
}
const admitSubmission = makeFunctionReference<
"mutation",
TokenArgs & { readonly input: AdmitSubmissionEnvelope },
@@ -410,7 +352,7 @@ type AppendBatchArgs = TokenArgs & {
readonly submission?: { readonly submissionId: string; readonly attemptId: string };
readonly recordsJson: string;
};
type AppendResult = { readonly offset: number; readonly appended: boolean };
interface AppendResult { readonly offset: number; readonly appended: boolean }
const appendConversationBatch = makeFunctionReference<
"mutation",
AppendBatchArgs,
@@ -422,11 +364,11 @@ type ReadBatchesArgs = TokenArgs & {
readonly afterOffset: number;
readonly limit: number;
};
type ReadBatchesResult = {
interface ReadBatchesResult {
readonly batches: ConversationBatchRow[];
readonly nextOffset: number;
readonly upToDate: boolean;
};
}
const readConversationBatches = makeFunctionReference<
"query",
ReadBatchesArgs,
@@ -478,12 +420,12 @@ type ReadEventsArgs = TokenArgs & {
readonly afterOffset: number;
readonly limit: number;
};
type ReadEventsResult = {
interface ReadEventsResult {
readonly events: EventEntryRow[];
readonly nextOffset: number;
readonly upToDate: boolean;
readonly closed: boolean;
};
}
const readEventsFn = makeFunctionReference<
"query",
ReadEventsArgs,
@@ -496,7 +438,7 @@ const closeEventStream = makeFunctionReference<
void
>("fluePersistence:closeEventStream");
type EventStreamMetaRow = { readonly nextOffset: number; readonly closed: boolean };
interface EventStreamMetaRow { readonly nextOffset: number; readonly closed: boolean }
const getEventStreamMeta = makeFunctionReference<
"query",
TokenArgs & { readonly path: string },
@@ -545,10 +487,10 @@ type ListRunsArgs = TokenArgs & {
readonly limit: number;
readonly cursor?: { readonly startedAt: string; readonly runId: string };
};
type ListRunsResult = {
interface ListRunsResult {
readonly rows: RunPointerRow[];
readonly hasMore: boolean;
};
}
const listRuns = makeFunctionReference<"query", ListRunsArgs, ListRunsResult>(
"fluePersistence:listRuns",
);
@@ -571,10 +513,10 @@ type GetAttachmentArgs = TokenArgs & {
readonly conversationId: string;
readonly attachmentId: string;
};
type AttachmentWire = {
interface AttachmentWire {
readonly attachment: AttachmentRefWire;
readonly bytes: ArrayBuffer;
};
}
const getAttachment = makeFunctionReference<
"query",
GetAttachmentArgs,
@@ -592,7 +534,7 @@ const deleteAttachmentsForInstance = makeFunctionReference<
// ---------------------------------------------------------------------------
const safeJsonParse = <T>(text: string | null | undefined): T | undefined => {
if (text === null || text === undefined || text === "") return undefined;
if (text === null || text === undefined || text === "") {return undefined;}
return JSON.parse(text) as T;
};
@@ -601,7 +543,7 @@ const jsonStringify = (value: unknown): string => JSON.stringify(value);
const parseAcceptedAt = (value: string, label: string): number => {
const ms = Date.parse(value);
if (!Number.isFinite(ms)) {
throw new Error(
throw new TypeError(
`[flue] ${label} produced non-finite acceptedAt: ${value}`,
);
}
@@ -613,18 +555,18 @@ const afterOffset = (offset: string | undefined): number =>
offset === undefined ? -1 : parseOffset(offset);
const wireRefToAttachmentRef = (wire: AttachmentRefWire): AttachmentRef => ({
digest: wire.digest,
id: wire.id,
mimeType: wire.mimeType,
size: wire.size,
digest: wire.digest,
...(wire.filename === undefined ? {} : { filename: wire.filename }),
});
const attachmentRefToWire = (ref: AttachmentRef): AttachmentRefWire => ({
digest: ref.digest,
id: ref.id,
mimeType: ref.mimeType,
size: ref.size,
digest: ref.digest,
...(ref.filename === undefined ? {} : { filename: ref.filename }),
});
@@ -636,17 +578,17 @@ const attachmentRefToWire = (ref: AttachmentRef): AttachmentRefWire => ({
*/
const hydrateSubmission = (row: SubmissionRow): AgentSubmission => {
const baseSubmission: Omit<AgentSubmission, "input"> = {
sequence: row.sequence,
submissionId: row.submissionId,
sessionKey: row.sessionKey,
kind: row.kind,
status: row.status,
acceptedAt: row.acceptedAt,
canonicalReadyAt: row.canonicalReadyAt,
attemptCount: row.attemptCount,
maxRetry: row.maxRetry,
timeoutAt: row.timeoutAt,
canonicalReadyAt: row.canonicalReadyAt,
kind: row.kind,
leaseExpiresAt: row.leaseExpiresAt,
maxRetry: row.maxRetry,
sequence: row.sequence,
sessionKey: row.sessionKey,
status: row.status,
submissionId: row.submissionId,
timeoutAt: row.timeoutAt,
...(row.attemptId === null ? {} : { attemptId: row.attemptId }),
...(row.inputAppliedAt === null
? {}
@@ -699,19 +641,19 @@ const hydrateObligation = (
);
}
return {
submissionId: row.submissionId,
sessionKey: row.sessionKey,
attemptId: row.attemptId,
recordId: row.recordId,
record,
recordId: row.recordId,
sessionKey: row.sessionKey,
submissionId: row.submissionId,
};
};
const toRunRecord = (row: RunRow): RunRecord => ({
runId: row.runId,
workflowName: row.workflowName,
status: row.status,
startedAt: row.startedAt,
status: row.status,
workflowName: row.workflowName,
...(row.endedAt === null ? {} : { endedAt: row.endedAt }),
...(row.isError === null ? {} : { isError: row.isError }),
...(row.durationMs === null ? {} : { durationMs: row.durationMs }),
@@ -725,9 +667,9 @@ const toRunRecord = (row: RunRow): RunRecord => ({
const toRunPointer = (row: RunPointerRow): RunPointer => ({
runId: row.runId,
workflowName: row.workflowName,
status: row.status,
startedAt: row.startedAt,
status: row.status,
workflowName: row.workflowName,
...(row.endedAt === null ? {} : { endedAt: row.endedAt }),
...(row.durationMs === null ? {} : { durationMs: row.durationMs }),
...(row.isError === null ? {} : { isError: row.isError }),
@@ -772,13 +714,13 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
async getSubmission(submissionId: string): Promise<AgentSubmission | null> {
const row = await this.convex.query(getSubmission, {
token: TOKEN(),
submissionId,
token: TOKEN(),
});
return row === null ? null : hydrateSubmission(row);
}
async hasUnsettledSubmissions(): Promise<boolean> {
hasUnsettledSubmissions(): Promise<boolean> {
return this.convex.query(hasUnsettledSubmissions, { token: TOKEN() });
}
@@ -818,10 +760,10 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
lease?: { ownerId: string; leaseExpiresAt: number },
): Promise<AgentSubmission | null> {
const row = await this.convex.mutation(replaceSubmissionAttempt, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
nextAttemptId,
submissionId: attempt.submissionId,
token: TOKEN(),
...(lease === undefined ? {} : lease),
});
return row === null ? null : hydrateSubmission(row);
@@ -833,25 +775,25 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
DispatchAgentSubmissionInput,
"kind" | "submissionId"
> = {
dispatchId: input.dispatchId,
acceptedAt: input.acceptedAt,
agent: input.agent,
dispatchId: input.dispatchId,
id: input.id,
input: input.input,
acceptedAt: input.acceptedAt,
};
const res = await this.convex.mutation(admitSubmission, {
token: TOKEN(),
input: {
kind: "dispatch",
submissionId: input.dispatchId,
sessionKey,
acceptedAt: parseAcceptedAt(input.acceptedAt, "admitDispatch"),
inputJson: jsonStringify({
kind: "dispatch",
submissionId: input.dispatchId,
...submissionInput,
}),
kind: "dispatch",
sessionKey,
submissionId: input.dispatchId,
},
token: TOKEN(),
});
if (res.kind === "submission" && res.submission !== undefined) {
return {
@@ -871,18 +813,18 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
const sessionKey = createSessionStorageKey(input.id, "default", "default");
const extracted = prepareDirectSubmission(input);
const res = await this.convex.mutation(admitSubmission, {
token: TOKEN(),
input: {
kind: "direct",
submissionId: input.submissionId,
sessionKey,
acceptedAt: parseAcceptedAt(input.acceptedAt, "admitDirect"),
inputJson: jsonStringify(extracted.value),
chunksJson: jsonStringify(extracted.chunks),
inputJson: jsonStringify(extracted.value),
kind: "direct",
sessionKey,
submissionId: input.submissionId,
...(input.traceCarrier === undefined
? {}
: { traceCarrierJson: jsonStringify(input.traceCarrier) }),
},
token: TOKEN(),
});
if (res.kind === "submission" && res.submission !== undefined) {
return hydrateSubmission(res.submission);
@@ -899,8 +841,8 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
submissionId: string,
): Promise<AgentSubmission | null> {
const row = await this.convex.mutation(markSubmissionCanonicalReady, {
token: TOKEN(),
submissionId,
token: TOKEN(),
});
return row === null ? null : hydrateSubmission(row);
}
@@ -909,51 +851,51 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
claim: SubmissionClaimRef,
): Promise<AgentSubmission | null> {
const row = await this.convex.mutation(claimSubmission, {
token: TOKEN(),
submissionId: claim.submissionId,
attemptId: claim.attemptId,
ownerId: claim.ownerId,
leaseExpiresAt: claim.leaseExpiresAt,
ownerId: claim.ownerId,
submissionId: claim.submissionId,
token: TOKEN(),
});
return row === null ? null : hydrateSubmission(row);
}
async markSubmissionInputApplied(
markSubmissionInputApplied(
attempt: SubmissionAttemptRef,
durability?: SubmissionDurability,
): Promise<boolean> {
return this.convex.mutation(markSubmissionInputApplied, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
submissionId: attempt.submissionId,
token: TOKEN(),
...(durability === undefined ? {} : durability),
});
}
async requestSubmissionRecovery(
requestSubmissionRecovery(
attempt: SubmissionAttemptRef,
): Promise<boolean> {
return this.convex.mutation(requestSubmissionRecovery, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
});
}
async requestSessionAbort(sessionKey: string): Promise<string[]> {
return this.convex.mutation(requestSessionAbort, {
submissionId: attempt.submissionId,
token: TOKEN(),
sessionKey,
});
}
async requeueSubmissionBeforeInputApplied(
requestSessionAbort(sessionKey: string): Promise<string[]> {
return this.convex.mutation(requestSessionAbort, {
sessionKey,
token: TOKEN(),
});
}
requeueSubmissionBeforeInputApplied(
attempt: SubmissionAttemptRef,
): Promise<boolean> {
return this.convex.mutation(requeueSubmissionBeforeInputApplied, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
@@ -962,62 +904,62 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
settlement: { recordId: string; record: SubmissionSettledRecord },
): Promise<SubmissionSettlementObligation | null> {
const row = await this.convex.mutation(reserveSubmissionSettlement, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
recordId: settlement.recordId,
recordJson: jsonStringify(settlement.record),
submissionId: attempt.submissionId,
token: TOKEN(),
});
return row === null ? null : hydrateObligation(row);
}
async finalizeSubmissionSettlement(
finalizeSubmissionSettlement(
attempt: SubmissionAttemptRef,
recordId: string,
): Promise<boolean> {
return this.convex.mutation(finalizeSubmissionSettlement, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
recordId,
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
async completeSubmission(attempt: SubmissionAttemptRef): Promise<boolean> {
completeSubmission(attempt: SubmissionAttemptRef): Promise<boolean> {
return this.convex.mutation(settleSubmission, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
outcome: "completed",
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
async failSubmission(
failSubmission(
attempt: SubmissionAttemptRef,
error: unknown,
): Promise<boolean> {
return this.convex.mutation(settleSubmission, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
outcome: "failed",
errorJson: jsonStringify(error),
outcome: "failed",
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
async insertAttemptMarker(attempt: SubmissionAttemptRef): Promise<void> {
await this.convex.mutation(insertAttemptMarker, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
async deleteAttemptMarker(attempt: SubmissionAttemptRef): Promise<void> {
await this.convex.mutation(deleteAttemptMarker, {
token: TOKEN(),
submissionId: attempt.submissionId,
attemptId: attempt.attemptId,
submissionId: attempt.submissionId,
token: TOKEN(),
});
}
@@ -1026,17 +968,17 @@ class ConvexAgentSubmissionStore implements AgentSubmissionStore {
token: TOKEN(),
});
return rows.map((row) => ({
submissionId: row.submissionId,
attemptId: row.attemptId,
createdAt: row.createdAt,
submissionId: row.submissionId,
}));
}
async renewLeases(ownerId: string, submissionIds: string[]): Promise<void> {
await this.convex.mutation(renewLeases, {
token: TOKEN(),
ownerId,
submissionIds,
token: TOKEN(),
});
}
@@ -1062,20 +1004,20 @@ class ConvexConversationStreamStore implements ConversationStreamStore {
identity: ConversationStreamIdentity,
): Promise<void> {
await this.convex.mutation(createConversationStream, {
token: TOKEN(),
path,
identity,
path,
token: TOKEN(),
});
}
async acquireProducer(
acquireProducer(
path: string,
producerId: string,
): Promise<ConversationProducerClaim> {
return this.convex.mutation(acquireConversationProducer, {
token: TOKEN(),
path,
producerId,
token: TOKEN(),
});
}
@@ -1089,14 +1031,14 @@ class ConvexConversationStreamStore implements ConversationStreamStore {
records: readonly ConversationRecord[];
}): Promise<{ offset: string }> {
const result = await this.convex.mutation(appendConversationBatch, {
token: TOKEN(),
path: input.path,
producerId: input.producerId,
producerEpoch: input.producerEpoch,
incarnation: input.incarnation,
path: input.path,
producerEpoch: input.producerEpoch,
producerId: input.producerId,
producerSequence: input.producerSequence,
...(input.submission === undefined ? {} : { submission: input.submission }),
recordsJson: jsonStringify(input.records),
token: TOKEN(),
...(input.submission === undefined ? {} : { submission: input.submission }),
});
if (result.appended) {
this.listeners.notify(input.path);
@@ -1114,10 +1056,10 @@ class ConvexConversationStreamStore implements ConversationStreamStore {
MAX_READ_LIMIT,
);
const result = await this.convex.query(readConversationBatches, {
token: TOKEN(),
path,
afterOffset: afterOffset(options?.offset),
limit,
path,
token: TOKEN(),
});
const batches: ConversationStreamBatch[] = result.batches.map((row) => ({
offset: formatOffset(row.offset),
@@ -1132,24 +1074,24 @@ class ConvexConversationStreamStore implements ConversationStreamStore {
async getMeta(path: string): Promise<ConversationStreamMeta | null> {
const row = await this.convex.query(getConversationStreamMeta, {
token: TOKEN(),
path,
token: TOKEN(),
});
if (row === null) return null;
if (row === null) {return null;}
return {
identity: row.identity,
incarnation: row.incarnation,
nextOffset: formatOffset(row.nextOffset),
producerId: row.producerId,
producerEpoch: row.producerEpoch,
nextProducerSequence: row.nextProducerSequence,
producerEpoch: row.producerEpoch,
producerId: row.producerId,
};
}
async delete(path: string): Promise<void> {
await this.convex.mutation(deleteConversationStream, {
token: TOKEN(),
path,
token: TOKEN(),
});
}
@@ -1169,16 +1111,16 @@ class ConvexEventStreamStore implements EventStreamStore {
async createStream(path: string): Promise<void> {
await this.convex.mutation(createEventStream, {
token: TOKEN(),
path,
token: TOKEN(),
});
}
async appendEvent(path: string, event: unknown): Promise<string> {
const result = await this.convex.mutation(appendEventFn, {
token: TOKEN(),
path,
dataJson: jsonStringify(event),
path,
token: TOKEN(),
});
if (result.appended) {
this.listeners.notify(path);
@@ -1192,10 +1134,10 @@ class ConvexEventStreamStore implements EventStreamStore {
event: unknown,
): Promise<string> {
const result = await this.convex.mutation(appendEventOnce, {
token: TOKEN(),
path,
key,
dataJson: jsonStringify(event),
key,
path,
token: TOKEN(),
});
if (result.appended) {
this.listeners.notify(path);
@@ -1209,38 +1151,38 @@ class ConvexEventStreamStore implements EventStreamStore {
): Promise<EventStreamReadResult> {
const limit = clampLimit(opts?.limit, DEFAULT_READ_LIMIT, MAX_READ_LIMIT);
const result = await this.convex.query(readEventsFn, {
token: TOKEN(),
path,
afterOffset: afterOffset(opts?.offset),
limit,
path,
token: TOKEN(),
});
return {
closed: result.closed,
events: result.events.map((row) => ({
offset: formatOffset(row.offset),
data: safeJsonParse(row.dataJson),
offset: formatOffset(row.offset),
})),
nextOffset: formatOffset(result.nextOffset),
upToDate: result.upToDate,
closed: result.closed,
};
}
async closeStream(path: string): Promise<void> {
await this.convex.mutation(closeEventStream, {
token: TOKEN(),
path,
token: TOKEN(),
});
}
async getStreamMeta(path: string): Promise<EventStreamMeta | null> {
const row = await this.convex.query(getEventStreamMeta, {
token: TOKEN(),
path,
token: TOKEN(),
});
if (row === null) return null;
if (row === null) {return null;}
return {
nextOffset: formatOffset(row.nextOffset),
closed: row.closed,
nextOffset: formatOffset(row.nextOffset),
};
}
@@ -1258,11 +1200,11 @@ class ConvexRunStore implements RunStore {
async createRun(input: CreateRunInput): Promise<void> {
await this.convex.mutation(createRun, {
token: TOKEN(),
runId: input.runId,
workflowName: input.workflowName,
startedAt: input.startedAt,
inputJson: jsonStringify(input.input),
runId: input.runId,
startedAt: input.startedAt,
token: TOKEN(),
workflowName: input.workflowName,
...(input.traceCarrier === undefined
? {}
: { traceCarrierJson: jsonStringify(input.traceCarrier) }),
@@ -1271,11 +1213,11 @@ class ConvexRunStore implements RunStore {
async endRun(input: EndRunInput): Promise<void> {
await this.convex.mutation(endRun, {
token: TOKEN(),
runId: input.runId,
durationMs: input.durationMs,
endedAt: input.endedAt,
isError: input.isError,
durationMs: input.durationMs,
runId: input.runId,
token: TOKEN(),
...(input.result === undefined
? {}
: { resultJson: jsonStringify(input.result) }),
@@ -1286,14 +1228,14 @@ class ConvexRunStore implements RunStore {
}
async getRun(runId: string): Promise<RunRecord | null> {
const row = await this.convex.query(getRun, { token: TOKEN(), runId });
const row = await this.convex.query(getRun, { runId, token: TOKEN() });
return row === null ? null : toRunRecord(row);
}
async lookupRun(
lookupRun(
runId: string,
): Promise<{ runId: string; workflowName: string } | null> {
return this.convex.query(lookupRun, { token: TOKEN(), runId });
return this.convex.query(lookupRun, { runId, token: TOKEN() });
}
async listRuns(opts?: {
@@ -1314,10 +1256,10 @@ class ConvexRunStore implements RunStore {
...(decoded === undefined ? {} : { cursor: decoded }),
});
const runs = result.rows.map(toRunPointer);
const last = runs[runs.length - 1];
const last = runs.at(-1);
const nextCursor =
result.hasMore && last !== undefined
? encodeRunCursor({ startedAt: last.startedAt, runId: last.runId })
? encodeRunCursor({ runId: last.runId, startedAt: last.startedAt })
: undefined;
return { runs, ...(nextCursor === undefined ? {} : { nextCursor }) };
}
@@ -1336,31 +1278,31 @@ class ConvexAttachmentStore implements AttachmentStore {
// payload cannot be mutated after the call returns.
const bytes = copyAttachmentBytes(input.bytes);
const status = await this.convex.mutation(putAttachment, {
token: TOKEN(),
streamPath: input.streamPath,
conversationId: input.conversationId,
attachment: attachmentRefToWire(input.attachment),
bytes: bytes.buffer.slice(
bytes.byteOffset,
bytes.byteOffset + bytes.byteLength,
) as ArrayBuffer,
conversationId: input.conversationId,
streamPath: input.streamPath,
token: TOKEN(),
});
if (status === "conflict") {
throw new AttachmentConflictError({
path: input.streamPath,
attachmentId: input.attachment.id,
path: input.streamPath,
});
}
}
async get(input: GetAttachmentInput): Promise<StoredAttachment | null> {
const row = await this.convex.query(getAttachment, {
token: TOKEN(),
streamPath: input.streamPath,
conversationId: input.conversationId,
attachmentId: input.attachmentId,
conversationId: input.conversationId,
streamPath: input.streamPath,
token: TOKEN(),
});
if (row === null) return null;
if (row === null) {return null;}
const attachment = wireRefToAttachmentRef(row.attachment);
const bytes = new Uint8Array(row.bytes);
// Verify integrity before handing bytes back to the runtime; throws
@@ -1374,8 +1316,8 @@ class ConvexAttachmentStore implements AttachmentStore {
async deleteForInstance(streamPath: string): Promise<void> {
await this.convex.mutation(deleteAttachmentsForInstance, {
token: TOKEN(),
streamPath,
token: TOKEN(),
});
}
}
@@ -1409,11 +1351,11 @@ class ConvexPersistenceAdapterImpl implements PersistenceAdapter {
const submissions = new ConvexAgentSubmissionStore(this.convex);
const executionStore: AgentExecutionStore = { submissions };
return {
attachmentStore: new ConvexAttachmentStore(this.convex),
conversationStreamStore: new ConvexConversationStreamStore(this.convex),
eventStreamStore: new ConvexEventStreamStore(this.convex),
executionStore,
runStore: new ConvexRunStore(this.convex),
eventStreamStore: new ConvexEventStreamStore(this.convex),
conversationStreamStore: new ConvexConversationStreamStore(this.convex),
attachmentStore: new ConvexAttachmentStore(this.convex),
};
}