@grackle-ai/core 0.75.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/dist/adapter-config.d.ts +6 -0
- package/dist/adapter-config.d.ts.map +1 -0
- package/dist/adapter-config.js +19 -0
- package/dist/adapter-config.js.map +1 -0
- package/dist/adapter-manager.d.ts +22 -0
- package/dist/adapter-manager.d.ts.map +1 -0
- package/dist/adapter-manager.js +81 -0
- package/dist/adapter-manager.js.map +1 -0
- package/dist/auto-reconnect.d.ts +23 -0
- package/dist/auto-reconnect.d.ts.map +1 -0
- package/dist/auto-reconnect.js +164 -0
- package/dist/auto-reconnect.js.map +1 -0
- package/dist/compute-task-status.d.ts +28 -0
- package/dist/compute-task-status.d.ts.map +1 -0
- package/dist/compute-task-status.js +70 -0
- package/dist/compute-task-status.js.map +1 -0
- package/dist/credential-bundle.d.ts +12 -0
- package/dist/credential-bundle.d.ts.map +1 -0
- package/dist/credential-bundle.js +183 -0
- package/dist/credential-bundle.js.map +1 -0
- package/dist/event-bus.d.ts +37 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +65 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/event-processor.d.ts +36 -0
- package/dist/event-processor.d.ts.map +1 -0
- package/dist/event-processor.js +312 -0
- package/dist/event-processor.js.map +1 -0
- package/dist/grpc-service.d.ts +22 -0
- package/dist/grpc-service.d.ts.map +1 -0
- package/dist/grpc-service.js +1724 -0
- package/dist/grpc-service.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge-init.d.ts +27 -0
- package/dist/knowledge-init.d.ts.map +1 -0
- package/dist/knowledge-init.js +212 -0
- package/dist/knowledge-init.js.map +1 -0
- package/dist/lifecycle.d.ts +36 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +112 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/log-writer.d.ts +32 -0
- package/dist/log-writer.d.ts.map +1 -0
- package/dist/log-writer.js +104 -0
- package/dist/log-writer.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +10 -0
- package/dist/logger.js.map +1 -0
- package/dist/pipe-delivery.d.ts +41 -0
- package/dist/pipe-delivery.d.ts.map +1 -0
- package/dist/pipe-delivery.js +186 -0
- package/dist/pipe-delivery.js.map +1 -0
- package/dist/processor-registry.d.ts +25 -0
- package/dist/processor-registry.d.ts.map +1 -0
- package/dist/processor-registry.js +58 -0
- package/dist/processor-registry.js.map +1 -0
- package/dist/reanimate-agent.d.ts +12 -0
- package/dist/reanimate-agent.d.ts.map +1 -0
- package/dist/reanimate-agent.js +76 -0
- package/dist/reanimate-agent.js.map +1 -0
- package/dist/session-recovery.d.ts +16 -0
- package/dist/session-recovery.d.ts.map +1 -0
- package/dist/session-recovery.js +129 -0
- package/dist/session-recovery.js.map +1 -0
- package/dist/signals/sigchld.d.ts +7 -0
- package/dist/signals/sigchld.d.ts.map +1 -0
- package/dist/signals/sigchld.js +167 -0
- package/dist/signals/sigchld.js.map +1 -0
- package/dist/signals/signal-delivery.d.ts +14 -0
- package/dist/signals/signal-delivery.d.ts.map +1 -0
- package/dist/signals/signal-delivery.js +166 -0
- package/dist/signals/signal-delivery.js.map +1 -0
- package/dist/stream-hub.d.ts +14 -0
- package/dist/stream-hub.d.ts.map +1 -0
- package/dist/stream-hub.js +95 -0
- package/dist/stream-hub.js.map +1 -0
- package/dist/stream-registry.d.ts +84 -0
- package/dist/stream-registry.d.ts.map +1 -0
- package/dist/stream-registry.js +363 -0
- package/dist/stream-registry.js.map +1 -0
- package/dist/test-utils/integration-setup.d.ts +11 -0
- package/dist/test-utils/integration-setup.d.ts.map +1 -0
- package/dist/test-utils/integration-setup.js +32 -0
- package/dist/test-utils/integration-setup.js.map +1 -0
- package/dist/test-utils/mock-database.d.ts +130 -0
- package/dist/test-utils/mock-database.d.ts.map +1 -0
- package/dist/test-utils/mock-database.js +147 -0
- package/dist/test-utils/mock-database.js.map +1 -0
- package/dist/token-push.d.ts +22 -0
- package/dist/token-push.d.ts.map +1 -0
- package/dist/token-push.js +78 -0
- package/dist/token-push.js.map +1 -0
- package/dist/transcript.d.ts +5 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/transcript.js +71 -0
- package/dist/transcript.js.map +1 -0
- package/dist/utils/exec.d.ts +17 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +21 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/format-gh-error.d.ts +6 -0
- package/dist/utils/format-gh-error.d.ts.map +1 -0
- package/dist/utils/format-gh-error.js +30 -0
- package/dist/utils/format-gh-error.js.map +1 -0
- package/dist/utils/network.d.ts +7 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +21 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/ports.d.ts +3 -0
- package/dist/utils/ports.d.ts.map +1 -0
- package/dist/utils/ports.js +19 -0
- package/dist/utils/ports.js.map +1 -0
- package/dist/utils/sleep.d.ts +3 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +5 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/ws-bridge.d.ts +30 -0
- package/dist/ws-bridge.d.ts.map +1 -0
- package/dist/ws-bridge.js +372 -0
- package/dist/ws-bridge.js.map +1 -0
- package/dist/ws-broadcast.d.ts +19 -0
- package/dist/ws-broadcast.d.ts.map +1 -0
- package/dist/ws-broadcast.js +60 -0
- package/dist/ws-broadcast.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { ConnectError, Code } from "@connectrpc/connect";
|
|
2
|
+
import { create } from "@bufbuild/protobuf";
|
|
3
|
+
import { grackle, powerline, eventTypeToEnum, SESSION_STATUS, LOGS_DIR, END_REASON } from "@grackle-ai/common";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { sessionStore, taskStore, grackleHome } from "@grackle-ai/database";
|
|
6
|
+
import * as logWriter from "./log-writer.js";
|
|
7
|
+
import { reanimateAgent } from "./reanimate-agent.js";
|
|
8
|
+
import { logger } from "./logger.js";
|
|
9
|
+
import { emit } from "./event-bus.js";
|
|
10
|
+
/** Set of environment IDs currently undergoing recovery to prevent concurrent attempts. */
|
|
11
|
+
const recoveringEnvironments = new Set();
|
|
12
|
+
/**
|
|
13
|
+
* Recover disconnected sessions for a newly reconnected environment.
|
|
14
|
+
*
|
|
15
|
+
* Finds sessions in SUSPENDED, RUNNING, or IDLE state (RUNNING/IDLE handles
|
|
16
|
+
* the "server died" scenario where sessions never got suspended). Drains
|
|
17
|
+
* buffered events from PowerLine, writes them to the session JSONL, then
|
|
18
|
+
* reanimates the first recoverable session. Remaining sessions are left
|
|
19
|
+
* SUSPENDED for later recovery (only one active session per environment).
|
|
20
|
+
*
|
|
21
|
+
* Fire-and-forget: logs errors but does not throw.
|
|
22
|
+
*/
|
|
23
|
+
export async function recoverSuspendedSessions(environmentId, connection) {
|
|
24
|
+
if (recoveringEnvironments.has(environmentId)) {
|
|
25
|
+
logger.warn({ environmentId }, "Recovery already in progress — skipping");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Find sessions that need recovery: SUSPENDED (normal path) plus
|
|
29
|
+
// RUNNING/IDLE (server-died path where sessions were never suspended).
|
|
30
|
+
const suspended = sessionStore.getSuspendedForEnv(environmentId);
|
|
31
|
+
const active = sessionStore.getActiveForEnv(environmentId);
|
|
32
|
+
// Transition any stale active session to SUSPENDED first so reanimate accepts it.
|
|
33
|
+
if (active) {
|
|
34
|
+
sessionStore.suspendSession(active.id);
|
|
35
|
+
suspended.unshift(active);
|
|
36
|
+
}
|
|
37
|
+
if (suspended.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
recoveringEnvironments.add(environmentId);
|
|
41
|
+
logger.info({ environmentId, count: suspended.length }, "Beginning recovery of suspended sessions");
|
|
42
|
+
try {
|
|
43
|
+
// Only reanimate the first session — the one-active-session-per-env
|
|
44
|
+
// constraint means subsequent sessions would fail. Leave the rest
|
|
45
|
+
// SUSPENDED for manual reanimate or future recovery.
|
|
46
|
+
const session = suspended[0];
|
|
47
|
+
try {
|
|
48
|
+
// Step 1: Drain buffered events from PowerLine and append to JSONL
|
|
49
|
+
const logPath = session.logPath || join(grackleHome, LOGS_DIR, session.id);
|
|
50
|
+
const drainReq = create(powerline.DrainRequestSchema, {
|
|
51
|
+
sessionId: session.id,
|
|
52
|
+
});
|
|
53
|
+
let drainedCount = 0;
|
|
54
|
+
try {
|
|
55
|
+
const drainStream = connection.client.drainBufferedEvents(drainReq);
|
|
56
|
+
logWriter.ensureLogInitialized(logPath);
|
|
57
|
+
for await (const event of drainStream) {
|
|
58
|
+
// Skip internal events (e.g. runtime_session_id) that would map
|
|
59
|
+
// to UNSPECIFIED — those are handled by processEventStream, not the drain.
|
|
60
|
+
const eventType = eventTypeToEnum(event.type);
|
|
61
|
+
if (eventType === grackle.EventType.UNSPECIFIED) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const sessionEvent = create(grackle.SessionEventSchema, {
|
|
65
|
+
sessionId: session.id,
|
|
66
|
+
type: eventType,
|
|
67
|
+
timestamp: event.timestamp,
|
|
68
|
+
content: event.content,
|
|
69
|
+
raw: event.raw,
|
|
70
|
+
});
|
|
71
|
+
logWriter.writeEvent(logPath, sessionEvent);
|
|
72
|
+
drainedCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (drainErr) {
|
|
76
|
+
// Drain may fail if PowerLine was restarted (no parked events).
|
|
77
|
+
// This is expected — continue to reanimate anyway.
|
|
78
|
+
logger.info({ sessionId: session.id, err: drainErr }, "Drain returned no buffered events (PowerLine may have restarted)");
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
// Always close the log stream to avoid leaking file descriptors.
|
|
82
|
+
logWriter.endSession(logPath);
|
|
83
|
+
}
|
|
84
|
+
if (drainedCount > 0) {
|
|
85
|
+
logger.info({ sessionId: session.id, drainedCount }, "Drained buffered events for suspended session");
|
|
86
|
+
}
|
|
87
|
+
// Re-check: a new session may have started during the async drain window
|
|
88
|
+
const currentActive = sessionStore.getActiveForEnv(environmentId);
|
|
89
|
+
if (currentActive) {
|
|
90
|
+
logger.info({ sessionId: session.id, activeSessionId: currentActive.id, environmentId }, "Skipping recovery — environment acquired a new active session during drain");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// Step 2: Reanimate the session (starts resume stream + processEventStream)
|
|
94
|
+
reanimateAgent(session.id);
|
|
95
|
+
logger.info({ sessionId: session.id }, "Successfully reanimated suspended session");
|
|
96
|
+
emitTaskUpdated(session.taskId);
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// If the environment acquired an active session between our check and
|
|
100
|
+
// reanimateAgent's check, this is a benign race — leave the session
|
|
101
|
+
// SUSPENDED for future recovery instead of marking it permanently failed.
|
|
102
|
+
if (err instanceof ConnectError && err.code === Code.FailedPrecondition
|
|
103
|
+
&& err.message.includes("already has active session")) {
|
|
104
|
+
logger.info({ sessionId: session.id, environmentId }, "Recovery skipped — environment already has an active session");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
logger.error({ sessionId: session.id, err }, "Failed to recover suspended session — marking stopped (interrupted)");
|
|
108
|
+
sessionStore.updateSession(session.id, SESSION_STATUS.STOPPED, undefined, `Recovery failed: ${String(err)}`, END_REASON.INTERRUPTED);
|
|
109
|
+
emitTaskUpdated(session.taskId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
recoveringEnvironments.delete(environmentId);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/** Emit a task.updated event with the correct workspaceId, if the session has a task. */
|
|
118
|
+
function emitTaskUpdated(taskId) {
|
|
119
|
+
if (!taskId) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const task = taskStore.getTask(taskId);
|
|
123
|
+
emit("task.updated", { taskId, workspaceId: task?.workspaceId || "" });
|
|
124
|
+
}
|
|
125
|
+
/** @internal Reset the recovery lock for testing. */
|
|
126
|
+
export function _resetForTesting() {
|
|
127
|
+
recoveringEnvironments.clear();
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=session-recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-recovery.js","sourceRoot":"","sources":["../src/session-recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigchld.d.ts","sourceRoot":"","sources":["../../src/signals/sigchld.ts"],"names":[],"mappings":"AAwCA,wBAAgB,qBAAqB,IAAI,IAAI,CA2B5C;AAyHD;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { SESSION_STATUS } from "@grackle-ai/common";
|
|
2
|
+
import { subscribe } from "../event-bus.js";
|
|
3
|
+
import { taskStore, sessionStore } from "@grackle-ai/database";
|
|
4
|
+
import { readLog } from "../log-writer.js";
|
|
5
|
+
import { deliverSignalToTask } from "./signal-delivery.js";
|
|
6
|
+
import { logger } from "../logger.js";
|
|
7
|
+
/** Maximum length for the child's last text message in the notification. */
|
|
8
|
+
const MAX_LAST_MESSAGE_LENGTH = 2000;
|
|
9
|
+
/**
|
|
10
|
+
* Session statuses that trigger SIGCHLD. LLM agents don't reliably exit() —
|
|
11
|
+
* they go IDLE when they stop working. STOPPED fires when the session's event
|
|
12
|
+
* stream ends (agent-initiated, not user-initiated — the user marking a task
|
|
13
|
+
* "Complete" emits task.completed which this subscriber does not listen to).
|
|
14
|
+
* Dedup prevents double notification if both IDLE and STOPPED fire for the
|
|
15
|
+
* same session.
|
|
16
|
+
*/
|
|
17
|
+
const SIGCHLD_STATUSES = new Set([
|
|
18
|
+
SESSION_STATUS.IDLE,
|
|
19
|
+
SESSION_STATUS.STOPPED,
|
|
20
|
+
]);
|
|
21
|
+
/** How long (ms) to remember a delivered notification before allowing re-delivery. */
|
|
22
|
+
const DEDUP_TTL_MS = 3_600_000; // 1 hour
|
|
23
|
+
/** Track delivered notifications to prevent duplicates: key → delivery timestamp. */
|
|
24
|
+
const delivered = new Map();
|
|
25
|
+
/** Human-readable status labels for the notification text (non-STOPPED statuses only). */
|
|
26
|
+
const STATUS_LABELS = {
|
|
27
|
+
[SESSION_STATUS.IDLE]: "finished working (awaiting review)",
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the SIGCHLD event-bus subscriber.
|
|
31
|
+
* Idempotent — safe to call multiple times.
|
|
32
|
+
*/
|
|
33
|
+
let initialized = false;
|
|
34
|
+
export function initSigchldSubscriber() {
|
|
35
|
+
if (initialized) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
initialized = true;
|
|
39
|
+
subscribe((event) => {
|
|
40
|
+
if (event.type !== "task.updated") {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const childTaskId = event.payload.taskId;
|
|
44
|
+
if (!childTaskId) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Fire-and-forget async handler — errors are logged, never thrown
|
|
48
|
+
(async () => {
|
|
49
|
+
try {
|
|
50
|
+
await handleTaskUpdated(childTaskId);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
logger.error({ err, childTaskId }, "SIGCHLD handler error");
|
|
54
|
+
}
|
|
55
|
+
})().catch(() => { });
|
|
56
|
+
});
|
|
57
|
+
logger.info("SIGCHLD subscriber initialized");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Handle a task.updated event: check if the task is a child whose latest
|
|
61
|
+
* session has reached a SIGCHLD-triggering status (idle or terminal),
|
|
62
|
+
* and if so, deliver a SIGCHLD notification to the parent.
|
|
63
|
+
*/
|
|
64
|
+
async function handleTaskUpdated(childTaskId) {
|
|
65
|
+
const childTask = taskStore.getTask(childTaskId);
|
|
66
|
+
if (!childTask) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Only child tasks (with a parent) trigger SIGCHLD
|
|
70
|
+
if (!childTask.parentTaskId) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Check if the child session is in a SIGCHLD-triggering status (idle or terminal)
|
|
74
|
+
const latestSession = sessionStore.getLatestSessionForTask(childTaskId);
|
|
75
|
+
if (!latestSession) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!SIGCHLD_STATUSES.has(latestSession.status)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Idempotency: don't re-deliver for the same child+session pair.
|
|
82
|
+
// Set the key optimistically to prevent concurrent async handlers from
|
|
83
|
+
// both passing the check (e.g. completed + interrupted firing together).
|
|
84
|
+
const dedupeKey = `${childTaskId}:${latestSession.id}`;
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const previousDelivery = delivered.get(dedupeKey);
|
|
87
|
+
if (previousDelivery !== undefined && now - previousDelivery < DEDUP_TTL_MS) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
delivered.set(dedupeKey, now);
|
|
91
|
+
// Prune expired entries to prevent unbounded growth
|
|
92
|
+
pruneDelivered(now);
|
|
93
|
+
// Extract the last text message from the child's session log
|
|
94
|
+
const lastTextMessage = extractLastTextMessage(latestSession.logPath || undefined);
|
|
95
|
+
// Format the notification with actionable instructions for the parent
|
|
96
|
+
let statusLabel;
|
|
97
|
+
if (latestSession.status === SESSION_STATUS.STOPPED) {
|
|
98
|
+
statusLabel = latestSession.endReason === "completed"
|
|
99
|
+
? "completed successfully"
|
|
100
|
+
: latestSession.endReason === "killed"
|
|
101
|
+
? "was killed"
|
|
102
|
+
: "crashed unexpectedly";
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
statusLabel = STATUS_LABELS[latestSession.status] || latestSession.status;
|
|
106
|
+
}
|
|
107
|
+
let message = `[SIGCHLD] Child task "${childTask.title}" (${childTaskId}) ${statusLabel}.`;
|
|
108
|
+
if (lastTextMessage) {
|
|
109
|
+
const truncated = lastTextMessage.length > MAX_LAST_MESSAGE_LENGTH
|
|
110
|
+
? lastTextMessage.slice(0, MAX_LAST_MESSAGE_LENGTH) + "..."
|
|
111
|
+
: lastTextMessage;
|
|
112
|
+
message += `\n\nLast message from child:\n> ${truncated}`;
|
|
113
|
+
}
|
|
114
|
+
if (latestSession.status === SESSION_STATUS.IDLE) {
|
|
115
|
+
message += "\n\nReview the child's work. If satisfactory, mark it complete with "
|
|
116
|
+
+ `task_complete({ taskId: "${childTaskId}" }). `
|
|
117
|
+
+ "If more work is needed, send additional input to the child's session.";
|
|
118
|
+
}
|
|
119
|
+
else if (latestSession.status === SESSION_STATUS.STOPPED && latestSession.endReason === "interrupted") {
|
|
120
|
+
message += "\n\nThe child task crashed unexpectedly. Review the error and decide whether to retry or reassign the work.";
|
|
121
|
+
}
|
|
122
|
+
logger.info({ childTaskId, parentTaskId: childTask.parentTaskId, status: latestSession.status }, "Delivering SIGCHLD to parent task");
|
|
123
|
+
const success = await deliverSignalToTask(childTask.parentTaskId, "sigchld", message);
|
|
124
|
+
// On failure, remove the optimistic dedup key so the next event can retry
|
|
125
|
+
if (!success) {
|
|
126
|
+
delivered.delete(dedupeKey);
|
|
127
|
+
logger.warn({ childTaskId, parentTaskId: childTask.parentTaskId }, "SIGCHLD delivery failed — will retry on next task.updated event");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/** Remove dedup entries older than DEDUP_TTL_MS. */
|
|
131
|
+
function pruneDelivered(now) {
|
|
132
|
+
for (const [key, timestamp] of delivered) {
|
|
133
|
+
if (now - timestamp >= DEDUP_TTL_MS) {
|
|
134
|
+
delivered.delete(key);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Read the session log and extract the content of the last "text" entry.
|
|
140
|
+
* Returns an empty string if no text entries exist or the log cannot be read.
|
|
141
|
+
*/
|
|
142
|
+
function extractLastTextMessage(logPath) {
|
|
143
|
+
if (!logPath) {
|
|
144
|
+
return "";
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const entries = readLog(logPath);
|
|
148
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
149
|
+
if (entries[i].type === "text") {
|
|
150
|
+
return entries[i].content;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return "";
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return "";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Reset module state. For use in tests only.
|
|
161
|
+
* @internal
|
|
162
|
+
*/
|
|
163
|
+
export function _resetForTesting() {
|
|
164
|
+
delivered.clear();
|
|
165
|
+
initialized = false;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=sigchld.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigchld.js","sourceRoot":"","sources":["../../src/signals/sigchld.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAqB,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,4EAA4E;AAC5E,MAAM,uBAAuB,GAAW,IAAI,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC;IACpD,cAAc,CAAC,IAAI;IACnB,cAAc,CAAC,OAAO;CACvB,CAAC,CAAC;AAEH,sFAAsF;AACtF,MAAM,YAAY,GAAW,SAAS,CAAC,CAAC,SAAS;AAEjD,qFAAqF;AACrF,MAAM,SAAS,GAAwB,IAAI,GAAG,EAAE,CAAC;AAEjD,0FAA0F;AAC1F,MAAM,aAAa,GAA2B;IAC5C,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,oCAAoC;CAC5D,CAAC;AAEF;;;GAGG;AACH,IAAI,WAAW,GAAY,KAAK,CAAC;AAEjC,MAAM,UAAU,qBAAqB;IACnC,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO;IACT,CAAC;IACD,WAAW,GAAG,IAAI,CAAC;IAEnB,SAAS,CAAC,CAAC,KAAmB,EAAE,EAAE;QAChC,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YAClC,OAAO;QACT,CAAC;QAED,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,MAA4B,CAAC;QAC/D,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QAED,kEAAkE;QAClE,CAAC,KAAK,IAAI,EAAE;YACV,IAAI,CAAC;gBACH,MAAM,iBAAiB,CAAC,WAAW,CAAC,CAAC;YACvC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAkC,CAAC,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;AAChD,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,iBAAiB,CAAC,WAAmB;IAClD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACjD,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;IACT,CAAC;IAED,mDAAmD;IACnD,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,kFAAkF;IAClF,MAAM,aAAa,GAAG,YAAY,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;IACxE,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAChD,OAAO;IACT,CAAC;IAED,iEAAiE;IACjE,uEAAuE;IACvE,yEAAyE;IACzE,MAAM,SAAS,GAAG,GAAG,WAAW,IAAI,aAAa,CAAC,EAAE,EAAE,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,gBAAgB,GAAG,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAClD,IAAI,gBAAgB,KAAK,SAAS,IAAI,GAAG,GAAG,gBAAgB,GAAG,YAAY,EAAE,CAAC;QAC5E,OAAO;IACT,CAAC;IACD,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAE9B,oDAAoD;IACpD,cAAc,CAAC,GAAG,CAAC,CAAC;IAEpB,6DAA6D;IAC7D,MAAM,eAAe,GAAG,sBAAsB,CAAC,aAAa,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC;IAEnF,sEAAsE;IACtE,IAAI,WAAmB,CAAC;IACxB,IAAI,aAAa,CAAC,MAAM,KAAK,cAAc,CAAC,OAAO,EAAE,CAAC;QACpD,WAAW,GAAG,aAAa,CAAC,SAAS,KAAK,WAAW;YACnD,CAAC,CAAC,wBAAwB;YAC1B,CAAC,CAAC,aAAa,CAAC,SAAS,KAAK,QAAQ;gBACpC,CAAC,CAAC,YAAY;gBACd,CAAC,CAAC,sBAAsB,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,WAAW,GAAG,aAAa,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC;IAC5E,CAAC;IACD,IAAI,OAAO,GAAG,yBAAyB,SAAS,CAAC,KAAK,MAAM,WAAW,KAAK,WAAW,GAAG,CAAC;IAE3F,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,GAAG,uBAAuB;YAChE,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,GAAG,KAAK;YAC3D,CAAC,CAAC,eAAe,CAAC;QACpB,OAAO,IAAI,mCAAmC,SAAS,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,EAAE,CAAC;QACjD,OAAO,IAAI,sEAAsE;cAC7E,4BAA4B,WAAW,QAAQ;cAC/C,uEAAuE,CAAC;IAC9E,CAAC;SAAM,IAAI,aAAa,CAAC,MAAM,KAAK,cAAc,CAAC,OAAO,IAAI,aAAa,CAAC,SAAS,KAAK,aAAa,EAAE,CAAC;QACxG,OAAO,IAAI,6GAA6G,CAAC;IAC3H,CAAC;IAED,MAAM,CAAC,IAAI,CACT,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,EAAE,EACnF,mCAAmC,CACpC,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,SAAS,CAAC,YAAY,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAEtF,0EAA0E;IAC1E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC5B,MAAM,CAAC,IAAI,CACT,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,CAAC,YAAY,EAAE,EACrD,iEAAiE,CAClE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,oDAAoD;AACpD,SAAS,cAAc,CAAC,GAAW;IACjC,KAAK,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,SAAS,EAAE,CAAC;QACzC,IAAI,GAAG,GAAG,SAAS,IAAI,YAAY,EAAE,CAAC;YACpC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,sBAAsB,CAAC,OAA2B;IACzD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC/B,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,SAAS,CAAC,KAAK,EAAE,CAAC;IAClB,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deliver a signal message to a task's agent session.
|
|
3
|
+
*
|
|
4
|
+
* Active session (IDLE/RUNNING/PENDING) → sendInput bypassing the IDLE guard.
|
|
5
|
+
* Dead session (COMPLETED/FAILED/INTERRUPTED) → reanimate + wait for IDLE + sendInput.
|
|
6
|
+
* No session at all → log warning, return false.
|
|
7
|
+
*
|
|
8
|
+
* @param taskId - Target task whose session should receive the signal.
|
|
9
|
+
* @param signalType - Signal kind for logging (e.g. "sigchld").
|
|
10
|
+
* @param message - The text content to deliver as user input.
|
|
11
|
+
* @returns true if the message was sent, false otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function deliverSignalToTask(taskId: string, signalType: string, message: string): Promise<boolean>;
|
|
14
|
+
//# sourceMappingURL=signal-delivery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-delivery.d.ts","sourceRoot":"","sources":["../../src/signals/signal-delivery.ts"],"names":[],"mappings":"AAmBA;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAuDlB"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { create } from "@bufbuild/protobuf";
|
|
2
|
+
import { grackle, powerline, SESSION_STATUS } from "@grackle-ai/common";
|
|
3
|
+
import { sessionStore } from "@grackle-ai/database";
|
|
4
|
+
import * as adapterManager from "../adapter-manager.js";
|
|
5
|
+
import { reanimateAgent } from "../reanimate-agent.js";
|
|
6
|
+
import * as streamHub from "../stream-hub.js";
|
|
7
|
+
import * as logWriter from "../log-writer.js";
|
|
8
|
+
import { logger } from "../logger.js";
|
|
9
|
+
/** Timeout (ms) to wait for a reanimated session to reach IDLE. */
|
|
10
|
+
const REANIMATE_IDLE_TIMEOUT_MS = 60_000;
|
|
11
|
+
/** Statuses considered active (the agent can accept input). */
|
|
12
|
+
const ACTIVE_STATUSES = new Set([
|
|
13
|
+
SESSION_STATUS.IDLE,
|
|
14
|
+
SESSION_STATUS.RUNNING,
|
|
15
|
+
SESSION_STATUS.PENDING,
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Deliver a signal message to a task's agent session.
|
|
19
|
+
*
|
|
20
|
+
* Active session (IDLE/RUNNING/PENDING) → sendInput bypassing the IDLE guard.
|
|
21
|
+
* Dead session (COMPLETED/FAILED/INTERRUPTED) → reanimate + wait for IDLE + sendInput.
|
|
22
|
+
* No session at all → log warning, return false.
|
|
23
|
+
*
|
|
24
|
+
* @param taskId - Target task whose session should receive the signal.
|
|
25
|
+
* @param signalType - Signal kind for logging (e.g. "sigchld").
|
|
26
|
+
* @param message - The text content to deliver as user input.
|
|
27
|
+
* @returns true if the message was sent, false otherwise.
|
|
28
|
+
*/
|
|
29
|
+
export async function deliverSignalToTask(taskId, signalType, message) {
|
|
30
|
+
// ── 1. Try an active session ──────────────────────────────
|
|
31
|
+
const activeSessions = sessionStore.getActiveSessionsForTask(taskId);
|
|
32
|
+
if (activeSessions.length > 0) {
|
|
33
|
+
const session = activeSessions[0];
|
|
34
|
+
return await sendInputToSession(session.id, session.environmentId, message, signalType);
|
|
35
|
+
}
|
|
36
|
+
// ── 2. Try reanimating the latest terminal session ────────
|
|
37
|
+
const latest = sessionStore.getLatestSessionForTask(taskId);
|
|
38
|
+
if (!latest) {
|
|
39
|
+
logger.warn({ taskId, signalType }, "No session exists for task — signal dropped");
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
if (ACTIVE_STATUSES.has(latest.status)) {
|
|
43
|
+
// Should not happen (getActiveSessionsForTask would have found it),
|
|
44
|
+
// but handle it defensively.
|
|
45
|
+
return await sendInputToSession(latest.id, latest.environmentId, message, signalType);
|
|
46
|
+
}
|
|
47
|
+
if (!latest.runtimeSessionId) {
|
|
48
|
+
logger.warn({ taskId, sessionId: latest.id, signalType }, "Latest session has no runtimeSessionId — cannot reanimate, signal dropped");
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
// Subscribe to the session stream BEFORE reanimating so we don't miss
|
|
52
|
+
// the waiting_input event if the runtime reaches IDLE quickly (e.g. stub).
|
|
53
|
+
const idleWaiter = waitForSessionIdle(latest.id, REANIMATE_IDLE_TIMEOUT_MS);
|
|
54
|
+
try {
|
|
55
|
+
reanimateAgent(latest.id);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
idleWaiter.cancel();
|
|
59
|
+
logger.error({ err, taskId, sessionId: latest.id, signalType }, "Failed to reanimate session for signal delivery");
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// Wait for the reanimated session to reach IDLE
|
|
63
|
+
const reachedIdle = await idleWaiter.promise;
|
|
64
|
+
if (!reachedIdle) {
|
|
65
|
+
logger.error({ taskId, sessionId: latest.id, signalType }, "Reanimated session did not reach IDLE within timeout — signal dropped");
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return await sendInputToSession(latest.id, latest.environmentId, message, signalType);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Send input text to a session via its environment's PowerLine connection.
|
|
72
|
+
* Bypasses the server-side IDLE guard — the agent runtime accepts input at any
|
|
73
|
+
* time and picks it up at the next turn boundary.
|
|
74
|
+
*/
|
|
75
|
+
async function sendInputToSession(sessionId, environmentId, text, signalType) {
|
|
76
|
+
const conn = adapterManager.getConnection(environmentId);
|
|
77
|
+
if (!conn) {
|
|
78
|
+
logger.error({ sessionId, environmentId, signalType }, "Environment not connected — signal delivery failed");
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
// Record the signal as a SIGNAL event in the session log and stream.
|
|
83
|
+
const session = sessionStore.getSession(sessionId);
|
|
84
|
+
const signalEvent = create(grackle.SessionEventSchema, {
|
|
85
|
+
sessionId,
|
|
86
|
+
type: grackle.EventType.SIGNAL,
|
|
87
|
+
timestamp: new Date().toISOString(),
|
|
88
|
+
content: text,
|
|
89
|
+
});
|
|
90
|
+
if (session?.logPath) {
|
|
91
|
+
// Ensure the stream is open before writing — the session may still be
|
|
92
|
+
// PENDING and processEventStream may not have called initLog yet.
|
|
93
|
+
logWriter.ensureLogInitialized(session.logPath);
|
|
94
|
+
logWriter.writeEvent(session.logPath, signalEvent);
|
|
95
|
+
}
|
|
96
|
+
streamHub.publish(signalEvent);
|
|
97
|
+
await conn.client.sendInput(create(powerline.InputMessageSchema, { sessionId, text }));
|
|
98
|
+
logger.info({ sessionId, signalType }, "Signal delivered to session");
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
logger.error({ err, sessionId, signalType }, "sendInput failed during signal delivery");
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Begin watching for a session to reach IDLE status via StreamHub events.
|
|
108
|
+
* Subscribes immediately so events are captured even before the caller starts
|
|
109
|
+
* the session (e.g. via reanimateAgent).
|
|
110
|
+
*/
|
|
111
|
+
function waitForSessionIdle(sessionId, timeoutMs) {
|
|
112
|
+
const stream = streamHub.createStream(sessionId);
|
|
113
|
+
let timer;
|
|
114
|
+
const promise = (async () => {
|
|
115
|
+
// Check DB after subscribing to close the race window.
|
|
116
|
+
const current = sessionStore.getSession(sessionId);
|
|
117
|
+
if (current?.status === SESSION_STATUS.IDLE) {
|
|
118
|
+
stream.cancel();
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
return await Promise.race([
|
|
123
|
+
(async () => {
|
|
124
|
+
for await (const event of stream) {
|
|
125
|
+
// Only inspect status events — other event types (text, tool_use, etc.)
|
|
126
|
+
// can have arbitrary content that might accidentally match status strings.
|
|
127
|
+
if (event.type !== grackle.EventType.STATUS) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
// "waiting_input" is the runtime status that maps to IDLE in the session store.
|
|
131
|
+
if (event.content === "waiting_input") {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// If the session hit a terminal state, stop waiting
|
|
135
|
+
if (["completed", "failed", "killed", "interrupted"].includes(event.content)) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
})(),
|
|
141
|
+
new Promise((resolve) => {
|
|
142
|
+
timer = setTimeout(() => {
|
|
143
|
+
stream.cancel();
|
|
144
|
+
resolve(false);
|
|
145
|
+
}, timeoutMs);
|
|
146
|
+
}),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
if (timer !== undefined) {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
stream.cancel();
|
|
154
|
+
}
|
|
155
|
+
})();
|
|
156
|
+
return {
|
|
157
|
+
promise,
|
|
158
|
+
cancel: () => {
|
|
159
|
+
stream.cancel();
|
|
160
|
+
if (timer !== undefined) {
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=signal-delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-delivery.js","sourceRoot":"","sources":["../../src/signals/signal-delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { grackle } from "@grackle-ai/common";
|
|
2
|
+
type SessionEvent = grackle.SessionEvent;
|
|
3
|
+
/** Broadcast a session event to all session-specific and global subscribers. */
|
|
4
|
+
export declare function publish(event: SessionEvent): void;
|
|
5
|
+
/** Create a cancellable async iterable that yields events for a specific session. */
|
|
6
|
+
export declare function createStream(sessionId: string): AsyncIterable<SessionEvent> & {
|
|
7
|
+
cancel(): void;
|
|
8
|
+
};
|
|
9
|
+
/** Create a cancellable async iterable that yields events from all sessions. */
|
|
10
|
+
export declare function createGlobalStream(): AsyncIterable<SessionEvent> & {
|
|
11
|
+
cancel(): void;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=stream-hub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-hub.d.ts","sourceRoot":"","sources":["../src/stream-hub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,KAAK,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;AAMzC,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAQjD;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,YAAY,CAAC,GAAG;IAAE,MAAM,IAAI,IAAI,CAAA;CAAE,CA4ChG;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI,aAAa,CAAC,YAAY,CAAC,GAAG;IAAE,MAAM,IAAI,IAAI,CAAA;CAAE,CAqCrF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const sessionSubs = new Map();
|
|
2
|
+
const globalSubs = new Set();
|
|
3
|
+
/** Broadcast a session event to all session-specific and global subscribers. */
|
|
4
|
+
export function publish(event) {
|
|
5
|
+
// Notify session-specific subscribers
|
|
6
|
+
const subs = sessionSubs.get(event.sessionId);
|
|
7
|
+
if (subs) {
|
|
8
|
+
for (const sub of subs)
|
|
9
|
+
sub(event);
|
|
10
|
+
}
|
|
11
|
+
// Notify global subscribers
|
|
12
|
+
for (const sub of globalSubs)
|
|
13
|
+
sub(event);
|
|
14
|
+
}
|
|
15
|
+
/** Create a cancellable async iterable that yields events for a specific session. */
|
|
16
|
+
export function createStream(sessionId) {
|
|
17
|
+
const queue = [];
|
|
18
|
+
let waiting = undefined;
|
|
19
|
+
const state = { done: false };
|
|
20
|
+
const subscriber = (event) => {
|
|
21
|
+
queue.push(event);
|
|
22
|
+
if (waiting) {
|
|
23
|
+
waiting();
|
|
24
|
+
waiting = undefined;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
// Subscribe
|
|
28
|
+
let subs = sessionSubs.get(sessionId);
|
|
29
|
+
if (!subs) {
|
|
30
|
+
subs = new Set();
|
|
31
|
+
sessionSubs.set(sessionId, subs);
|
|
32
|
+
}
|
|
33
|
+
subs.add(subscriber);
|
|
34
|
+
const stream = {
|
|
35
|
+
cancel() {
|
|
36
|
+
state.done = true;
|
|
37
|
+
subs.delete(subscriber);
|
|
38
|
+
if (subs.size === 0)
|
|
39
|
+
sessionSubs.delete(sessionId);
|
|
40
|
+
if (waiting)
|
|
41
|
+
waiting();
|
|
42
|
+
},
|
|
43
|
+
[Symbol.asyncIterator]() {
|
|
44
|
+
return {
|
|
45
|
+
async next() {
|
|
46
|
+
while (queue.length === 0 && !state.done) {
|
|
47
|
+
await new Promise((resolve) => { waiting = resolve; });
|
|
48
|
+
}
|
|
49
|
+
if (queue.length > 0) {
|
|
50
|
+
return { value: queue.shift(), done: false };
|
|
51
|
+
}
|
|
52
|
+
return { value: undefined, done: true };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
return stream;
|
|
58
|
+
}
|
|
59
|
+
/** Create a cancellable async iterable that yields events from all sessions. */
|
|
60
|
+
export function createGlobalStream() {
|
|
61
|
+
const queue = [];
|
|
62
|
+
let waiting = undefined;
|
|
63
|
+
const state = { done: false };
|
|
64
|
+
const subscriber = (event) => {
|
|
65
|
+
queue.push(event);
|
|
66
|
+
if (waiting) {
|
|
67
|
+
waiting();
|
|
68
|
+
waiting = undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
globalSubs.add(subscriber);
|
|
72
|
+
const stream = {
|
|
73
|
+
cancel() {
|
|
74
|
+
state.done = true;
|
|
75
|
+
globalSubs.delete(subscriber);
|
|
76
|
+
if (waiting)
|
|
77
|
+
waiting();
|
|
78
|
+
},
|
|
79
|
+
[Symbol.asyncIterator]() {
|
|
80
|
+
return {
|
|
81
|
+
async next() {
|
|
82
|
+
while (queue.length === 0 && !state.done) {
|
|
83
|
+
await new Promise((resolve) => { waiting = resolve; });
|
|
84
|
+
}
|
|
85
|
+
if (queue.length > 0) {
|
|
86
|
+
return { value: queue.shift(), done: false };
|
|
87
|
+
}
|
|
88
|
+
return { value: undefined, done: true };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
return stream;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=stream-hub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-hub.js","sourceRoot":"","sources":["../src/stream-hub.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,GAAiC,IAAI,GAAG,EAA2B,CAAC;AACrF,MAAM,UAAU,GAAoB,IAAI,GAAG,EAAc,CAAC;AAE1D,gFAAgF;AAChF,MAAM,UAAU,OAAO,CAAC,KAAmB;IACzC,sCAAsC;IACtC,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,IAAI,EAAE,CAAC;QACT,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IACD,4BAA4B;IAC5B,KAAK,MAAM,GAAG,IAAI,UAAU;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,IAAI,OAAO,GAA6B,SAAS,CAAC;IAClD,MAAM,KAAK,GAAsB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAEjD,MAAM,UAAU,GAAe,CAAC,KAAmB,EAAE,EAAE;QACrD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,YAAY;IACZ,IAAI,IAAI,GAAgC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;QACjB,WAAW,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAErB,MAAM,MAAM,GAAqD;QAC/D,MAAM;YACJ,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;YAClB,IAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACzB,IAAI,IAAK,CAAC,IAAI,KAAK,CAAC;gBAAE,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACpD,IAAI,OAAO;gBAAE,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,CAAC,MAAM,CAAC,aAAa,CAAC;YACpB,OAAO;gBACL,KAAK,CAAC,IAAI;oBACR,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;wBACzC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAmB,EAAE,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3E,CAAC;oBACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACrB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;oBAChD,CAAC;oBACD,OAAO,EAAE,KAAK,EAAE,SAAoC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACrE,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,IAAI,OAAO,GAA6B,SAAS,CAAC;IAClD,MAAM,KAAK,GAAsB,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAEjD,MAAM,UAAU,GAAe,CAAC,KAAmB,EAAE,EAAE;QACrD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,EAAE,CAAC;YACV,OAAO,GAAG,SAAS,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,UAAU,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAqD;QAC/D,MAAM;YACJ,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;YAClB,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,OAAO;gBAAE,OAAO,EAAE,CAAC;QACzB,CAAC;QACD,CAAC,MAAM,CAAC,aAAa,CAAC;YACpB,OAAO;gBACL,KAAK,CAAC,IAAI;oBACR,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;wBACzC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAmB,EAAE,EAAE,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC3E,CAAC;oBACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACrB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;oBAChD,CAAC;oBACD,OAAO,EAAE,KAAK,EAAE,SAAoC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACrE,CAAC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC"}
|