@superblocksteam/gateway 2.0.155-next.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/LICENSE.txt +87 -0
- package/README.md +170 -0
- package/dist/agents/resolve-orchestrator-url.d.ts +22 -0
- package/dist/agents/resolve-orchestrator-url.js +38 -0
- package/dist/agents/resolve-orchestrator-url.js.map +1 -0
- package/dist/capabilities/import-prompt.d.ts +11 -0
- package/dist/capabilities/import-prompt.js +96 -0
- package/dist/capabilities/import-prompt.js.map +1 -0
- package/dist/capabilities/lifecycle.d.ts +73 -0
- package/dist/capabilities/lifecycle.js +2531 -0
- package/dist/capabilities/lifecycle.js.map +1 -0
- package/dist/capabilities/persisted-progress.d.ts +46 -0
- package/dist/capabilities/persisted-progress.js +246 -0
- package/dist/capabilities/persisted-progress.js.map +1 -0
- package/dist/capabilities/types.d.ts +446 -0
- package/dist/capabilities/types.js +47 -0
- package/dist/capabilities/types.js.map +1 -0
- package/dist/capture/browser-contract.d.ts +36 -0
- package/dist/capture/browser-contract.js +24 -0
- package/dist/capture/browser-contract.js.map +1 -0
- package/dist/capture/browser-instructions.d.ts +9 -0
- package/dist/capture/browser-instructions.js +17 -0
- package/dist/capture/browser-instructions.js.map +1 -0
- package/dist/capture/capture-library-probe.d.ts +55 -0
- package/dist/capture/capture-library-probe.js +177 -0
- package/dist/capture/capture-library-probe.js.map +1 -0
- package/dist/capture/capture-library.d.ts +52 -0
- package/dist/capture/capture-library.js +249 -0
- package/dist/capture/capture-library.js.map +1 -0
- package/dist/capture/classify-library-paint.d.ts +21 -0
- package/dist/capture/classify-library-paint.js +13 -0
- package/dist/capture/classify-library-paint.js.map +1 -0
- package/dist/capture/host-mcp-scan.d.ts +16 -0
- package/dist/capture/host-mcp-scan.js +64 -0
- package/dist/capture/host-mcp-scan.js.map +1 -0
- package/dist/capture/library-parent-script.d.ts +34 -0
- package/dist/capture/library-parent-script.js +82 -0
- package/dist/capture/library-parent-script.js.map +1 -0
- package/dist/capture/mode.d.ts +22 -0
- package/dist/capture/mode.js +45 -0
- package/dist/capture/mode.js.map +1 -0
- package/dist/config.d.ts +141 -0
- package/dist/config.js +109 -0
- package/dist/config.js.map +1 -0
- package/dist/debug.d.ts +2 -0
- package/dist/debug.js +9 -0
- package/dist/debug.js.map +1 -0
- package/dist/deps.d.ts +36 -0
- package/dist/deps.js +2 -0
- package/dist/deps.js.map +1 -0
- package/dist/events/cursor.d.ts +43 -0
- package/dist/events/cursor.js +78 -0
- package/dist/events/cursor.js.map +1 -0
- package/dist/events/memory-event-store.d.ts +34 -0
- package/dist/events/memory-event-store.js +110 -0
- package/dist/events/memory-event-store.js.map +1 -0
- package/dist/events/merge.d.ts +23 -0
- package/dist/events/merge.js +97 -0
- package/dist/events/merge.js.map +1 -0
- package/dist/events/normalized-collector.d.ts +62 -0
- package/dist/events/normalized-collector.js +156 -0
- package/dist/events/normalized-collector.js.map +1 -0
- package/dist/events/schema.d.ts +9 -0
- package/dist/events/schema.js +93 -0
- package/dist/events/schema.js.map +1 -0
- package/dist/events/snapshot.d.ts +32 -0
- package/dist/events/snapshot.js +57 -0
- package/dist/events/snapshot.js.map +1 -0
- package/dist/events/stream-key.d.ts +2 -0
- package/dist/events/stream-key.js +31 -0
- package/dist/events/stream-key.js.map +1 -0
- package/dist/events/types.d.ts +179 -0
- package/dist/events/types.js +66 -0
- package/dist/events/types.js.map +1 -0
- package/dist/health/readiness.d.ts +99 -0
- package/dist/health/readiness.js +202 -0
- package/dist/health/readiness.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/filters.d.ts +8 -0
- package/dist/integrations/filters.js +40 -0
- package/dist/integrations/filters.js.map +1 -0
- package/dist/integrations/map.d.ts +26 -0
- package/dist/integrations/map.js +43 -0
- package/dist/integrations/map.js.map +1 -0
- package/dist/integrations/profile-key.d.ts +19 -0
- package/dist/integrations/profile-key.js +35 -0
- package/dist/integrations/profile-key.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -0
- package/dist/orchestrator/client.d.ts +62 -0
- package/dist/orchestrator/client.js +160 -0
- package/dist/orchestrator/client.js.map +1 -0
- package/dist/playwright/ensure-chromium.d.ts +26 -0
- package/dist/playwright/ensure-chromium.js +60 -0
- package/dist/playwright/ensure-chromium.js.map +1 -0
- package/dist/preview/capture-screenshot.d.ts +52 -0
- package/dist/preview/capture-screenshot.js +158 -0
- package/dist/preview/capture-screenshot.js.map +1 -0
- package/dist/preview/viewer-url.d.ts +82 -0
- package/dist/preview/viewer-url.js +128 -0
- package/dist/preview/viewer-url.js.map +1 -0
- package/dist/process/fault-barrier.d.ts +55 -0
- package/dist/process/fault-barrier.js +113 -0
- package/dist/process/fault-barrier.js.map +1 -0
- package/dist/resume/memory-progress-store.d.ts +39 -0
- package/dist/resume/memory-progress-store.js +82 -0
- package/dist/resume/memory-progress-store.js.map +1 -0
- package/dist/resume/memory-recent-app-store.d.ts +14 -0
- package/dist/resume/memory-recent-app-store.js +27 -0
- package/dist/resume/memory-recent-app-store.js.map +1 -0
- package/dist/resume/memory-turn-store.d.ts +18 -0
- package/dist/resume/memory-turn-store.js +73 -0
- package/dist/resume/memory-turn-store.js.map +1 -0
- package/dist/resume/progress-key.d.ts +21 -0
- package/dist/resume/progress-key.js +58 -0
- package/dist/resume/progress-key.js.map +1 -0
- package/dist/resume/stores.d.ts +14 -0
- package/dist/resume/stores.js +18 -0
- package/dist/resume/stores.js.map +1 -0
- package/dist/resume/types.d.ts +124 -0
- package/dist/resume/types.js +13 -0
- package/dist/resume/types.js.map +1 -0
- package/dist/sabs/agent-facing-text.d.ts +33 -0
- package/dist/sabs/agent-facing-text.js +125 -0
- package/dist/sabs/agent-facing-text.js.map +1 -0
- package/dist/sabs/browser-context.d.ts +53 -0
- package/dist/sabs/browser-context.js +91 -0
- package/dist/sabs/browser-context.js.map +1 -0
- package/dist/sabs/editor-client-methods.d.ts +147 -0
- package/dist/sabs/editor-client-methods.js +715 -0
- package/dist/sabs/editor-client-methods.js.map +1 -0
- package/dist/sabs/recent-tool-runs.d.ts +14 -0
- package/dist/sabs/recent-tool-runs.js +61 -0
- package/dist/sabs/recent-tool-runs.js.map +1 -0
- package/dist/sabs/session-peer.d.ts +351 -0
- package/dist/sabs/session-peer.js +2 -0
- package/dist/sabs/session-peer.js.map +1 -0
- package/dist/sabs/turn-collector.d.ts +152 -0
- package/dist/sabs/turn-collector.js +573 -0
- package/dist/sabs/turn-collector.js.map +1 -0
- package/dist/sabs/websocket-session-peer.d.ts +514 -0
- package/dist/sabs/websocket-session-peer.js +2487 -0
- package/dist/sabs/websocket-session-peer.js.map +1 -0
- package/dist/security/constant-time.d.ts +9 -0
- package/dist/security/constant-time.js +13 -0
- package/dist/security/constant-time.js.map +1 -0
- package/dist/server/client.d.ts +371 -0
- package/dist/server/client.js +572 -0
- package/dist/server/client.js.map +1 -0
- package/dist/server/identity-reads.d.ts +76 -0
- package/dist/server/identity-reads.js +209 -0
- package/dist/server/identity-reads.js.map +1 -0
- package/dist/start.d.ts +14 -0
- package/dist/start.js +75 -0
- package/dist/start.js.map +1 -0
- package/dist/telemetry/metrics.d.ts +32 -0
- package/dist/telemetry/metrics.js +57 -0
- package/dist/telemetry/metrics.js.map +1 -0
- package/dist/transports/mcp/admin-tools.d.ts +47 -0
- package/dist/transports/mcp/admin-tools.js +164 -0
- package/dist/transports/mcp/admin-tools.js.map +1 -0
- package/dist/transports/mcp/app-status-html.d.ts +15 -0
- package/dist/transports/mcp/app-status-html.js +563 -0
- package/dist/transports/mcp/app-status-html.js.map +1 -0
- package/dist/transports/mcp/decision-elicitation.d.ts +74 -0
- package/dist/transports/mcp/decision-elicitation.js +233 -0
- package/dist/transports/mcp/decision-elicitation.js.map +1 -0
- package/dist/transports/mcp/dispatch-probe.d.ts +24 -0
- package/dist/transports/mcp/dispatch-probe.js +48 -0
- package/dist/transports/mcp/dispatch-probe.js.map +1 -0
- package/dist/transports/mcp/format-tool-content.d.ts +25 -0
- package/dist/transports/mcp/format-tool-content.js +48 -0
- package/dist/transports/mcp/format-tool-content.js.map +1 -0
- package/dist/transports/mcp/mount.d.ts +9 -0
- package/dist/transports/mcp/mount.js +469 -0
- package/dist/transports/mcp/mount.js.map +1 -0
- package/dist/transports/mcp/progress-notifier.d.ts +39 -0
- package/dist/transports/mcp/progress-notifier.js +52 -0
- package/dist/transports/mcp/progress-notifier.js.map +1 -0
- package/package.json +72 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { PRODUCERS_IN_ORDER, sortEvents } from "./merge.js";
|
|
2
|
+
import { buildSnapshot } from "./snapshot.js";
|
|
3
|
+
import { streamSuffix } from "./stream-key.js";
|
|
4
|
+
import { EVENT_STREAM_TTL_MS, MAX_EVENTS_PER_STREAM, } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Local-only fallback for a laptop stack with no Redis, matching the same
|
|
7
|
+
* fallback the turn store makes. It is not a second source of truth: a
|
|
8
|
+
* multi-replica deployment would need an external store, because two
|
|
9
|
+
* processes with private maps cannot agree on what was already delivered.
|
|
10
|
+
*
|
|
11
|
+
* A shared test suite runs both implementations against the same behaviour, so
|
|
12
|
+
* "works locally, breaks on Redis" shows up here rather than in production.
|
|
13
|
+
*/
|
|
14
|
+
export class MemoryEventStore {
|
|
15
|
+
streams = new Map();
|
|
16
|
+
async append(event) {
|
|
17
|
+
const stream = this.streamFor(event, event.producer);
|
|
18
|
+
stream.expiresAtMs = Date.now() + EVENT_STREAM_TTL_MS;
|
|
19
|
+
// Presence is the de-duplication record, so it cannot outlive the event
|
|
20
|
+
// or be evicted separately from it.
|
|
21
|
+
if (stream.byEventId.has(event.eventId)) {
|
|
22
|
+
return "duplicate";
|
|
23
|
+
}
|
|
24
|
+
stream.byEventId.set(event.eventId, event);
|
|
25
|
+
this.trim(stream);
|
|
26
|
+
return "accepted";
|
|
27
|
+
}
|
|
28
|
+
async list(input) {
|
|
29
|
+
this.evictExpired();
|
|
30
|
+
const producers = input.producer ? [input.producer] : PRODUCERS_IN_ORDER;
|
|
31
|
+
const collected = [];
|
|
32
|
+
for (const producer of producers) {
|
|
33
|
+
const stream = this.streams.get(key(input, producer));
|
|
34
|
+
for (const event of stream?.byEventId.values() ?? []) {
|
|
35
|
+
if (input.afterSequence !== undefined &&
|
|
36
|
+
event.sequence <= input.afterSequence) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
collected.push(event);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const ordered = sortEvents(collected);
|
|
43
|
+
return input.limit === undefined ? ordered : ordered.slice(0, input.limit);
|
|
44
|
+
}
|
|
45
|
+
async snapshot(input) {
|
|
46
|
+
this.evictExpired();
|
|
47
|
+
return await buildSnapshot(input, {
|
|
48
|
+
firstRetainedSequence: async (producer) => this.ordered(input, producer)[0]?.sequence,
|
|
49
|
+
readAfter: async ({ afterSequence, limit, producer }) => {
|
|
50
|
+
const after = this.ordered(input, producer).filter((event) => event.sequence > afterSequence);
|
|
51
|
+
return limit === undefined ? after : after.slice(0, limit);
|
|
52
|
+
},
|
|
53
|
+
readTail: async ({ count, producer }) => this.ordered(input, producer).slice(-count),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** One producer's retained stream in sequence order, as Redis reads it. */
|
|
57
|
+
ordered(scope, producer) {
|
|
58
|
+
const stream = this.streams.get(key(scope, producer));
|
|
59
|
+
return [...(stream?.byEventId.values() ?? [])].sort((left, right) => left.sequence - right.sequence ||
|
|
60
|
+
(left.eventId < right.eventId
|
|
61
|
+
? -1
|
|
62
|
+
: left.eventId > right.eventId
|
|
63
|
+
? 1
|
|
64
|
+
: 0));
|
|
65
|
+
}
|
|
66
|
+
streamFor(scope, producer) {
|
|
67
|
+
this.evictExpired();
|
|
68
|
+
const streamKey = key(scope, producer);
|
|
69
|
+
const existing = this.streams.get(streamKey);
|
|
70
|
+
if (existing) {
|
|
71
|
+
return existing;
|
|
72
|
+
}
|
|
73
|
+
const created = {
|
|
74
|
+
byEventId: new Map(),
|
|
75
|
+
expiresAtMs: Date.now() + EVENT_STREAM_TTL_MS,
|
|
76
|
+
};
|
|
77
|
+
this.streams.set(streamKey, created);
|
|
78
|
+
return created;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Drops the lowest-sequence events past the bound, which takes their
|
|
82
|
+
* de-duplication records with them: keeping every id forever would make the
|
|
83
|
+
* bound on events pointless.
|
|
84
|
+
*/
|
|
85
|
+
trim(stream) {
|
|
86
|
+
if (stream.byEventId.size <= MAX_EVENTS_PER_STREAM) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const oldestFirst = [...stream.byEventId.values()].sort((left, right) => left.sequence - right.sequence);
|
|
90
|
+
for (const dropped of oldestFirst.slice(0, stream.byEventId.size - MAX_EVENTS_PER_STREAM)) {
|
|
91
|
+
stream.byEventId.delete(dropped.eventId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
evictExpired() {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
for (const [streamKey, stream] of this.streams) {
|
|
97
|
+
if (stream.expiresAtMs <= now) {
|
|
98
|
+
this.streams.delete(streamKey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The same addressing the Redis store uses, from the same builder, so the two
|
|
105
|
+
* cannot disagree about which events share a stream.
|
|
106
|
+
*/
|
|
107
|
+
function key(scope, producer) {
|
|
108
|
+
return streamSuffix(scope, producer);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=memory-event-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-event-store.js","sourceRoot":"","sources":["../../src/events/memory-event-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EventProducer, LiveEditEvent } from "./types.js";
|
|
2
|
+
export declare const PRODUCERS_IN_ORDER: readonly EventProducer[];
|
|
3
|
+
/**
|
|
4
|
+
* Merges each producer's stream into one order.
|
|
5
|
+
*
|
|
6
|
+
* This cannot be a comparator. "Sequence within a producer, timestamp across
|
|
7
|
+
* producers" has no total order behind it: if one producer's clock steps
|
|
8
|
+
* backwards between two of its own events (NTP correction, container skew),
|
|
9
|
+
* its first event can be later than another producer's event that is itself
|
|
10
|
+
* later than its second. Sorting on such a rule is a cycle, and `Array#sort`
|
|
11
|
+
* answers a cycle differently depending on the order it read the input — so
|
|
12
|
+
* two Gateway replicas holding the same events could disagree about what the
|
|
13
|
+
* turn looked like.
|
|
14
|
+
*
|
|
15
|
+
* A merge has no such freedom. Each producer is walked strictly in sequence
|
|
16
|
+
* order, so its own events can never be reordered against each other whatever
|
|
17
|
+
* its clock did. Only the choice of which producer to take from next depends
|
|
18
|
+
* on time, and that is decided by a monotone clock — a producer's event is
|
|
19
|
+
* never treated as earlier than one already emitted from the same producer —
|
|
20
|
+
* with producer name and then sequence settling a dead heat. The result
|
|
21
|
+
* depends on the set of events alone, never on the order they were read.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sortEvents(events: LiveEditEvent[]): LiveEditEvent[];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export const PRODUCERS_IN_ORDER = [
|
|
2
|
+
"dev_server",
|
|
3
|
+
"sabs",
|
|
4
|
+
];
|
|
5
|
+
/**
|
|
6
|
+
* Merges each producer's stream into one order.
|
|
7
|
+
*
|
|
8
|
+
* This cannot be a comparator. "Sequence within a producer, timestamp across
|
|
9
|
+
* producers" has no total order behind it: if one producer's clock steps
|
|
10
|
+
* backwards between two of its own events (NTP correction, container skew),
|
|
11
|
+
* its first event can be later than another producer's event that is itself
|
|
12
|
+
* later than its second. Sorting on such a rule is a cycle, and `Array#sort`
|
|
13
|
+
* answers a cycle differently depending on the order it read the input — so
|
|
14
|
+
* two Gateway replicas holding the same events could disagree about what the
|
|
15
|
+
* turn looked like.
|
|
16
|
+
*
|
|
17
|
+
* A merge has no such freedom. Each producer is walked strictly in sequence
|
|
18
|
+
* order, so its own events can never be reordered against each other whatever
|
|
19
|
+
* its clock did. Only the choice of which producer to take from next depends
|
|
20
|
+
* on time, and that is decided by a monotone clock — a producer's event is
|
|
21
|
+
* never treated as earlier than one already emitted from the same producer —
|
|
22
|
+
* with producer name and then sequence settling a dead heat. The result
|
|
23
|
+
* depends on the set of events alone, never on the order they were read.
|
|
24
|
+
*/
|
|
25
|
+
export function sortEvents(events) {
|
|
26
|
+
const byProducer = new Map();
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
const stream = byProducer.get(event.producer);
|
|
29
|
+
if (stream) {
|
|
30
|
+
stream.push(event);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
byProducer.set(event.producer, [event]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const cursors = [...byProducer.entries()]
|
|
37
|
+
.sort(([left], [right]) => compareProducers(left, right))
|
|
38
|
+
.map(([producer, stream]) => ({
|
|
39
|
+
emittedThroughMs: Number.NEGATIVE_INFINITY,
|
|
40
|
+
// Sequence is the only authority within a producer. The event id settles
|
|
41
|
+
// the case of one sequence claimed twice, so even that stays stable.
|
|
42
|
+
events: [...stream].sort((left, right) => left.sequence - right.sequence ||
|
|
43
|
+
compareStrings(left.eventId, right.eventId)),
|
|
44
|
+
position: 0,
|
|
45
|
+
producer,
|
|
46
|
+
}));
|
|
47
|
+
const merged = [];
|
|
48
|
+
for (;;) {
|
|
49
|
+
const next = takeNext(cursors);
|
|
50
|
+
if (!next) {
|
|
51
|
+
return merged;
|
|
52
|
+
}
|
|
53
|
+
merged.push(next);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Removes and returns the event that comes next across all producers, or
|
|
58
|
+
* undefined once every stream is exhausted.
|
|
59
|
+
*/
|
|
60
|
+
function takeNext(cursors) {
|
|
61
|
+
let chosen;
|
|
62
|
+
let chosenAtMs = Number.POSITIVE_INFINITY;
|
|
63
|
+
for (const cursor of cursors) {
|
|
64
|
+
const head = cursor.events[cursor.position];
|
|
65
|
+
if (!head) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const atMs = Math.max(head.occurredAtMs, cursor.emittedThroughMs);
|
|
69
|
+
// Cursors are already in producer order, so `<` alone keeps the earlier
|
|
70
|
+
// producer on a tie.
|
|
71
|
+
if (atMs < chosenAtMs) {
|
|
72
|
+
chosen = cursor;
|
|
73
|
+
chosenAtMs = atMs;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (!chosen) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
chosen.emittedThroughMs = chosenAtMs;
|
|
80
|
+
const head = chosen.events[chosen.position];
|
|
81
|
+
chosen.position += 1;
|
|
82
|
+
return head;
|
|
83
|
+
}
|
|
84
|
+
function compareProducers(left, right) {
|
|
85
|
+
const leftRank = PRODUCERS_IN_ORDER.indexOf(left);
|
|
86
|
+
const rightRank = PRODUCERS_IN_ORDER.indexOf(right);
|
|
87
|
+
return leftRank !== rightRank
|
|
88
|
+
? leftRank - rightRank
|
|
89
|
+
: compareStrings(left, right);
|
|
90
|
+
}
|
|
91
|
+
function compareStrings(left, right) {
|
|
92
|
+
if (left === right) {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
return left < right ? -1 : 1;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../src/events/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ClarkPeerEvent } from "../sabs/editor-client-methods.js";
|
|
2
|
+
import type { TurnCollector } from "../sabs/turn-collector.js";
|
|
3
|
+
import type { LiveEditEvent, SESSION_STATUSES } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* How many recent events are remembered for cross-transport de-duplication.
|
|
6
|
+
* A long build pushes far more than this; forgetting the oldest is safe
|
|
7
|
+
* because the overlap between the socket and the push path is measured in
|
|
8
|
+
* seconds, not in thousands of events.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_TRACKED_FINGERPRINTS = 256;
|
|
11
|
+
export type NormalizedTurnCollectorDeps = {
|
|
12
|
+
collector: TurnCollector;
|
|
13
|
+
/**
|
|
14
|
+
* Clark needs an interactive authorization before it can continue. Reported
|
|
15
|
+
* rather than collected: it is not something Clark did, and a caller that
|
|
16
|
+
* only reads the turn's reply would never learn the build is stuck.
|
|
17
|
+
*/
|
|
18
|
+
onAuthRequired?: (reason: string) => void;
|
|
19
|
+
/** The live-edit session changed state underneath the turn. */
|
|
20
|
+
onSessionStatus?: (status: (typeof SESSION_STATUSES)[number]) => void;
|
|
21
|
+
/** The turn id Gateway minted with the prompt this collector is driving. */
|
|
22
|
+
turnId: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* One door into a turn's collector for both transports.
|
|
26
|
+
*
|
|
27
|
+
* Clark's activity reaches Gateway two ways during the transition: directly on
|
|
28
|
+
* the held editor socket, and relayed through SABS over the push channel. They
|
|
29
|
+
* overlap, so this applies each underlying fact once, and it drops anything
|
|
30
|
+
* carrying a turn id other than the one it is collecting for.
|
|
31
|
+
*
|
|
32
|
+
* Session lifecycle and auth-required are not turn activity. They are recorded
|
|
33
|
+
* here for the caller to read rather than pushed into the collector, which
|
|
34
|
+
* only understands what Clark did.
|
|
35
|
+
*/
|
|
36
|
+
export declare class NormalizedTurnCollector {
|
|
37
|
+
private readonly deps;
|
|
38
|
+
private readonly seen;
|
|
39
|
+
private authReason;
|
|
40
|
+
private session;
|
|
41
|
+
constructor(deps: NormalizedTurnCollectorDeps);
|
|
42
|
+
/** An event observed on the held editor socket. */
|
|
43
|
+
fromSocket(event: ClarkPeerEvent): void;
|
|
44
|
+
/** An event relayed through SABS over the push channel. */
|
|
45
|
+
fromPush(event: LiveEditEvent): void;
|
|
46
|
+
/** Reason Clark cannot proceed without an interactive authorization. */
|
|
47
|
+
authRequired(): string | undefined;
|
|
48
|
+
sessionStatus(): (typeof SESSION_STATUSES)[number] | undefined;
|
|
49
|
+
/** Exposed so the bound on remembered events can be asserted. */
|
|
50
|
+
trackedFingerprintCount(): number;
|
|
51
|
+
/**
|
|
52
|
+
* A session-scoped event carries no turn id and belongs to every turn on the
|
|
53
|
+
* live edit; a turn-scoped one belongs only to the turn that minted the id.
|
|
54
|
+
*/
|
|
55
|
+
private belongsToThisTurn;
|
|
56
|
+
/**
|
|
57
|
+
* Records a fingerprint, returning false when it was already known.
|
|
58
|
+
* Insertion order is iteration order for a Set, so evicting the first entry
|
|
59
|
+
* drops the oldest.
|
|
60
|
+
*/
|
|
61
|
+
private remember;
|
|
62
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many recent events are remembered for cross-transport de-duplication.
|
|
3
|
+
* A long build pushes far more than this; forgetting the oldest is safe
|
|
4
|
+
* because the overlap between the socket and the push path is measured in
|
|
5
|
+
* seconds, not in thousands of events.
|
|
6
|
+
*/
|
|
7
|
+
export const MAX_TRACKED_FINGERPRINTS = 256;
|
|
8
|
+
/**
|
|
9
|
+
* One door into a turn's collector for both transports.
|
|
10
|
+
*
|
|
11
|
+
* Clark's activity reaches Gateway two ways during the transition: directly on
|
|
12
|
+
* the held editor socket, and relayed through SABS over the push channel. They
|
|
13
|
+
* overlap, so this applies each underlying fact once, and it drops anything
|
|
14
|
+
* carrying a turn id other than the one it is collecting for.
|
|
15
|
+
*
|
|
16
|
+
* Session lifecycle and auth-required are not turn activity. They are recorded
|
|
17
|
+
* here for the caller to read rather than pushed into the collector, which
|
|
18
|
+
* only understands what Clark did.
|
|
19
|
+
*/
|
|
20
|
+
export class NormalizedTurnCollector {
|
|
21
|
+
deps;
|
|
22
|
+
seen = new Set();
|
|
23
|
+
authReason;
|
|
24
|
+
session;
|
|
25
|
+
constructor(deps) {
|
|
26
|
+
this.deps = deps;
|
|
27
|
+
}
|
|
28
|
+
/** An event observed on the held editor socket. */
|
|
29
|
+
fromSocket(event) {
|
|
30
|
+
const fingerprint = socketFingerprint(event);
|
|
31
|
+
if (fingerprint && !this.remember(fingerprint)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.deps.collector.handle(event);
|
|
35
|
+
}
|
|
36
|
+
/** An event relayed through SABS over the push channel. */
|
|
37
|
+
fromPush(event) {
|
|
38
|
+
if (!this.belongsToThisTurn(event)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// The event id is authoritative for a redelivery of the same push; the
|
|
42
|
+
// content fingerprint is what catches the same fact arriving on the other
|
|
43
|
+
// transport, which has no event id to compare against.
|
|
44
|
+
if (!this.remember(`event:${event.eventId}`)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const fingerprint = pushFingerprint(event);
|
|
48
|
+
if (fingerprint && !this.remember(fingerprint)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
switch (event.payload.kind) {
|
|
52
|
+
case "auth_required": {
|
|
53
|
+
this.authReason = event.payload.reason ?? "auth_required";
|
|
54
|
+
this.deps.onAuthRequired?.(this.authReason);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
case "session": {
|
|
58
|
+
this.session = event.payload.status;
|
|
59
|
+
this.deps.onSessionStatus?.(this.session);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
case "chat_message": {
|
|
63
|
+
this.deps.collector.handle({
|
|
64
|
+
kind: "message",
|
|
65
|
+
message: {
|
|
66
|
+
content: event.payload.text,
|
|
67
|
+
...(event.payload.messageId ? { id: event.payload.messageId } : {}),
|
|
68
|
+
role: event.payload.role,
|
|
69
|
+
type: event.payload.messageType ?? "text",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
case "generation_state": {
|
|
75
|
+
this.deps.collector.handle({
|
|
76
|
+
...(event.payload.hasError === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { hasError: event.payload.hasError }),
|
|
79
|
+
...(event.payload.hasSuggestions === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { hasSuggestions: event.payload.hasSuggestions }),
|
|
82
|
+
kind: "generation_state",
|
|
83
|
+
state: event.payload.state,
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
case "progress": {
|
|
88
|
+
this.deps.collector.handle({
|
|
89
|
+
kind: "task_status",
|
|
90
|
+
label: event.payload.label,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Reason Clark cannot proceed without an interactive authorization. */
|
|
96
|
+
authRequired() {
|
|
97
|
+
return this.authReason;
|
|
98
|
+
}
|
|
99
|
+
sessionStatus() {
|
|
100
|
+
return this.session;
|
|
101
|
+
}
|
|
102
|
+
/** Exposed so the bound on remembered events can be asserted. */
|
|
103
|
+
trackedFingerprintCount() {
|
|
104
|
+
return this.seen.size;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A session-scoped event carries no turn id and belongs to every turn on the
|
|
108
|
+
* live edit; a turn-scoped one belongs only to the turn that minted the id.
|
|
109
|
+
*/
|
|
110
|
+
belongsToThisTurn(event) {
|
|
111
|
+
if (!event.gatewayTurnId) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return event.gatewayTurnId === this.deps.turnId;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Records a fingerprint, returning false when it was already known.
|
|
118
|
+
* Insertion order is iteration order for a Set, so evicting the first entry
|
|
119
|
+
* drops the oldest.
|
|
120
|
+
*/
|
|
121
|
+
remember(fingerprint) {
|
|
122
|
+
if (this.seen.has(fingerprint)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (this.seen.size >= MAX_TRACKED_FINGERPRINTS) {
|
|
126
|
+
const oldest = this.seen.values().next();
|
|
127
|
+
if (!oldest.done) {
|
|
128
|
+
this.seen.delete(oldest.value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
this.seen.add(fingerprint);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Identity of a fact as seen on the socket, in the same terms
|
|
137
|
+
* {@link pushFingerprint} uses, so the two transports collide on it.
|
|
138
|
+
*
|
|
139
|
+
* Only events with a stable identity are fingerprinted. A state or label with
|
|
140
|
+
* no id genuinely recurs during a build, and suppressing a real repeat is
|
|
141
|
+
* worse than letting a transitional duplicate through: the collector already
|
|
142
|
+
* drops consecutive identical progress lines.
|
|
143
|
+
*/
|
|
144
|
+
function socketFingerprint(event) {
|
|
145
|
+
if (event.kind === "message" && typeof event.message.id === "string") {
|
|
146
|
+
return `message:${event.message.id}`;
|
|
147
|
+
}
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
function pushFingerprint(event) {
|
|
151
|
+
if (event.payload.kind === "chat_message" && event.payload.messageId) {
|
|
152
|
+
return `message:${event.payload.messageId}`;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=normalized-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalized-collector.js","sourceRoot":"","sources":["../../src/events/normalized-collector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ParseResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validates one inbound event against the contract.
|
|
4
|
+
*
|
|
5
|
+
* SABS validates the same shape before pushing it, which is not a reason to
|
|
6
|
+
* skip it here: a validated producer is not a trusted one, and this is the
|
|
7
|
+
* process boundary where the bytes become Gateway's problem.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseLiveEditEvent(input: unknown): ParseResult;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CHAT_ROLES, EVENT_CONTRACT_VERSION, EVENT_PRODUCERS, GENERATION_STATES, MAX_IDENTIFIER_LENGTH, MAX_LABEL_LENGTH, MAX_SEQUENCE, MAX_TEXT_LENGTH, SESSION_STATUSES, } from "./types.js";
|
|
3
|
+
const identifier = z.string().min(1).max(MAX_IDENTIFIER_LENGTH);
|
|
4
|
+
/**
|
|
5
|
+
* Payloads are strict objects: an unrecognized key fails the parse rather than
|
|
6
|
+
* being dropped. A field nobody declared is a field nobody bounded, and this
|
|
7
|
+
* body arrives from outside the process.
|
|
8
|
+
*/
|
|
9
|
+
const payloadByKind = {
|
|
10
|
+
auth_required: z
|
|
11
|
+
.object({ reason: z.string().max(MAX_LABEL_LENGTH).optional() })
|
|
12
|
+
.strict(),
|
|
13
|
+
chat_message: z
|
|
14
|
+
.object({
|
|
15
|
+
messageId: z.string().max(MAX_IDENTIFIER_LENGTH).optional(),
|
|
16
|
+
messageType: z.string().max(MAX_IDENTIFIER_LENGTH).optional(),
|
|
17
|
+
role: z.enum(CHAT_ROLES),
|
|
18
|
+
text: z.string().min(1).max(MAX_TEXT_LENGTH),
|
|
19
|
+
})
|
|
20
|
+
.strict(),
|
|
21
|
+
generation_state: z
|
|
22
|
+
.object({
|
|
23
|
+
hasError: z.boolean().optional(),
|
|
24
|
+
hasSuggestions: z.boolean().optional(),
|
|
25
|
+
state: z.enum(GENERATION_STATES),
|
|
26
|
+
})
|
|
27
|
+
.strict(),
|
|
28
|
+
progress: z
|
|
29
|
+
.object({ label: z.string().min(1).max(MAX_LABEL_LENGTH) })
|
|
30
|
+
.strict(),
|
|
31
|
+
session: z.object({ status: z.enum(SESSION_STATUSES) }).strict(),
|
|
32
|
+
};
|
|
33
|
+
const envelope = z
|
|
34
|
+
.object({
|
|
35
|
+
applicationId: identifier,
|
|
36
|
+
eventId: identifier,
|
|
37
|
+
gatewayTurnId: z.string().max(MAX_IDENTIFIER_LENGTH).optional(),
|
|
38
|
+
kind: z.enum(Object.keys(payloadByKind)),
|
|
39
|
+
liveEditId: identifier,
|
|
40
|
+
occurredAt: z.string().min(1),
|
|
41
|
+
organizationId: identifier,
|
|
42
|
+
payload: z.unknown(),
|
|
43
|
+
producer: z.enum(EVENT_PRODUCERS),
|
|
44
|
+
sequence: z.number().int().positive().max(MAX_SEQUENCE),
|
|
45
|
+
version: z.number().int(),
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
/**
|
|
49
|
+
* Validates one inbound event against the contract.
|
|
50
|
+
*
|
|
51
|
+
* SABS validates the same shape before pushing it, which is not a reason to
|
|
52
|
+
* skip it here: a validated producer is not a trusted one, and this is the
|
|
53
|
+
* process boundary where the bytes become Gateway's problem.
|
|
54
|
+
*/
|
|
55
|
+
export function parseLiveEditEvent(input) {
|
|
56
|
+
const parsed = envelope.safeParse(input);
|
|
57
|
+
if (!parsed.success) {
|
|
58
|
+
return { ok: false, reason: "malformed_event" };
|
|
59
|
+
}
|
|
60
|
+
// Version is checked before the payload so an event from a future contract
|
|
61
|
+
// is reported as such rather than as a malformed body.
|
|
62
|
+
if (parsed.data.version !== EVENT_CONTRACT_VERSION) {
|
|
63
|
+
return { ok: false, reason: "unsupported_version" };
|
|
64
|
+
}
|
|
65
|
+
const occurredAtMs = Date.parse(parsed.data.occurredAt);
|
|
66
|
+
if (Number.isNaN(occurredAtMs)) {
|
|
67
|
+
return { ok: false, reason: "malformed_event" };
|
|
68
|
+
}
|
|
69
|
+
const payload = payloadByKind[parsed.data.kind].safeParse(parsed.data.payload);
|
|
70
|
+
if (!payload.success) {
|
|
71
|
+
return { ok: false, reason: "malformed_event" };
|
|
72
|
+
}
|
|
73
|
+
const event = {
|
|
74
|
+
applicationId: parsed.data.applicationId,
|
|
75
|
+
eventId: parsed.data.eventId,
|
|
76
|
+
...(parsed.data.gatewayTurnId
|
|
77
|
+
? { gatewayTurnId: parsed.data.gatewayTurnId }
|
|
78
|
+
: {}),
|
|
79
|
+
kind: parsed.data.kind,
|
|
80
|
+
liveEditId: parsed.data.liveEditId,
|
|
81
|
+
occurredAtMs,
|
|
82
|
+
organizationId: parsed.data.organizationId,
|
|
83
|
+
payload: {
|
|
84
|
+
kind: parsed.data.kind,
|
|
85
|
+
...payload.data,
|
|
86
|
+
},
|
|
87
|
+
producer: parsed.data.producer,
|
|
88
|
+
sequence: parsed.data.sequence,
|
|
89
|
+
version: EVENT_CONTRACT_VERSION,
|
|
90
|
+
};
|
|
91
|
+
return { ok: true, event };
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/events/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type EventCursor } from "./cursor.js";
|
|
2
|
+
import type { EventProducer, EventSnapshot, LiveEditEvent } from "./types.js";
|
|
3
|
+
export type SnapshotReads = {
|
|
4
|
+
/**
|
|
5
|
+
* Lowest sequence this producer's stream still holds, or undefined when the
|
|
6
|
+
* stream is empty. This is the retention boundary a stale cursor fell off.
|
|
7
|
+
*/
|
|
8
|
+
firstRetainedSequence(producer: EventProducer): Promise<number | undefined>;
|
|
9
|
+
/** Strictly-after-sequence read, done by the store rather than by filtering. */
|
|
10
|
+
readAfter(input: {
|
|
11
|
+
afterSequence: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
producer: EventProducer;
|
|
14
|
+
}): Promise<LiveEditEvent[]>;
|
|
15
|
+
/** Newest `count` events of one producer's stream, oldest first. */
|
|
16
|
+
readTail(input: {
|
|
17
|
+
count: number;
|
|
18
|
+
producer: EventProducer;
|
|
19
|
+
}): Promise<LiveEditEvent[]>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Assembles a snapshot from a store's three reads.
|
|
23
|
+
*
|
|
24
|
+
* Shared rather than written twice so the memory and Redis stores cannot drift
|
|
25
|
+
* into disagreeing about what resuming means — the same reason both build
|
|
26
|
+
* their keys from one builder. Each store supplies only the reads; the cursor
|
|
27
|
+
* arithmetic and the staleness rule live here.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildSnapshot(input: {
|
|
30
|
+
cursor?: EventCursor;
|
|
31
|
+
limit?: number;
|
|
32
|
+
}, reads: SnapshotReads): Promise<EventSnapshot>;
|