@openscout/runtime 0.2.37 → 0.2.38
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/dist/broker-daemon.js +125 -48
- package/dist/broker-journal.d.ts +13 -2
- package/dist/broker-journal.js +156 -6
- package/dist/broker-service.js +43 -20
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +17 -0
- package/dist/scout-dispatcher.d.ts +34 -0
- package/dist/scout-dispatcher.js +125 -0
- package/dist/sqlite-projection.js +147 -2
- package/dist/sqlite-store.d.ts +2 -1
- package/dist/sqlite-store.js +6 -0
- package/package.json +2 -2
- package/src/broker-daemon.test.ts +171 -1
- package/src/broker-daemon.ts +170 -55
- package/src/broker-journal.test.ts +127 -0
- package/src/broker-journal.ts +190 -8
- package/src/broker-service.ts +40 -17
- package/src/schema.ts +17 -0
- package/src/scout-dispatcher.test.ts +198 -0
- package/src/scout-dispatcher.ts +192 -0
- package/src/sqlite-projection.ts +162 -3
- package/src/sqlite-store.ts +21 -0
package/dist/broker-daemon.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { assertValidCollaborationEvent, assertValidCollaborationRecord, buildRelayReturnAddress, } from "@openscout/protocol";
|
|
4
|
+
import { assertValidCollaborationEvent, assertValidCollaborationRecord, buildRelayReturnAddress, SCOUT_DISPATCHER_AGENT_ID, } from "@openscout/protocol";
|
|
5
5
|
import { createInMemoryControlRuntime } from "./broker.js";
|
|
6
6
|
import { FileBackedBrokerJournal } from "./broker-journal.js";
|
|
7
|
+
import { buildDispatchEnvelope, resolveAgentLabel, } from "./scout-dispatcher.js";
|
|
7
8
|
import { buildCollaborationInvocation } from "./collaboration-invocations.js";
|
|
8
9
|
import { discoverMeshNodes } from "./mesh-discovery.js";
|
|
9
10
|
import { buildMeshCollaborationEventBundle, buildMeshCollaborationRecordBundle, buildMeshInvocationBundle, buildMeshMessageBundle, forwardMeshCollaborationEvent, forwardMeshCollaborationRecord, forwardMeshInvocation, fetchPeerAgents, forwardMeshMessage, } from "./mesh-forwarding.js";
|
|
@@ -192,13 +193,12 @@ function runDurableWrite(work) {
|
|
|
192
193
|
durableWriteQueue = next.then(() => undefined, () => undefined);
|
|
193
194
|
return next;
|
|
194
195
|
}
|
|
195
|
-
async function appendJournalEntries(entries) {
|
|
196
|
-
await journal.appendEntries(entries);
|
|
197
|
-
}
|
|
198
196
|
async function commitDurableEntries(entriesInput, applyRuntime, options = {}) {
|
|
199
|
-
const entries = normalizeJournalEntries(entriesInput);
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
const entries = await journal.appendEntries(normalizeJournalEntries(entriesInput));
|
|
198
|
+
if (entries.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
await applyRuntime(entries);
|
|
202
202
|
if (options.enqueueProjection !== false) {
|
|
203
203
|
projection.enqueueEntries(entries);
|
|
204
204
|
}
|
|
@@ -223,9 +223,13 @@ async function upsertAgentDurably(agent) {
|
|
|
223
223
|
await commitDurableEntries([
|
|
224
224
|
{ kind: "actor.upsert", actor: agent },
|
|
225
225
|
{ kind: "agent.upsert", agent },
|
|
226
|
-
], async () => {
|
|
227
|
-
|
|
228
|
-
|
|
226
|
+
], async (entries) => {
|
|
227
|
+
if (entries.some((entry) => entry.kind === "actor.upsert")) {
|
|
228
|
+
await runtime.upsertActor(agent);
|
|
229
|
+
}
|
|
230
|
+
if (entries.some((entry) => entry.kind === "agent.upsert")) {
|
|
231
|
+
await runtime.upsertAgent(agent);
|
|
232
|
+
}
|
|
229
233
|
});
|
|
230
234
|
});
|
|
231
235
|
}
|
|
@@ -284,6 +288,49 @@ async function recordMessageDurably(message, options = {}) {
|
|
|
284
288
|
return { deliveries, entries };
|
|
285
289
|
});
|
|
286
290
|
}
|
|
291
|
+
async function recordScoutDispatchDurably(envelope, options = {}) {
|
|
292
|
+
const record = {
|
|
293
|
+
id: createRuntimeId("scout-dispatch"),
|
|
294
|
+
invocationId: options.invocationId,
|
|
295
|
+
conversationId: options.conversationId,
|
|
296
|
+
requesterId: options.requesterId,
|
|
297
|
+
...envelope,
|
|
298
|
+
};
|
|
299
|
+
const dispatchEntries = [
|
|
300
|
+
{ kind: "scout.dispatch.record", dispatch: record },
|
|
301
|
+
];
|
|
302
|
+
let syntheticMessage = null;
|
|
303
|
+
if (options.conversationId) {
|
|
304
|
+
syntheticMessage = {
|
|
305
|
+
id: createRuntimeId("msg-scout"),
|
|
306
|
+
conversationId: options.conversationId,
|
|
307
|
+
actorId: SCOUT_DISPATCHER_AGENT_ID,
|
|
308
|
+
originNodeId: nodeId,
|
|
309
|
+
class: "system",
|
|
310
|
+
body: record.detail,
|
|
311
|
+
visibility: "workspace",
|
|
312
|
+
policy: "best_effort",
|
|
313
|
+
createdAt: record.dispatchedAt,
|
|
314
|
+
metadata: {
|
|
315
|
+
scoutDispatch: record,
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return runDurableWrite(async () => {
|
|
320
|
+
const appended = await commitDurableEntries(dispatchEntries, async () => { });
|
|
321
|
+
if (!syntheticMessage) {
|
|
322
|
+
return { record, message: null, entries: appended };
|
|
323
|
+
}
|
|
324
|
+
const deliveries = runtime.planMessage(syntheticMessage, { localOnly: true });
|
|
325
|
+
const messageEntries = await commitDurableEntries([
|
|
326
|
+
{ kind: "message.record", message: syntheticMessage },
|
|
327
|
+
{ kind: "deliveries.record", deliveries },
|
|
328
|
+
], async () => {
|
|
329
|
+
await runtime.commitMessage(syntheticMessage, deliveries);
|
|
330
|
+
});
|
|
331
|
+
return { record, message: syntheticMessage, entries: [...appended, ...messageEntries] };
|
|
332
|
+
});
|
|
333
|
+
}
|
|
287
334
|
async function recordInvocationDurably(invocation, options = {}) {
|
|
288
335
|
return runDurableWrite(async () => {
|
|
289
336
|
const flight = options.flight ?? runtime.planInvocation(invocation);
|
|
@@ -388,44 +435,33 @@ async function applyMeshBundleDurably(bundle, options = {}) {
|
|
|
388
435
|
assertValidCollaborationEvent(bundle.collaborationEvent, record);
|
|
389
436
|
}
|
|
390
437
|
const entries = buildMeshBundleEntries(bundle);
|
|
391
|
-
return commitDurableEntries(entries, async () => {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
for (const binding of bundle.bindings ?? []) {
|
|
418
|
-
if (appliedBindingIds.has(binding.id)) {
|
|
419
|
-
continue;
|
|
438
|
+
return commitDurableEntries(entries, async (retainedEntries) => {
|
|
439
|
+
for (const entry of retainedEntries) {
|
|
440
|
+
switch (entry.kind) {
|
|
441
|
+
case "node.upsert":
|
|
442
|
+
await runtime.upsertNode(entry.node);
|
|
443
|
+
break;
|
|
444
|
+
case "actor.upsert":
|
|
445
|
+
await runtime.upsertActor(entry.actor);
|
|
446
|
+
break;
|
|
447
|
+
case "agent.upsert":
|
|
448
|
+
await runtime.upsertAgent(entry.agent);
|
|
449
|
+
break;
|
|
450
|
+
case "conversation.upsert":
|
|
451
|
+
await runtime.upsertConversation(entry.conversation);
|
|
452
|
+
break;
|
|
453
|
+
case "binding.upsert":
|
|
454
|
+
await runtime.upsertBinding(entry.binding);
|
|
455
|
+
break;
|
|
456
|
+
case "collaboration.record":
|
|
457
|
+
await runtime.upsertCollaboration(entry.record);
|
|
458
|
+
break;
|
|
459
|
+
case "collaboration.event.record":
|
|
460
|
+
await runtime.appendCollaborationEvent(entry.event);
|
|
461
|
+
break;
|
|
462
|
+
default:
|
|
463
|
+
break;
|
|
420
464
|
}
|
|
421
|
-
appliedBindingIds.add(binding.id);
|
|
422
|
-
await runtime.upsertBinding(binding);
|
|
423
|
-
}
|
|
424
|
-
if (bundle.collaborationRecord) {
|
|
425
|
-
await runtime.upsertCollaboration(bundle.collaborationRecord);
|
|
426
|
-
}
|
|
427
|
-
if (bundle.collaborationEvent) {
|
|
428
|
-
await runtime.appendCollaborationEvent(bundle.collaborationEvent);
|
|
429
465
|
}
|
|
430
466
|
}, options);
|
|
431
467
|
}
|
|
@@ -1817,7 +1853,22 @@ async function routeRequest(request, response) {
|
|
|
1817
1853
|
}
|
|
1818
1854
|
if (method === "POST" && url.pathname === "/v1/invocations") {
|
|
1819
1855
|
try {
|
|
1820
|
-
const
|
|
1856
|
+
const payload = await readRequestBody(request);
|
|
1857
|
+
const resolved = resolveInvocationTarget(payload);
|
|
1858
|
+
if (resolved.kind !== "resolved") {
|
|
1859
|
+
const envelope = buildDispatchEnvelope(resolved, payload.targetLabel?.trim() || payload.targetAgentId || "", nodeId, runtime.snapshot(), { homeEndpointFor: homeEndpointForAgent });
|
|
1860
|
+
const { record } = await recordScoutDispatchDurably(envelope, {
|
|
1861
|
+
invocationId: payload.id,
|
|
1862
|
+
conversationId: payload.conversationId,
|
|
1863
|
+
requesterId: payload.requesterId,
|
|
1864
|
+
});
|
|
1865
|
+
json(response, 200, buildScoutDispatchResponse(record, payload));
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
const invocation = {
|
|
1869
|
+
...payload,
|
|
1870
|
+
targetAgentId: resolved.agent.id,
|
|
1871
|
+
};
|
|
1821
1872
|
const result = await handleCommand({ kind: "agent.invoke", invocation });
|
|
1822
1873
|
json(response, 200, result);
|
|
1823
1874
|
}
|
|
@@ -1828,6 +1879,32 @@ async function routeRequest(request, response) {
|
|
|
1828
1879
|
}
|
|
1829
1880
|
notFound(response);
|
|
1830
1881
|
}
|
|
1882
|
+
function resolveInvocationTarget(payload) {
|
|
1883
|
+
const snapshot = runtime.snapshot();
|
|
1884
|
+
const directId = payload.targetAgentId?.trim();
|
|
1885
|
+
if (directId) {
|
|
1886
|
+
const agent = snapshot.agents[directId];
|
|
1887
|
+
if (agent && !isStaleLocalAgent(agent)) {
|
|
1888
|
+
return { kind: "resolved", agent };
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
const label = payload.targetLabel?.trim() || directId || "";
|
|
1892
|
+
if (!label) {
|
|
1893
|
+
return { kind: "unparseable", label: "" };
|
|
1894
|
+
}
|
|
1895
|
+
return resolveAgentLabel(snapshot, label, {
|
|
1896
|
+
preferLocalNodeId: nodeId,
|
|
1897
|
+
helpers: { isStale: isStaleLocalAgent },
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
function buildScoutDispatchResponse(record, payload) {
|
|
1901
|
+
return {
|
|
1902
|
+
ok: false,
|
|
1903
|
+
dispatchedTo: SCOUT_DISPATCHER_AGENT_ID,
|
|
1904
|
+
invocationId: payload.id,
|
|
1905
|
+
dispatch: record,
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1831
1908
|
const server = createServer((request, response) => {
|
|
1832
1909
|
routeRequest(request, response).catch((error) => {
|
|
1833
1910
|
json(response, 500, {
|
package/dist/broker-journal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ActorIdentity, AgentDefinition, AgentEndpoint, CollaborationEvent, CollaborationRecord, ConversationBinding, ConversationDefinition, DeliveryAttempt, DeliveryIntent, FlightRecord, InvocationRequest, MessageRecord, NodeDefinition } from "@openscout/protocol";
|
|
1
|
+
import type { ActorIdentity, AgentDefinition, AgentEndpoint, CollaborationEvent, CollaborationRecord, ConversationBinding, ConversationDefinition, DeliveryAttempt, DeliveryIntent, FlightRecord, InvocationRequest, MessageRecord, NodeDefinition, ScoutDispatchRecord } from "@openscout/protocol";
|
|
2
2
|
import { type RuntimeRegistrySnapshot } from "./registry.js";
|
|
3
3
|
export type BrokerJournalEntry = {
|
|
4
4
|
kind: "node.upsert";
|
|
@@ -46,6 +46,9 @@ export type BrokerJournalEntry = {
|
|
|
46
46
|
metadata?: Record<string, unknown>;
|
|
47
47
|
leaseOwner?: string | null;
|
|
48
48
|
leaseExpiresAt?: number | null;
|
|
49
|
+
} | {
|
|
50
|
+
kind: "scout.dispatch.record";
|
|
51
|
+
dispatch: ScoutDispatchRecord;
|
|
49
52
|
};
|
|
50
53
|
export declare class FileBackedBrokerJournal {
|
|
51
54
|
private readonly filePath;
|
|
@@ -57,7 +60,7 @@ export declare class FileBackedBrokerJournal {
|
|
|
57
60
|
readEntries(): Promise<BrokerJournalEntry[]>;
|
|
58
61
|
replay(visitor: (entry: BrokerJournalEntry) => void | Promise<void>): Promise<void>;
|
|
59
62
|
snapshot(): RuntimeRegistrySnapshot;
|
|
60
|
-
appendEntries(entriesInput: BrokerJournalEntry | BrokerJournalEntry[]): Promise<
|
|
63
|
+
appendEntries(entriesInput: BrokerJournalEntry | BrokerJournalEntry[]): Promise<BrokerJournalEntry[]>;
|
|
61
64
|
listCollaborationRecords(options?: {
|
|
62
65
|
limit?: number;
|
|
63
66
|
kind?: CollaborationRecord["kind"];
|
|
@@ -75,5 +78,13 @@ export declare class FileBackedBrokerJournal {
|
|
|
75
78
|
limit?: number;
|
|
76
79
|
}): DeliveryIntent[];
|
|
77
80
|
listDeliveryAttempts(deliveryId: string): DeliveryAttempt[];
|
|
81
|
+
private rewriteEntries;
|
|
82
|
+
private selectEntriesToAppend;
|
|
83
|
+
private shouldAppendEntry;
|
|
84
|
+
private applyToSnapshot;
|
|
78
85
|
private apply;
|
|
86
|
+
listScoutDispatches(options?: {
|
|
87
|
+
limit?: number;
|
|
88
|
+
askedLabel?: string;
|
|
89
|
+
}): ScoutDispatchRecord[];
|
|
79
90
|
}
|
package/dist/broker-journal.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
1
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { createRuntimeRegistrySnapshot, } from "./registry.js";
|
|
4
4
|
function cloneSnapshot(snapshot) {
|
|
@@ -35,6 +35,61 @@ function parseEntry(rawLine) {
|
|
|
35
35
|
return null;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
function normalizeComparableValue(value) {
|
|
39
|
+
if (value === undefined) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return value.map((entry) => normalizeComparableValue(entry));
|
|
44
|
+
}
|
|
45
|
+
if (value && typeof value === "object") {
|
|
46
|
+
const normalizedEntries = Object.entries(value)
|
|
47
|
+
.filter(([, entry]) => entry !== undefined)
|
|
48
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
49
|
+
.map(([key, entry]) => [key, normalizeComparableValue(entry)]);
|
|
50
|
+
return Object.fromEntries(normalizedEntries);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function sameValue(left, right) {
|
|
55
|
+
return JSON.stringify(normalizeComparableValue(left))
|
|
56
|
+
=== JSON.stringify(normalizeComparableValue(right));
|
|
57
|
+
}
|
|
58
|
+
function dedupeKey(entry) {
|
|
59
|
+
switch (entry.kind) {
|
|
60
|
+
case "node.upsert":
|
|
61
|
+
return `${entry.kind}:${entry.node.id}`;
|
|
62
|
+
case "actor.upsert":
|
|
63
|
+
return `${entry.kind}:${entry.actor.id}`;
|
|
64
|
+
case "agent.upsert":
|
|
65
|
+
return `${entry.kind}:${entry.agent.id}`;
|
|
66
|
+
case "agent.endpoint.upsert":
|
|
67
|
+
return `${entry.kind}:${entry.endpoint.id}`;
|
|
68
|
+
case "conversation.upsert":
|
|
69
|
+
return `${entry.kind}:${entry.conversation.id}`;
|
|
70
|
+
case "binding.upsert":
|
|
71
|
+
return `${entry.kind}:${entry.binding.id}`;
|
|
72
|
+
default:
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function isDedupableEntry(entry) {
|
|
77
|
+
return dedupeKey(entry) !== null;
|
|
78
|
+
}
|
|
79
|
+
function compactRedundantEntries(entries) {
|
|
80
|
+
const latestIndexByKey = new Map();
|
|
81
|
+
for (const [index, entry] of entries.entries()) {
|
|
82
|
+
const key = dedupeKey(entry);
|
|
83
|
+
if (!key) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
latestIndexByKey.set(key, index);
|
|
87
|
+
}
|
|
88
|
+
return entries.filter((entry, index) => {
|
|
89
|
+
const key = dedupeKey(entry);
|
|
90
|
+
return !key || latestIndexByKey.get(key) === index;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
38
93
|
export class FileBackedBrokerJournal {
|
|
39
94
|
filePath;
|
|
40
95
|
state = {
|
|
@@ -42,6 +97,7 @@ export class FileBackedBrokerJournal {
|
|
|
42
97
|
collaborationEvents: [],
|
|
43
98
|
deliveries: new Map(),
|
|
44
99
|
deliveryAttempts: new Map(),
|
|
100
|
+
scoutDispatches: [],
|
|
45
101
|
};
|
|
46
102
|
loaded = false;
|
|
47
103
|
writeQueue = Promise.resolve();
|
|
@@ -52,9 +108,14 @@ export class FileBackedBrokerJournal {
|
|
|
52
108
|
if (this.loaded) {
|
|
53
109
|
return;
|
|
54
110
|
}
|
|
55
|
-
|
|
111
|
+
const entries = await this.readEntries();
|
|
112
|
+
for (const entry of entries) {
|
|
56
113
|
this.apply(entry);
|
|
57
114
|
}
|
|
115
|
+
const compacted = compactRedundantEntries(entries);
|
|
116
|
+
if (compacted.length < entries.length) {
|
|
117
|
+
await this.rewriteEntries(compacted);
|
|
118
|
+
}
|
|
58
119
|
this.loaded = true;
|
|
59
120
|
}
|
|
60
121
|
async readEntries() {
|
|
@@ -89,17 +150,22 @@ export class FileBackedBrokerJournal {
|
|
|
89
150
|
async appendEntries(entriesInput) {
|
|
90
151
|
const entries = Array.isArray(entriesInput) ? entriesInput : [entriesInput];
|
|
91
152
|
if (entries.length === 0) {
|
|
92
|
-
return;
|
|
153
|
+
return [];
|
|
93
154
|
}
|
|
94
|
-
const
|
|
155
|
+
const retained = this.selectEntriesToAppend(entries);
|
|
95
156
|
this.writeQueue = this.writeQueue.then(async () => {
|
|
96
157
|
await mkdir(dirname(this.filePath), { recursive: true });
|
|
158
|
+
if (retained.length === 0) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const payload = retained.map((entry) => JSON.stringify(entry)).join("\n") + "\n";
|
|
97
162
|
await appendFile(this.filePath, payload, "utf8");
|
|
98
|
-
for (const entry of
|
|
163
|
+
for (const entry of retained) {
|
|
99
164
|
this.apply(entry);
|
|
100
165
|
}
|
|
101
166
|
});
|
|
102
|
-
|
|
167
|
+
await this.writeQueue;
|
|
168
|
+
return retained;
|
|
103
169
|
}
|
|
104
170
|
listCollaborationRecords(options = {}) {
|
|
105
171
|
const limit = options.limit ?? 200;
|
|
@@ -131,6 +197,80 @@ export class FileBackedBrokerJournal {
|
|
|
131
197
|
? left.createdAt - right.createdAt
|
|
132
198
|
: left.attempt - right.attempt));
|
|
133
199
|
}
|
|
200
|
+
async rewriteEntries(entries) {
|
|
201
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
202
|
+
const payload = entries.length > 0
|
|
203
|
+
? `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`
|
|
204
|
+
: "";
|
|
205
|
+
await writeFile(this.filePath, payload, "utf8");
|
|
206
|
+
}
|
|
207
|
+
selectEntriesToAppend(entries) {
|
|
208
|
+
const nextSnapshot = cloneSnapshot(this.state.snapshot);
|
|
209
|
+
const retained = [];
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (!this.shouldAppendEntry(entry, nextSnapshot)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
retained.push(entry);
|
|
215
|
+
this.applyToSnapshot(nextSnapshot, entry);
|
|
216
|
+
}
|
|
217
|
+
return retained;
|
|
218
|
+
}
|
|
219
|
+
shouldAppendEntry(entry, snapshot) {
|
|
220
|
+
if (!isDedupableEntry(entry)) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
switch (entry.kind) {
|
|
224
|
+
case "node.upsert":
|
|
225
|
+
return !sameValue(snapshot.nodes[entry.node.id], entry.node);
|
|
226
|
+
case "actor.upsert":
|
|
227
|
+
return !sameValue(snapshot.actors[entry.actor.id], entry.actor);
|
|
228
|
+
case "agent.upsert":
|
|
229
|
+
return !sameValue(snapshot.agents[entry.agent.id], entry.agent);
|
|
230
|
+
case "agent.endpoint.upsert":
|
|
231
|
+
return !sameValue(snapshot.endpoints[entry.endpoint.id], entry.endpoint);
|
|
232
|
+
case "conversation.upsert":
|
|
233
|
+
return !sameValue(snapshot.conversations[entry.conversation.id], entry.conversation);
|
|
234
|
+
case "binding.upsert":
|
|
235
|
+
return !sameValue(snapshot.bindings[entry.binding.id], entry.binding);
|
|
236
|
+
default:
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
applyToSnapshot(snapshot, entry) {
|
|
241
|
+
switch (entry.kind) {
|
|
242
|
+
case "node.upsert":
|
|
243
|
+
snapshot.nodes[entry.node.id] = entry.node;
|
|
244
|
+
return;
|
|
245
|
+
case "actor.upsert":
|
|
246
|
+
snapshot.actors[entry.actor.id] = entry.actor;
|
|
247
|
+
return;
|
|
248
|
+
case "agent.upsert":
|
|
249
|
+
snapshot.agents[entry.agent.id] = entry.agent;
|
|
250
|
+
if (!snapshot.actors[entry.agent.id]) {
|
|
251
|
+
snapshot.actors[entry.agent.id] = {
|
|
252
|
+
id: entry.agent.id,
|
|
253
|
+
kind: entry.agent.kind,
|
|
254
|
+
displayName: entry.agent.displayName,
|
|
255
|
+
handle: entry.agent.handle,
|
|
256
|
+
labels: entry.agent.labels,
|
|
257
|
+
metadata: entry.agent.metadata,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
case "agent.endpoint.upsert":
|
|
262
|
+
snapshot.endpoints[entry.endpoint.id] = entry.endpoint;
|
|
263
|
+
return;
|
|
264
|
+
case "conversation.upsert":
|
|
265
|
+
snapshot.conversations[entry.conversation.id] = entry.conversation;
|
|
266
|
+
return;
|
|
267
|
+
case "binding.upsert":
|
|
268
|
+
snapshot.bindings[entry.binding.id] = entry.binding;
|
|
269
|
+
return;
|
|
270
|
+
default:
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
134
274
|
apply(entry) {
|
|
135
275
|
switch (entry.kind) {
|
|
136
276
|
case "node.upsert":
|
|
@@ -200,10 +340,20 @@ export class FileBackedBrokerJournal {
|
|
|
200
340
|
});
|
|
201
341
|
return;
|
|
202
342
|
}
|
|
343
|
+
case "scout.dispatch.record":
|
|
344
|
+
this.state.scoutDispatches.push(entry.dispatch);
|
|
345
|
+
return;
|
|
203
346
|
default: {
|
|
204
347
|
const exhaustive = entry;
|
|
205
348
|
return exhaustive;
|
|
206
349
|
}
|
|
207
350
|
}
|
|
208
351
|
}
|
|
352
|
+
listScoutDispatches(options = {}) {
|
|
353
|
+
const limit = options.limit ?? 200;
|
|
354
|
+
return [...this.state.scoutDispatches]
|
|
355
|
+
.filter((record) => !options.askedLabel || record.askedLabel === options.askedLabel)
|
|
356
|
+
.sort((left, right) => right.dispatchedAt - left.dispatchedAt)
|
|
357
|
+
.slice(0, limit);
|
|
358
|
+
}
|
|
209
359
|
}
|
package/dist/broker-service.js
CHANGED
|
@@ -4,6 +4,10 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { basename, dirname, join, resolve } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { ensureOpenScoutCleanSlateSync, resolveOpenScoutSupportPaths } from "./support-paths.js";
|
|
7
|
+
/** True for paths under /tmp or /private/tmp — transient remote-install dirs. */
|
|
8
|
+
function isTmpPath(p) {
|
|
9
|
+
return /^\/(?:private\/)?tmp\//.test(p);
|
|
10
|
+
}
|
|
7
11
|
export const DEFAULT_BROKER_HOST = "127.0.0.1";
|
|
8
12
|
export const DEFAULT_BROKER_PORT = 65535;
|
|
9
13
|
const BROKER_SERVICE_POLL_INTERVAL_MS = 100;
|
|
@@ -34,18 +38,36 @@ function isInstalledRuntimePackageDir(candidate) {
|
|
|
34
38
|
&& existsSync(join(candidate, "bin", "openscout-runtime.mjs"));
|
|
35
39
|
}
|
|
36
40
|
function findGlobalRuntimeDir() {
|
|
37
|
-
// bun global
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
// Static candidates: bun global install layouts
|
|
42
|
+
const candidates = [
|
|
43
|
+
join(homedir(), ".bun", "node_modules", "@openscout", "runtime"),
|
|
44
|
+
join(homedir(), ".bun", "install", "global", "node_modules", "@openscout", "runtime"),
|
|
45
|
+
join(homedir(), ".bun", "install", "global", "node_modules", "@openscout", "scout", "node_modules", "@openscout", "runtime"),
|
|
46
|
+
];
|
|
47
|
+
for (const c of candidates) {
|
|
48
|
+
if (isInstalledRuntimePackageDir(c))
|
|
49
|
+
return c;
|
|
50
|
+
}
|
|
51
|
+
// Dynamic: resolve from `which scout` — works regardless of how it was installed
|
|
52
|
+
// (npm -g, bun -g, Homebrew prefix, etc.)
|
|
53
|
+
try {
|
|
54
|
+
const result = spawnSync("which", ["scout"], { encoding: "utf8", timeout: 3000 });
|
|
55
|
+
const scoutBin = result.stdout?.trim();
|
|
56
|
+
if (scoutBin) {
|
|
57
|
+
// scout bin → ../../lib/node_modules/@openscout/scout/node_modules/@openscout/runtime
|
|
58
|
+
const scoutPkg = resolve(scoutBin, "..", "..");
|
|
59
|
+
const nested = join(scoutPkg, "node_modules", "@openscout", "runtime");
|
|
60
|
+
if (isInstalledRuntimePackageDir(nested))
|
|
61
|
+
return nested;
|
|
62
|
+
// or runtime is a sibling: ../../lib/node_modules/@openscout/runtime
|
|
63
|
+
const sibling = resolve(scoutPkg, "..", "runtime");
|
|
64
|
+
if (isInstalledRuntimePackageDir(sibling))
|
|
65
|
+
return sibling;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// which not available or timed out
|
|
70
|
+
}
|
|
49
71
|
return null;
|
|
50
72
|
}
|
|
51
73
|
function findWorkspaceRuntimeDir(startDir) {
|
|
@@ -118,15 +140,15 @@ export function resolveBrokerServiceConfig() {
|
|
|
118
140
|
const mode = resolveBrokerServiceMode();
|
|
119
141
|
const label = resolveBrokerServiceLabel(mode);
|
|
120
142
|
const uid = typeof process.getuid === "function" ? process.getuid() : Number.parseInt(process.env.UID ?? "0", 10);
|
|
143
|
+
// Resolve paths but reject anything under /tmp — remote-install sessions
|
|
144
|
+
// set env vars to transient tmp dirs that don't survive reboots.
|
|
121
145
|
const supportPaths = resolveOpenScoutSupportPaths();
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const rawControlHome = supportPaths.controlHome;
|
|
127
|
-
const controlHome = /^\/(?:private\/)?tmp\//.test(rawControlHome)
|
|
146
|
+
const defaultSupportDir = join(homedir(), "Library", "Application Support", "OpenScout");
|
|
147
|
+
const supportDirectory = isTmpPath(supportPaths.supportDirectory) ? defaultSupportDir : supportPaths.supportDirectory;
|
|
148
|
+
const logsDirectory = join(supportDirectory, "logs", "broker");
|
|
149
|
+
const controlHome = isTmpPath(supportPaths.controlHome)
|
|
128
150
|
? join(homedir(), ".openscout", "control-plane")
|
|
129
|
-
:
|
|
151
|
+
: supportPaths.controlHome;
|
|
130
152
|
const brokerHost = process.env.OPENSCOUT_BROKER_HOST ?? DEFAULT_BROKER_HOST;
|
|
131
153
|
const brokerPort = Number.parseInt(process.env.OPENSCOUT_BROKER_PORT ?? String(DEFAULT_BROKER_PORT), 10);
|
|
132
154
|
const brokerUrl = process.env.OPENSCOUT_BROKER_URL ?? buildDefaultBrokerUrl(brokerHost, brokerPort);
|
|
@@ -229,7 +251,8 @@ function resolveLaunchAgentPATH() {
|
|
|
229
251
|
"/usr/sbin",
|
|
230
252
|
"/sbin",
|
|
231
253
|
];
|
|
232
|
-
|
|
254
|
+
// Strip transient tmp dirs from PATH — remote-install sessions prepend them.
|
|
255
|
+
return Array.from(new Set(entries)).filter((e) => !isTmpPath(e)).join(":");
|
|
233
256
|
}
|
|
234
257
|
function xmlEscape(value) {
|
|
235
258
|
return value
|
package/dist/schema.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const CONTROL_PLANE_SCHEMA_VERSION = 1;
|
|
2
|
-
export declare const CONTROL_PLANE_SQLITE_SCHEMA = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n mesh_id TEXT NOT NULL,\n name TEXT NOT NULL,\n host_name TEXT,\n advertise_scope TEXT NOT NULL,\n broker_url TEXT,\n tailnet_name TEXT,\n capabilities_json TEXT,\n labels_json TEXT,\n metadata_json TEXT,\n last_seen_at INTEGER,\n registered_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS actors (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n display_name TEXT NOT NULL,\n handle TEXT,\n labels_json TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY REFERENCES actors(id) ON DELETE CASCADE,\n definition_id TEXT NOT NULL,\n node_qualifier TEXT,\n workspace_qualifier TEXT,\n selector TEXT,\n default_selector TEXT,\n agent_class TEXT NOT NULL,\n capabilities_json TEXT NOT NULL,\n wake_policy TEXT NOT NULL,\n home_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n authority_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n advertise_scope TEXT NOT NULL,\n owner_id TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS agent_endpoints (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,\n node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n harness TEXT NOT NULL,\n transport TEXT NOT NULL,\n state TEXT NOT NULL,\n address TEXT,\n session_id TEXT,\n pane TEXT,\n cwd TEXT,\n project_root TEXT,\n metadata_json TEXT,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS conversations (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n title TEXT NOT NULL,\n visibility TEXT NOT NULL,\n share_mode TEXT NOT NULL,\n authority_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n topic TEXT,\n parent_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n message_id TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS conversation_members (\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE CASCADE,\n role TEXT,\n PRIMARY KEY (conversation_id, actor_id)\n);\n\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n origin_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n class TEXT NOT NULL,\n body TEXT NOT NULL,\n reply_to_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n thread_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n speech_json TEXT,\n audience_json TEXT,\n visibility TEXT NOT NULL,\n policy TEXT NOT NULL,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS message_mentions (\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE CASCADE,\n label TEXT,\n PRIMARY KEY (message_id, actor_id)\n);\n\nCREATE TABLE IF NOT EXISTS message_attachments (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n media_type TEXT NOT NULL,\n file_name TEXT,\n blob_key TEXT,\n url TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS invocations (\n id TEXT PRIMARY KEY,\n requester_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n requester_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n target_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,\n target_node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL,\n action TEXT NOT NULL,\n task TEXT NOT NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n context_json TEXT,\n execution_json TEXT,\n ensure_awake INTEGER NOT NULL DEFAULT 1,\n stream INTEGER NOT NULL DEFAULT 1,\n timeout_ms INTEGER,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS flights (\n id TEXT PRIMARY KEY,\n invocation_id TEXT NOT NULL REFERENCES invocations(id) ON DELETE CASCADE,\n requester_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n target_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,\n state TEXT NOT NULL,\n summary TEXT,\n output TEXT,\n error TEXT,\n metadata_json TEXT,\n started_at INTEGER,\n completed_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS bindings (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n platform TEXT NOT NULL,\n mode TEXT NOT NULL,\n external_channel_id TEXT NOT NULL,\n external_thread_id TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS deliveries (\n id TEXT PRIMARY KEY,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n invocation_id TEXT REFERENCES invocations(id) ON DELETE CASCADE,\n target_id TEXT NOT NULL,\n target_node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL,\n target_kind TEXT NOT NULL,\n transport TEXT NOT NULL,\n reason TEXT NOT NULL,\n policy TEXT NOT NULL,\n status TEXT NOT NULL,\n binding_id TEXT REFERENCES bindings(id) ON DELETE SET NULL,\n lease_owner TEXT,\n lease_expires_at INTEGER,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS delivery_attempts (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL REFERENCES deliveries(id) ON DELETE CASCADE,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n error TEXT,\n external_ref TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS collaboration_records (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n state TEXT NOT NULL,\n acceptance_state TEXT NOT NULL,\n title TEXT NOT NULL,\n summary TEXT,\n created_by_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n owner_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n next_move_owner_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n parent_id TEXT REFERENCES collaboration_records(id) ON DELETE SET NULL,\n priority TEXT,\n labels_json TEXT,\n relations_json TEXT,\n detail_json TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS collaboration_events (\n id TEXT PRIMARY KEY,\n record_id TEXT NOT NULL REFERENCES collaboration_records(id) ON DELETE CASCADE,\n record_kind TEXT NOT NULL,\n kind TEXT NOT NULL,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n summary TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n node_id TEXT,\n ts INTEGER NOT NULL,\n payload_json TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_items (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n ts INTEGER NOT NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n invocation_id TEXT REFERENCES invocations(id) ON DELETE CASCADE,\n flight_id TEXT REFERENCES flights(id) ON DELETE CASCADE,\n record_id TEXT REFERENCES collaboration_records(id) ON DELETE CASCADE,\n actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n counterpart_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,\n workspace_root TEXT,\n session_id TEXT,\n title TEXT,\n summary TEXT,\n payload_json TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_mesh_id\n ON nodes (mesh_id);\nCREATE INDEX IF NOT EXISTS idx_agent_endpoints_agent_updated_at\n ON agent_endpoints (agent_id, updated_at DESC);\nCREATE INDEX IF NOT EXISTS idx_messages_conversation_created_at\n ON messages (conversation_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_invocations_target_created_at\n ON invocations (target_agent_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_flights_target_state\n ON flights (target_agent_id, state);\nCREATE INDEX IF NOT EXISTS idx_deliveries_status_transport\n ON deliveries (status, transport);\nCREATE INDEX IF NOT EXISTS idx_collaboration_records_state\n ON collaboration_records (state);\nCREATE INDEX IF NOT EXISTS idx_collaboration_records_updated_at\n ON collaboration_records (updated_at);\nCREATE INDEX IF NOT EXISTS idx_collaboration_events_record_created_at\n ON collaboration_events (record_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_events_kind_ts\n ON events (kind, ts);\nCREATE INDEX IF NOT EXISTS idx_activity_items_agent_ts\n ON activity_items (agent_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_actor_ts\n ON activity_items (actor_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_conversation_ts\n ON activity_items (conversation_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_workspace_ts\n ON activity_items (workspace_root, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_kind_ts\n ON activity_items (kind, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_session_ts\n ON activity_items (session_id, ts DESC);\n";
|
|
2
|
+
export declare const CONTROL_PLANE_SQLITE_SCHEMA = "\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT PRIMARY KEY,\n mesh_id TEXT NOT NULL,\n name TEXT NOT NULL,\n host_name TEXT,\n advertise_scope TEXT NOT NULL,\n broker_url TEXT,\n tailnet_name TEXT,\n capabilities_json TEXT,\n labels_json TEXT,\n metadata_json TEXT,\n last_seen_at INTEGER,\n registered_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS actors (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n display_name TEXT NOT NULL,\n handle TEXT,\n labels_json TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS agents (\n id TEXT PRIMARY KEY REFERENCES actors(id) ON DELETE CASCADE,\n definition_id TEXT NOT NULL,\n node_qualifier TEXT,\n workspace_qualifier TEXT,\n selector TEXT,\n default_selector TEXT,\n agent_class TEXT NOT NULL,\n capabilities_json TEXT NOT NULL,\n wake_policy TEXT NOT NULL,\n home_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n authority_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n advertise_scope TEXT NOT NULL,\n owner_id TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS agent_endpoints (\n id TEXT PRIMARY KEY,\n agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,\n node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n harness TEXT NOT NULL,\n transport TEXT NOT NULL,\n state TEXT NOT NULL,\n address TEXT,\n session_id TEXT,\n pane TEXT,\n cwd TEXT,\n project_root TEXT,\n metadata_json TEXT,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS conversations (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n title TEXT NOT NULL,\n visibility TEXT NOT NULL,\n share_mode TEXT NOT NULL,\n authority_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n topic TEXT,\n parent_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n message_id TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS conversation_members (\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE CASCADE,\n role TEXT,\n PRIMARY KEY (conversation_id, actor_id)\n);\n\nCREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n origin_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n class TEXT NOT NULL,\n body TEXT NOT NULL,\n reply_to_message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n thread_conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n speech_json TEXT,\n audience_json TEXT,\n visibility TEXT NOT NULL,\n policy TEXT NOT NULL,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS message_mentions (\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE CASCADE,\n label TEXT,\n PRIMARY KEY (message_id, actor_id)\n);\n\nCREATE TABLE IF NOT EXISTS message_attachments (\n id TEXT PRIMARY KEY,\n message_id TEXT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,\n media_type TEXT NOT NULL,\n file_name TEXT,\n blob_key TEXT,\n url TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS invocations (\n id TEXT PRIMARY KEY,\n requester_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n requester_node_id TEXT NOT NULL REFERENCES nodes(id) ON DELETE RESTRICT,\n target_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,\n target_node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL,\n action TEXT NOT NULL,\n task TEXT NOT NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n message_id TEXT REFERENCES messages(id) ON DELETE SET NULL,\n context_json TEXT,\n execution_json TEXT,\n ensure_awake INTEGER NOT NULL DEFAULT 1,\n stream INTEGER NOT NULL DEFAULT 1,\n timeout_ms INTEGER,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS flights (\n id TEXT PRIMARY KEY,\n invocation_id TEXT NOT NULL REFERENCES invocations(id) ON DELETE CASCADE,\n requester_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n target_agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE RESTRICT,\n state TEXT NOT NULL,\n summary TEXT,\n output TEXT,\n error TEXT,\n metadata_json TEXT,\n started_at INTEGER,\n completed_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS bindings (\n id TEXT PRIMARY KEY,\n conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,\n platform TEXT NOT NULL,\n mode TEXT NOT NULL,\n external_channel_id TEXT NOT NULL,\n external_thread_id TEXT,\n metadata_json TEXT\n);\n\nCREATE TABLE IF NOT EXISTS deliveries (\n id TEXT PRIMARY KEY,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n invocation_id TEXT REFERENCES invocations(id) ON DELETE CASCADE,\n target_id TEXT NOT NULL,\n target_node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL,\n target_kind TEXT NOT NULL,\n transport TEXT NOT NULL,\n reason TEXT NOT NULL,\n policy TEXT NOT NULL,\n status TEXT NOT NULL,\n binding_id TEXT REFERENCES bindings(id) ON DELETE SET NULL,\n lease_owner TEXT,\n lease_expires_at INTEGER,\n metadata_json TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE TABLE IF NOT EXISTS delivery_attempts (\n id TEXT PRIMARY KEY,\n delivery_id TEXT NOT NULL REFERENCES deliveries(id) ON DELETE CASCADE,\n attempt INTEGER NOT NULL,\n status TEXT NOT NULL,\n error TEXT,\n external_ref TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS collaboration_records (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n state TEXT NOT NULL,\n acceptance_state TEXT NOT NULL,\n title TEXT NOT NULL,\n summary TEXT,\n created_by_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n owner_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n next_move_owner_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE SET NULL,\n parent_id TEXT REFERENCES collaboration_records(id) ON DELETE SET NULL,\n priority TEXT,\n labels_json TEXT,\n relations_json TEXT,\n detail_json TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS collaboration_events (\n id TEXT PRIMARY KEY,\n record_id TEXT NOT NULL REFERENCES collaboration_records(id) ON DELETE CASCADE,\n record_kind TEXT NOT NULL,\n kind TEXT NOT NULL,\n actor_id TEXT NOT NULL REFERENCES actors(id) ON DELETE RESTRICT,\n summary TEXT,\n metadata_json TEXT,\n created_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n actor_id TEXT NOT NULL,\n node_id TEXT,\n ts INTEGER NOT NULL,\n payload_json TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS scout_dispatches (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n asked_label TEXT NOT NULL,\n detail TEXT NOT NULL,\n invocation_id TEXT,\n conversation_id TEXT,\n requester_id TEXT,\n dispatcher_node_id TEXT NOT NULL,\n dispatched_at INTEGER NOT NULL,\n payload_json TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_items (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL,\n ts INTEGER NOT NULL,\n conversation_id TEXT REFERENCES conversations(id) ON DELETE CASCADE,\n message_id TEXT REFERENCES messages(id) ON DELETE CASCADE,\n invocation_id TEXT REFERENCES invocations(id) ON DELETE CASCADE,\n flight_id TEXT REFERENCES flights(id) ON DELETE CASCADE,\n record_id TEXT REFERENCES collaboration_records(id) ON DELETE CASCADE,\n actor_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n counterpart_id TEXT REFERENCES actors(id) ON DELETE SET NULL,\n agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,\n workspace_root TEXT,\n session_id TEXT,\n title TEXT,\n summary TEXT,\n payload_json TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_mesh_id\n ON nodes (mesh_id);\nCREATE INDEX IF NOT EXISTS idx_agent_endpoints_agent_updated_at\n ON agent_endpoints (agent_id, updated_at DESC);\nCREATE INDEX IF NOT EXISTS idx_messages_conversation_created_at\n ON messages (conversation_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_invocations_target_created_at\n ON invocations (target_agent_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_flights_target_state\n ON flights (target_agent_id, state);\nCREATE INDEX IF NOT EXISTS idx_deliveries_status_transport\n ON deliveries (status, transport);\nCREATE INDEX IF NOT EXISTS idx_collaboration_records_state\n ON collaboration_records (state);\nCREATE INDEX IF NOT EXISTS idx_collaboration_records_updated_at\n ON collaboration_records (updated_at);\nCREATE INDEX IF NOT EXISTS idx_collaboration_events_record_created_at\n ON collaboration_events (record_id, created_at);\nCREATE INDEX IF NOT EXISTS idx_events_kind_ts\n ON events (kind, ts);\nCREATE INDEX IF NOT EXISTS idx_activity_items_agent_ts\n ON activity_items (agent_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_actor_ts\n ON activity_items (actor_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_conversation_ts\n ON activity_items (conversation_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_workspace_ts\n ON activity_items (workspace_root, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_kind_ts\n ON activity_items (kind, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_activity_items_session_ts\n ON activity_items (session_id, ts DESC);\nCREATE INDEX IF NOT EXISTS idx_scout_dispatches_dispatched_at\n ON scout_dispatches (dispatched_at DESC);\nCREATE INDEX IF NOT EXISTS idx_scout_dispatches_conversation_ts\n ON scout_dispatches (conversation_id, dispatched_at DESC);\n";
|
package/dist/schema.js
CHANGED
|
@@ -228,6 +228,19 @@ CREATE TABLE IF NOT EXISTS events (
|
|
|
228
228
|
payload_json TEXT NOT NULL
|
|
229
229
|
);
|
|
230
230
|
|
|
231
|
+
CREATE TABLE IF NOT EXISTS scout_dispatches (
|
|
232
|
+
id TEXT PRIMARY KEY,
|
|
233
|
+
kind TEXT NOT NULL,
|
|
234
|
+
asked_label TEXT NOT NULL,
|
|
235
|
+
detail TEXT NOT NULL,
|
|
236
|
+
invocation_id TEXT,
|
|
237
|
+
conversation_id TEXT,
|
|
238
|
+
requester_id TEXT,
|
|
239
|
+
dispatcher_node_id TEXT NOT NULL,
|
|
240
|
+
dispatched_at INTEGER NOT NULL,
|
|
241
|
+
payload_json TEXT NOT NULL
|
|
242
|
+
);
|
|
243
|
+
|
|
231
244
|
CREATE TABLE IF NOT EXISTS activity_items (
|
|
232
245
|
id TEXT PRIMARY KEY,
|
|
233
246
|
kind TEXT NOT NULL,
|
|
@@ -279,4 +292,8 @@ CREATE INDEX IF NOT EXISTS idx_activity_items_kind_ts
|
|
|
279
292
|
ON activity_items (kind, ts DESC);
|
|
280
293
|
CREATE INDEX IF NOT EXISTS idx_activity_items_session_ts
|
|
281
294
|
ON activity_items (session_id, ts DESC);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_scout_dispatches_dispatched_at
|
|
296
|
+
ON scout_dispatches (dispatched_at DESC);
|
|
297
|
+
CREATE INDEX IF NOT EXISTS idx_scout_dispatches_conversation_ts
|
|
298
|
+
ON scout_dispatches (conversation_id, dispatched_at DESC);
|
|
282
299
|
`;
|