@bastani/atomic 0.9.16-alpha.6 → 0.9.16-alpha.8
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/CHANGELOG.md +19 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/README.md +4 -4
- package/dist/builtin/intercom/broker/broker.ts +617 -22
- package/dist/builtin/intercom/broker/client-message-validation.ts +9 -0
- package/dist/builtin/intercom/broker/client.ts +200 -10
- package/dist/builtin/intercom/broker/pending-send-registry.ts +4 -0
- package/dist/builtin/intercom/broker/send-handler.ts +90 -36
- package/dist/builtin/intercom/broker/send-signature.ts +20 -8
- package/dist/builtin/intercom/group.ts +85 -0
- package/dist/builtin/intercom/index.bundle.mjs +680 -53
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/session-target.ts +54 -0
- package/dist/builtin/intercom/skills/intercom/SKILL.md +18 -3
- package/dist/builtin/intercom/source-ownership.ts +31 -0
- package/dist/builtin/intercom/types.ts +115 -0
- package/dist/builtin/mcp/index.bundle.mjs +109 -35
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/qlty/SKILL.md +123 -0
- package/dist/builtin/subagents/skills/qlty/references/coding-with-ai-agents.md +64 -0
- package/dist/builtin/subagents/skills/qlty/references/commands.md +291 -0
- package/dist/builtin/subagents/skills/qlty/references/plugins-and-extensions.md +228 -0
- package/dist/builtin/subagents/skills/qlty/references/quickstart.md +110 -0
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +173 -51
- package/dist/builtin/web-access/index.bundle.mjs +109 -35
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +23 -0
- package/dist/builtin/workflows/README.md +40 -37
- package/dist/builtin/workflows/builtin/adversarial-verification.js +13 -0
- package/dist/builtin/workflows/builtin/{shared-prompts.ts → chunk-0x6e303p.js} +137 -133
- package/dist/builtin/workflows/builtin/chunk-29wrp38a.js +199 -0
- package/dist/builtin/workflows/builtin/chunk-2dqb5s2q.js +602 -0
- package/dist/builtin/workflows/builtin/chunk-4febxsv4.js +264 -0
- package/dist/builtin/workflows/builtin/chunk-5n10x7j2.js +69730 -0
- package/dist/builtin/workflows/builtin/chunk-5wgwscd0.js +4 -0
- package/dist/builtin/workflows/builtin/chunk-6fqs7c01.js +155 -0
- package/dist/builtin/workflows/builtin/chunk-7430zyas.js +126 -0
- package/dist/builtin/workflows/builtin/chunk-7at6dnkr.js +33 -0
- package/dist/builtin/workflows/builtin/chunk-82ha8p41.js +175 -0
- package/dist/builtin/workflows/builtin/chunk-bfkmzv9h.js +156 -0
- package/dist/builtin/workflows/builtin/chunk-c53y8bdh.js +345 -0
- package/dist/builtin/workflows/builtin/chunk-cdtd3m3w.js +529 -0
- package/dist/builtin/workflows/builtin/chunk-cg9tmks0.js +206 -0
- package/dist/builtin/workflows/builtin/chunk-hdpj1dkw.js +2447 -0
- package/dist/builtin/workflows/builtin/chunk-hzzn6adg.js +221 -0
- package/dist/builtin/workflows/builtin/chunk-nqr34qp3.js +1061 -0
- package/dist/builtin/workflows/builtin/chunk-qwzvgxnq.js +404 -0
- package/dist/builtin/workflows/builtin/chunk-qx2ptjs3.js +215 -0
- package/dist/builtin/workflows/builtin/chunk-v04c5qmd.js +1207 -0
- package/dist/builtin/workflows/builtin/chunk-wpckd35c.js +12 -0
- package/dist/builtin/workflows/builtin/chunk-x2ghn0je.js +4110 -0
- package/dist/builtin/workflows/builtin/chunk-zf28603f.js +1080 -0
- package/dist/builtin/workflows/builtin/classify-and-act.js +12 -0
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.js +10 -0
- package/dist/builtin/workflows/builtin/generate-and-filter.js +12 -0
- package/dist/builtin/workflows/builtin/goal.js +16 -0
- package/dist/builtin/workflows/builtin/index.js +49 -0
- package/dist/builtin/workflows/builtin/loop-until-done.js +12 -0
- package/dist/builtin/workflows/builtin/open-claude-design.js +11 -0
- package/dist/builtin/workflows/builtin/ralph.js +16 -0
- package/dist/builtin/workflows/builtin/steering-context.d.ts +6 -0
- package/dist/builtin/workflows/builtin/steering-context.js +7 -0
- package/dist/builtin/workflows/builtin/tournament.js +13 -0
- package/dist/builtin/workflows/package.json +10 -7
- package/dist/builtin/workflows/src/authoring/typebox-defaults.d.ts +41 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1250 -428
- package/dist/builtin/workflows/src/{index.bundle.mjs → index.js} +956 -427
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +403 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +327 -0
- package/dist/builtin/workflows/src/shared/budget.d.ts +67 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +60 -0
- package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.d.ts +21 -0
- package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
- package/dist/core/agent-session-custom-message-commit.js +12 -5
- package/dist/core/agent-session-custom-message-commit.js.map +1 -1
- package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
- package/dist/core/agent-session-persistent-custom-messages.js +2 -0
- package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +2 -0
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +4 -0
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/builtin-install-layout.d.ts +1 -1
- package/dist/core/builtin-install-layout.d.ts.map +1 -1
- package/dist/core/builtin-install-layout.js +1 -1
- package/dist/core/builtin-install-layout.js.map +1 -1
- package/dist/core/codex-fast-mode.d.ts +10 -7
- package/dist/core/codex-fast-mode.d.ts.map +1 -1
- package/dist/core/codex-fast-mode.js +31 -13
- package/dist/core/codex-fast-mode.js.map +1 -1
- package/dist/core/extensions/context-types.d.ts +35 -0
- package/dist/core/extensions/context-types.d.ts.map +1 -1
- package/dist/core/extensions/context-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +6 -1
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +4 -0
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +1 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -1
- package/dist/core/runtime-credentials.js +8 -0
- package/dist/core/runtime-credentials.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +2 -0
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +4 -2
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/workflow-stage-admission.d.ts +6 -1
- package/dist/core/workflow-stage-admission.d.ts.map +1 -1
- package/dist/core/workflow-stage-admission.js +8 -9
- package/dist/core/workflow-stage-admission.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js +1 -1
- package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +1 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts +4 -0
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +69 -10
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/rpc/rpc-client-api.d.ts +18 -2
- package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-api.js +33 -5
- package/dist/modes/rpc/rpc-client-api.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +8 -4
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +13 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/intercom.md +15 -6
- package/docs/packages.md +2 -2
- package/docs/providers.md +8 -3
- package/docs/quickstart.md +3 -2
- package/docs/rpc.md +21 -4
- package/docs/settings.md +6 -4
- package/docs/skills.md +4 -0
- package/docs/subagents.md +1 -1
- package/docs/usage.md +1 -1
- package/docs/workflows.md +112 -239
- package/npm-shrinkwrap.json +32 -32
- package/package.json +9 -13
- package/dist/builtin/workflows/ambient.d.ts +0 -61
- package/dist/builtin/workflows/builtin/adversarial-verification-prompts.ts +0 -26
- package/dist/builtin/workflows/builtin/adversarial-verification-runner.ts +0 -391
- package/dist/builtin/workflows/builtin/adversarial-verification.ts +0 -34
- package/dist/builtin/workflows/builtin/classify-and-act-prompts.ts +0 -14
- package/dist/builtin/workflows/builtin/classify-and-act-runner.ts +0 -108
- package/dist/builtin/workflows/builtin/classify-and-act.ts +0 -40
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize-prompts.ts +0 -14
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize-runner.ts +0 -96
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +0 -37
- package/dist/builtin/workflows/builtin/generate-and-filter-prompts.ts +0 -41
- package/dist/builtin/workflows/builtin/generate-and-filter-runner.ts +0 -90
- package/dist/builtin/workflows/builtin/generate-and-filter.ts +0 -31
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +0 -64
- package/dist/builtin/workflows/builtin/goal-convergence.ts +0 -87
- package/dist/builtin/workflows/builtin/goal-ledger.ts +0 -139
- package/dist/builtin/workflows/builtin/goal-models.ts +0 -72
- package/dist/builtin/workflows/builtin/goal-orchestrator-prompts.ts +0 -94
- package/dist/builtin/workflows/builtin/goal-prompts.ts +0 -211
- package/dist/builtin/workflows/builtin/goal-reducer.ts +0 -176
- package/dist/builtin/workflows/builtin/goal-reports.ts +0 -76
- package/dist/builtin/workflows/builtin/goal-reverify.ts +0 -305
- package/dist/builtin/workflows/builtin/goal-review.ts +0 -139
- package/dist/builtin/workflows/builtin/goal-runner.ts +0 -504
- package/dist/builtin/workflows/builtin/goal-schemas.ts +0 -89
- package/dist/builtin/workflows/builtin/goal-types.ts +0 -164
- package/dist/builtin/workflows/builtin/goal.ts +0 -78
- package/dist/builtin/workflows/builtin/index.ts +0 -16
- package/dist/builtin/workflows/builtin/loop-until-done-prompts.ts +0 -87
- package/dist/builtin/workflows/builtin/loop-until-done-runner.ts +0 -282
- package/dist/builtin/workflows/builtin/loop-until-done.ts +0 -51
- package/dist/builtin/workflows/builtin/open-claude-design-live-protocol.ts +0 -305
- package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +0 -337
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +0 -347
- package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +0 -313
- package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +0 -311
- package/dist/builtin/workflows/builtin/open-claude-design.ts +0 -57
- package/dist/builtin/workflows/builtin/pattern-artifact-root.ts +0 -28
- package/dist/builtin/workflows/builtin/progress-scoring.ts +0 -230
- package/dist/builtin/workflows/builtin/ralph-core.ts +0 -449
- package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +0 -100
- package/dist/builtin/workflows/builtin/ralph-models.ts +0 -183
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +0 -103
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +0 -100
- package/dist/builtin/workflows/builtin/ralph-runner.ts +0 -455
- package/dist/builtin/workflows/builtin/ralph.ts +0 -82
- package/dist/builtin/workflows/builtin/review-convergence.ts +0 -229
- package/dist/builtin/workflows/builtin/selection-math.ts +0 -156
- package/dist/builtin/workflows/builtin/steering-context.ts +0 -51
- package/dist/builtin/workflows/builtin/tournament-prompts.ts +0 -70
- package/dist/builtin/workflows/builtin/tournament-runner.ts +0 -400
- package/dist/builtin/workflows/builtin/tournament.ts +0 -73
- package/dist/builtin/workflows/builtin/verification-criteria.ts +0 -330
- package/dist/builtin/workflows/builtin/verification-prompts.ts +0 -206
- package/dist/builtin/workflows/builtin/verification-usage.ts +0 -44
|
@@ -6,9 +6,16 @@ import { writeFileSync, unlinkSync, mkdirSync } from "fs";
|
|
|
6
6
|
import { randomUUID } from "crypto";
|
|
7
7
|
import { writeMessage, createMessageReader } from "./framing.js";
|
|
8
8
|
import { getBrokerPidPath, getBrokerSocketPath, getIntercomDirPath } from "./paths.js";
|
|
9
|
-
import type { SessionInfo, BrokerMessage, SupervisorRegistration } from "../types.js";
|
|
9
|
+
import type { SessionInfo, Message, BrokerMessage, SupervisorRegistration } from "../types.js";
|
|
10
10
|
import { DeliveredMessageCache } from "./delivered-message-cache.js";
|
|
11
|
-
import {
|
|
11
|
+
import { isMessage } from "./client-message-validation.js";
|
|
12
|
+
import { buildMessageSendSignature } from "./send-signature.js";
|
|
13
|
+
import {
|
|
14
|
+
handleBrokerSend,
|
|
15
|
+
parsePendingStageTarget,
|
|
16
|
+
type BrokerConnectedSession,
|
|
17
|
+
type PendingStageRoute,
|
|
18
|
+
} from "./send-handler.js";
|
|
12
19
|
import { SupervisorChannelCache } from "./supervisor-channel.js";
|
|
13
20
|
import { normalizeGroup } from "../group.js";
|
|
14
21
|
import { handleBrokerPresence } from "./presence-handler.js";
|
|
@@ -18,6 +25,49 @@ const INTERCOM_DIR = getIntercomDirPath();
|
|
|
18
25
|
const SOCKET_PATH = getBrokerSocketPath();
|
|
19
26
|
const PID_PATH = getBrokerPidPath();
|
|
20
27
|
|
|
28
|
+
const PENDING_STAGE_MESSAGE_TIMEOUT_MS = 10_000;
|
|
29
|
+
|
|
30
|
+
interface PendingStageRouteRegistration {
|
|
31
|
+
readonly sessionId: string;
|
|
32
|
+
readonly group: string;
|
|
33
|
+
readonly capability: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PendingStageAcknowledgment {
|
|
37
|
+
readonly ownerSessionId: string;
|
|
38
|
+
readonly senderSocket: net.Socket;
|
|
39
|
+
readonly messageId: string;
|
|
40
|
+
readonly attemptId?: string;
|
|
41
|
+
readonly runId: string;
|
|
42
|
+
readonly stageKey: string;
|
|
43
|
+
readonly senderSessionId: string;
|
|
44
|
+
readonly sender: SessionInfo;
|
|
45
|
+
readonly message: Message;
|
|
46
|
+
readonly liveTargetId?: string;
|
|
47
|
+
readonly signature?: string;
|
|
48
|
+
readonly timeout: NodeJS.Timeout;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface PendingStageNotificationAcknowledgment {
|
|
52
|
+
readonly ownerSessionId: string;
|
|
53
|
+
readonly recipientSessionId: string;
|
|
54
|
+
readonly senderSocket: net.Socket;
|
|
55
|
+
readonly messageId: string;
|
|
56
|
+
readonly attemptId?: string;
|
|
57
|
+
readonly signature: string;
|
|
58
|
+
readonly timeout: NodeJS.Timeout;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface LiveWorkflowStageRouteRegistration {
|
|
62
|
+
readonly sessionId: string;
|
|
63
|
+
readonly capability: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface LiveWorkflowStageRouteActivation {
|
|
67
|
+
readonly sessionId: string;
|
|
68
|
+
readonly pendingRequestIds: Set<string>;
|
|
69
|
+
}
|
|
70
|
+
|
|
21
71
|
type ConnectedSession = BrokerConnectedSession;
|
|
22
72
|
|
|
23
73
|
function isSessionRegistration(value: unknown): value is Omit<SessionInfo, "id"> {
|
|
@@ -62,6 +112,11 @@ class IntercomBroker {
|
|
|
62
112
|
private deliveredMessages = new DeliveredMessageCache();
|
|
63
113
|
private supervisorChannel = new SupervisorChannelCache();
|
|
64
114
|
private pendingQuestions = new PendingQuestionIndex();
|
|
115
|
+
private pendingStageRoutes = new Map<string, PendingStageRouteRegistration>();
|
|
116
|
+
private pendingStageAcknowledgments = new Map<string, PendingStageAcknowledgment>();
|
|
117
|
+
private pendingStageNotificationAcknowledgments = new Map<string, PendingStageNotificationAcknowledgment>();
|
|
118
|
+
private liveWorkflowStageRoutes = new Map<string, LiveWorkflowStageRouteRegistration>();
|
|
119
|
+
private liveWorkflowStageRouteActivations = new Map<string, LiveWorkflowStageRouteActivation>();
|
|
65
120
|
|
|
66
121
|
constructor() {
|
|
67
122
|
mkdirSync(INTERCOM_DIR, { recursive: true });
|
|
@@ -88,6 +143,7 @@ class IntercomBroker {
|
|
|
88
143
|
let sessionId: string | null = null;
|
|
89
144
|
|
|
90
145
|
const reader = createMessageReader((msg) => {
|
|
146
|
+
if (socket.destroyed || socket.writableEnded) return;
|
|
91
147
|
this.handleMessage(socket, msg, sessionId, (id) => {
|
|
92
148
|
sessionId = id;
|
|
93
149
|
});
|
|
@@ -120,6 +176,375 @@ class IntercomBroker {
|
|
|
120
176
|
}
|
|
121
177
|
}, 5000);
|
|
122
178
|
}
|
|
179
|
+
private resolveLiveWorkflowStage = (target: string): ConnectedSession | undefined => {
|
|
180
|
+
const registration = this.liveWorkflowStageRoutes.get(target);
|
|
181
|
+
if (registration === undefined) return undefined;
|
|
182
|
+
const parsedTarget = parsePendingStageTarget(target);
|
|
183
|
+
const currentOwner = parsedTarget === undefined ? undefined : this.pendingStageRoutes.get(parsedTarget.runId);
|
|
184
|
+
if (currentOwner === undefined) return undefined;
|
|
185
|
+
if (currentOwner.capability !== registration.capability) {
|
|
186
|
+
this.liveWorkflowStageRoutes.delete(target);
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const session = this.sessions.get(registration.sessionId);
|
|
190
|
+
if (session !== undefined) return session;
|
|
191
|
+
this.liveWorkflowStageRoutes.delete(target);
|
|
192
|
+
return undefined;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
private acknowledgeLiveWorkflowStageRoute(requestId: string): void {
|
|
196
|
+
const activation = this.liveWorkflowStageRouteActivations.get(requestId);
|
|
197
|
+
if (activation === undefined || activation.pendingRequestIds.size > 0) return;
|
|
198
|
+
this.liveWorkflowStageRouteActivations.delete(requestId);
|
|
199
|
+
const stage = this.sessions.get(activation.sessionId);
|
|
200
|
+
if (stage !== undefined) writeMessage(stage.socket, { type: "live_workflow_stage_route_registered", requestId });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private releasePendingStageAcknowledgment(requestId: string): void {
|
|
204
|
+
for (const [activationId, activation] of this.liveWorkflowStageRouteActivations) {
|
|
205
|
+
if (!activation.pendingRequestIds.delete(requestId)) continue;
|
|
206
|
+
this.acknowledgeLiveWorkflowStageRoute(activationId);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private registerLiveWorkflowStageRoute(
|
|
211
|
+
currentId: string,
|
|
212
|
+
requestId: string,
|
|
213
|
+
runId: string,
|
|
214
|
+
stageKeys: readonly string[],
|
|
215
|
+
capability: string,
|
|
216
|
+
): boolean {
|
|
217
|
+
const uniqueStageKeys = [...new Set(stageKeys)];
|
|
218
|
+
const targets = uniqueStageKeys.map((stageKey) => `${runId}:${stageKey}`);
|
|
219
|
+
for (const target of targets) {
|
|
220
|
+
const existing = this.liveWorkflowStageRoutes.get(target);
|
|
221
|
+
if (
|
|
222
|
+
existing !== undefined &&
|
|
223
|
+
existing.sessionId !== currentId &&
|
|
224
|
+
this.sessions.has(existing.sessionId)
|
|
225
|
+
) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const target of targets) {
|
|
230
|
+
this.liveWorkflowStageRoutes.set(target, { sessionId: currentId, capability });
|
|
231
|
+
}
|
|
232
|
+
const pendingRequestIds = new Set(
|
|
233
|
+
[...this.pendingStageAcknowledgments]
|
|
234
|
+
.filter(([, pending]) => pending.runId === runId && uniqueStageKeys.includes(pending.stageKey))
|
|
235
|
+
.map(([pendingRequestId]) => pendingRequestId),
|
|
236
|
+
);
|
|
237
|
+
this.liveWorkflowStageRouteActivations.set(requestId, { sessionId: currentId, pendingRequestIds });
|
|
238
|
+
this.acknowledgeLiveWorkflowStageRoute(requestId);
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private routePendingStage = (route: PendingStageRoute): boolean => {
|
|
243
|
+
const ownerRegistration = this.pendingStageRoutes.get(route.runId);
|
|
244
|
+
if (ownerRegistration === undefined) return false;
|
|
245
|
+
const owner = this.sessions.get(ownerRegistration.sessionId);
|
|
246
|
+
if (owner === undefined) {
|
|
247
|
+
this.pendingStageRoutes.delete(route.runId);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (route.liveTargetId === undefined && normalizeGroup(route.from.info.group) !== normalizeGroup(ownerRegistration.group)) {
|
|
251
|
+
writeMessage(route.socket, {
|
|
252
|
+
type: "delivery_failed",
|
|
253
|
+
messageId: route.message.id,
|
|
254
|
+
...(route.attemptId ? { attemptId: route.attemptId } : {}),
|
|
255
|
+
reason: "Target workflow run is in a different intercom group",
|
|
256
|
+
});
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
const requestId = randomUUID();
|
|
260
|
+
const timeout = setTimeout(() => {
|
|
261
|
+
const pending = this.pendingStageAcknowledgments.get(requestId);
|
|
262
|
+
if (pending === undefined) return;
|
|
263
|
+
this.pendingStageAcknowledgments.delete(requestId);
|
|
264
|
+
this.releasePendingStageAcknowledgment(requestId);
|
|
265
|
+
writeMessage(pending.senderSocket, {
|
|
266
|
+
type: "delivery_failed",
|
|
267
|
+
messageId: pending.messageId,
|
|
268
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
269
|
+
reason: "Pending-stage route did not acknowledge the message",
|
|
270
|
+
});
|
|
271
|
+
}, PENDING_STAGE_MESSAGE_TIMEOUT_MS);
|
|
272
|
+
this.pendingStageAcknowledgments.set(requestId, {
|
|
273
|
+
ownerSessionId: ownerRegistration.sessionId,
|
|
274
|
+
senderSocket: route.socket,
|
|
275
|
+
messageId: route.message.id,
|
|
276
|
+
...(route.attemptId ? { attemptId: route.attemptId } : {}),
|
|
277
|
+
runId: route.runId,
|
|
278
|
+
stageKey: route.stageKey,
|
|
279
|
+
senderSessionId: route.from.info.id,
|
|
280
|
+
sender: { ...route.from.info },
|
|
281
|
+
message: route.message,
|
|
282
|
+
...(route.liveTargetId === undefined ? {} : { liveTargetId: route.liveTargetId }),
|
|
283
|
+
...(route.signature === undefined ? {} : { signature: route.signature }),
|
|
284
|
+
timeout,
|
|
285
|
+
});
|
|
286
|
+
writeMessage(owner.socket, {
|
|
287
|
+
type: "pending_stage_message",
|
|
288
|
+
requestId,
|
|
289
|
+
from: route.from.info,
|
|
290
|
+
...(route.from.registrationName === undefined ? {} : { senderRegistrationName: route.from.registrationName }),
|
|
291
|
+
...(route.from.registrationReturnAddress === undefined
|
|
292
|
+
? {}
|
|
293
|
+
: { senderReturnAddress: route.from.registrationReturnAddress }),
|
|
294
|
+
runId: route.runId,
|
|
295
|
+
stageKey: route.stageKey,
|
|
296
|
+
message: route.message,
|
|
297
|
+
...(route.liveTargetId === undefined ? {} : { live: true }),
|
|
298
|
+
});
|
|
299
|
+
return true;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
private settlePendingStageMessageRequest(
|
|
303
|
+
currentId: string,
|
|
304
|
+
requestId: string,
|
|
305
|
+
outcome: "queued" | "delivered" | "forward" | "refused",
|
|
306
|
+
position: number | undefined,
|
|
307
|
+
reason: string | undefined,
|
|
308
|
+
reasonCode: "message_id_conflict" | undefined,
|
|
309
|
+
): void {
|
|
310
|
+
const pending = this.pendingStageAcknowledgments.get(requestId);
|
|
311
|
+
if (pending === undefined || pending.ownerSessionId !== currentId) return;
|
|
312
|
+
clearTimeout(pending.timeout);
|
|
313
|
+
this.pendingStageAcknowledgments.delete(requestId);
|
|
314
|
+
this.releasePendingStageAcknowledgment(requestId);
|
|
315
|
+
if (outcome === "forward") {
|
|
316
|
+
this.forwardValidatedLiveStageMessage(pending);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (outcome === "delivered") {
|
|
320
|
+
writeMessage(pending.senderSocket, {
|
|
321
|
+
type: "delivered",
|
|
322
|
+
messageId: pending.messageId,
|
|
323
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (outcome === "queued" && typeof position === "number" && position > 0) {
|
|
328
|
+
writeMessage(pending.senderSocket, {
|
|
329
|
+
type: "queued",
|
|
330
|
+
messageId: pending.messageId,
|
|
331
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
332
|
+
runId: pending.runId,
|
|
333
|
+
stageKey: pending.stageKey,
|
|
334
|
+
position,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
writeMessage(pending.senderSocket, {
|
|
339
|
+
type: "delivery_failed",
|
|
340
|
+
messageId: pending.messageId,
|
|
341
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
342
|
+
reason: reason ?? "Pending-stage delivery was refused",
|
|
343
|
+
...(reasonCode === undefined ? {} : { reasonCode }),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private forwardValidatedLiveStageMessage(pending: PendingStageAcknowledgment): void {
|
|
348
|
+
const from = this.sessions.get(pending.senderSessionId);
|
|
349
|
+
const target = this.resolveLiveWorkflowStage(`${pending.runId}:${pending.stageKey}`);
|
|
350
|
+
if (
|
|
351
|
+
from === undefined ||
|
|
352
|
+
target === undefined ||
|
|
353
|
+
target.info.id !== pending.liveTargetId ||
|
|
354
|
+
pending.signature === undefined
|
|
355
|
+
) {
|
|
356
|
+
writeMessage(pending.senderSocket, {
|
|
357
|
+
type: "delivery_failed",
|
|
358
|
+
messageId: pending.messageId,
|
|
359
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
360
|
+
reason: "Session not found",
|
|
361
|
+
});
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const deliveredMatch = this.deliveredMessages.lookup(pending.messageId, pending.signature);
|
|
365
|
+
if (deliveredMatch === "conflict") {
|
|
366
|
+
writeMessage(pending.senderSocket, {
|
|
367
|
+
type: "delivery_failed",
|
|
368
|
+
messageId: pending.messageId,
|
|
369
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
370
|
+
reason: `Intercom message ID '${pending.messageId}' was already delivered with a different target or payload`,
|
|
371
|
+
reasonCode: "message_id_conflict",
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (deliveredMatch === "miss") {
|
|
376
|
+
writeMessage(target.socket, { type: "message", from: pending.sender, message: pending.message });
|
|
377
|
+
this.deliveredMessages.record(pending.messageId, pending.signature);
|
|
378
|
+
if (pending.message.expectsReply === true) {
|
|
379
|
+
this.pendingQuestions.record(from.info.id, target.info.id, pending.messageId);
|
|
380
|
+
}
|
|
381
|
+
if (pending.message.replyTo !== undefined) {
|
|
382
|
+
this.pendingQuestions.clearReply(from.info.id, target.info.id, pending.message.replyTo);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
writeMessage(pending.senderSocket, {
|
|
386
|
+
type: "delivered",
|
|
387
|
+
messageId: pending.messageId,
|
|
388
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
private failPendingStageNotification(
|
|
394
|
+
socket: net.Socket,
|
|
395
|
+
messageId: string,
|
|
396
|
+
attemptId: string | undefined,
|
|
397
|
+
reason: string,
|
|
398
|
+
reasonCode?: "message_id_conflict",
|
|
399
|
+
): void {
|
|
400
|
+
writeMessage(socket, {
|
|
401
|
+
type: "delivery_failed",
|
|
402
|
+
messageId,
|
|
403
|
+
...(attemptId === undefined ? {} : { attemptId }),
|
|
404
|
+
reason,
|
|
405
|
+
...(reasonCode === undefined ? {} : { reasonCode }),
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private handlePendingStageNotificationSend(
|
|
410
|
+
socket: net.Socket,
|
|
411
|
+
clientMessage: Record<string, unknown>,
|
|
412
|
+
currentId: string,
|
|
413
|
+
): void {
|
|
414
|
+
const message = clientMessage.message;
|
|
415
|
+
const messageId = typeof message === "object" && message !== null && typeof (message as { id?: unknown }).id === "string"
|
|
416
|
+
? (message as { id: string }).id
|
|
417
|
+
: "unknown";
|
|
418
|
+
const attemptId = typeof clientMessage.attemptId === "string" ? clientMessage.attemptId : undefined;
|
|
419
|
+
if (
|
|
420
|
+
typeof clientMessage.runId !== "string" ||
|
|
421
|
+
typeof clientMessage.capability !== "string" ||
|
|
422
|
+
typeof clientMessage.to !== "string" ||
|
|
423
|
+
(clientMessage.senderRegistrationName !== undefined && typeof clientMessage.senderRegistrationName !== "string") ||
|
|
424
|
+
(clientMessage.senderReturnAddress !== undefined &&
|
|
425
|
+
(typeof clientMessage.senderReturnAddress !== "string" || clientMessage.senderReturnAddress.length === 0)) ||
|
|
426
|
+
(clientMessage.attemptId !== undefined && typeof clientMessage.attemptId !== "string") ||
|
|
427
|
+
!isMessage(message)
|
|
428
|
+
) {
|
|
429
|
+
this.failPendingStageNotification(socket, messageId, attemptId, "Invalid pending-stage notification format");
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
|
|
433
|
+
const owner = this.sessions.get(currentId);
|
|
434
|
+
const routeGroup = normalizeGroup(ownerRegistration?.group);
|
|
435
|
+
if (
|
|
436
|
+
ownerRegistration === undefined ||
|
|
437
|
+
owner === undefined ||
|
|
438
|
+
ownerRegistration.sessionId !== currentId ||
|
|
439
|
+
ownerRegistration.capability !== clientMessage.capability ||
|
|
440
|
+
normalizeGroup(owner.registrationGroup) !== routeGroup
|
|
441
|
+
) {
|
|
442
|
+
this.failPendingStageNotification(socket, message.id, attemptId, "Pending-stage notification is not authorized");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const logicalTarget = [
|
|
446
|
+
clientMessage.runId,
|
|
447
|
+
clientMessage.to,
|
|
448
|
+
clientMessage.senderReturnAddress ?? "",
|
|
449
|
+
clientMessage.senderRegistrationName ?? "",
|
|
450
|
+
routeGroup,
|
|
451
|
+
].join(":");
|
|
452
|
+
const signature = buildMessageSendSignature(logicalTarget, message, clientMessage.capability);
|
|
453
|
+
const deliveredMatch = this.deliveredMessages.lookup(message.id, signature);
|
|
454
|
+
if (deliveredMatch === "match") {
|
|
455
|
+
writeMessage(socket, { type: "delivered", messageId: message.id, ...(attemptId === undefined ? {} : { attemptId }) });
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (deliveredMatch === "conflict") {
|
|
459
|
+
this.failPendingStageNotification(
|
|
460
|
+
socket,
|
|
461
|
+
message.id,
|
|
462
|
+
attemptId,
|
|
463
|
+
`Intercom message ID '${message.id}' was already delivered with a different target or payload`,
|
|
464
|
+
"message_id_conflict",
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let target = this.sessions.get(clientMessage.to);
|
|
470
|
+
if (target === undefined && clientMessage.senderReturnAddress !== undefined) {
|
|
471
|
+
const matches = [...this.sessions.values()].filter(
|
|
472
|
+
(session) =>
|
|
473
|
+
session.registrationReturnAddress === clientMessage.senderReturnAddress &&
|
|
474
|
+
normalizeGroup(session.registrationGroup) === routeGroup,
|
|
475
|
+
);
|
|
476
|
+
if (matches.length === 1) target = matches[0];
|
|
477
|
+
} else if (target === undefined && clientMessage.senderRegistrationName !== undefined) {
|
|
478
|
+
const alias = clientMessage.senderRegistrationName.trim().toLowerCase();
|
|
479
|
+
if (alias.length > 0) {
|
|
480
|
+
// Legacy durable records can only use the immutable registration name/group fallback.
|
|
481
|
+
const matches = [...this.sessions.values()].filter(
|
|
482
|
+
(session) =>
|
|
483
|
+
session.registrationName?.toLowerCase() === alias &&
|
|
484
|
+
normalizeGroup(session.registrationGroup) === routeGroup,
|
|
485
|
+
);
|
|
486
|
+
if (matches.length === 1) target = matches[0];
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
target === undefined ||
|
|
491
|
+
target.info.id === currentId ||
|
|
492
|
+
normalizeGroup(target.registrationGroup) !== routeGroup ||
|
|
493
|
+
normalizeGroup(target.info.group) !== routeGroup
|
|
494
|
+
) {
|
|
495
|
+
this.failPendingStageNotification(socket, message.id, attemptId, "Session not found");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const requestId = randomUUID();
|
|
500
|
+
const timeout = setTimeout(() => {
|
|
501
|
+
const pending = this.pendingStageNotificationAcknowledgments.get(requestId);
|
|
502
|
+
if (pending === undefined) return;
|
|
503
|
+
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
504
|
+
this.failPendingStageNotification(
|
|
505
|
+
pending.senderSocket,
|
|
506
|
+
pending.messageId,
|
|
507
|
+
pending.attemptId,
|
|
508
|
+
"Recipient did not acknowledge the pending-stage notification",
|
|
509
|
+
);
|
|
510
|
+
}, PENDING_STAGE_MESSAGE_TIMEOUT_MS);
|
|
511
|
+
this.pendingStageNotificationAcknowledgments.set(requestId, {
|
|
512
|
+
ownerSessionId: currentId,
|
|
513
|
+
recipientSessionId: target.info.id,
|
|
514
|
+
senderSocket: socket,
|
|
515
|
+
messageId: message.id,
|
|
516
|
+
...(attemptId === undefined ? {} : { attemptId }),
|
|
517
|
+
signature,
|
|
518
|
+
timeout,
|
|
519
|
+
});
|
|
520
|
+
writeMessage(target.socket, { type: "pending_stage_notification", requestId, from: owner.info, message });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
private settlePendingStageNotification(
|
|
524
|
+
currentId: string,
|
|
525
|
+
requestId: string,
|
|
526
|
+
delivered: boolean,
|
|
527
|
+
): void {
|
|
528
|
+
const pending = this.pendingStageNotificationAcknowledgments.get(requestId);
|
|
529
|
+
if (pending === undefined || pending.recipientSessionId !== currentId) return;
|
|
530
|
+
clearTimeout(pending.timeout);
|
|
531
|
+
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
532
|
+
if (!delivered) {
|
|
533
|
+
this.failPendingStageNotification(
|
|
534
|
+
pending.senderSocket,
|
|
535
|
+
pending.messageId,
|
|
536
|
+
pending.attemptId,
|
|
537
|
+
"Recipient did not admit the pending-stage notification",
|
|
538
|
+
);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
this.deliveredMessages.record(pending.messageId, pending.signature);
|
|
542
|
+
writeMessage(pending.senderSocket, {
|
|
543
|
+
type: "delivered",
|
|
544
|
+
messageId: pending.messageId,
|
|
545
|
+
...(pending.attemptId === undefined ? {} : { attemptId: pending.attemptId }),
|
|
546
|
+
});
|
|
547
|
+
}
|
|
123
548
|
|
|
124
549
|
private handleMessage(
|
|
125
550
|
socket: net.Socket,
|
|
@@ -142,10 +567,14 @@ class IntercomBroker {
|
|
|
142
567
|
if (!isSessionRegistration(clientMessage.session)) {
|
|
143
568
|
throw new Error("Invalid register message");
|
|
144
569
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
570
|
+
if (clientMessage.returnAddress !== undefined &&
|
|
571
|
+
(typeof clientMessage.returnAddress !== "string" || clientMessage.returnAddress.length === 0)) {
|
|
572
|
+
throw new Error("Invalid return address");
|
|
573
|
+
}
|
|
574
|
+
if (clientMessage.supervisorOwnerToken !== undefined
|
|
575
|
+
&& (typeof clientMessage.supervisorOwnerToken !== "string" || !clientMessage.supervisorOwnerToken)) {
|
|
576
|
+
throw new Error("Invalid supervisor owner token");
|
|
577
|
+
}
|
|
149
578
|
|
|
150
579
|
if (currentId) {
|
|
151
580
|
throw new Error("Received duplicate register message");
|
|
@@ -175,6 +604,13 @@ class IntercomBroker {
|
|
|
175
604
|
this.sessions.set(id, {
|
|
176
605
|
socket,
|
|
177
606
|
info,
|
|
607
|
+
registrationGroup: info.group,
|
|
608
|
+
...(typeof info.name === "string" && info.name.trim().length > 0
|
|
609
|
+
? { registrationName: info.name.trim() }
|
|
610
|
+
: {}),
|
|
611
|
+
...(typeof clientMessage.returnAddress === "string"
|
|
612
|
+
? { registrationReturnAddress: clientMessage.returnAddress }
|
|
613
|
+
: {}),
|
|
178
614
|
...(supervisorId ? { supervisorId } : {}),
|
|
179
615
|
...(typeof clientMessage.supervisorOwnerToken === "string"
|
|
180
616
|
? { supervisorOwnerToken: clientMessage.supervisorOwnerToken }
|
|
@@ -243,21 +679,142 @@ class IntercomBroker {
|
|
|
243
679
|
break;
|
|
244
680
|
}
|
|
245
681
|
|
|
246
|
-
case "
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
clientMessage
|
|
682
|
+
case "register_pending_stage_route": {
|
|
683
|
+
if (
|
|
684
|
+
typeof clientMessage.runId !== "string" ||
|
|
685
|
+
!clientMessage.runId ||
|
|
686
|
+
typeof clientMessage.group !== "string" ||
|
|
687
|
+
typeof clientMessage.capability !== "string" ||
|
|
688
|
+
!clientMessage.capability
|
|
689
|
+
) {
|
|
690
|
+
throw new Error("Invalid pending-stage route registration");
|
|
691
|
+
}
|
|
692
|
+
const owner = this.sessions.get(currentId);
|
|
693
|
+
const existing = this.pendingStageRoutes.get(clientMessage.runId);
|
|
694
|
+
const ownerGroup = normalizeGroup(owner?.registrationGroup);
|
|
695
|
+
const activeExisting = existing !== undefined && this.sessions.has(existing.sessionId) ? existing : undefined;
|
|
696
|
+
if (
|
|
697
|
+
owner === undefined ||
|
|
698
|
+
normalizeGroup(clientMessage.group) !== ownerGroup ||
|
|
699
|
+
(activeExisting !== undefined &&
|
|
700
|
+
(activeExisting.capability !== clientMessage.capability || activeExisting.group !== ownerGroup))
|
|
701
|
+
) {
|
|
702
|
+
writeMessage(socket, { type: "registration_failed", reason: "Pending-stage route is not authorized" });
|
|
703
|
+
socket.end();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (activeExisting !== undefined && activeExisting.sessionId !== currentId) {
|
|
707
|
+
// A stage replays the process-shared owner announcement before
|
|
708
|
+
// registering its live aliases. Authenticate it without replacing
|
|
709
|
+
// the workflow owner that handles pending delivery.
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
this.pendingStageRoutes.set(clientMessage.runId, {
|
|
713
|
+
sessionId: currentId,
|
|
714
|
+
group: ownerGroup,
|
|
715
|
+
capability: clientMessage.capability,
|
|
716
|
+
});
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
case "register_live_workflow_stage_route": {
|
|
721
|
+
if (
|
|
722
|
+
typeof clientMessage.requestId !== "string" ||
|
|
723
|
+
typeof clientMessage.runId !== "string" ||
|
|
724
|
+
typeof clientMessage.capability !== "string" ||
|
|
725
|
+
!clientMessage.capability ||
|
|
726
|
+
!Array.isArray(clientMessage.stageKeys) ||
|
|
727
|
+
clientMessage.stageKeys.length < 1 ||
|
|
728
|
+
clientMessage.stageKeys.length > 2 ||
|
|
729
|
+
!clientMessage.stageKeys.every(
|
|
730
|
+
(stageKey) =>
|
|
731
|
+
typeof stageKey === "string" &&
|
|
732
|
+
stageKey.length > 0 &&
|
|
733
|
+
parsePendingStageTarget(`${clientMessage.runId}:${stageKey}`) !== undefined,
|
|
734
|
+
)
|
|
735
|
+
) {
|
|
736
|
+
throw new Error("Invalid live workflow-stage route registration");
|
|
737
|
+
}
|
|
738
|
+
const ownerRegistration = this.pendingStageRoutes.get(clientMessage.runId);
|
|
739
|
+
const registeringSession = this.sessions.get(currentId);
|
|
740
|
+
if (
|
|
741
|
+
ownerRegistration === undefined ||
|
|
742
|
+
registeringSession === undefined ||
|
|
743
|
+
ownerRegistration.capability !== clientMessage.capability ||
|
|
744
|
+
normalizeGroup(ownerRegistration.group) !==
|
|
745
|
+
normalizeGroup(registeringSession.registrationGroup ?? registeringSession.info.group) ||
|
|
746
|
+
!this.registerLiveWorkflowStageRoute(
|
|
747
|
+
currentId,
|
|
748
|
+
clientMessage.requestId,
|
|
749
|
+
clientMessage.runId,
|
|
750
|
+
clientMessage.stageKeys,
|
|
751
|
+
clientMessage.capability,
|
|
752
|
+
)
|
|
753
|
+
) {
|
|
754
|
+
writeMessage(socket, {
|
|
755
|
+
type: "registration_failed",
|
|
756
|
+
reason: "Live workflow-stage route is owned by another active session",
|
|
757
|
+
});
|
|
758
|
+
socket.end();
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
break;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
case "pending_stage_message_result": {
|
|
765
|
+
if (
|
|
766
|
+
typeof clientMessage.requestId !== "string" ||
|
|
767
|
+
(clientMessage.outcome !== "queued" &&
|
|
768
|
+
clientMessage.outcome !== "delivered" &&
|
|
769
|
+
clientMessage.outcome !== "forward" &&
|
|
770
|
+
clientMessage.outcome !== "refused") ||
|
|
771
|
+
(clientMessage.position !== undefined && typeof clientMessage.position !== "number") ||
|
|
772
|
+
(clientMessage.reason !== undefined && typeof clientMessage.reason !== "string") ||
|
|
773
|
+
(clientMessage.reasonCode !== undefined && clientMessage.reasonCode !== "message_id_conflict")
|
|
774
|
+
) {
|
|
775
|
+
throw new Error("Invalid pending-stage message result");
|
|
776
|
+
}
|
|
777
|
+
this.settlePendingStageMessageRequest(
|
|
251
778
|
currentId,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
779
|
+
clientMessage.requestId,
|
|
780
|
+
clientMessage.outcome,
|
|
781
|
+
clientMessage.position,
|
|
782
|
+
clientMessage.reason,
|
|
783
|
+
clientMessage.reasonCode,
|
|
257
784
|
);
|
|
258
785
|
break;
|
|
259
786
|
}
|
|
260
787
|
|
|
788
|
+
case "send":
|
|
789
|
+
case "supervisor_send": {
|
|
790
|
+
handleBrokerSend(
|
|
791
|
+
socket,
|
|
792
|
+
clientMessage,
|
|
793
|
+
currentId,
|
|
794
|
+
this.sessions,
|
|
795
|
+
this.deliveredMessages,
|
|
796
|
+
writeMessage,
|
|
797
|
+
this.supervisorChannel,
|
|
798
|
+
this.pendingQuestions,
|
|
799
|
+
this.routePendingStage,
|
|
800
|
+
this.resolveLiveWorkflowStage,
|
|
801
|
+
);
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
case "send_pending_stage_notification": {
|
|
806
|
+
this.handlePendingStageNotificationSend(socket, clientMessage, currentId);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
case "pending_stage_notification_result": {
|
|
811
|
+
if (typeof clientMessage.requestId !== "string" || typeof clientMessage.delivered !== "boolean") {
|
|
812
|
+
throw new Error("Invalid pending-stage notification result");
|
|
813
|
+
}
|
|
814
|
+
this.settlePendingStageNotification(currentId, clientMessage.requestId, clientMessage.delivered);
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
|
|
261
818
|
case "presence": {
|
|
262
819
|
handleBrokerPresence(
|
|
263
820
|
socket,
|
|
@@ -290,10 +847,42 @@ class IntercomBroker {
|
|
|
290
847
|
});
|
|
291
848
|
}
|
|
292
849
|
|
|
293
|
-
this.
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
850
|
+
for (const [runId, owner] of this.pendingStageRoutes) {
|
|
851
|
+
if (owner.sessionId === sessionId) this.pendingStageRoutes.delete(runId);
|
|
852
|
+
}
|
|
853
|
+
for (const [requestId, pending] of this.pendingStageAcknowledgments) {
|
|
854
|
+
if (pending.ownerSessionId !== sessionId) continue;
|
|
855
|
+
clearTimeout(pending.timeout);
|
|
856
|
+
this.pendingStageAcknowledgments.delete(requestId);
|
|
857
|
+
this.releasePendingStageAcknowledgment(requestId);
|
|
858
|
+
writeMessage(pending.senderSocket, {
|
|
859
|
+
type: "delivery_failed",
|
|
860
|
+
messageId: pending.messageId,
|
|
861
|
+
...(pending.attemptId ? { attemptId: pending.attemptId } : {}),
|
|
862
|
+
reason: "Pending-stage route disconnected before acknowledging the message",
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
for (const [requestId, pending] of this.pendingStageNotificationAcknowledgments) {
|
|
866
|
+
if (pending.ownerSessionId !== sessionId && pending.recipientSessionId !== sessionId) continue;
|
|
867
|
+
clearTimeout(pending.timeout);
|
|
868
|
+
this.pendingStageNotificationAcknowledgments.delete(requestId);
|
|
869
|
+
if (pending.ownerSessionId === sessionId) continue;
|
|
870
|
+
this.failPendingStageNotification(
|
|
871
|
+
pending.senderSocket,
|
|
872
|
+
pending.messageId,
|
|
873
|
+
pending.attemptId,
|
|
874
|
+
"Recipient disconnected before acknowledging the pending-stage notification",
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
for (const [target, registration] of this.liveWorkflowStageRoutes) {
|
|
878
|
+
if (registration.sessionId === sessionId) this.liveWorkflowStageRoutes.delete(target);
|
|
879
|
+
}
|
|
880
|
+
for (const [requestId, activation] of this.liveWorkflowStageRouteActivations) {
|
|
881
|
+
if (activation.sessionId === sessionId) this.liveWorkflowStageRouteActivations.delete(requestId);
|
|
882
|
+
}
|
|
883
|
+
this.sessions.delete(sessionId);
|
|
884
|
+
this.broadcastToGroup({ type: "session_left", sessionId }, departed.info.group, sessionId);
|
|
885
|
+
}
|
|
297
886
|
/** Deliver a broadcast only to sessions in the given (normalized) group. */
|
|
298
887
|
private broadcastToGroup(msg: BrokerMessage, group: string | undefined, exclude?: string): void {
|
|
299
888
|
const target = normalizeGroup(group);
|
|
@@ -311,6 +900,12 @@ class IntercomBroker {
|
|
|
311
900
|
session.socket.end();
|
|
312
901
|
}
|
|
313
902
|
this.sessions.clear();
|
|
903
|
+
this.pendingStageRoutes.clear();
|
|
904
|
+
this.pendingStageAcknowledgments.clear();
|
|
905
|
+
this.liveWorkflowStageRoutes.clear();
|
|
906
|
+
this.liveWorkflowStageRouteActivations.clear();
|
|
907
|
+
for (const pending of this.pendingStageNotificationAcknowledgments.values()) clearTimeout(pending.timeout);
|
|
908
|
+
this.pendingStageNotificationAcknowledgments.clear();
|
|
314
909
|
if (process.platform !== "win32") {
|
|
315
910
|
try {
|
|
316
911
|
unlinkSync(SOCKET_PATH);
|
|
@@ -318,8 +913,8 @@ class IntercomBroker {
|
|
|
318
913
|
// The socket may already be gone if shutdown started after a disconnect.
|
|
319
914
|
}
|
|
320
915
|
}
|
|
321
|
-
|
|
322
|
-
|
|
916
|
+
try {
|
|
917
|
+
unlinkSync(PID_PATH);
|
|
323
918
|
} catch {
|
|
324
919
|
// The PID file may already be gone if startup never completed.
|
|
325
920
|
}
|