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:
@@ -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:*",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user