feat(client): define digital sync contract
This commit is contained in:
@@ -6,6 +6,7 @@ import { ensureDigitalEmployeeSchema, getDb, readSetting } from "../../db";
|
||||
const DEFAULT_SYNC_PATH = "/api/digital-employee/sync/outbox";
|
||||
const MANAGEMENT_SYNC_RECORD_PATH =
|
||||
"/system/content/content-digital-employee-sync";
|
||||
const DIGITAL_EMPLOYEE_SYNC_CONTRACT_VERSION = "qimingclaw.digital_employee.sync.v1";
|
||||
const DEFAULT_INTERVAL_MS = 30_000;
|
||||
const DEFAULT_BATCH_SIZE = 50;
|
||||
const REQUEST_TIMEOUT_MS = 15_000;
|
||||
@@ -357,13 +358,8 @@ async function postOutbox(
|
||||
body: JSON.stringify({
|
||||
client_key: config.clientKey,
|
||||
device_id: getDeviceId(),
|
||||
items: rows.map((row) => ({
|
||||
outbox_id: row.id,
|
||||
entity_type: row.entity_type,
|
||||
entity_id: row.entity_id,
|
||||
operation: row.operation,
|
||||
payload: parsePayload(row.payload),
|
||||
})),
|
||||
contract_version: DIGITAL_EMPLOYEE_SYNC_CONTRACT_VERSION,
|
||||
items: rows.map(buildSyncItem),
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
@@ -378,6 +374,90 @@ async function postOutbox(
|
||||
}
|
||||
}
|
||||
|
||||
function buildSyncItem(row: OutboxRow): Record<string, unknown> {
|
||||
const payload = parsePayload(row.payload);
|
||||
const summary = readEntitySummary(row.entity_type, row.entity_id);
|
||||
return {
|
||||
outbox_id: row.id,
|
||||
entity_type: row.entity_type,
|
||||
entity_id: row.entity_id,
|
||||
operation: row.operation,
|
||||
contract_version: DIGITAL_EMPLOYEE_SYNC_CONTRACT_VERSION,
|
||||
entity_summary: summary,
|
||||
business_view: buildBusinessView(row, payload, summary),
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
function buildBusinessView(
|
||||
row: OutboxRow,
|
||||
payload: unknown,
|
||||
summary: DigitalEmployeeSyncEntitySummary | null,
|
||||
): Record<string, unknown> {
|
||||
const record = objectRecord(payload) ?? {};
|
||||
const base = {
|
||||
entity_type: row.entity_type,
|
||||
local_id: row.entity_id,
|
||||
operation: row.operation,
|
||||
title: stringField(record.title) || summary?.title || null,
|
||||
status: stringField(record.status) || summary?.status || null,
|
||||
summary: stringField(record.summary) || summary?.summary || null,
|
||||
};
|
||||
|
||||
switch (row.entity_type) {
|
||||
case "plan":
|
||||
return {
|
||||
...base,
|
||||
objective: stringField(record.objective) || summary?.summary || null,
|
||||
source: stringField(record.source) || "qimingclaw",
|
||||
};
|
||||
case "task":
|
||||
return {
|
||||
...base,
|
||||
plan_id: stringField(record.planId ?? record.plan_id) || null,
|
||||
assigned_skill_id: stringField(record.assignedSkillId ?? record.assigned_skill_id) || null,
|
||||
};
|
||||
case "run":
|
||||
return {
|
||||
...base,
|
||||
plan_id: stringField(record.planId ?? record.plan_id) || null,
|
||||
task_id: stringField(record.taskId ?? record.task_id) || null,
|
||||
started_at: stringField(record.startedAt ?? record.started_at ?? record.now) || null,
|
||||
finished_at: stringField(record.finishedAt ?? record.finished_at) || null,
|
||||
};
|
||||
case "event":
|
||||
return {
|
||||
...base,
|
||||
plan_id: stringField(record.plan_id ?? record.planId) || null,
|
||||
task_id: stringField(record.task_id ?? record.taskId) || null,
|
||||
run_id: stringField(record.run_id ?? record.runId) || null,
|
||||
kind: stringField(record.kind) || summary?.status || null,
|
||||
message: stringField(record.message) || summary?.summary || null,
|
||||
occurred_at: stringField(record.occurred_at ?? record.occurredAt) || null,
|
||||
};
|
||||
case "artifact":
|
||||
return {
|
||||
...base,
|
||||
plan_id: stringField(record.planId ?? record.plan_id) || null,
|
||||
task_id: stringField(record.taskId ?? record.task_id) || null,
|
||||
run_id: stringField(record.runId ?? record.run_id) || null,
|
||||
kind: stringField(record.kind) || summary?.status || null,
|
||||
name: stringField(record.name) || summary?.title || null,
|
||||
uri: stringField(record.uri) || summary?.summary || null,
|
||||
};
|
||||
case "approval":
|
||||
return {
|
||||
...base,
|
||||
plan_id: stringField(record.planId ?? record.plan_id) || null,
|
||||
task_id: stringField(record.taskId ?? record.task_id) || null,
|
||||
run_id: stringField(record.runId ?? record.run_id) || null,
|
||||
decision: stringField(record.decision) || null,
|
||||
};
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
function markRowsSyncing(rows: OutboxRow[], now: string): void {
|
||||
const db = getDigitalEmployeeDb();
|
||||
if (!db) return;
|
||||
@@ -654,6 +734,12 @@ function stringField(value: unknown): string {
|
||||
return typeof value === "string" && value.trim() ? value.trim() : "";
|
||||
}
|
||||
|
||||
function objectRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? value as Record<string, unknown>
|
||||
: null;
|
||||
}
|
||||
|
||||
function readAttemptHistory(
|
||||
outboxId: string,
|
||||
limit = 5,
|
||||
|
||||
Reference in New Issue
Block a user