@bastani/atomic 0.9.16-alpha.7 → 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/package.json +1 -1
- 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 +15 -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} +133 -154
- 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 +1228 -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 +2 -2
- package/docs/rpc.md +21 -4
- package/docs/settings.md +6 -4
- package/docs/subagents.md +1 -1
- package/docs/usage.md +1 -1
- package/docs/workflows.md +108 -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 -217
- 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 -104
- package/dist/builtin/workflows/builtin/ralph-runner.ts +0 -459
- 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
|
@@ -8,6 +8,14 @@ function isAttachment(value: unknown): value is Attachment {
|
|
|
8
8
|
return attachment.language === undefined || typeof attachment.language === "string";
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
function isMessageSource(value: unknown): value is NonNullable<Message["source"]> {
|
|
12
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
13
|
+
const source = value as Record<string, unknown>;
|
|
14
|
+
return typeof source.subagentRunId === "string"
|
|
15
|
+
&& (source.subagentAgent === undefined || typeof source.subagentAgent === "string")
|
|
16
|
+
&& (source.subagentIndex === undefined || typeof source.subagentIndex === "number");
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export function isMessage(value: unknown): value is Message {
|
|
12
20
|
if (typeof value !== "object" || value === null) return false;
|
|
13
21
|
const message = value as Record<string, unknown>;
|
|
@@ -15,6 +23,7 @@ export function isMessage(value: unknown): value is Message {
|
|
|
15
23
|
if (message.replyTo !== undefined && typeof message.replyTo !== "string") return false;
|
|
16
24
|
if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") return false;
|
|
17
25
|
if (message.replyError !== undefined && typeof message.replyError !== "string") return false;
|
|
26
|
+
if (message.source !== undefined && !isMessageSource(message.source)) return false;
|
|
18
27
|
if (typeof message.content !== "object" || message.content === null) return false;
|
|
19
28
|
const content = message.content as Record<string, unknown>;
|
|
20
29
|
if (typeof content.text !== "string") return false;
|
|
@@ -24,8 +24,36 @@ export interface SendOptions {
|
|
|
24
24
|
export interface SendResult {
|
|
25
25
|
id: string;
|
|
26
26
|
delivered: boolean;
|
|
27
|
+
queued?: boolean;
|
|
27
28
|
reason?: string;
|
|
29
|
+
reasonCode?: "message_id_conflict";
|
|
30
|
+
runId?: string;
|
|
31
|
+
stageKey?: string;
|
|
32
|
+
position?: number;
|
|
28
33
|
}
|
|
34
|
+
|
|
35
|
+
export interface PendingStageMessageRequest {
|
|
36
|
+
readonly requestId: string;
|
|
37
|
+
readonly from: SessionInfo;
|
|
38
|
+
readonly runId: string;
|
|
39
|
+
readonly stageKey: string;
|
|
40
|
+
readonly message: Message;
|
|
41
|
+
readonly senderRegistrationName?: string;
|
|
42
|
+
readonly senderReturnAddress?: string;
|
|
43
|
+
readonly live?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PendingStageNotificationRequest {
|
|
47
|
+
readonly requestId: string;
|
|
48
|
+
readonly from: SessionInfo;
|
|
49
|
+
readonly message: Message;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type PendingStageMessageResult =
|
|
53
|
+
| { readonly outcome: "queued"; readonly position: number }
|
|
54
|
+
| { readonly outcome: "delivered" }
|
|
55
|
+
| { readonly outcome: "forward" }
|
|
56
|
+
| { readonly outcome: "refused"; readonly reason: string; readonly reasonCode?: "message_id_conflict" };
|
|
29
57
|
export interface PresenceUpdates {
|
|
30
58
|
name?: string;
|
|
31
59
|
status?: string;
|
|
@@ -44,6 +72,7 @@ function toError(error: unknown): Error {
|
|
|
44
72
|
|
|
45
73
|
|
|
46
74
|
export class IntercomClient extends EventEmitter {
|
|
75
|
+
private readonly returnAddress: string;
|
|
47
76
|
private socket: net.Socket | null = null;
|
|
48
77
|
private _sessionId: string | null = null;
|
|
49
78
|
private _supervisorSessionId: string | null = null;
|
|
@@ -52,6 +81,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
52
81
|
private pendingSends = new PendingSendRegistry();
|
|
53
82
|
private pendingLists = new Map<string, { resolve: (sessions: SessionInfo[]) => void; reject: (e: Error) => void }>();
|
|
54
83
|
private pendingPresence = new Map<string, { resolve: (group: string) => void; reject: (error: Error) => void }>();
|
|
84
|
+
private pendingLiveWorkflowStageRoutes = new Map<string, { resolve(): void; reject(error: Error): void }>();
|
|
55
85
|
|
|
56
86
|
private pendingSupervisorAuthorizations = new Map<string, {
|
|
57
87
|
resolve: (authorization: SupervisorAuthorization) => void;
|
|
@@ -60,14 +90,22 @@ export class IntercomClient extends EventEmitter {
|
|
|
60
90
|
private disconnecting = false;
|
|
61
91
|
private disconnectError: Error | null = null;
|
|
62
92
|
|
|
93
|
+
constructor(returnAddress: string = randomUUID()) {
|
|
94
|
+
super();
|
|
95
|
+
if (returnAddress.length === 0) throw new Error("Intercom return address must not be empty");
|
|
96
|
+
this.returnAddress = returnAddress;
|
|
97
|
+
}
|
|
98
|
+
|
|
63
99
|
private failPending(error: Error): void {
|
|
64
100
|
this.pendingSends.rejectAll(error);
|
|
65
101
|
for (const pending of this.pendingLists.values()) pending.reject(error);
|
|
66
102
|
for (const pending of this.pendingSupervisorAuthorizations.values()) pending.reject(error);
|
|
67
103
|
for (const pending of this.pendingPresence.values()) pending.reject(error);
|
|
104
|
+
for (const pending of this.pendingLiveWorkflowStageRoutes.values()) pending.reject(error);
|
|
68
105
|
this.pendingLists.clear();
|
|
69
106
|
this.pendingSupervisorAuthorizations.clear();
|
|
70
107
|
this.pendingPresence.clear();
|
|
108
|
+
this.pendingLiveWorkflowStageRoutes.clear();
|
|
71
109
|
}
|
|
72
110
|
|
|
73
111
|
|
|
@@ -220,6 +258,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
220
258
|
writeMessage(socket, {
|
|
221
259
|
type: "register",
|
|
222
260
|
session,
|
|
261
|
+
returnAddress: this.returnAddress,
|
|
223
262
|
...(supervisor ? { supervisor } : {}),
|
|
224
263
|
...(supervisorOwnerToken ? { supervisorOwnerToken } : {}),
|
|
225
264
|
});
|
|
@@ -303,6 +342,70 @@ export class IntercomClient extends EventEmitter {
|
|
|
303
342
|
}
|
|
304
343
|
this.emit("message", from, message, channel);
|
|
305
344
|
break;
|
|
345
|
+
}
|
|
346
|
+
case "pending_stage_message": {
|
|
347
|
+
const { requestId, from, senderRegistrationName, senderReturnAddress, runId, stageKey, message, live } = brokerMessage;
|
|
348
|
+
if (
|
|
349
|
+
typeof requestId !== "string" ||
|
|
350
|
+
!isSessionInfo(from) ||
|
|
351
|
+
(senderRegistrationName !== undefined && typeof senderRegistrationName !== "string") ||
|
|
352
|
+
(senderReturnAddress !== undefined && typeof senderReturnAddress !== "string") ||
|
|
353
|
+
typeof runId !== "string" ||
|
|
354
|
+
typeof stageKey !== "string" ||
|
|
355
|
+
!isMessage(message) ||
|
|
356
|
+
(live !== undefined && typeof live !== "boolean")
|
|
357
|
+
) {
|
|
358
|
+
throw new Error("Invalid pending-stage message event");
|
|
359
|
+
}
|
|
360
|
+
this.emit("pending_stage_message", {
|
|
361
|
+
requestId,
|
|
362
|
+
from,
|
|
363
|
+
...(senderRegistrationName === undefined ? {} : { senderRegistrationName }),
|
|
364
|
+
...(senderReturnAddress === undefined ? {} : { senderReturnAddress }),
|
|
365
|
+
runId,
|
|
366
|
+
stageKey,
|
|
367
|
+
message,
|
|
368
|
+
...(live === true ? { live: true } : {}),
|
|
369
|
+
} satisfies PendingStageMessageRequest);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
case "pending_stage_notification": {
|
|
373
|
+
const { requestId, from, message } = brokerMessage;
|
|
374
|
+
if (typeof requestId !== "string" || !isSessionInfo(from) || !isMessage(message)) {
|
|
375
|
+
throw new Error("Invalid pending-stage notification event");
|
|
376
|
+
}
|
|
377
|
+
this.emit("pending_stage_notification", {
|
|
378
|
+
requestId,
|
|
379
|
+
from,
|
|
380
|
+
message,
|
|
381
|
+
} satisfies PendingStageNotificationRequest);
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "live_workflow_stage_route_registered": {
|
|
385
|
+
const { requestId } = brokerMessage;
|
|
386
|
+
if (typeof requestId !== "string") throw new Error("Invalid live workflow-stage route registration");
|
|
387
|
+
const pending = this.pendingLiveWorkflowStageRoutes.get(requestId);
|
|
388
|
+
if (!pending) return;
|
|
389
|
+
this.pendingLiveWorkflowStageRoutes.delete(requestId);
|
|
390
|
+
pending.resolve();
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
case "queued": {
|
|
394
|
+
const { messageId, attemptId, runId, stageKey, position } = brokerMessage;
|
|
395
|
+
if (
|
|
396
|
+
typeof messageId !== "string" ||
|
|
397
|
+
(attemptId !== undefined && typeof attemptId !== "string") ||
|
|
398
|
+
typeof runId !== "string" ||
|
|
399
|
+
typeof stageKey !== "string" ||
|
|
400
|
+
typeof position !== "number" ||
|
|
401
|
+
position < 1
|
|
402
|
+
) {
|
|
403
|
+
throw new Error("Invalid queued message");
|
|
404
|
+
}
|
|
405
|
+
const result = { id: messageId, delivered: false, queued: true, runId, stageKey, position } as const;
|
|
406
|
+
if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
|
|
407
|
+
else this.pendingSends.resolve(messageId, attemptId, result);
|
|
408
|
+
break;
|
|
306
409
|
}
|
|
307
410
|
case "delivered": {
|
|
308
411
|
const { messageId, attemptId } = brokerMessage;
|
|
@@ -315,14 +418,24 @@ export class IntercomClient extends EventEmitter {
|
|
|
315
418
|
break;
|
|
316
419
|
}
|
|
317
420
|
case "delivery_failed": {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
421
|
+
const { messageId, attemptId, reason, reasonCode } = brokerMessage;
|
|
422
|
+
if (
|
|
423
|
+
typeof messageId !== "string" ||
|
|
424
|
+
(attemptId !== undefined && typeof attemptId !== "string") ||
|
|
425
|
+
typeof reason !== "string" ||
|
|
426
|
+
(reasonCode !== undefined && reasonCode !== "message_id_conflict")
|
|
427
|
+
) {
|
|
428
|
+
throw new Error("Invalid delivery_failed message");
|
|
429
|
+
}
|
|
430
|
+
const result = {
|
|
431
|
+
id: messageId,
|
|
432
|
+
delivered: false,
|
|
433
|
+
reason,
|
|
434
|
+
...(reasonCode === undefined ? {} : { reasonCode }),
|
|
435
|
+
} as const;
|
|
436
|
+
if (attemptId === undefined) this.pendingSends.resolveLegacy(messageId, result);
|
|
437
|
+
else this.pendingSends.resolve(messageId, attemptId, result);
|
|
438
|
+
break;
|
|
326
439
|
}
|
|
327
440
|
case "session_joined": {
|
|
328
441
|
if (!isSessionInfo(brokerMessage.session)) {
|
|
@@ -490,15 +603,92 @@ export class IntercomClient extends EventEmitter {
|
|
|
490
603
|
}
|
|
491
604
|
});
|
|
492
605
|
}
|
|
606
|
+
registerPendingStageRoute(runId: string, group: string, capability: string): void {
|
|
607
|
+
writeMessage(this.requireActiveSocket(), { type: "register_pending_stage_route", runId, group, capability });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
registerLiveWorkflowStageRoute(runId: string, stageKeys: readonly string[], capability: string): Promise<void> {
|
|
611
|
+
let socket: net.Socket;
|
|
612
|
+
try {
|
|
613
|
+
socket = this.requireActiveSocket();
|
|
614
|
+
} catch (error) {
|
|
615
|
+
return Promise.reject(toError(error));
|
|
616
|
+
}
|
|
617
|
+
const requestId = randomUUID();
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
this.pendingLiveWorkflowStageRoutes.set(requestId, { resolve, reject });
|
|
620
|
+
try {
|
|
621
|
+
writeMessage(socket, {
|
|
622
|
+
type: "register_live_workflow_stage_route",
|
|
623
|
+
requestId,
|
|
624
|
+
runId,
|
|
625
|
+
stageKeys: [...stageKeys],
|
|
626
|
+
capability,
|
|
627
|
+
});
|
|
628
|
+
} catch (error) {
|
|
629
|
+
this.pendingLiveWorkflowStageRoutes.delete(requestId);
|
|
630
|
+
reject(toError(error));
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
respondPendingStageMessage(requestId: string, result: PendingStageMessageResult): void {
|
|
636
|
+
const socket = this.requireActiveSocket();
|
|
637
|
+
writeMessage(
|
|
638
|
+
socket,
|
|
639
|
+
result.outcome === "queued"
|
|
640
|
+
? { type: "pending_stage_message_result", requestId, outcome: "queued", position: result.position }
|
|
641
|
+
: result.outcome === "refused"
|
|
642
|
+
? {
|
|
643
|
+
type: "pending_stage_message_result",
|
|
644
|
+
requestId,
|
|
645
|
+
outcome: "refused",
|
|
646
|
+
reason: result.reason,
|
|
647
|
+
...(result.reasonCode === undefined ? {} : { reasonCode: result.reasonCode }),
|
|
648
|
+
}
|
|
649
|
+
: { type: "pending_stage_message_result", requestId, outcome: result.outcome },
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
respondPendingStageNotification(requestId: string, delivered: boolean): void {
|
|
654
|
+
writeMessage(this.requireActiveSocket(), { type: "pending_stage_notification_result", requestId, delivered });
|
|
655
|
+
}
|
|
656
|
+
|
|
493
657
|
send(to: string, options: SendOptions): Promise<SendResult> {
|
|
494
658
|
return this.sendFrame("send", to, options);
|
|
495
659
|
}
|
|
496
660
|
|
|
661
|
+
sendPendingStageNotification(
|
|
662
|
+
runId: string,
|
|
663
|
+
capability: string,
|
|
664
|
+
to: string,
|
|
665
|
+
senderRegistrationName: string | undefined,
|
|
666
|
+
options: SendOptions,
|
|
667
|
+
senderReturnAddress?: string,
|
|
668
|
+
): Promise<SendResult> {
|
|
669
|
+
return this.sendFrame("send_pending_stage_notification", to, options, {
|
|
670
|
+
runId,
|
|
671
|
+
capability,
|
|
672
|
+
...(senderRegistrationName === undefined ? {} : { senderRegistrationName }),
|
|
673
|
+
...(senderReturnAddress === undefined ? {} : { senderReturnAddress }),
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
497
677
|
sendToSupervisor(to: string, options: SendOptions): Promise<SendResult> {
|
|
498
678
|
return this.sendFrame("supervisor_send", to, options);
|
|
499
679
|
}
|
|
500
680
|
|
|
501
|
-
|
|
681
|
+
private sendFrame(
|
|
682
|
+
type: "send" | "supervisor_send" | "send_pending_stage_notification",
|
|
683
|
+
to: string,
|
|
684
|
+
options: SendOptions,
|
|
685
|
+
extra: {
|
|
686
|
+
readonly runId: string;
|
|
687
|
+
readonly capability: string;
|
|
688
|
+
readonly senderRegistrationName?: string;
|
|
689
|
+
readonly senderReturnAddress?: string;
|
|
690
|
+
} | undefined = undefined,
|
|
691
|
+
): Promise<SendResult> {
|
|
502
692
|
let socket: net.Socket;
|
|
503
693
|
try {
|
|
504
694
|
socket = this.requireActiveSocket();
|
|
@@ -523,7 +713,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
523
713
|
content: { text: options.text, attachments: options.attachments },
|
|
524
714
|
};
|
|
525
715
|
try {
|
|
526
|
-
|
|
716
|
+
writeMessage(socket, { type, to, message, attemptId: acquired.attempt.attemptId, ...extra });
|
|
527
717
|
} catch (error) {
|
|
528
718
|
this.pendingSends.reject(acquired.attempt, toError(error));
|
|
529
719
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type net from "node:net";
|
|
2
|
-
import type {
|
|
2
|
+
import type { BrokerMessage, Message, SessionInfo } from "../types.js";
|
|
3
|
+
import { isMessage } from "./client-message-validation.js";
|
|
3
4
|
import { resolveSessionTarget, sessionTargetFailureReason } from "../session-target.js";
|
|
4
5
|
import { DeliveredMessageCache } from "./delivered-message-cache.js";
|
|
5
6
|
import { buildMessageSendSignature } from "./send-signature.js";
|
|
@@ -11,36 +12,60 @@ import { PendingQuestionIndex } from "./pending-question-index.js";
|
|
|
11
12
|
export interface BrokerConnectedSession {
|
|
12
13
|
socket: net.Socket;
|
|
13
14
|
info: SessionInfo;
|
|
15
|
+
/**
|
|
16
|
+
* Immutable route authority from the host-admitted registration context.
|
|
17
|
+
* Model-accessible presence changes only `info.group`; they never rewrite this field.
|
|
18
|
+
* The local broker trusts initial registration metadata, matching all existing session fields.
|
|
19
|
+
*/
|
|
20
|
+
readonly registrationGroup?: string;
|
|
21
|
+
/** Immutable registration-time name; mutable presence names never rewrite this reconnect alias. */
|
|
22
|
+
readonly registrationName?: string;
|
|
23
|
+
/** Opaque host-owned return identity; immutable after initial registration. */
|
|
24
|
+
readonly registrationReturnAddress?: string;
|
|
14
25
|
/** Broker-bound supervisor relationship established by a capability. */
|
|
15
26
|
supervisorId?: string;
|
|
16
27
|
/** Private issuer identity used to restore child capabilities after reconnects. */
|
|
17
28
|
supervisorOwnerToken?: string;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
|
-
interface
|
|
21
|
-
|
|
31
|
+
export interface PendingStageRoute {
|
|
32
|
+
readonly socket: net.Socket;
|
|
33
|
+
readonly from: BrokerConnectedSession;
|
|
34
|
+
readonly runId: string;
|
|
35
|
+
readonly stageKey: string;
|
|
36
|
+
readonly message: Message;
|
|
37
|
+
readonly attemptId?: string;
|
|
38
|
+
readonly liveTargetId?: string;
|
|
39
|
+
readonly signature?: string;
|
|
22
40
|
}
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
42
|
+
export type PendingStageRouter = (route: PendingStageRoute) => boolean;
|
|
43
|
+
|
|
44
|
+
export type LiveWorkflowStageResolver = (target: string) => BrokerConnectedSession | undefined;
|
|
45
|
+
|
|
46
|
+
const WORKFLOW_RUN_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
47
|
+
|
|
48
|
+
export function parsePendingStageTarget(target: string): { runId: string; stageKey: string } | undefined {
|
|
49
|
+
const separator = target.indexOf(":");
|
|
50
|
+
if (separator < 0) return undefined;
|
|
51
|
+
const runId = target.slice(0, separator);
|
|
52
|
+
const stageKey = target.slice(separator + 1);
|
|
53
|
+
return WORKFLOW_RUN_ID_PATTERN.test(runId) && stageKey.length > 0 ? { runId, stageKey } : undefined;
|
|
30
54
|
}
|
|
55
|
+
export const PENDING_STAGE_ASK_REFUSAL =
|
|
56
|
+
"Cannot ask a workflow stage whose session has not initialized. Use send; Atomic will queue the message until the stage session initializes.";
|
|
57
|
+
|
|
31
58
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const content = message.content as Record<string, unknown>;
|
|
40
|
-
if (typeof content.text !== "string") return false;
|
|
41
|
-
return content.attachments === undefined || (Array.isArray(content.attachments) && content.attachments.every(isAttachment));
|
|
59
|
+
interface SendClientMessage extends Record<string, unknown> {
|
|
60
|
+
type: string;
|
|
61
|
+
}
|
|
62
|
+
function wireMessageId(value: unknown): string {
|
|
63
|
+
if (typeof value !== "object" || value === null) return "unknown";
|
|
64
|
+
const id = (value as Record<string, unknown>).id;
|
|
65
|
+
return typeof id === "string" ? id : "unknown";
|
|
42
66
|
}
|
|
43
67
|
|
|
68
|
+
|
|
44
69
|
/** Validate and route one wire-level send request. */
|
|
45
70
|
export function handleBrokerSend(
|
|
46
71
|
socket: net.Socket,
|
|
@@ -51,9 +76,11 @@ export function handleBrokerSend(
|
|
|
51
76
|
write: (target: net.Socket, message: BrokerMessage) => void,
|
|
52
77
|
supervisorCache: SupervisorChannelCache = new SupervisorChannelCache(),
|
|
53
78
|
pendingQuestions: PendingQuestionIndex = new PendingQuestionIndex(),
|
|
79
|
+
routePendingStage?: PendingStageRouter,
|
|
80
|
+
resolveLiveWorkflowStage?: LiveWorkflowStageResolver,
|
|
54
81
|
): void {
|
|
55
82
|
const message = clientMessage.message;
|
|
56
|
-
const messageId =
|
|
83
|
+
const messageId = wireMessageId(message);
|
|
57
84
|
const hasAttemptId = Object.prototype.hasOwnProperty.call(clientMessage, "attemptId");
|
|
58
85
|
if (hasAttemptId && typeof clientMessage.attemptId !== "string") {
|
|
59
86
|
write(socket, {
|
|
@@ -74,27 +101,28 @@ export function handleBrokerSend(
|
|
|
74
101
|
}
|
|
75
102
|
const supervisorSend = clientMessage.type === "supervisor_send";
|
|
76
103
|
|
|
77
|
-
const signature = buildMessageSendSignature(clientMessage.to, message);
|
|
78
|
-
const deliveredMatch = deliveredMessages.lookup(message.id, signature);
|
|
79
|
-
if (deliveredMatch === "match") {
|
|
80
|
-
write(socket, { type: "delivered", messageId: message.id, attemptId });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (deliveredMatch === "conflict") {
|
|
84
|
-
write(socket, {
|
|
85
|
-
type: "delivery_failed",
|
|
86
|
-
messageId: message.id,
|
|
87
|
-
attemptId,
|
|
88
|
-
reason: `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
|
|
89
|
-
});
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
104
|
|
|
93
105
|
const fromSession = currentId ? sessions.get(currentId) : undefined;
|
|
94
106
|
if (!fromSession) {
|
|
95
107
|
write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Sender session not found" });
|
|
96
108
|
return;
|
|
97
109
|
}
|
|
110
|
+
const signature = buildMessageSendSignature(clientMessage.to, message, fromSession.info.id);
|
|
111
|
+
const deliveredMatch = deliveredMessages.lookup(message.id, signature);
|
|
112
|
+
if (deliveredMatch === "match") {
|
|
113
|
+
write(socket, { type: "delivered", messageId: message.id, attemptId });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (deliveredMatch === "conflict") {
|
|
117
|
+
write(socket, {
|
|
118
|
+
type: "delivery_failed",
|
|
119
|
+
messageId: message.id,
|
|
120
|
+
attemptId,
|
|
121
|
+
reason: `Intercom message ID '${message.id}' was already delivered with a different target or payload`,
|
|
122
|
+
reasonCode: "message_id_conflict",
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
98
126
|
const trimmedTo = clientMessage.to.trim();
|
|
99
127
|
if (supervisorSend && !fromSession.supervisorId) {
|
|
100
128
|
write(socket, { type: "delivery_failed", messageId: message.id, attemptId, reason: "Supervisor channel is not authorized" });
|
|
@@ -109,7 +137,8 @@ export function handleBrokerSend(
|
|
|
109
137
|
// Exact-id targeting always resolves against the full pool so a cross-group id
|
|
110
138
|
// is caught by the defense-in-depth group check below. Only a broker-authorized
|
|
111
139
|
// supervisor frame or an exact recorded reply may resolve across groups.
|
|
112
|
-
|
|
140
|
+
const liveWorkflowTarget = sessions.has(trimmedTo) ? undefined : resolveLiveWorkflowStage?.(trimmedTo);
|
|
141
|
+
const exactIdTarget = sessions.get(trimmedTo) ?? liveWorkflowTarget;
|
|
113
142
|
const senderGroup = normalizeGroup(fromSession.info.group);
|
|
114
143
|
const reachableAcrossGroups = supervisorSend || Boolean(message.replyTo);
|
|
115
144
|
const candidates = reachableAcrossGroups
|
|
@@ -145,6 +174,22 @@ export function handleBrokerSend(
|
|
|
145
174
|
});
|
|
146
175
|
return;
|
|
147
176
|
}
|
|
177
|
+
const liveTarget = parsePendingStageTarget(trimmedTo);
|
|
178
|
+
if (
|
|
179
|
+
liveWorkflowTarget !== undefined &&
|
|
180
|
+
liveTarget !== undefined &&
|
|
181
|
+
routePendingStage?.({
|
|
182
|
+
socket,
|
|
183
|
+
from: fromSession,
|
|
184
|
+
...liveTarget,
|
|
185
|
+
message,
|
|
186
|
+
...(attemptId ? { attemptId } : {}),
|
|
187
|
+
liveTargetId: target.info.id,
|
|
188
|
+
signature,
|
|
189
|
+
})
|
|
190
|
+
) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
148
193
|
write(target.socket, supervisorSend
|
|
149
194
|
? { type: "message", from: fromSession.info, message, channel: "supervisor" }
|
|
150
195
|
: { type: "message", from: fromSession.info, message });
|
|
@@ -159,6 +204,15 @@ export function handleBrokerSend(
|
|
|
159
204
|
write(socket, { type: "delivered", messageId: message.id, attemptId });
|
|
160
205
|
return;
|
|
161
206
|
}
|
|
207
|
+
if (resolution.kind === "not_found" && !supervisorSend && routePendingStage !== undefined) {
|
|
208
|
+
const pendingTarget = parsePendingStageTarget(trimmedTo);
|
|
209
|
+
if (
|
|
210
|
+
pendingTarget !== undefined &&
|
|
211
|
+
routePendingStage({ socket, from: fromSession, ...pendingTarget, message, ...(attemptId ? { attemptId } : {}) })
|
|
212
|
+
) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
162
216
|
write(socket, {
|
|
163
217
|
type: "delivery_failed",
|
|
164
218
|
messageId: message.id,
|
|
@@ -29,12 +29,24 @@ export function buildSendSignature(to: string, options: LogicalSendOptions): str
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export function buildMessageSendSignature(to: string, message: Message): string {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
export function buildMessageSendSignature(to: string, message: Message, senderId?: string): string {
|
|
33
|
+
const logicalSignature = buildSendSignature(to, {
|
|
34
|
+
text: message.content.text,
|
|
35
|
+
attachments: message.content.attachments,
|
|
36
|
+
replyTo: message.replyTo,
|
|
37
|
+
expectsReply: message.expectsReply,
|
|
38
|
+
replyError: message.replyError,
|
|
39
|
+
});
|
|
40
|
+
if (senderId === undefined && message.source === undefined) return logicalSignature;
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
senderId: senderId ?? null,
|
|
43
|
+
logicalSignature,
|
|
44
|
+
source: message.source === undefined
|
|
45
|
+
? null
|
|
46
|
+
: {
|
|
47
|
+
subagentRunId: message.source.subagentRunId,
|
|
48
|
+
subagentAgent: message.source.subagentAgent ?? null,
|
|
49
|
+
subagentIndex: message.source.subagentIndex ?? null,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
40
52
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group primitives shared by the detached broker and the host extension.
|
|
3
|
+
* Keep this module free of host-package imports: the broker loads it in a
|
|
4
|
+
* detached process with only Node built-ins and Intercom-local modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Mirrors `getEnvValue("ATOMIC_INTERCOM_GROUP")` locally so the detached broker subprocess
|
|
9
|
+
* needs no host package. `getEnvValue` returns the first value that is `!== undefined` across
|
|
10
|
+
* `[ATOMIC_INTERCOM_GROUP, PI_INTERCOM_GROUP]`, so a defined-but-empty ATOMIC value
|
|
11
|
+
* deliberately shadows the legacy name and yields `""`; `resolveHomeGroup` then falls through
|
|
12
|
+
* on the empty string. `??` reproduces that exactly — do not "improve" it to `||`.
|
|
13
|
+
*/
|
|
14
|
+
function intercomGroupFromEnv(): string | undefined {
|
|
15
|
+
return process.env.ATOMIC_INTERCOM_GROUP ?? process.env.PI_INTERCOM_GROUP;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The implicit group every ungrouped session belongs to. */
|
|
19
|
+
export const DEFAULT_GROUP = "default";
|
|
20
|
+
|
|
21
|
+
const RESERVED_RUNTIME_GROUPS = new Set(["true", "auto"]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Normalize an intercom group id. Undefined, empty, or whitespace-only values
|
|
25
|
+
* collapse to the shared {@link DEFAULT_GROUP} so ungrouped sessions all compare
|
|
26
|
+
* equal and can still talk to each other (backward compatible).
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeGroup(value?: string | null): string {
|
|
29
|
+
if (typeof value !== "string") return DEFAULT_GROUP;
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
return trimmed.length > 0 ? trimmed : DEFAULT_GROUP;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validate a group requested by the runtime join action. `default` is a real,
|
|
36
|
+
* explicit group name; `true` and `auto` remain reserved for subagent auto-grouping.
|
|
37
|
+
*/
|
|
38
|
+
export function validateRuntimeGroup(value: unknown): string {
|
|
39
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
40
|
+
throw new Error("A non-empty intercom group name is required");
|
|
41
|
+
}
|
|
42
|
+
const group = normalizeGroup(value);
|
|
43
|
+
if (RESERVED_RUNTIME_GROUPS.has(group.toLowerCase())) {
|
|
44
|
+
throw new Error(`Intercom group name "${group}" is reserved; choose another name`);
|
|
45
|
+
}
|
|
46
|
+
return group;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface HomeGroupContext {
|
|
50
|
+
orchestrationContext?: { intercomGroup?: string } | undefined;
|
|
51
|
+
subagentPolicy?: { intercomGroup?: string } | undefined;
|
|
52
|
+
/** Accepted from the live extension context but intentionally ignored for home resolution. */
|
|
53
|
+
sessionManager?: { getSessionId(): string } | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a session's static home intercom group with precedence (most specific first):
|
|
58
|
+
* admitted subagentPolicy.intercomGroup > orchestrationContext.intercomGroup >
|
|
59
|
+
* env ATOMIC_INTERCOM_GROUP (also PI_INTERCOM_GROUP legacy) > config.json "group" > "default".
|
|
60
|
+
* Always returns a concrete normalized string.
|
|
61
|
+
*
|
|
62
|
+
* Runtime join entries are deliberately not read here. They are child-inheritance state, not
|
|
63
|
+
* home state, so a reconnect cannot turn the joined group into the destination of `leave`.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveHomeGroup(
|
|
66
|
+
config: { group?: string } | undefined,
|
|
67
|
+
ctx?: HomeGroupContext | null,
|
|
68
|
+
): string {
|
|
69
|
+
const fromPolicy = ctx?.subagentPolicy?.intercomGroup;
|
|
70
|
+
if (typeof fromPolicy === "string" && fromPolicy.trim().length > 0) {
|
|
71
|
+
return normalizeGroup(fromPolicy);
|
|
72
|
+
}
|
|
73
|
+
const fromContext = ctx?.orchestrationContext?.intercomGroup;
|
|
74
|
+
if (typeof fromContext === "string" && fromContext.trim().length > 0) {
|
|
75
|
+
return normalizeGroup(fromContext);
|
|
76
|
+
}
|
|
77
|
+
const fromEnv = intercomGroupFromEnv();
|
|
78
|
+
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
|
|
79
|
+
return normalizeGroup(fromEnv);
|
|
80
|
+
}
|
|
81
|
+
if (config && typeof config.group === "string" && config.group.trim().length > 0) {
|
|
82
|
+
return normalizeGroup(config.group);
|
|
83
|
+
}
|
|
84
|
+
return DEFAULT_GROUP;
|
|
85
|
+
}
|