@hunterzhu/pulse-runtime 0.1.0
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/context/builder.d.ts +68 -0
- package/dist/context/builder.js +127 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.js +2 -0
- package/dist/context/merger.d.ts +25 -0
- package/dist/context/merger.js +125 -0
- package/dist/core/actions.d.ts +1 -0
- package/dist/core/actions.js +1 -0
- package/dist/core/errors.d.ts +8 -0
- package/dist/core/errors.js +36 -0
- package/dist/core/events.d.ts +10 -0
- package/dist/core/events.js +24 -0
- package/dist/core/factory.d.ts +35 -0
- package/dist/core/factory.js +27 -0
- package/dist/core/inbox.d.ts +119 -0
- package/dist/core/inbox.js +217 -0
- package/dist/core/mutations.d.ts +80 -0
- package/dist/core/mutations.js +127 -0
- package/dist/core/records.d.ts +1 -0
- package/dist/core/records.js +1 -0
- package/dist/core/types.d.ts +615 -0
- package/dist/core/types.js +109 -0
- package/dist/dependencies/graph.d.ts +25 -0
- package/dist/dependencies/graph.js +92 -0
- package/dist/dependencies/index.d.ts +1 -0
- package/dist/dependencies/index.js +1 -0
- package/dist/dsl/context-proxy.d.ts +20 -0
- package/dist/dsl/context-proxy.js +64 -0
- package/dist/dsl/index.d.ts +4 -0
- package/dist/dsl/index.js +4 -0
- package/dist/dsl/program.d.ts +314 -0
- package/dist/dsl/program.js +756 -0
- package/dist/dsl/session.d.ts +45 -0
- package/dist/dsl/session.js +93 -0
- package/dist/dsl/templates-index.d.ts +1 -0
- package/dist/dsl/templates-index.js +1 -0
- package/dist/dsl/templates.d.ts +85 -0
- package/dist/dsl/templates.js +110 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +15 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/lifecycle/scopes.d.ts +38 -0
- package/dist/lifecycle/scopes.js +50 -0
- package/dist/lifecycle/watchdog.d.ts +16 -0
- package/dist/lifecycle/watchdog.js +66 -0
- package/dist/models/actions.d.ts +10 -0
- package/dist/models/actions.js +68 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models/router.d.ts +187 -0
- package/dist/models/router.js +353 -0
- package/dist/scheduler/clock.d.ts +45 -0
- package/dist/scheduler/clock.js +92 -0
- package/dist/scheduler/decision.d.ts +72 -0
- package/dist/scheduler/decision.js +63 -0
- package/dist/scheduler/index.d.ts +6 -0
- package/dist/scheduler/index.js +6 -0
- package/dist/scheduler/locks.d.ts +18 -0
- package/dist/scheduler/locks.js +106 -0
- package/dist/scheduler/ready-queue.d.ts +32 -0
- package/dist/scheduler/ready-queue.js +40 -0
- package/dist/scheduler/runtime.d.ts +486 -0
- package/dist/scheduler/runtime.js +3445 -0
- package/dist/scheduler/telemetry.d.ts +111 -0
- package/dist/scheduler/telemetry.js +177 -0
- package/dist/scheduler/worker.d.ts +158 -0
- package/dist/scheduler/worker.js +744 -0
- package/dist/storage/artifacts.d.ts +17 -0
- package/dist/storage/artifacts.js +90 -0
- package/dist/storage/findings.d.ts +12 -0
- package/dist/storage/findings.js +70 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.js +8 -0
- package/dist/storage/memory.d.ts +11 -0
- package/dist/storage/memory.js +21 -0
- package/dist/storage/mutation-log.d.ts +41 -0
- package/dist/storage/mutation-log.js +140 -0
- package/dist/storage/outbox.d.ts +30 -0
- package/dist/storage/outbox.js +59 -0
- package/dist/storage/persistence.d.ts +183 -0
- package/dist/storage/persistence.js +999 -0
- package/dist/storage/policy.d.ts +80 -0
- package/dist/storage/policy.js +268 -0
- package/dist/storage/session.d.ts +140 -0
- package/dist/storage/session.js +447 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +308 -0
- package/dist/transitions/index.d.ts +2 -0
- package/dist/transitions/index.js +1 -0
- package/dist/transitions/validate.d.ts +4 -0
- package/dist/transitions/validate.js +1118 -0
- package/package.json +21 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
const EMPTY_FACT_INBOX_DEDUPE_DIGEST = createHash('sha256').update('pulse.fact-inbox.dedupe.v1').digest('hex');
|
|
3
|
+
export function factInboxDedupeDigestFrom(previousDigest, entries) {
|
|
4
|
+
let digest = previousDigest;
|
|
5
|
+
for (const entry of entries)
|
|
6
|
+
digest = createHash('sha256').update(`${digest}\u0000${entry.eventId}\u0000${entry.receivedSeq}`).digest('hex');
|
|
7
|
+
return digest;
|
|
8
|
+
}
|
|
9
|
+
export function factInboxDedupeDigest(entries) {
|
|
10
|
+
return factInboxDedupeDigestFrom(EMPTY_FACT_INBOX_DEDUPE_DIGEST, entries);
|
|
11
|
+
}
|
|
12
|
+
export class FactInbox {
|
|
13
|
+
queue = [];
|
|
14
|
+
seen = new Map();
|
|
15
|
+
nextSeq = 1;
|
|
16
|
+
archivedThrough = 0;
|
|
17
|
+
dedupeArchive;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.dedupeArchive = options.dedupeArchive;
|
|
20
|
+
}
|
|
21
|
+
/** Attach a backend-provided archive before the first compaction. */
|
|
22
|
+
attachDedupeArchive(archive) {
|
|
23
|
+
if (this.dedupeArchive !== undefined && this.dedupeArchive !== archive)
|
|
24
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_MISMATCH');
|
|
25
|
+
if (this.archivedThrough > 0)
|
|
26
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_ALREADY_COMPACTED');
|
|
27
|
+
this.dedupeArchive = archive;
|
|
28
|
+
}
|
|
29
|
+
enqueue(fact, eventId) {
|
|
30
|
+
if (!eventId || this.seen.has(eventId) || this.dedupeArchive?.contains(eventId))
|
|
31
|
+
return undefined;
|
|
32
|
+
const envelope = { eventId, receivedSeq: this.nextSeq++, fact: structuredClone(fact) };
|
|
33
|
+
this.seen.set(eventId, envelope.receivedSeq);
|
|
34
|
+
this.queue.push(envelope);
|
|
35
|
+
return structuredClone(envelope);
|
|
36
|
+
}
|
|
37
|
+
drain(limit = Number.POSITIVE_INFINITY) {
|
|
38
|
+
if (limit !== Number.POSITIVE_INFINITY && (!Number.isInteger(limit) || limit < 0))
|
|
39
|
+
throw new Error('INVALID_FACT_DRAIN_LIMIT');
|
|
40
|
+
return this.queue.splice(0, limit).map((envelope) => structuredClone(envelope));
|
|
41
|
+
}
|
|
42
|
+
get size() { return this.queue.length; }
|
|
43
|
+
get dedupeWatermark() { return this.archivedThrough; }
|
|
44
|
+
has(eventId) { return this.seen.has(eventId) || Boolean(this.dedupeArchive?.contains(eventId)); }
|
|
45
|
+
snapshot() {
|
|
46
|
+
const entries = [...this.seen.entries()].filter((entry) => entry[1] !== undefined).map(([eventId, receivedSeq]) => ({ eventId, receivedSeq }));
|
|
47
|
+
const legacyEventIds = [...this.seen.entries()].filter((entry) => entry[1] === undefined).map(([eventId]) => eventId);
|
|
48
|
+
return {
|
|
49
|
+
schemaVersion: 2,
|
|
50
|
+
nextSeq: this.nextSeq,
|
|
51
|
+
seen: [...this.seen.keys()],
|
|
52
|
+
queue: this.queue.map((envelope) => structuredClone(envelope)),
|
|
53
|
+
dedupeLedger: {
|
|
54
|
+
schemaVersion: 1,
|
|
55
|
+
archivedThrough: this.archivedThrough,
|
|
56
|
+
entries,
|
|
57
|
+
...(legacyEventIds.length === 0 ? {} : { legacyEventIds }),
|
|
58
|
+
...(this.archivedThrough === 0 || this.dedupeArchive === undefined ? {} : { archiveId: this.dedupeArchive.archiveId, archiveDigest: this.dedupeArchive.digestThrough(this.archivedThrough) }),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build the exact ledger batch that a durable checkpoint/archive must
|
|
64
|
+
* acknowledge before local dedupe entries can be compacted.
|
|
65
|
+
*/
|
|
66
|
+
createDedupeArchiveBatch(through = this.nextSeq - 1) {
|
|
67
|
+
if (!Number.isInteger(through) || through < this.archivedThrough || through >= this.nextSeq)
|
|
68
|
+
throw new Error('INVALID_FACT_INBOX_DEDUPE_WATERMARK');
|
|
69
|
+
const bySeq = new Map();
|
|
70
|
+
for (const [eventId, receivedSeq] of this.seen) {
|
|
71
|
+
if (receivedSeq !== undefined && receivedSeq > this.archivedThrough && receivedSeq <= through)
|
|
72
|
+
bySeq.set(receivedSeq, { eventId, receivedSeq });
|
|
73
|
+
}
|
|
74
|
+
const entries = [];
|
|
75
|
+
for (let receivedSeq = this.archivedThrough + 1; receivedSeq <= through; receivedSeq++) {
|
|
76
|
+
const entry = bySeq.get(receivedSeq);
|
|
77
|
+
if (!entry)
|
|
78
|
+
throw new Error('FACT_INBOX_DEDUPE_LEDGER_INCOMPLETE');
|
|
79
|
+
entries.push({ ...entry });
|
|
80
|
+
}
|
|
81
|
+
if (!this.dedupeArchive || this.dedupeArchive.watermark < this.archivedThrough)
|
|
82
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_REQUIRED');
|
|
83
|
+
return { schemaVersion: 1, archiveId: this.dedupeArchive.archiveId, through, ledgerDigest: factInboxDedupeDigestFrom(this.dedupeArchive.digestThrough(this.archivedThrough), entries), entries };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Acknowledge a batch only after its exact entries are durably archived.
|
|
87
|
+
* This is the sole operation that advances the watermark or removes local
|
|
88
|
+
* dedupe entries; clear() intentionally does not affect deduplication.
|
|
89
|
+
*/
|
|
90
|
+
compactDedupeThrough(batch) {
|
|
91
|
+
if (!batch || batch.schemaVersion !== 1 || !Number.isInteger(batch.through) || batch.through < 1 || batch.archiveId !== this.dedupeArchive?.archiveId)
|
|
92
|
+
throw new Error('INVALID_FACT_INBOX_DEDUPE_ARCHIVE_BATCH');
|
|
93
|
+
if (batch.through <= this.archivedThrough)
|
|
94
|
+
return 0;
|
|
95
|
+
const expected = this.createDedupeArchiveBatch(batch.through);
|
|
96
|
+
if (expected.ledgerDigest !== batch.ledgerDigest || expected.entries.length !== batch.entries.length || expected.entries.some((entry, index) => entry.eventId !== batch.entries[index]?.eventId || entry.receivedSeq !== batch.entries[index]?.receivedSeq))
|
|
97
|
+
throw new Error('INVALID_FACT_INBOX_DEDUPE_ARCHIVE_BATCH');
|
|
98
|
+
if (this.queue.some((envelope) => envelope.receivedSeq <= batch.through))
|
|
99
|
+
throw new Error('FACT_INBOX_DEDUPE_PENDING_FACTS');
|
|
100
|
+
if (this.dedupeArchive.watermark < batch.through)
|
|
101
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_NOT_DURABLE');
|
|
102
|
+
for (const entry of expected.entries)
|
|
103
|
+
if (!this.dedupeArchive.contains(entry.eventId, entry.receivedSeq))
|
|
104
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_NOT_DURABLE');
|
|
105
|
+
if (this.dedupeArchive.digestThrough(batch.through) !== batch.ledgerDigest)
|
|
106
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_NOT_DURABLE');
|
|
107
|
+
for (const entry of expected.entries)
|
|
108
|
+
this.seen.delete(entry.eventId);
|
|
109
|
+
this.archivedThrough = batch.through;
|
|
110
|
+
return expected.entries.length;
|
|
111
|
+
}
|
|
112
|
+
restore(snapshot) {
|
|
113
|
+
const restored = FactInbox.fromSnapshot(snapshot, this.dedupeArchive === undefined ? {} : { dedupeArchive: this.dedupeArchive });
|
|
114
|
+
this.queue.splice(0, this.queue.length, ...restored.queue.map((envelope) => structuredClone(envelope)));
|
|
115
|
+
this.seen.clear();
|
|
116
|
+
for (const [eventId, receivedSeq] of restored.seen)
|
|
117
|
+
this.seen.set(eventId, receivedSeq);
|
|
118
|
+
this.nextSeq = restored.nextSeq;
|
|
119
|
+
this.archivedThrough = restored.archivedThrough;
|
|
120
|
+
}
|
|
121
|
+
static fromSnapshot(snapshot, options = {}) {
|
|
122
|
+
const value = snapshot;
|
|
123
|
+
if (!value || (value.schemaVersion !== 1 && value.schemaVersion !== 2) || !Number.isInteger(value.nextSeq) || value.nextSeq < 1 || !Array.isArray(value.seen) || value.seen.some((eventId) => typeof eventId !== 'string' || eventId.length === 0) || !Array.isArray(value.queue))
|
|
124
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
125
|
+
if (new Set(value.seen).size !== value.seen.length)
|
|
126
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
127
|
+
const ledger = value.dedupeLedger;
|
|
128
|
+
if (ledger !== undefined && (ledger.schemaVersion !== 1 || !Number.isInteger(ledger.archivedThrough) || ledger.archivedThrough < 0 || ledger.archivedThrough >= value.nextSeq || !Array.isArray(ledger.entries) || !Array.isArray(ledger.legacyEventIds ?? []) || ledger.entries.some((entry) => !entry || typeof entry.eventId !== 'string' || entry.eventId.length === 0 || !Number.isInteger(entry.receivedSeq) || entry.receivedSeq < 1 || entry.receivedSeq <= ledger.archivedThrough || entry.receivedSeq >= value.nextSeq) || ledger.legacyEventIds?.some((eventId) => typeof eventId !== 'string' || eventId.length === 0) || (ledger.archivedThrough > 0 && ((ledger.legacyEventIds ?? []).length > 0 || !ledger.archiveId || !ledger.archiveDigest || !/^[a-f0-9]{64}$/.test(ledger.archiveDigest))) || (ledger.archivedThrough === 0 && ledger.archiveDigest !== undefined) || new Set(ledger.entries.map((entry) => entry.eventId)).size !== ledger.entries.length || new Set(ledger.entries.map((entry) => entry.receivedSeq)).size !== ledger.entries.length || new Set(ledger.legacyEventIds ?? []).size !== (ledger.legacyEventIds ?? []).length || ledger.entries.some((entry) => (ledger.legacyEventIds ?? []).includes(entry.eventId))))
|
|
129
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
130
|
+
if (ledger?.archivedThrough && (!options.dedupeArchive || ledger.archiveId !== options.dedupeArchive.archiveId || options.dedupeArchive.watermark < ledger.archivedThrough || options.dedupeArchive.digestThrough(ledger.archivedThrough) !== ledger.archiveDigest))
|
|
131
|
+
throw new Error('FACT_INBOX_DEDUPE_ARCHIVE_REQUIRED');
|
|
132
|
+
const inbox = new FactInbox(options);
|
|
133
|
+
const seen = new Map();
|
|
134
|
+
if (ledger) {
|
|
135
|
+
for (const entry of ledger.entries)
|
|
136
|
+
seen.set(entry.eventId, entry.receivedSeq);
|
|
137
|
+
for (const eventId of ledger.legacyEventIds ?? [])
|
|
138
|
+
seen.set(eventId, undefined);
|
|
139
|
+
if (new Set(seen.keys()).size !== value.seen.length || value.seen.some((eventId) => !seen.has(eventId)))
|
|
140
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
141
|
+
inbox.archivedThrough = ledger.archivedThrough;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const queued = new Map(value.queue.map((envelope) => [envelope.eventId, envelope.receivedSeq]));
|
|
145
|
+
for (const eventId of value.seen)
|
|
146
|
+
seen.set(eventId, queued.get(eventId));
|
|
147
|
+
}
|
|
148
|
+
let maxReceivedSeq = 0;
|
|
149
|
+
for (const envelope of value.queue) {
|
|
150
|
+
if (!envelope || typeof envelope.eventId !== 'string' || (!seen.has(envelope.eventId) && !options.dedupeArchive?.contains(envelope.eventId, envelope.receivedSeq)) || !Number.isInteger(envelope.receivedSeq) || envelope.receivedSeq < 1 || envelope.fact === undefined)
|
|
151
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
152
|
+
if (inbox.queue.some((candidate) => candidate.eventId === envelope.eventId || candidate.receivedSeq === envelope.receivedSeq))
|
|
153
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
154
|
+
if (envelope.receivedSeq <= maxReceivedSeq)
|
|
155
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
156
|
+
const knownSeq = seen.get(envelope.eventId);
|
|
157
|
+
if (knownSeq !== undefined && knownSeq !== envelope.receivedSeq)
|
|
158
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
159
|
+
if ([...seen.entries()].some(([eventId, receivedSeq]) => eventId !== envelope.eventId && receivedSeq === envelope.receivedSeq))
|
|
160
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
161
|
+
inbox.queue.push({ eventId: envelope.eventId, receivedSeq: envelope.receivedSeq, fact: structuredClone(envelope.fact) });
|
|
162
|
+
maxReceivedSeq = Math.max(maxReceivedSeq, envelope.receivedSeq);
|
|
163
|
+
}
|
|
164
|
+
if (value.nextSeq <= maxReceivedSeq)
|
|
165
|
+
throw new Error('INVALID_FACT_INBOX_SNAPSHOT');
|
|
166
|
+
for (const [eventId, receivedSeq] of seen)
|
|
167
|
+
inbox.seen.set(eventId, receivedSeq);
|
|
168
|
+
inbox.nextSeq = value.nextSeq;
|
|
169
|
+
return inbox;
|
|
170
|
+
}
|
|
171
|
+
clear() { this.queue.length = 0; }
|
|
172
|
+
}
|
|
173
|
+
export class ObservationInbox {
|
|
174
|
+
maxEntries;
|
|
175
|
+
maxBytes;
|
|
176
|
+
queue = [];
|
|
177
|
+
droppedThroughByAgent = new Map();
|
|
178
|
+
bytes = 0;
|
|
179
|
+
nextSeq = 1;
|
|
180
|
+
constructor(maxEntries = 4096, maxBytes = 1_000_000) {
|
|
181
|
+
this.maxEntries = maxEntries;
|
|
182
|
+
this.maxBytes = maxBytes;
|
|
183
|
+
}
|
|
184
|
+
enqueue(input) {
|
|
185
|
+
const event = structuredClone({ ...input, seq: this.nextSeq++ });
|
|
186
|
+
this.queue.push(event);
|
|
187
|
+
this.bytes += this.eventBytes(event);
|
|
188
|
+
while (this.queue.length > this.maxEntries || this.bytes > this.maxBytes) {
|
|
189
|
+
const dropped = this.queue.shift();
|
|
190
|
+
this.bytes -= this.eventBytes(dropped);
|
|
191
|
+
this.droppedThroughByAgent.set(dropped.agentId, Math.max(this.droppedThroughByAgent.get(dropped.agentId) ?? 0, dropped.seq));
|
|
192
|
+
}
|
|
193
|
+
return structuredClone(event);
|
|
194
|
+
}
|
|
195
|
+
drain(agentId) {
|
|
196
|
+
if (agentId === undefined) {
|
|
197
|
+
const selected = this.queue.splice(0);
|
|
198
|
+
this.bytes = 0;
|
|
199
|
+
return selected.map((event) => structuredClone(event));
|
|
200
|
+
}
|
|
201
|
+
const selected = this.queue.filter((event) => event.agentId === agentId);
|
|
202
|
+
if (selected.length) {
|
|
203
|
+
const ids = new Set(selected.map((event) => event.seq));
|
|
204
|
+
for (let index = this.queue.length - 1; index >= 0; index--)
|
|
205
|
+
if (ids.has(this.queue[index].seq)) {
|
|
206
|
+
this.bytes -= this.eventBytes(this.queue[index]);
|
|
207
|
+
this.queue.splice(index, 1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return selected.map((event) => structuredClone(event));
|
|
211
|
+
}
|
|
212
|
+
get size() { return this.queue.length; }
|
|
213
|
+
get sizeBytes() { return this.bytes; }
|
|
214
|
+
droppedThrough(agentId) { return this.droppedThroughByAgent.get(agentId) ?? 0; }
|
|
215
|
+
snapshot() { return this.queue.map((event) => structuredClone(event)); }
|
|
216
|
+
eventBytes(event) { return Buffer.byteLength(JSON.stringify(event), 'utf8'); }
|
|
217
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { RuntimeEventInput, RuntimeState, RuntimeError, ContextVersion, JsonValue, AgentRecord, LaneRecord, EffectRecord, WaitRecord, ResultRecord, FindingRecord, ArtifactRecord, LaneId, WaitId, EffectId, HistoryRecord, MergeProposal, ToolCallCorrelation, PrivacyMetadata } from './types.js';
|
|
2
|
+
export type Mutation = {
|
|
3
|
+
op: 'setAgent';
|
|
4
|
+
agentId: string;
|
|
5
|
+
record: AgentRecord;
|
|
6
|
+
} | {
|
|
7
|
+
op: 'setLane';
|
|
8
|
+
laneId: LaneId;
|
|
9
|
+
record: LaneRecord;
|
|
10
|
+
} | {
|
|
11
|
+
op: 'setEffect';
|
|
12
|
+
effectId: EffectId;
|
|
13
|
+
record: EffectRecord;
|
|
14
|
+
} | {
|
|
15
|
+
op: 'setWait';
|
|
16
|
+
waitId: WaitId;
|
|
17
|
+
record: WaitRecord;
|
|
18
|
+
} | {
|
|
19
|
+
op: 'insertLane';
|
|
20
|
+
record: LaneRecord;
|
|
21
|
+
} | {
|
|
22
|
+
op: 'insertEffect';
|
|
23
|
+
record: EffectRecord;
|
|
24
|
+
} | {
|
|
25
|
+
op: 'insertWait';
|
|
26
|
+
record: WaitRecord;
|
|
27
|
+
} | {
|
|
28
|
+
op: 'publishResult';
|
|
29
|
+
record: ResultRecord;
|
|
30
|
+
} | {
|
|
31
|
+
op: 'publishFinding';
|
|
32
|
+
record: FindingRecord;
|
|
33
|
+
} | {
|
|
34
|
+
op: 'publishArtifact';
|
|
35
|
+
record: ArtifactRecord;
|
|
36
|
+
} | {
|
|
37
|
+
op: 'setToolCallCorrelation';
|
|
38
|
+
record: ToolCallCorrelation;
|
|
39
|
+
} | {
|
|
40
|
+
op: 'insertMergeProposal';
|
|
41
|
+
proposal: MergeProposal;
|
|
42
|
+
} | {
|
|
43
|
+
op: 'removeMergeProposal';
|
|
44
|
+
proposalId: string;
|
|
45
|
+
} | {
|
|
46
|
+
op: 'setGlobal';
|
|
47
|
+
agentId: string;
|
|
48
|
+
version: ContextVersion;
|
|
49
|
+
value: JsonValue;
|
|
50
|
+
metadata?: PrivacyMetadata;
|
|
51
|
+
} | {
|
|
52
|
+
op: 'setLaneContext';
|
|
53
|
+
laneId: LaneId;
|
|
54
|
+
value: JsonValue;
|
|
55
|
+
version: ContextVersion;
|
|
56
|
+
history?: HistoryRecord[];
|
|
57
|
+
metadata?: PrivacyMetadata;
|
|
58
|
+
} | {
|
|
59
|
+
op: 'setNextIds';
|
|
60
|
+
nextIds: RuntimeState['nextIds'];
|
|
61
|
+
} | {
|
|
62
|
+
op: 'appendEvent';
|
|
63
|
+
event: RuntimeEventInput;
|
|
64
|
+
} | {
|
|
65
|
+
op: 'setNow';
|
|
66
|
+
now: number;
|
|
67
|
+
};
|
|
68
|
+
export interface ValidationSuccess {
|
|
69
|
+
mutations: Mutation[];
|
|
70
|
+
}
|
|
71
|
+
export interface ValidationFailure {
|
|
72
|
+
rejection: RuntimeError;
|
|
73
|
+
}
|
|
74
|
+
export type ValidationResult = ValidationSuccess | ValidationFailure;
|
|
75
|
+
/** Shallow-fork maps and clone only records `apply()` mutates in place. */
|
|
76
|
+
export declare function forkRuntimeStateForAdmission(state: RuntimeState, mutations: Mutation[]): RuntimeState;
|
|
77
|
+
export declare function apply(state: RuntimeState, mutations: Mutation[], defaults?: {
|
|
78
|
+
sessionId?: string;
|
|
79
|
+
timestamp?: number;
|
|
80
|
+
}): void;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { appendRuntimeEvent } from './events.js';
|
|
2
|
+
/** Shallow-fork maps and clone only records `apply()` mutates in place. */
|
|
3
|
+
export function forkRuntimeStateForAdmission(state, mutations) {
|
|
4
|
+
const dirtyAgents = new Set();
|
|
5
|
+
const dirtyLanes = new Set();
|
|
6
|
+
for (const mutation of mutations) {
|
|
7
|
+
if (mutation.op === 'setGlobal')
|
|
8
|
+
dirtyAgents.add(mutation.agentId);
|
|
9
|
+
else if (mutation.op === 'setLaneContext')
|
|
10
|
+
dirtyLanes.add(mutation.laneId);
|
|
11
|
+
else if (mutation.op === 'publishFinding')
|
|
12
|
+
dirtyLanes.add(mutation.record.laneId);
|
|
13
|
+
}
|
|
14
|
+
const agents = new Map(state.agents);
|
|
15
|
+
const lanes = new Map(state.lanes);
|
|
16
|
+
for (const id of dirtyAgents) {
|
|
17
|
+
const agent = agents.get(id);
|
|
18
|
+
if (agent)
|
|
19
|
+
agents.set(id, { ...agent, globalVersions: new Map(agent.globalVersions), ...(agent.globalPrivacy === undefined ? {} : { globalPrivacy: new Map(agent.globalPrivacy) }) });
|
|
20
|
+
}
|
|
21
|
+
for (const id of dirtyLanes) {
|
|
22
|
+
const lane = lanes.get(id);
|
|
23
|
+
if (lane)
|
|
24
|
+
lanes.set(id, { ...lane, ...(lane.visibleResultRefs === undefined ? {} : { visibleResultRefs: new Set(lane.visibleResultRefs) }) });
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...state,
|
|
28
|
+
agents,
|
|
29
|
+
lanes,
|
|
30
|
+
effects: new Map(state.effects),
|
|
31
|
+
waits: new Map(state.waits),
|
|
32
|
+
results: new Map(state.results),
|
|
33
|
+
artifacts: new Map(state.artifacts),
|
|
34
|
+
toolCallCorrelations: new Map(state.toolCallCorrelations),
|
|
35
|
+
mergeProposals: new Map(state.mergeProposals),
|
|
36
|
+
events: state.events.slice(),
|
|
37
|
+
nextIds: { ...state.nextIds },
|
|
38
|
+
trustedSanitizerIds: new Set(state.trustedSanitizerIds),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function apply(state, mutations, defaults = {}) {
|
|
42
|
+
for (const mutation of mutations) {
|
|
43
|
+
switch (mutation.op) {
|
|
44
|
+
case 'setAgent':
|
|
45
|
+
state.agents.set(mutation.agentId, mutation.record);
|
|
46
|
+
break;
|
|
47
|
+
case 'setLane':
|
|
48
|
+
state.lanes.set(mutation.laneId, mutation.record);
|
|
49
|
+
break;
|
|
50
|
+
case 'setEffect':
|
|
51
|
+
state.effects.set(mutation.effectId, mutation.record);
|
|
52
|
+
break;
|
|
53
|
+
case 'setWait':
|
|
54
|
+
state.waits.set(mutation.waitId, mutation.record);
|
|
55
|
+
break;
|
|
56
|
+
case 'insertLane':
|
|
57
|
+
state.lanes.set(mutation.record.id, mutation.record);
|
|
58
|
+
break;
|
|
59
|
+
case 'insertEffect':
|
|
60
|
+
state.effects.set(mutation.record.id, mutation.record);
|
|
61
|
+
break;
|
|
62
|
+
case 'insertWait':
|
|
63
|
+
state.waits.set(mutation.record.id, mutation.record);
|
|
64
|
+
break;
|
|
65
|
+
case 'publishResult': {
|
|
66
|
+
state.results.set(mutation.record.id, mutation.record);
|
|
67
|
+
const match = /^result-(\d+)$/.exec(mutation.record.id);
|
|
68
|
+
if (match)
|
|
69
|
+
state.nextIds.result = Math.max(state.nextIds.result, Number(match[1]) + 1);
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'publishFinding': {
|
|
73
|
+
state.results.set(mutation.record.id, mutation.record);
|
|
74
|
+
const lane = state.lanes.get(mutation.record.laneId);
|
|
75
|
+
if (lane?.visibleResultRefs)
|
|
76
|
+
lane.visibleResultRefs.add(mutation.record.id);
|
|
77
|
+
else if (lane)
|
|
78
|
+
lane.visibleResultRefs = new Set([mutation.record.id]);
|
|
79
|
+
const match = /^finding-(\d+)$/.exec(mutation.record.id);
|
|
80
|
+
if (match)
|
|
81
|
+
state.nextIds.result = Math.max(state.nextIds.result, Number(match[1]) + 1);
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'publishArtifact': {
|
|
85
|
+
state.artifacts.set(mutation.record.ref, mutation.record);
|
|
86
|
+
const match = /^artifact-(\d+)$/.exec(mutation.record.ref);
|
|
87
|
+
if (match)
|
|
88
|
+
state.nextIds.artifact = Math.max(state.nextIds.artifact, Number(match[1]) + 1);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case 'setToolCallCorrelation':
|
|
92
|
+
state.toolCallCorrelations.set(mutation.record.toolCallId, mutation.record);
|
|
93
|
+
break;
|
|
94
|
+
case 'insertMergeProposal':
|
|
95
|
+
state.mergeProposals.set(mutation.proposal.id, mutation.proposal);
|
|
96
|
+
break;
|
|
97
|
+
case 'removeMergeProposal':
|
|
98
|
+
state.mergeProposals.delete(mutation.proposalId);
|
|
99
|
+
break;
|
|
100
|
+
case 'setGlobal': {
|
|
101
|
+
const agent = state.agents.get(mutation.agentId);
|
|
102
|
+
agent.globalVersions.set(mutation.version, mutation.value);
|
|
103
|
+
if (mutation.metadata) {
|
|
104
|
+
if (!agent.globalPrivacy)
|
|
105
|
+
agent.globalPrivacy = new Map();
|
|
106
|
+
agent.globalPrivacy.set(mutation.version, structuredClone(mutation.metadata));
|
|
107
|
+
}
|
|
108
|
+
agent.latestGlobalVersion = mutation.version;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'setLaneContext': {
|
|
112
|
+
const lane = state.lanes.get(mutation.laneId);
|
|
113
|
+
lane.context = { ...lane.context, state: mutation.value, version: mutation.version, ...(mutation.history === undefined ? {} : { history: structuredClone(mutation.history) }), ...(mutation.metadata === undefined ? {} : structuredClone(mutation.metadata)) };
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'setNextIds':
|
|
117
|
+
state.nextIds = { ...mutation.nextIds };
|
|
118
|
+
break;
|
|
119
|
+
case 'appendEvent':
|
|
120
|
+
appendRuntimeEvent(state, mutation.event, defaults);
|
|
121
|
+
break;
|
|
122
|
+
case 'setNow':
|
|
123
|
+
state.now = mutation.now;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AgentRecord, LaneRecord, EffectRecord, WaitRecord, ResultRecord, FindingRecord, RuntimeState, LaneContext, HistoryRecord, ProgressWatchdogState, MergeProposal, ArtifactRecord } from './types.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|