pi-sessions 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -37
- package/extensions/pi-sessions.ts +125 -0
- package/extensions/session-ask/agent.ts +34 -14
- package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
- package/extensions/session-ask/navigate.ts +3 -1
- package/extensions/session-ask/presenter.ts +23 -0
- package/extensions/session-ask/renderer.ts +63 -0
- package/extensions/session-ask/tool-contract.ts +27 -0
- package/extensions/session-ask/view-model.ts +18 -0
- package/extensions/session-auto-title/generate.ts +18 -18
- package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +67 -46
- package/extensions/session-auto-title/model.ts +25 -17
- package/extensions/session-auto-title/retitle.ts +26 -12
- package/extensions/session-auto-title/wizard.ts +12 -6
- package/extensions/session-handoff/board-view-model.ts +181 -0
- package/extensions/session-handoff/board.ts +663 -0
- package/extensions/session-handoff/bootstrap.ts +174 -0
- package/extensions/session-handoff/extract.ts +101 -122
- package/extensions/session-handoff/install.ts +225 -0
- package/extensions/session-handoff/kickoff.ts +92 -0
- package/extensions/session-handoff/launch/backend.ts +16 -0
- package/extensions/session-handoff/launch/deferred.ts +20 -0
- package/extensions/session-handoff/launch/ghostty.ts +99 -0
- package/extensions/session-handoff/launch/resolution.ts +57 -0
- package/extensions/session-handoff/launch/shell.ts +7 -0
- package/extensions/session-handoff/launch/tmux.ts +31 -0
- package/extensions/session-handoff/launch-options.ts +61 -0
- package/extensions/session-handoff/launch-target.ts +91 -0
- package/extensions/session-handoff/metadata.ts +152 -63
- package/extensions/session-handoff/model.ts +58 -0
- package/extensions/session-handoff/query.ts +0 -1
- package/extensions/session-handoff/receipt.ts +96 -0
- package/extensions/session-handoff/review.ts +2 -28
- package/extensions/session-handoff/spawn.ts +76 -181
- package/extensions/session-handoff/strong-modal.ts +13 -0
- package/extensions/session-handoff/tool-contract.ts +12 -0
- package/extensions/session-handoff/tool-presenter.ts +35 -0
- package/extensions/session-handoff/tool-renderer.ts +66 -0
- package/extensions/session-handoff/tool-schema.ts +54 -0
- package/extensions/session-handoff/tool-view-model.ts +49 -0
- package/extensions/session-handoff/tool.ts +206 -0
- package/extensions/session-handoff/ui.ts +66 -0
- package/extensions/session-hooks/install.ts +75 -0
- package/extensions/{session-index.ts → session-index/install.ts} +9 -7
- package/extensions/session-messaging/broker/process.ts +21 -28
- package/extensions/session-messaging/install.ts +78 -0
- package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
- package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
- package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
- package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
- package/extensions/session-messaging/pi/message-contracts.ts +32 -0
- package/extensions/session-messaging/pi/renderer.ts +19 -12
- package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
- package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
- package/extensions/session-messaging/pi/service.ts +262 -81
- package/extensions/session-messaging/pi/tools.ts +184 -40
- package/extensions/session-search/extract.ts +60 -14
- package/extensions/session-search/hooks.ts +10 -13
- package/extensions/session-search/install.ts +373 -0
- package/extensions/session-search/renderer.ts +195 -0
- package/extensions/session-search/tool-contract.ts +51 -0
- package/extensions/shared/composition.ts +20 -0
- package/extensions/shared/errors.ts +3 -0
- package/extensions/shared/model-resolution.ts +73 -0
- package/extensions/shared/model-runtime.ts +33 -0
- package/extensions/shared/model.ts +7 -10
- package/extensions/shared/rendering/collapsible-text.ts +89 -0
- package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
- package/extensions/shared/rendering/theme.ts +5 -0
- package/extensions/shared/session-broker/client.ts +30 -29
- package/extensions/shared/session-broker/protocol.ts +128 -10
- package/extensions/shared/session-index/common.ts +5 -5
- package/extensions/shared/session-index/lineage.ts +0 -3
- package/extensions/shared/session-index/schema.ts +0 -1
- package/extensions/shared/session-index/scoring.ts +1 -2
- package/extensions/shared/session-index/search.ts +11 -4
- package/extensions/shared/session-index/store.ts +4 -9
- package/extensions/shared/settings.ts +80 -38
- package/extensions/shared/text.ts +5 -0
- package/extensions/shared/thinking-levels.ts +22 -0
- package/extensions/shared/tmux.ts +205 -0
- package/extensions/subagents/cancel.ts +111 -0
- package/extensions/subagents/classify.ts +52 -0
- package/extensions/subagents/install.ts +344 -0
- package/extensions/subagents/launch-target.ts +75 -0
- package/extensions/subagents/ledger.ts +217 -0
- package/extensions/subagents/reconcile.ts +386 -0
- package/extensions/subagents/report-message-presenter.ts +36 -0
- package/extensions/subagents/report-message-renderer.ts +21 -0
- package/extensions/subagents/report-message-view-model.ts +29 -0
- package/extensions/subagents/report.ts +159 -0
- package/extensions/subagents/roster.ts +262 -0
- package/extensions/subagents/wake.ts +173 -0
- package/images/handoff-board-subagents.png +0 -0
- package/images/handoff-board-user-sessions.png +0 -0
- package/images/handoff-subagent-report.png +0 -0
- package/images/session-handoff-tool.png +0 -0
- package/package.json +10 -16
- package/extensions/session-handoff.ts +0 -741
- package/extensions/session-hooks.ts +0 -75
- package/extensions/session-messaging/pi/message-view.ts +0 -131
- package/extensions/session-messaging.ts +0 -30
- package/extensions/session-search.ts +0 -419
- package/extensions/shared/session-broker/active.ts +0 -26
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { formatError } from "../../shared/errors.ts";
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../../shared/session-broker/active.ts";
|
|
7
|
-
import {
|
|
8
|
-
INCOMING_SESSION_MESSAGE_EVENT,
|
|
9
|
-
type SessionMessageSendResult,
|
|
5
|
+
INCOMING_SESSION_ENVELOPE_EVENT,
|
|
6
|
+
type SessionEnvelopeSendResult,
|
|
10
7
|
SessionMessagingClient,
|
|
11
8
|
} from "../../shared/session-broker/client.ts";
|
|
12
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
OutboundSessionEnvelope,
|
|
11
|
+
SessionCancelEnvelope,
|
|
12
|
+
SessionEnvelope,
|
|
13
|
+
SessionEnvelopeSendFailureReason,
|
|
14
|
+
SessionMessageEnvelope,
|
|
15
|
+
SessionSubagentReportEnvelope,
|
|
16
|
+
TaskReport,
|
|
17
|
+
} from "../../shared/session-broker/protocol.ts";
|
|
13
18
|
import type {
|
|
14
19
|
SessionLineageRelation,
|
|
15
20
|
SessionLineageRow,
|
|
@@ -20,12 +25,10 @@ import {
|
|
|
20
25
|
withSessionIndex,
|
|
21
26
|
} from "../../shared/session-index/index.ts";
|
|
22
27
|
import { spawnSessionMessagingBrokerIfNeeded } from "../broker/spawn.ts";
|
|
23
|
-
import type { IncomingSessionMessageRuntime } from "./incoming-runtime.ts";
|
|
24
28
|
import type { ReceivedMessageEndpoint, ReceivedMessageEntry } from "./message-contracts.ts";
|
|
25
29
|
|
|
26
|
-
export const MESSAGE_SENT_CUSTOM_TYPE = "pi-sessions.message_sent";
|
|
27
|
-
|
|
28
30
|
const RECONNECT_DELAYS_MS = [250, 500, 1_000, 2_000, 5_000, 10_000, 30_000] as const;
|
|
31
|
+
const SESSION_WAIT_POLL_MS = 100;
|
|
29
32
|
|
|
30
33
|
export interface SendMessageRequest {
|
|
31
34
|
target: string;
|
|
@@ -34,6 +37,49 @@ export interface SendMessageRequest {
|
|
|
34
37
|
sourceToolCallId?: string | undefined;
|
|
35
38
|
}
|
|
36
39
|
|
|
40
|
+
export type SendMessageResult =
|
|
41
|
+
| {
|
|
42
|
+
messageId: string;
|
|
43
|
+
delivered: false;
|
|
44
|
+
reason?: SessionEnvelopeSendFailureReason | undefined;
|
|
45
|
+
error?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
| {
|
|
48
|
+
messageId: string;
|
|
49
|
+
delivered: true;
|
|
50
|
+
target: ReceivedMessageEndpoint;
|
|
51
|
+
relation?: SessionLineageRelation | undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type CancelSessionResult =
|
|
55
|
+
| {
|
|
56
|
+
kind: "transport";
|
|
57
|
+
cancelId: string;
|
|
58
|
+
delivered: false;
|
|
59
|
+
reason?: SessionEnvelopeSendFailureReason | undefined;
|
|
60
|
+
error?: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
kind: "transport";
|
|
64
|
+
cancelId: string;
|
|
65
|
+
delivered: true;
|
|
66
|
+
target: ReceivedMessageEndpoint;
|
|
67
|
+
relation?: SessionLineageRelation | undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type IncomingMessageHandler = (envelope: SessionMessageEnvelope) => Promise<void> | void;
|
|
71
|
+
export type IncomingCancelHandler = (envelope: SessionCancelEnvelope) => Promise<void> | void;
|
|
72
|
+
export type IncomingSubagentReportHandler = (
|
|
73
|
+
envelope: SessionSubagentReportEnvelope,
|
|
74
|
+
) => Promise<void> | void;
|
|
75
|
+
|
|
76
|
+
export type SendSubagentReportResult = SessionEnvelopeSendResult;
|
|
77
|
+
|
|
78
|
+
export interface SendSubagentReportRequest extends TaskReport {
|
|
79
|
+
target: string;
|
|
80
|
+
reportId: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
37
83
|
interface IncomingMessageIndexContext {
|
|
38
84
|
source: ReceivedMessageEndpoint;
|
|
39
85
|
target: ReceivedMessageEndpoint;
|
|
@@ -42,68 +88,171 @@ interface IncomingMessageIndexContext {
|
|
|
42
88
|
|
|
43
89
|
export class SessionMessagingService {
|
|
44
90
|
private readonly indexPath: string;
|
|
45
|
-
private readonly incomingRuntime: IncomingSessionMessageRuntime;
|
|
46
|
-
private readonly appendEntry: (customType: string, data: unknown) => void;
|
|
47
91
|
private relationBySessionId = new Map<string, SessionLineageRelation | undefined>();
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
private incomingMessageHandler: IncomingMessageHandler | undefined;
|
|
93
|
+
private incomingCancelHandler: IncomingCancelHandler | undefined;
|
|
94
|
+
private incomingSubagentReportHandler: IncomingSubagentReportHandler | undefined;
|
|
95
|
+
private readonly connection = new BrokerConnection((envelope) => this.acceptIncoming(envelope));
|
|
51
96
|
|
|
52
|
-
constructor(
|
|
53
|
-
indexPath: string,
|
|
54
|
-
incomingRuntime: IncomingSessionMessageRuntime,
|
|
55
|
-
appendEntry: (customType: string, data: unknown) => void,
|
|
56
|
-
) {
|
|
97
|
+
constructor(indexPath: string) {
|
|
57
98
|
this.indexPath = indexPath;
|
|
58
|
-
this.incomingRuntime = incomingRuntime;
|
|
59
|
-
this.appendEntry = appendEntry;
|
|
60
99
|
}
|
|
61
100
|
|
|
62
101
|
async start(ctx: ExtensionContext): Promise<void> {
|
|
63
102
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
64
103
|
this.refreshCachedRelations(sessionId);
|
|
65
|
-
setActiveSessionBrokerConnection(this.connection);
|
|
66
104
|
await this.connection.start(sessionId);
|
|
67
105
|
}
|
|
68
106
|
|
|
69
107
|
stop(): void {
|
|
70
|
-
setActiveSessionBrokerConnection(undefined);
|
|
71
108
|
this.relationBySessionId.clear();
|
|
72
109
|
this.connection.stop();
|
|
73
110
|
}
|
|
74
111
|
|
|
75
|
-
|
|
112
|
+
onIncomingMessage(handler: IncomingMessageHandler): void {
|
|
113
|
+
if (this.incomingMessageHandler) {
|
|
114
|
+
throw new Error("An incoming message handler is already registered.");
|
|
115
|
+
}
|
|
116
|
+
this.incomingMessageHandler = handler;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onIncomingCancel(handler: IncomingCancelHandler): void {
|
|
120
|
+
if (this.incomingCancelHandler) {
|
|
121
|
+
throw new Error("An incoming cancel handler is already registered.");
|
|
122
|
+
}
|
|
123
|
+
this.incomingCancelHandler = handler;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
onIncomingSubagentReport(handler: IncomingSubagentReportHandler): void {
|
|
127
|
+
if (this.incomingSubagentReportHandler) {
|
|
128
|
+
throw new Error("An incoming subagent report handler is already registered.");
|
|
129
|
+
}
|
|
130
|
+
this.incomingSubagentReportHandler = handler;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async listSessions(): Promise<string[]> {
|
|
134
|
+
return this.connection.listSessionIds();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async waitForSession(sessionId: string, timeoutMs: number): Promise<boolean> {
|
|
138
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
139
|
+
throw new Error("Session wait timeout must be a non-negative finite number.");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const deadline = Date.now() + timeoutMs;
|
|
143
|
+
while (true) {
|
|
144
|
+
if ((await this.listSessions()).includes(sessionId)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const remainingMs = deadline - Date.now();
|
|
149
|
+
if (remainingMs <= 0) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
await delay(Math.min(SESSION_WAIT_POLL_MS, remainingMs));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async sendMessage(request: SendMessageRequest): Promise<SendMessageResult> {
|
|
76
157
|
const relation = this.getCachedRelationTo(request.target, true);
|
|
77
|
-
const sentAt = new Date().toISOString();
|
|
78
158
|
const messageId = randomUUID();
|
|
79
|
-
const result = await this.connection.
|
|
159
|
+
const result = await this.connection.sendEnvelope({
|
|
160
|
+
target: request.target,
|
|
161
|
+
envelope: {
|
|
162
|
+
kind: "message",
|
|
163
|
+
messageId,
|
|
164
|
+
body: request.body,
|
|
165
|
+
...(request.requestResponse === undefined
|
|
166
|
+
? {}
|
|
167
|
+
: { requestResponse: request.requestResponse }),
|
|
168
|
+
...(request.sourceToolCallId === undefined
|
|
169
|
+
? {}
|
|
170
|
+
: { sourceToolCallId: request.sourceToolCallId }),
|
|
171
|
+
sentAt: new Date().toISOString(),
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!result.delivered) {
|
|
176
|
+
return {
|
|
177
|
+
messageId,
|
|
178
|
+
delivered: false,
|
|
179
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
180
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
80
185
|
messageId,
|
|
186
|
+
delivered: true,
|
|
187
|
+
target: this.getTargetEndpoint(request.target),
|
|
188
|
+
...(relation === undefined ? {} : { relation }),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async sendSubagentReport(request: SendSubagentReportRequest): Promise<SendSubagentReportResult> {
|
|
193
|
+
return this.connection.sendEnvelope({
|
|
81
194
|
target: request.target,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
195
|
+
envelope: {
|
|
196
|
+
kind: "subagent_report",
|
|
197
|
+
reportId: request.reportId,
|
|
198
|
+
status: request.status,
|
|
199
|
+
summary: request.summary,
|
|
200
|
+
...(request.details ? { details: request.details } : {}),
|
|
201
|
+
...(request.references ? { references: request.references } : {}),
|
|
202
|
+
...(request.nextSteps ? { nextSteps: request.nextSteps } : {}),
|
|
203
|
+
sentAt: new Date().toISOString(),
|
|
204
|
+
},
|
|
86
205
|
});
|
|
206
|
+
}
|
|
87
207
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
208
|
+
async cancelSession(sessionId: string): Promise<CancelSessionResult> {
|
|
209
|
+
const relation = this.getCachedRelationTo(sessionId, true);
|
|
210
|
+
const cancelId = randomUUID();
|
|
211
|
+
const result = await this.connection.sendEnvelope({
|
|
212
|
+
target: sessionId,
|
|
213
|
+
envelope: {
|
|
214
|
+
kind: "cancel",
|
|
215
|
+
cancelId,
|
|
216
|
+
sentAt: new Date().toISOString(),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (!result.delivered) {
|
|
221
|
+
return {
|
|
222
|
+
kind: "transport",
|
|
223
|
+
cancelId,
|
|
224
|
+
delivered: false,
|
|
225
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
226
|
+
...(result.error === undefined ? {} : { error: result.error }),
|
|
227
|
+
};
|
|
98
228
|
}
|
|
99
229
|
|
|
100
|
-
return
|
|
230
|
+
return {
|
|
231
|
+
kind: "transport",
|
|
232
|
+
cancelId,
|
|
233
|
+
delivered: true,
|
|
234
|
+
target: this.getTargetEndpoint(sessionId),
|
|
235
|
+
...(relation === undefined ? {} : { relation }),
|
|
236
|
+
};
|
|
101
237
|
}
|
|
102
238
|
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
239
|
+
buildReceivedMessage(message: SessionMessageEnvelope): ReceivedMessageEntry {
|
|
240
|
+
const context = this.getIncomingMessageIndexContext(message.source, message.target);
|
|
241
|
+
return {
|
|
242
|
+
messageId: message.messageId,
|
|
243
|
+
source: context.source,
|
|
244
|
+
target: context.target,
|
|
245
|
+
body: message.body,
|
|
246
|
+
sentAt: message.sentAt,
|
|
247
|
+
receivedAt: new Date().toISOString(),
|
|
248
|
+
...(message.requestResponse === undefined
|
|
249
|
+
? {}
|
|
250
|
+
: { requestResponse: message.requestResponse }),
|
|
251
|
+
...(message.sourceToolCallId === undefined
|
|
252
|
+
? {}
|
|
253
|
+
: { sourceToolCallId: message.sourceToolCallId }),
|
|
254
|
+
...(context.relation === undefined ? {} : { relation: context.relation }),
|
|
255
|
+
};
|
|
107
256
|
}
|
|
108
257
|
|
|
109
258
|
getCachedRelationTo(
|
|
@@ -135,23 +284,46 @@ export class SessionMessagingService {
|
|
|
135
284
|
return this.relationBySessionId.get(sessionId);
|
|
136
285
|
}
|
|
137
286
|
|
|
138
|
-
private
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
287
|
+
private async acceptIncoming(envelope: SessionEnvelope): Promise<IncomingAcceptance> {
|
|
288
|
+
try {
|
|
289
|
+
switch (envelope.kind) {
|
|
290
|
+
case "message": {
|
|
291
|
+
if (!this.incomingMessageHandler) {
|
|
292
|
+
throw new Error("Target session has no incoming message handler.");
|
|
293
|
+
}
|
|
294
|
+
await this.incomingMessageHandler(envelope);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "cancel": {
|
|
298
|
+
if (!this.incomingCancelHandler) {
|
|
299
|
+
throw new Error("Target session has no incoming cancel handler.");
|
|
300
|
+
}
|
|
301
|
+
await this.incomingCancelHandler(envelope);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case "subagent_report": {
|
|
305
|
+
if (!this.incomingSubagentReportHandler) {
|
|
306
|
+
throw new Error("Target session has no incoming subagent report handler.");
|
|
307
|
+
}
|
|
308
|
+
await this.incomingSubagentReportHandler(envelope);
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return { delivered: true };
|
|
313
|
+
} catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
delivered: false,
|
|
316
|
+
error: formatError(error),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
private getTargetEndpoint(targetSessionId: string): ReceivedMessageEndpoint {
|
|
322
|
+
return (
|
|
323
|
+
withSessionIndex(this.indexPath, { mode: "read", required: false }, ({ db }) =>
|
|
324
|
+
buildReceivedMessageEndpoint(targetSessionId, getSessionById(db, targetSessionId)),
|
|
325
|
+
) ?? buildReceivedMessageEndpoint(targetSessionId)
|
|
326
|
+
);
|
|
155
327
|
}
|
|
156
328
|
|
|
157
329
|
private getIncomingMessageIndexContext(
|
|
@@ -207,7 +379,9 @@ export class SessionMessagingService {
|
|
|
207
379
|
}
|
|
208
380
|
}
|
|
209
381
|
|
|
210
|
-
|
|
382
|
+
type IncomingAcceptance = { delivered: true } | { delivered: false; error: string };
|
|
383
|
+
|
|
384
|
+
class BrokerConnection {
|
|
211
385
|
private client: SessionMessagingClient | undefined;
|
|
212
386
|
private sessionId: string | undefined;
|
|
213
387
|
private active = false;
|
|
@@ -216,7 +390,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
|
|
|
216
390
|
private connectPromise: Promise<void> | undefined;
|
|
217
391
|
|
|
218
392
|
constructor(
|
|
219
|
-
private readonly onIncoming: (
|
|
393
|
+
private readonly onIncoming: (envelope: SessionEnvelope) => Promise<IncomingAcceptance>,
|
|
220
394
|
) {}
|
|
221
395
|
|
|
222
396
|
get currentSessionId(): string | undefined {
|
|
@@ -243,25 +417,26 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
|
|
|
243
417
|
return this.requireClient().listSessionIds();
|
|
244
418
|
}
|
|
245
419
|
|
|
246
|
-
async
|
|
247
|
-
messageId: string;
|
|
420
|
+
async sendEnvelope(options: {
|
|
248
421
|
target: string;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
requestResponse?: boolean | undefined;
|
|
252
|
-
sourceToolCallId?: string | undefined;
|
|
253
|
-
}): Promise<SessionMessageSendResult> {
|
|
422
|
+
envelope: OutboundSessionEnvelope;
|
|
423
|
+
}): Promise<SessionEnvelopeSendResult> {
|
|
254
424
|
await this.ensureConnectedNow();
|
|
255
|
-
return this.requireClient().
|
|
425
|
+
return this.requireClient().sendEnvelope(options);
|
|
256
426
|
}
|
|
257
427
|
|
|
258
|
-
|
|
428
|
+
private async acceptIncoming(
|
|
429
|
+
client: SessionMessagingClient,
|
|
259
430
|
requestId: string,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
431
|
+
envelope: SessionEnvelope,
|
|
432
|
+
): Promise<void> {
|
|
433
|
+
const result = await this.onIncoming(envelope);
|
|
434
|
+
if (this.client !== client || !client.isConnected) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
263
438
|
try {
|
|
264
|
-
|
|
439
|
+
client.acknowledgeIncoming(requestId, result);
|
|
265
440
|
} catch {}
|
|
266
441
|
}
|
|
267
442
|
|
|
@@ -299,7 +474,9 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
|
|
|
299
474
|
|
|
300
475
|
await spawnSessionMessagingBrokerIfNeeded();
|
|
301
476
|
const client = new SessionMessagingClient();
|
|
302
|
-
client.on(
|
|
477
|
+
client.on(INCOMING_SESSION_ENVELOPE_EVENT, (requestId: string, envelope: SessionEnvelope) => {
|
|
478
|
+
void this.acceptIncoming(client, requestId, envelope);
|
|
479
|
+
});
|
|
303
480
|
client.on("disconnect", () => {
|
|
304
481
|
if (this.client === client) {
|
|
305
482
|
this.client = undefined;
|
|
@@ -317,7 +494,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
|
|
|
317
494
|
return;
|
|
318
495
|
}
|
|
319
496
|
|
|
320
|
-
const
|
|
497
|
+
const delayMs = RECONNECT_DELAYS_MS[this.reconnectDelayIndex] ?? 30_000;
|
|
321
498
|
this.reconnectDelayIndex = Math.min(
|
|
322
499
|
this.reconnectDelayIndex + 1,
|
|
323
500
|
RECONNECT_DELAYS_MS.length - 1,
|
|
@@ -325,7 +502,7 @@ class BrokerConnection implements ActiveSessionBrokerConnection {
|
|
|
325
502
|
this.reconnectTimer = setTimeout(() => {
|
|
326
503
|
this.reconnectTimer = undefined;
|
|
327
504
|
this.ensureConnectedNow().catch(() => {});
|
|
328
|
-
},
|
|
505
|
+
}, delayMs);
|
|
329
506
|
}
|
|
330
507
|
|
|
331
508
|
private clearReconnectTimer(): void {
|
|
@@ -368,3 +545,7 @@ function getRelationMapForSession(
|
|
|
368
545
|
) ?? new Map()
|
|
369
546
|
);
|
|
370
547
|
}
|
|
548
|
+
|
|
549
|
+
function delay(ms: number): Promise<void> {
|
|
550
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
551
|
+
}
|