@sgtbeatdown/pi-tasks 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -9
- package/package.json +6 -2
- package/skills/wolfpack-pi-task-delegation/SKILL.md +19 -10
- package/src/extension.ts +181 -137
- package/src/gateway-client.ts +32 -8
- package/src/in-memory-task-relay.ts +114 -0
- package/src/index.ts +35 -0
- package/src/legacy-extension.ts +248 -0
- package/src/legacy-task-inbox.ts +148 -0
- package/src/sqlite-database.ts +68 -0
- package/src/task-core.ts +383 -0
- package/src/task-inbox.ts +55 -87
- package/src/task-protocol.ts +129 -0
- package/src/task-relay-conformance.ts +23 -0
- package/src/task-store.ts +238 -0
- package/src/v1-compat-extension.ts +1 -0
- package/src/wolfpack-task-relay.ts +319 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
export const TASK_PROTOCOL_VERSION = "pi-tasks/v2" as const;
|
|
2
|
+
export const MAX_RELAY_PAYLOAD_BYTES = 48 * 1024;
|
|
3
|
+
|
|
4
|
+
export interface TaskEndpoint {
|
|
5
|
+
readonly relay: string;
|
|
6
|
+
readonly id: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A provider-defined reference which core resolves without interpreting. */
|
|
10
|
+
export interface RelayTargetReference {
|
|
11
|
+
readonly relay: string;
|
|
12
|
+
readonly reference: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface RelayConnectInput {
|
|
16
|
+
readonly endpoint: TaskEndpoint;
|
|
17
|
+
readonly protocolVersion: string;
|
|
18
|
+
readonly receiveCursor: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RelayConnection {
|
|
22
|
+
readonly endpoint: TaskEndpoint;
|
|
23
|
+
readonly receiveCursor: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const TaskEnvelopeKind = {
|
|
27
|
+
assignment: "assignment",
|
|
28
|
+
intent: "intent",
|
|
29
|
+
canonicalEvent: "canonical_event",
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
export type TaskEnvelopeKind = (typeof TaskEnvelopeKind)[keyof typeof TaskEnvelopeKind];
|
|
33
|
+
|
|
34
|
+
export interface RelayEnvelope {
|
|
35
|
+
readonly envelopeId: string;
|
|
36
|
+
readonly protocolVersion: string;
|
|
37
|
+
readonly source: TaskEndpoint;
|
|
38
|
+
readonly target: TaskEndpoint;
|
|
39
|
+
readonly taskId: string;
|
|
40
|
+
readonly kind: TaskEnvelopeKind;
|
|
41
|
+
/** Opaque to relays. The endpoint protocol defines its JSON representation. */
|
|
42
|
+
readonly payload: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface RelayAcceptance {
|
|
46
|
+
readonly envelopeId: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface RelayReceiveRequest {
|
|
50
|
+
readonly endpoint: TaskEndpoint;
|
|
51
|
+
readonly cursor: string;
|
|
52
|
+
readonly limit: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RelayDelivery {
|
|
56
|
+
readonly cursor: string;
|
|
57
|
+
readonly envelope: RelayEnvelope;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RelayInboxPage {
|
|
61
|
+
readonly deliveries: readonly RelayDelivery[];
|
|
62
|
+
readonly nextCursor: string;
|
|
63
|
+
readonly hasMore: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RelayDeliveryAck {
|
|
67
|
+
readonly endpoint: TaskEndpoint;
|
|
68
|
+
readonly cursor: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TaskRelay {
|
|
72
|
+
readonly id: string;
|
|
73
|
+
connect(input: RelayConnectInput, signal?: AbortSignal): Promise<RelayConnection>;
|
|
74
|
+
resolve(input: RelayTargetReference, signal?: AbortSignal): Promise<TaskEndpoint>;
|
|
75
|
+
send(input: RelayEnvelope, signal?: AbortSignal): Promise<RelayAcceptance>;
|
|
76
|
+
receive(input: RelayReceiveRequest, signal?: AbortSignal): Promise<RelayInboxPage>;
|
|
77
|
+
acknowledgeDelivery(input: RelayDeliveryAck, signal?: AbortSignal): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TaskEvent {
|
|
81
|
+
readonly eventId: string;
|
|
82
|
+
readonly taskId: string;
|
|
83
|
+
readonly type: string;
|
|
84
|
+
readonly sequence: string;
|
|
85
|
+
readonly source: TaskEndpoint;
|
|
86
|
+
readonly target: TaskEndpoint;
|
|
87
|
+
readonly occurredAt: number;
|
|
88
|
+
readonly payload: Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface TaskRecord {
|
|
92
|
+
readonly taskId: string;
|
|
93
|
+
readonly protocolVersion: string;
|
|
94
|
+
readonly origin: TaskEndpoint;
|
|
95
|
+
readonly target: TaskEndpoint;
|
|
96
|
+
readonly task: string;
|
|
97
|
+
readonly createdAt: number;
|
|
98
|
+
readonly expiresAt: number;
|
|
99
|
+
readonly status: "active" | "completed" | "failed" | "cancelled" | "timed_out";
|
|
100
|
+
readonly events: readonly TaskEvent[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface TaskIntent {
|
|
104
|
+
readonly intentId: string;
|
|
105
|
+
readonly taskId: string;
|
|
106
|
+
readonly type: "task.completed" | "task.failed" | "task.cancelled" | "task.information" | "task.question" | "task.answer" | "task.delivery_receipt";
|
|
107
|
+
readonly payload: Record<string, unknown>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class TaskProtocolError extends Error {
|
|
111
|
+
readonly code: string;
|
|
112
|
+
readonly retryable: boolean;
|
|
113
|
+
readonly details: Readonly<Record<string, unknown>> | undefined;
|
|
114
|
+
|
|
115
|
+
constructor(code: string, message: string, options: { readonly retryable?: boolean; readonly details?: Readonly<Record<string, unknown>> } = {}) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = "TaskProtocolError";
|
|
118
|
+
this.code = code;
|
|
119
|
+
this.retryable = options.retryable ?? true;
|
|
120
|
+
this.details = options.details;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export class TaskOutboxDeliveryError extends TaskProtocolError {
|
|
125
|
+
constructor(code: string, message: string, options: { readonly retryable?: boolean; readonly details?: Readonly<Record<string, unknown>> } = {}) {
|
|
126
|
+
super(code, message, options);
|
|
127
|
+
this.name = "TaskOutboxDeliveryError";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { TASK_PROTOCOL_VERSION } from "./task-protocol";
|
|
4
|
+
import type { TaskRelay } from "./task-protocol";
|
|
5
|
+
|
|
6
|
+
/** Shared assertions external relay adapters can run against their implementation. */
|
|
7
|
+
export function runTaskRelayConformance(createRelay: () => TaskRelay): void {
|
|
8
|
+
test("enforces registration, accepts duplicate envelopes once, and retains delivery until acknowledgement", async () => {
|
|
9
|
+
const relay = createRelay();
|
|
10
|
+
const source = { relay: relay.id, id: "source" };
|
|
11
|
+
const target = { relay: relay.id, id: "target" };
|
|
12
|
+
await relay.connect({ endpoint: source, protocolVersion: TASK_PROTOCOL_VERSION, receiveCursor: "0" });
|
|
13
|
+
await relay.connect({ endpoint: target, protocolVersion: TASK_PROTOCOL_VERSION, receiveCursor: "0" });
|
|
14
|
+
await expect(relay.resolve({ relay: relay.id, reference: "missing" })).rejects.toThrow("not registered");
|
|
15
|
+
const envelope = { envelopeId: "envelope-1", protocolVersion: TASK_PROTOCOL_VERSION, source, target, taskId: "task-1", kind: "assignment" as const, payload: "{}" };
|
|
16
|
+
await relay.send(envelope);
|
|
17
|
+
await relay.send(envelope);
|
|
18
|
+
const page = await relay.receive({ endpoint: target, cursor: "0", limit: 10 });
|
|
19
|
+
expect(page.deliveries).toHaveLength(1);
|
|
20
|
+
await relay.acknowledgeDelivery({ endpoint: target, cursor: page.nextCursor });
|
|
21
|
+
expect((await relay.receive({ endpoint: target, cursor: page.nextCursor, limit: 10 })).deliveries).toHaveLength(0);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { openSqliteDatabase } from "./sqlite-database";
|
|
6
|
+
import type { SqliteDatabase } from "./sqlite-database";
|
|
7
|
+
import type { RelayEnvelope, TaskEvent, TaskRecord } from "./task-protocol";
|
|
8
|
+
|
|
9
|
+
const SCHEMA_VERSION = 3;
|
|
10
|
+
const OWNER_ONLY_MODE = 0o700;
|
|
11
|
+
const OWNER_ONLY_FILE_MODE = 0o600;
|
|
12
|
+
const ENDPOINT_GENERATION_STATE_KEY = "endpoint_generation";
|
|
13
|
+
|
|
14
|
+
export interface TaskStoreOptions {
|
|
15
|
+
readonly path?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface OutboxRecord {
|
|
19
|
+
readonly envelope: RelayEnvelope;
|
|
20
|
+
readonly state: "pending" | "accepted";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TaskStore {
|
|
24
|
+
transaction<T>(operation: () => T): T;
|
|
25
|
+
putTask(task: Omit<TaskRecord, "events">): void;
|
|
26
|
+
getTask(taskId: string): TaskRecord | undefined;
|
|
27
|
+
listTasks(): readonly TaskRecord[];
|
|
28
|
+
setStatus(taskId: string, status: TaskRecord["status"]): void;
|
|
29
|
+
appendEvent(event: TaskEvent): boolean;
|
|
30
|
+
putOutbox(envelope: RelayEnvelope): void;
|
|
31
|
+
outbox(state: OutboxRecord["state"]): readonly OutboxRecord[];
|
|
32
|
+
markOutboxAccepted(envelopeId: string): void;
|
|
33
|
+
persistInbox(envelope: RelayEnvelope, cursor: string): boolean;
|
|
34
|
+
getReceiveCursor(): string;
|
|
35
|
+
setReceiveCursor(cursor: string): void;
|
|
36
|
+
putIntent(intentId: string, taskId: string, envelopeId: string): void;
|
|
37
|
+
putInsertionReceipt(taskId: string, eventId: string): boolean;
|
|
38
|
+
getEndpointGeneration(): string | undefined;
|
|
39
|
+
setEndpointGeneration(generation: string): void;
|
|
40
|
+
close(): void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createTaskStore(options: TaskStoreOptions = {}): TaskStore {
|
|
44
|
+
const path = options.path ?? join(homedir(), ".pi", "tasks", "v2", "tasks.sqlite");
|
|
45
|
+
preparePath(path);
|
|
46
|
+
const database = openSqliteDatabase(path);
|
|
47
|
+
database.exec("PRAGMA journal_mode = WAL; PRAGMA foreign_keys = ON;");
|
|
48
|
+
migrate(database);
|
|
49
|
+
|
|
50
|
+
const store: TaskStore = {
|
|
51
|
+
transaction(operation) {
|
|
52
|
+
return database.transaction(operation)();
|
|
53
|
+
},
|
|
54
|
+
putTask(task) {
|
|
55
|
+
database.query(`INSERT INTO tasks (task_id, protocol_version, origin_relay, origin_id, target_relay, target_id, task, created_at, expires_at, status)
|
|
56
|
+
VALUES ($taskId, $protocolVersion, $originRelay, $originId, $targetRelay, $targetId, $task, $createdAt, $expiresAt, $status)
|
|
57
|
+
ON CONFLICT(task_id) DO NOTHING`).run(taskRow(task));
|
|
58
|
+
},
|
|
59
|
+
getTask(taskId) {
|
|
60
|
+
const row = database.query("SELECT * FROM tasks WHERE task_id = ?").get(taskId) as TaskRow | null;
|
|
61
|
+
if (!row) return undefined;
|
|
62
|
+
const eventRows = database.query("SELECT * FROM events WHERE task_id = ? ORDER BY CAST(sequence AS INTEGER), event_id").all(taskId) as EventRow[];
|
|
63
|
+
return taskFromRow(row, eventRows);
|
|
64
|
+
},
|
|
65
|
+
listTasks() {
|
|
66
|
+
const rows = database.query("SELECT * FROM tasks ORDER BY created_at, task_id").all() as TaskRow[];
|
|
67
|
+
const events = database.query("SELECT * FROM events ORDER BY CAST(sequence AS INTEGER), event_id").all() as EventRow[];
|
|
68
|
+
const eventsByTask = new Map<string, EventRow[]>();
|
|
69
|
+
for (const event of events) {
|
|
70
|
+
const current = eventsByTask.get(event.task_id) ?? [];
|
|
71
|
+
current.push(event);
|
|
72
|
+
eventsByTask.set(event.task_id, current);
|
|
73
|
+
}
|
|
74
|
+
return rows.map((row) => taskFromRow(row, eventsByTask.get(row.task_id) ?? []));
|
|
75
|
+
},
|
|
76
|
+
setStatus(taskId, status) {
|
|
77
|
+
database.query("UPDATE tasks SET status = ? WHERE task_id = ?").run(status, taskId);
|
|
78
|
+
},
|
|
79
|
+
appendEvent(event) {
|
|
80
|
+
const result = database.query(`INSERT INTO events (event_id, task_id, type, sequence, source_relay, source_id, target_relay, target_id, occurred_at, payload)
|
|
81
|
+
VALUES ($eventId, $taskId, $type, $sequence, $sourceRelay, $sourceId, $targetRelay, $targetId, $occurredAt, $payload)
|
|
82
|
+
ON CONFLICT(event_id) DO NOTHING`).run(eventRow(event));
|
|
83
|
+
return result.changes === 1;
|
|
84
|
+
},
|
|
85
|
+
putOutbox(envelope) {
|
|
86
|
+
database.query("INSERT INTO outbox (envelope_id, envelope, state) VALUES (?, ?, 'pending') ON CONFLICT(envelope_id) DO NOTHING").run(envelope.envelopeId, JSON.stringify(envelope));
|
|
87
|
+
},
|
|
88
|
+
outbox(state) {
|
|
89
|
+
const rows = database.query("SELECT envelope FROM outbox WHERE state = ? ORDER BY rowid").all(state) as Array<{ readonly envelope: string }>;
|
|
90
|
+
return rows.map((row) => ({ envelope: parseEnvelope(row.envelope), state }));
|
|
91
|
+
},
|
|
92
|
+
markOutboxAccepted(envelopeId) {
|
|
93
|
+
database.query("UPDATE outbox SET state = 'accepted' WHERE envelope_id = ?").run(envelopeId);
|
|
94
|
+
},
|
|
95
|
+
persistInbox(envelope, cursor) {
|
|
96
|
+
const result = database.query("INSERT INTO inbox (envelope_id, cursor, envelope) VALUES (?, ?, ?) ON CONFLICT(envelope_id) DO NOTHING").run(envelope.envelopeId, cursor, JSON.stringify(envelope));
|
|
97
|
+
return result.changes === 1;
|
|
98
|
+
},
|
|
99
|
+
getReceiveCursor() {
|
|
100
|
+
const row = database.query("SELECT value FROM relay_state WHERE name = 'receive_cursor'").get() as { readonly value: string } | null;
|
|
101
|
+
return row?.value ?? "0";
|
|
102
|
+
},
|
|
103
|
+
setReceiveCursor(cursor) {
|
|
104
|
+
database.query("INSERT INTO relay_state (name, value) VALUES ('receive_cursor', ?) ON CONFLICT(name) DO UPDATE SET value = excluded.value").run(cursor);
|
|
105
|
+
},
|
|
106
|
+
putIntent(intentId, taskId, envelopeId) {
|
|
107
|
+
database.query("INSERT INTO intents (intent_id, task_id, envelope_id) VALUES (?, ?, ?) ON CONFLICT(intent_id) DO NOTHING").run(intentId, taskId, envelopeId);
|
|
108
|
+
},
|
|
109
|
+
putInsertionReceipt(taskId, eventId) {
|
|
110
|
+
const result = database.query("INSERT INTO insertion_receipts (task_id, event_id) VALUES (?, ?) ON CONFLICT(task_id, event_id) DO NOTHING").run(taskId, eventId);
|
|
111
|
+
return result.changes === 1;
|
|
112
|
+
},
|
|
113
|
+
getEndpointGeneration() {
|
|
114
|
+
const row = database.query("SELECT value FROM relay_state WHERE name = ?").get(ENDPOINT_GENERATION_STATE_KEY) as { readonly value: string } | null;
|
|
115
|
+
return row?.value;
|
|
116
|
+
},
|
|
117
|
+
setEndpointGeneration(generation) {
|
|
118
|
+
database.query("INSERT INTO relay_state (name, value) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET value = excluded.value").run(ENDPOINT_GENERATION_STATE_KEY, generation);
|
|
119
|
+
},
|
|
120
|
+
close() { database.close(); },
|
|
121
|
+
};
|
|
122
|
+
return store;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface TaskRow {
|
|
126
|
+
readonly task_id: string;
|
|
127
|
+
readonly protocol_version: string;
|
|
128
|
+
readonly origin_relay: string;
|
|
129
|
+
readonly origin_id: string;
|
|
130
|
+
readonly target_relay: string;
|
|
131
|
+
readonly target_id: string;
|
|
132
|
+
readonly task: string;
|
|
133
|
+
readonly created_at: number;
|
|
134
|
+
readonly expires_at: number;
|
|
135
|
+
readonly status: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface EventRow {
|
|
139
|
+
readonly event_id: string;
|
|
140
|
+
readonly task_id: string;
|
|
141
|
+
readonly type: string;
|
|
142
|
+
readonly sequence: string;
|
|
143
|
+
readonly source_relay: string;
|
|
144
|
+
readonly source_id: string;
|
|
145
|
+
readonly target_relay: string;
|
|
146
|
+
readonly target_id: string;
|
|
147
|
+
readonly occurred_at: number;
|
|
148
|
+
readonly payload: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function migrate(database: SqliteDatabase): void {
|
|
152
|
+
const version = database.query("PRAGMA user_version").get() as { readonly user_version: number };
|
|
153
|
+
if (version.user_version > SCHEMA_VERSION) throw new Error("task store schema is newer than this pi-tasks version");
|
|
154
|
+
if (version.user_version === 0) {
|
|
155
|
+
database.exec(`
|
|
156
|
+
CREATE TABLE tasks (task_id TEXT PRIMARY KEY, protocol_version TEXT NOT NULL, origin_relay TEXT NOT NULL, origin_id TEXT NOT NULL, target_relay TEXT NOT NULL, target_id TEXT NOT NULL, task TEXT NOT NULL, created_at INTEGER NOT NULL, expires_at INTEGER NOT NULL, status TEXT NOT NULL);
|
|
157
|
+
CREATE TABLE events (event_id TEXT PRIMARY KEY, task_id TEXT NOT NULL REFERENCES tasks(task_id), type TEXT NOT NULL, sequence TEXT NOT NULL, source_relay TEXT NOT NULL, source_id TEXT NOT NULL, target_relay TEXT NOT NULL, target_id TEXT NOT NULL, occurred_at INTEGER NOT NULL, payload TEXT NOT NULL);
|
|
158
|
+
CREATE TABLE outbox (envelope_id TEXT PRIMARY KEY, envelope TEXT NOT NULL, state TEXT NOT NULL CHECK(state IN ('pending', 'accepted')));
|
|
159
|
+
CREATE TABLE inbox (envelope_id TEXT PRIMARY KEY, cursor TEXT NOT NULL, envelope TEXT NOT NULL);
|
|
160
|
+
CREATE TABLE intents (intent_id TEXT PRIMARY KEY, task_id TEXT NOT NULL, envelope_id TEXT NOT NULL);
|
|
161
|
+
CREATE TABLE relay_state (name TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
162
|
+
PRAGMA user_version = 1;
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
165
|
+
if (version.user_version <= 1) {
|
|
166
|
+
database.exec(`
|
|
167
|
+
CREATE INDEX IF NOT EXISTS tasks_origin_expiry ON tasks (origin_relay, origin_id, status, expires_at);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS inbox_cursor ON inbox (cursor);
|
|
169
|
+
PRAGMA user_version = 2;
|
|
170
|
+
`);
|
|
171
|
+
}
|
|
172
|
+
if (version.user_version <= 2) {
|
|
173
|
+
database.exec(`
|
|
174
|
+
CREATE TABLE insertion_receipts (task_id TEXT NOT NULL REFERENCES tasks(task_id), event_id TEXT NOT NULL, PRIMARY KEY (task_id, event_id));
|
|
175
|
+
PRAGMA user_version = 3;
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function preparePath(path: string): void {
|
|
181
|
+
if (path === ":memory:") return;
|
|
182
|
+
const directory = dirname(path);
|
|
183
|
+
mkdirSync(directory, { recursive: true, mode: OWNER_ONLY_MODE });
|
|
184
|
+
const directoryMode = statSync(directory).mode & 0o777;
|
|
185
|
+
if ((directoryMode & 0o077) !== 0) throw new Error(`task store directory is not owner-only: ${directory}`);
|
|
186
|
+
if (existsSync(path)) {
|
|
187
|
+
const fileMode = statSync(path).mode & 0o777;
|
|
188
|
+
if ((fileMode & 0o077) !== 0) throw new Error(`task store file is not owner-only: ${path}`);
|
|
189
|
+
} else {
|
|
190
|
+
writeFileSync(path, "", { mode: OWNER_ONLY_FILE_MODE });
|
|
191
|
+
chmodSync(path, OWNER_ONLY_FILE_MODE);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function taskRow(task: Omit<TaskRecord, "events">): Record<string, string | number> {
|
|
196
|
+
return {
|
|
197
|
+
taskId: task.taskId, protocolVersion: task.protocolVersion, originRelay: task.origin.relay, originId: task.origin.id,
|
|
198
|
+
targetRelay: task.target.relay, targetId: task.target.id, task: task.task, createdAt: task.createdAt, expiresAt: task.expiresAt, status: task.status,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function taskFromRow(row: TaskRow, events: readonly EventRow[]): TaskRecord {
|
|
203
|
+
return {
|
|
204
|
+
taskId: row.task_id,
|
|
205
|
+
protocolVersion: row.protocol_version,
|
|
206
|
+
origin: { relay: row.origin_relay, id: row.origin_id },
|
|
207
|
+
target: { relay: row.target_relay, id: row.target_id },
|
|
208
|
+
task: row.task,
|
|
209
|
+
createdAt: row.created_at,
|
|
210
|
+
expiresAt: row.expires_at,
|
|
211
|
+
status: row.status as TaskRecord["status"],
|
|
212
|
+
events: events.map(eventFromRow),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function eventRow(event: TaskEvent): Record<string, string | number> {
|
|
217
|
+
return {
|
|
218
|
+
eventId: event.eventId, taskId: event.taskId, type: event.type, sequence: event.sequence,
|
|
219
|
+
sourceRelay: event.source.relay, sourceId: event.source.id, targetRelay: event.target.relay, targetId: event.target.id,
|
|
220
|
+
occurredAt: event.occurredAt, payload: JSON.stringify(event.payload),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function eventFromRow(row: EventRow): TaskEvent {
|
|
225
|
+
return {
|
|
226
|
+
eventId: row.event_id, taskId: row.task_id, type: row.type, sequence: row.sequence,
|
|
227
|
+
source: { relay: row.source_relay, id: row.source_id }, target: { relay: row.target_relay, id: row.target_id },
|
|
228
|
+
occurredAt: row.occurred_at, payload: parseRecord(row.payload),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseEnvelope(value: string): RelayEnvelope {
|
|
233
|
+
return JSON.parse(value) as RelayEnvelope;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function parseRecord(value: string): Record<string, unknown> {
|
|
237
|
+
return JSON.parse(value) as Record<string, unknown>;
|
|
238
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default, registerAgentTaskTools } from "./legacy-extension";
|