@band-ai/sdk 0.1.2

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.
Files changed (91) hide show
  1. package/dist/CodexAdapter-A1k_LMdZ.d.ts +1278 -0
  2. package/dist/CodexAdapter-BbZD-nBd.d.cts +1278 -0
  3. package/dist/adapters.cjs +10643 -0
  4. package/dist/adapters.d.cts +217 -0
  5. package/dist/adapters.d.ts +217 -0
  6. package/dist/adapters.js +92 -0
  7. package/dist/backends-BnOLsG7w.d.cts +20 -0
  8. package/dist/backends-C4n6cKOf.d.ts +20 -0
  9. package/dist/chunk-3BKAC4OZ.js +44 -0
  10. package/dist/chunk-5CXIMAH2.js +52 -0
  11. package/dist/chunk-6AJA2OPC.js +3682 -0
  12. package/dist/chunk-A3TDK6GP.js +6674 -0
  13. package/dist/chunk-AVVDPXP4.js +39 -0
  14. package/dist/chunk-EDPKUUJL.js +177 -0
  15. package/dist/chunk-FUODYQRE.js +2562 -0
  16. package/dist/chunk-GVEQ3RAH.js +373 -0
  17. package/dist/chunk-INA6YZTZ.js +88 -0
  18. package/dist/chunk-IZPV6QS6.js +115 -0
  19. package/dist/chunk-LY55KYVI.js +0 -0
  20. package/dist/chunk-NIAQBANR.js +577 -0
  21. package/dist/chunk-OD2G5LFQ.js +78 -0
  22. package/dist/chunk-SFVKOEQH.js +103 -0
  23. package/dist/chunk-SG4WLD2H.js +1093 -0
  24. package/dist/chunk-YR3BOQNW.js +39 -0
  25. package/dist/chunk-ZZJRRBPQ.js +82 -0
  26. package/dist/claude-CcnQ5OUC.d.ts +53 -0
  27. package/dist/claude-CwTAxhwI.d.cts +53 -0
  28. package/dist/config.cjs +164 -0
  29. package/dist/config.d.cts +19 -0
  30. package/dist/config.d.ts +19 -0
  31. package/dist/config.js +9 -0
  32. package/dist/converters.cjs +1086 -0
  33. package/dist/converters.d.cts +143 -0
  34. package/dist/converters.d.ts +143 -0
  35. package/dist/converters.js +468 -0
  36. package/dist/core.cjs +185 -0
  37. package/dist/core.d.cts +22 -0
  38. package/dist/core.d.ts +22 -0
  39. package/dist/core.js +24 -0
  40. package/dist/customTools-BzF0IISO.d.cts +10 -0
  41. package/dist/customTools-BzF0IISO.d.ts +10 -0
  42. package/dist/dtos-DUpbIu8k.d.cts +163 -0
  43. package/dist/dtos-DUpbIu8k.d.ts +163 -0
  44. package/dist/history-ByessXNq.d.ts +24 -0
  45. package/dist/history-i6yN7neC.d.cts +24 -0
  46. package/dist/index.cjs +13914 -0
  47. package/dist/index.d.cts +63 -0
  48. package/dist/index.d.ts +63 -0
  49. package/dist/index.js +198 -0
  50. package/dist/linear.cjs +2207 -0
  51. package/dist/linear.d.cts +167 -0
  52. package/dist/linear.d.ts +167 -0
  53. package/dist/linear.js +2139 -0
  54. package/dist/logger-CABQOnlR.d.cts +21 -0
  55. package/dist/logger-CABQOnlR.d.ts +21 -0
  56. package/dist/mcp-claude.cjs +584 -0
  57. package/dist/mcp-claude.d.cts +4 -0
  58. package/dist/mcp-claude.d.ts +4 -0
  59. package/dist/mcp-claude.js +11 -0
  60. package/dist/mcp.cjs +1247 -0
  61. package/dist/mcp.d.cts +97 -0
  62. package/dist/mcp.d.ts +97 -0
  63. package/dist/mcp.js +27 -0
  64. package/dist/opencode-BBcDchcN.d.cts +159 -0
  65. package/dist/opencode-DthQQUsZ.d.ts +159 -0
  66. package/dist/pagination-BPiys10t.d.cts +25 -0
  67. package/dist/pagination-CcDkVFxK.d.ts +25 -0
  68. package/dist/protocols-CInhtFpA.d.cts +116 -0
  69. package/dist/protocols-DrhzOH6K.d.ts +116 -0
  70. package/dist/rest.cjs +1159 -0
  71. package/dist/rest.d.cts +144 -0
  72. package/dist/rest.d.ts +144 -0
  73. package/dist/rest.js +18 -0
  74. package/dist/runtime.cjs +5353 -0
  75. package/dist/runtime.d.cts +276 -0
  76. package/dist/runtime.d.ts +276 -0
  77. package/dist/runtime.js +274 -0
  78. package/dist/schemas-BpEBkq5V.d.cts +592 -0
  79. package/dist/schemas-vos1AaBc.d.ts +592 -0
  80. package/dist/sdk-HJUVSSWA.js +10 -0
  81. package/dist/simpleAdapter-9OE_p6QW.d.ts +29 -0
  82. package/dist/simpleAdapter-DqhvjsGr.d.cts +29 -0
  83. package/dist/testing.cjs +217 -0
  84. package/dist/testing.d.cts +126 -0
  85. package/dist/testing.d.ts +126 -0
  86. package/dist/testing.js +186 -0
  87. package/dist/types-7jVzOeOP.d.ts +446 -0
  88. package/dist/types-CSHFipIc.d.cts +331 -0
  89. package/dist/types-CovcHpYf.d.cts +446 -0
  90. package/dist/types-CxII3XkA.d.ts +331 -0
  91. package/package.json +189 -0
@@ -0,0 +1,2207 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/linear/index.ts
31
+ var linear_exports = {};
32
+ __export(linear_exports, {
33
+ DEFAULT_STATUS_MAPPING: () => DEFAULT_STATUS_MAPPING,
34
+ buildLinearAuthorizationHeader: () => buildLinearAuthorizationHeader,
35
+ completeLinearSession: () => completeLinearSession,
36
+ createInProcessLinearBridgeDispatcher: () => createInProcessLinearBridgeDispatcher,
37
+ createInlineLinearBridgeDispatcher: () => createInlineLinearBridgeDispatcher,
38
+ createLinearBridgeRuntime: () => createLinearBridgeRuntime,
39
+ createLinearClient: () => createLinearClient,
40
+ createLinearTools: () => createLinearTools,
41
+ createLinearWebhookHandler: () => createLinearWebhookHandler,
42
+ createSqliteSessionRoomStore: () => createSqliteSessionRoomStore,
43
+ dedupeHandles: () => dedupeHandles,
44
+ handleAgentSessionEvent: () => handleAgentSessionEvent,
45
+ isLinearApiKey: () => isLinearApiKey,
46
+ postAction: () => postAction,
47
+ postElicitation: () => postElicitation,
48
+ postError: () => postError,
49
+ postResponse: () => postResponse,
50
+ postThought: () => postThought,
51
+ stripHandlePrefix: () => stripHandlePrefix,
52
+ updatePlan: () => updatePlan
53
+ });
54
+ module.exports = __toCommonJS(linear_exports);
55
+
56
+ // src/integrations/linear/bridge/handler.ts
57
+ var import_sdk2 = require("@linear/sdk");
58
+
59
+ // src/core/errors.ts
60
+ var ThenvoiSdkError = class extends Error {
61
+ constructor(message, cause) {
62
+ super(message, cause !== void 0 ? { cause } : void 0);
63
+ this.name = "ThenvoiSdkError";
64
+ }
65
+ };
66
+ var UnsupportedFeatureError = class extends ThenvoiSdkError {
67
+ constructor(message) {
68
+ super(message);
69
+ this.name = "UnsupportedFeatureError";
70
+ }
71
+ };
72
+
73
+ // src/core/logger.ts
74
+ var noop = () => void 0;
75
+ var NoopLogger = class {
76
+ debug = noop;
77
+ info = noop;
78
+ warn = noop;
79
+ error = noop;
80
+ };
81
+
82
+ // src/integrations/linear/handles.ts
83
+ function stripHandlePrefix(handle) {
84
+ return handle.trim().replace(/^@+/, "").toLowerCase();
85
+ }
86
+ function dedupeHandles(handles) {
87
+ const deduped = [];
88
+ const seen = /* @__PURE__ */ new Set();
89
+ for (const handle of handles) {
90
+ const normalized = stripHandlePrefix(handle);
91
+ if (!normalized || seen.has(normalized)) {
92
+ continue;
93
+ }
94
+ seen.add(normalized);
95
+ deduped.push(normalized);
96
+ }
97
+ return deduped;
98
+ }
99
+
100
+ // src/integrations/linear/activities.ts
101
+ var import_sdk = require("@linear/sdk");
102
+ async function postActivity(client, sessionId, content) {
103
+ await client.createAgentActivity({
104
+ agentSessionId: sessionId,
105
+ content
106
+ });
107
+ }
108
+ async function postBodyActivity(client, sessionId, type, body) {
109
+ await postActivity(client, sessionId, { type, body });
110
+ }
111
+ async function postThought(client, sessionId, body) {
112
+ await postBodyActivity(client, sessionId, import_sdk.LinearDocument.AgentActivityType.Thought, body);
113
+ }
114
+ async function postError(client, sessionId, body) {
115
+ await postBodyActivity(client, sessionId, import_sdk.LinearDocument.AgentActivityType.Error, body);
116
+ }
117
+ async function postResponse(client, sessionId, body) {
118
+ await postBodyActivity(client, sessionId, import_sdk.LinearDocument.AgentActivityType.Response, body);
119
+ }
120
+ async function postElicitation(client, sessionId, body) {
121
+ await postBodyActivity(client, sessionId, import_sdk.LinearDocument.AgentActivityType.Elicitation, body);
122
+ }
123
+ async function postAction(client, sessionId, body) {
124
+ await postActivity(client, sessionId, {
125
+ type: import_sdk.LinearDocument.AgentActivityType.Action,
126
+ action: body,
127
+ parameter: ""
128
+ });
129
+ }
130
+ async function updatePlan(client, sessionId, steps) {
131
+ const planSummary = steps.map((step) => {
132
+ const icon = step.status === "completed" ? "\u2705" : step.status === "in_progress" ? "\u23F3" : step.status === "failed" ? "\u274C" : "\u2B1C";
133
+ return `${icon} ${step.title}`;
134
+ }).join("\n");
135
+ await postActivity(client, sessionId, {
136
+ type: import_sdk.LinearDocument.AgentActivityType.Thought,
137
+ body: `**Plan:**
138
+ ${planSummary}`
139
+ });
140
+ }
141
+
142
+ // src/integrations/linear/bridge/message.ts
143
+ function buildBridgeMessage(input) {
144
+ const header = input.action === "created" ? "[Linear]: Agent session created." : "[Linear]: Agent session updated.";
145
+ const userRequest = firstNonEmpty(
146
+ input.commentBody,
147
+ input.promptContext,
148
+ input.issueTitle ? `Please handle ${input.issueTitle}.` : null
149
+ ) ?? "Please handle this Linear request.";
150
+ const issueIdLine = input.issueId ? `issue_id: ${input.issueId}` : "issue_id: none";
151
+ const issueIdentifierLine = input.issueIdentifier ? `issue_identifier: ${input.issueIdentifier}` : "issue_identifier: none";
152
+ const issueTitleLine = input.issueTitle ? `issue_title: ${input.issueTitle}` : "issue_title: none";
153
+ const issueUrlLine = input.issueUrl ? `issue_url: ${input.issueUrl}` : "issue_url: none";
154
+ const issueTeamLine = firstNonEmpty(input.issueTeamKey, input.issueTeamName) ? `issue_team: ${firstNonEmpty(input.issueTeamKey, input.issueTeamName)}` : "issue_team: none";
155
+ const issueTeamIdLine = input.issueTeamId ? `issue_team_id: ${input.issueTeamId}` : "issue_team_id: none";
156
+ const issueStateLine = firstNonEmpty(input.issueStateName, input.issueStateType) ? `issue_state: ${firstNonEmpty(input.issueStateName, input.issueStateType)}` : "issue_state: none";
157
+ const issueStateIdLine = input.issueStateId ? `issue_state_id: ${input.issueStateId}` : "issue_state_id: none";
158
+ const issueStateTypeLine = input.issueStateType ? `issue_state_type: ${input.issueStateType}` : "issue_state_type: none";
159
+ const issueAssigneeLine = input.issueAssigneeName ? `issue_assignee: ${input.issueAssigneeName}` : "issue_assignee: none";
160
+ const issueAssigneeIdLine = input.issueAssigneeId ? `issue_assignee_id: ${input.issueAssigneeId}` : "issue_assignee_id: none";
161
+ const sessionStatusLine = input.sessionStatus ? `session_status: ${input.sessionStatus}` : "session_status: none";
162
+ const sessionTypeLine = input.sessionType ? `session_type: ${input.sessionType}` : "session_type: none";
163
+ const sessionCreatedLine = input.sessionCreatedAt ? `session_created_at: ${input.sessionCreatedAt}` : "session_created_at: none";
164
+ const sessionUpdatedLine = input.sessionUpdatedAt ? `session_updated_at: ${input.sessionUpdatedAt}` : "session_updated_at: none";
165
+ const appUserLine = input.appUserId ? `app_user_id: ${input.appUserId}` : "app_user_id: none";
166
+ const organizationLine = input.organizationId ? `organization_id: ${input.organizationId}` : "organization_id: none";
167
+ const oauthClientLine = input.oauthClientId ? `oauth_client_id: ${input.oauthClientId}` : "oauth_client_id: none";
168
+ const webhookIdLine = input.webhookId ? `webhook_id: ${input.webhookId}` : "webhook_id: none";
169
+ const webhookTimestampLine = typeof input.webhookTimestamp === "number" ? `webhook_timestamp: ${input.webhookTimestamp}` : "webhook_timestamp: none";
170
+ const commentIdLine = input.commentId ? `comment_id: ${input.commentId}` : "comment_id: none";
171
+ const promptContext = firstNonEmpty(input.promptContext) ?? "none";
172
+ const issueDescription = firstNonEmpty(input.issueDescription) ?? "none";
173
+ const commentBody = firstNonEmpty(input.commentBody) ?? "none";
174
+ const suggestedPeersLine = input.suggestedPeerHandles.length > 0 ? input.suggestedPeerHandles.map((handle) => ` - ${handle}`).join("\n") : " - none";
175
+ return `${userRequest}
176
+
177
+ ${header}
178
+
179
+ Linear session context:
180
+ - session_id: ${input.sessionId}
181
+ - ${sessionStatusLine}
182
+ - ${sessionTypeLine}
183
+ - ${sessionCreatedLine}
184
+ - ${sessionUpdatedLine}
185
+ - ${issueIdLine}
186
+ - ${issueIdentifierLine}
187
+ - ${issueTitleLine}
188
+ - ${issueUrlLine}
189
+ - ${issueTeamLine}
190
+ - ${issueTeamIdLine}
191
+ - ${issueStateLine}
192
+ - ${issueStateIdLine}
193
+ - ${issueStateTypeLine}
194
+ - ${issueAssigneeLine}
195
+ - ${issueAssigneeIdLine}
196
+ - inferred_session_intent: ${input.sessionIntent}
197
+ - writeback_mode: ${input.writebackMode}
198
+ - ${appUserLine}
199
+ - ${organizationLine}
200
+ - ${oauthClientLine}
201
+ - ${webhookIdLine}
202
+ - ${webhookTimestampLine}
203
+
204
+ Bridge responsibilities:
205
+ - own orchestration for this Linear session
206
+ - decide whether you can answer alone or need help
207
+ - discover and invite specialists only when needed
208
+ - keep Linear updated with meaningful milestones
209
+ - use linear_post_thought and linear_post_action for meaningful progress updates
210
+ - call linear_post_response when the work is actually finished
211
+ - when you delegate inside Thenvoi, include the relevant ticket context in the room message so the specialist can act without hidden state
212
+
213
+ Suggested peers available in the registry right now. They are not in the room yet:
214
+ ${suggestedPeersLine}
215
+
216
+ Prompt context:
217
+ ${promptContext}
218
+
219
+ Issue description:
220
+ ${issueDescription}
221
+
222
+ Linked comment:
223
+ - ${commentIdLine}
224
+ ${commentBody}`;
225
+ }
226
+ function extractPromptedResponseBody(payload) {
227
+ if (typeof payload !== "object" || payload === null) {
228
+ return "";
229
+ }
230
+ const record = payload;
231
+ const activity = record.agentActivity;
232
+ if (typeof activity !== "object" || activity === null) {
233
+ return "";
234
+ }
235
+ const content = activity.content;
236
+ if (typeof content !== "object" || content === null) {
237
+ return "";
238
+ }
239
+ const body = content.body;
240
+ return typeof body === "string" ? body : "";
241
+ }
242
+ function detectSessionIntent(input) {
243
+ if (input.issueStateType?.trim().toLowerCase() === "started") {
244
+ return "implementation";
245
+ }
246
+ const explicitDirective = [
247
+ input.promptContext,
248
+ input.commentBody
249
+ ].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase();
250
+ if (/(please implement|implement (?:this|it|now|the)|build (?:this|it|now|the)|code (?:this|it|now|the)|ship (?:it|this)|start coding|start implementation|tighten|adjust|refine)/.test(explicitDirective)) {
251
+ return "implementation";
252
+ }
253
+ return "planning";
254
+ }
255
+ function extractIssueTeamKey(issue) {
256
+ const team = extractNestedRecord(issue, "team");
257
+ return typeof team?.key === "string" ? team.key : null;
258
+ }
259
+ function extractIssueTeamName(issue) {
260
+ const team = extractNestedRecord(issue, "team");
261
+ return typeof team?.name === "string" ? team.name : null;
262
+ }
263
+ function extractIssueTeamId(issue) {
264
+ const issueRecord = extractRecord(issue);
265
+ if (typeof issueRecord?.teamId === "string") {
266
+ return issueRecord.teamId;
267
+ }
268
+ const team = extractNestedRecord(issue, "team");
269
+ return typeof team?.id === "string" ? team.id : null;
270
+ }
271
+ function extractIssueStateField(issue, field) {
272
+ const state = extractNestedRecord(issue, "state");
273
+ const value = state?.[field];
274
+ return typeof value === "string" ? value : null;
275
+ }
276
+ function extractIssueAssigneeField(issue, field) {
277
+ const assignee = extractNestedRecord(issue, "assignee");
278
+ const value = assignee?.[field];
279
+ return typeof value === "string" ? value : null;
280
+ }
281
+ function firstNonEmpty(...values) {
282
+ for (const value of values) {
283
+ if (!value) {
284
+ continue;
285
+ }
286
+ const trimmed = value.trim();
287
+ if (trimmed.length > 0) {
288
+ return trimmed;
289
+ }
290
+ }
291
+ return null;
292
+ }
293
+ function extractNestedRecord(value, key) {
294
+ const record = extractRecord(value);
295
+ const nested = record?.[key];
296
+ return extractRecord(nested);
297
+ }
298
+ function extractRecord(value) {
299
+ return typeof value === "object" && value !== null ? value : null;
300
+ }
301
+
302
+ // src/integrations/linear/bridge/handler.ts
303
+ var SUPPORTED_ACTIONS = /* @__PURE__ */ new Set(["created", "updated", "canceled", "prompted"]);
304
+ var MAX_PEER_LOOKUP_PAGES = 25;
305
+ var PEER_PAGE_SIZE = 100;
306
+ var RECOVERED_ROOM_EVENT_RETRY_LIMIT = 2;
307
+ var RECOVERED_ROOM_EVENT_RETRY_BASE_DELAY_MS = 1e3;
308
+ function createLinearBridgeRuntime() {
309
+ return {
310
+ roomResolutionLocks: /* @__PURE__ */ new Map(),
311
+ resolvedHostHandleCache: /* @__PURE__ */ new WeakMap(),
312
+ authenticatedHostHandleCache: /* @__PURE__ */ new WeakMap()
313
+ };
314
+ }
315
+ function getAgentSessionEventKey(payload) {
316
+ return [
317
+ payload.agentSession.id,
318
+ normalizeAction(payload.action) ?? String(payload.action ?? "unknown"),
319
+ payload.agentSession.updatedAt ?? payload.agentSession.createdAt ?? "",
320
+ typeof payload.webhookTimestamp === "number" ? String(payload.webhookTimestamp) : ""
321
+ ].join(":");
322
+ }
323
+ async function handleAgentSessionEvent(input, options) {
324
+ const logger = input.deps.logger ?? new NoopLogger();
325
+ const config = normalizeConfig(input.config);
326
+ const action = normalizeAction(input.payload.action);
327
+ const runtime = options?.runtime ?? createLinearBridgeRuntime();
328
+ if (!action) {
329
+ logger.warn("linear_thenvoi_bridge.ignored_unknown_action", {
330
+ action: input.payload.action,
331
+ sessionId: input.payload.agentSession.id
332
+ });
333
+ return;
334
+ }
335
+ const sessionId = input.payload.agentSession.id;
336
+ const issueId = input.payload.agentSession.issueId ?? input.payload.agentSession.issue?.id ?? null;
337
+ const eventKey = options?.expectedEventKey ?? getAgentSessionEventKey(input.payload);
338
+ const existingBySession = await input.deps.store.getBySessionId(sessionId);
339
+ if (existingBySession?.lastEventKey === eventKey) {
340
+ logger.info("linear_thenvoi_bridge.duplicate_event_skipped", {
341
+ sessionId,
342
+ issueId,
343
+ action,
344
+ eventKey
345
+ });
346
+ return;
347
+ }
348
+ if (action === "canceled") {
349
+ await handleCanceledAction({
350
+ deps: input.deps,
351
+ logger,
352
+ sessionId,
353
+ issueId,
354
+ eventKey,
355
+ payloadAction: input.payload.action,
356
+ skipRoomWrite: options?.skipRoomWrite ?? false
357
+ });
358
+ return;
359
+ }
360
+ if (action === "prompted") {
361
+ await handlePromptedAction({
362
+ deps: input.deps,
363
+ config,
364
+ logger,
365
+ sessionId,
366
+ eventKey,
367
+ payload: input.payload,
368
+ skipRoomWrite: options?.skipRoomWrite ?? false
369
+ });
370
+ return;
371
+ }
372
+ if (action === "created" && !options?.skipAcknowledgment) {
373
+ try {
374
+ await postThought(input.deps.linearClient, sessionId, "Received session. Setting up workspace...");
375
+ } catch (ackError) {
376
+ logger.warn("linear_thenvoi_bridge.acknowledgment_failed", {
377
+ sessionId,
378
+ error: ackError instanceof Error ? ackError.message : String(ackError)
379
+ });
380
+ }
381
+ }
382
+ let roomRecord = null;
383
+ try {
384
+ const hostAgentHandle = await resolveHostAgentHandle({
385
+ thenvoiRest: input.deps.thenvoiRest,
386
+ configuredHostHandle: config.hostAgentHandle,
387
+ logger,
388
+ runtime
389
+ });
390
+ const existingByIssue = config.roomStrategy === "issue" && issueId ? await input.deps.store.getByIssueId(issueId) : null;
391
+ roomRecord = await resolveRoomRecord({
392
+ thenvoiRest: input.deps.thenvoiRest,
393
+ store: input.deps.store,
394
+ roomStrategy: config.roomStrategy,
395
+ sessionId,
396
+ issueId,
397
+ logger,
398
+ runtime
399
+ });
400
+ const sessionIntent = detectSessionIntent({
401
+ issueStateType: extractIssueStateField(input.payload.agentSession.issue, "type"),
402
+ promptContext: input.payload.promptContext,
403
+ commentBody: input.payload.agentSession.comment?.body,
404
+ issueTitle: input.payload.agentSession.issue?.title,
405
+ issueDescription: input.payload.agentSession.issue?.description
406
+ });
407
+ const suggestedPeerHandles = await selectRelevantPeerHandles({
408
+ thenvoiRest: input.deps.thenvoiRest,
409
+ roomId: roomRecord.thenvoiRoomId,
410
+ intent: sessionIntent,
411
+ hostAgentHandle,
412
+ planningAgentHandles: input.config.planningAgentHandles,
413
+ implementationAgentHandles: input.config.implementationAgentHandles,
414
+ logger
415
+ });
416
+ await ensureRoomParticipants({
417
+ thenvoiRest: input.deps.thenvoiRest,
418
+ roomId: roomRecord.thenvoiRoomId,
419
+ handles: [hostAgentHandle],
420
+ logger
421
+ });
422
+ const message = buildBridgeMessage({
423
+ sessionId,
424
+ issueId,
425
+ issueIdentifier: input.payload.agentSession.issue?.identifier,
426
+ sessionStatus: input.payload.agentSession.status,
427
+ sessionType: input.payload.agentSession.type,
428
+ sessionCreatedAt: input.payload.agentSession.createdAt,
429
+ sessionUpdatedAt: input.payload.agentSession.updatedAt,
430
+ action,
431
+ hostHandle: hostAgentHandle,
432
+ promptContext: input.payload.promptContext,
433
+ issueTitle: input.payload.agentSession.issue?.title,
434
+ issueDescription: input.payload.agentSession.issue?.description,
435
+ issueUrl: input.payload.agentSession.issue?.url,
436
+ issueTeamKey: extractIssueTeamKey(input.payload.agentSession.issue),
437
+ issueTeamName: extractIssueTeamName(input.payload.agentSession.issue),
438
+ issueTeamId: extractIssueTeamId(input.payload.agentSession.issue),
439
+ issueStateId: extractIssueStateField(input.payload.agentSession.issue, "id"),
440
+ issueStateName: extractIssueStateField(input.payload.agentSession.issue, "name"),
441
+ issueStateType: extractIssueStateField(input.payload.agentSession.issue, "type"),
442
+ issueAssigneeId: extractIssueAssigneeField(input.payload.agentSession.issue, "id"),
443
+ issueAssigneeName: extractIssueAssigneeField(input.payload.agentSession.issue, "displayName") ?? extractIssueAssigneeField(input.payload.agentSession.issue, "name"),
444
+ commentBody: input.payload.agentSession.comment?.body,
445
+ commentId: input.payload.agentSession.comment?.id,
446
+ sessionIntent,
447
+ suggestedPeerHandles,
448
+ webhookId: input.payload.webhookId,
449
+ webhookTimestamp: input.payload.webhookTimestamp,
450
+ oauthClientId: input.payload.oauthClientId,
451
+ organizationId: input.payload.organizationId,
452
+ appUserId: input.payload.appUserId,
453
+ writebackMode: config.writebackMode
454
+ });
455
+ const messageMetadata = {
456
+ linear_event_action: action,
457
+ linear_session_id: sessionId,
458
+ linear_issue_id: issueId,
459
+ linear_prompt_context: input.payload.promptContext ?? null,
460
+ linear_writeback_mode: config.writebackMode,
461
+ linear_bridge: "thenvoi",
462
+ linear_host_handle: hostAgentHandle
463
+ };
464
+ const shouldResetRoomSession = Boolean(
465
+ existingByIssue && existingByIssue.linearSessionId !== sessionId && existingByIssue.thenvoiRoomId === roomRecord.thenvoiRoomId && (existingByIssue.status === "completed" || existingByIssue.status === "errored")
466
+ );
467
+ const authenticatedHostHandle = runtime.authenticatedHostHandleCache.get(input.deps.thenvoiRest) ?? null;
468
+ const canBootstrapDirectly = options?.skipRoomWrite === true || authenticatedHostHandle === null || authenticatedHostHandle === hostAgentHandle;
469
+ if (!options?.skipRoomWrite || !canBootstrapDirectly) {
470
+ roomRecord = await forwardBridgeMessage({
471
+ thenvoiRest: input.deps.thenvoiRest,
472
+ store: input.deps.store,
473
+ logger,
474
+ roomRecord,
475
+ hostAgentHandle,
476
+ sessionId,
477
+ issueId,
478
+ message,
479
+ messageType: "task",
480
+ metadata: messageMetadata,
481
+ recoveredRoomRetryBaseDelayMs: input.config.recoveredRoomRetryBaseDelayMs
482
+ });
483
+ }
484
+ if (canBootstrapDirectly) {
485
+ const linearActor = resolveLinearActor(input.payload);
486
+ await enqueueBootstrapRequest(input.deps.store, {
487
+ eventKey,
488
+ linearSessionId: sessionId,
489
+ thenvoiRoomId: roomRecord.thenvoiRoomId,
490
+ expectedContent: message,
491
+ messageType: "task",
492
+ senderId: linearActor.id,
493
+ senderName: linearActor.name,
494
+ metadata: shouldResetRoomSession ? {
495
+ ...messageMetadata,
496
+ linear_reset_room_session: true
497
+ } : messageMetadata
498
+ });
499
+ }
500
+ await saveSessionRecord(input.deps.store, {
501
+ ...roomRecord,
502
+ status: "active",
503
+ lastEventKey: eventKey,
504
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
505
+ });
506
+ logger.info("linear_thenvoi_bridge.message_forwarded", {
507
+ sessionId,
508
+ issueId,
509
+ roomId: roomRecord.thenvoiRoomId,
510
+ action
511
+ });
512
+ } catch (error) {
513
+ try {
514
+ await postError(
515
+ input.deps.linearClient,
516
+ sessionId,
517
+ `Bridge error: ${error instanceof Error ? error.message : String(error)}`
518
+ );
519
+ } catch (reportError) {
520
+ logger.warn("linear_thenvoi_bridge.error_reporting_failed", {
521
+ sessionId,
522
+ error: reportError instanceof Error ? reportError.message : String(reportError)
523
+ });
524
+ }
525
+ if (roomRecord) {
526
+ await saveSessionRecord(input.deps.store, {
527
+ ...roomRecord,
528
+ status: "errored",
529
+ lastEventKey: eventKey,
530
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
531
+ });
532
+ } else if (existingBySession) {
533
+ await saveSessionRecord(input.deps.store, {
534
+ ...existingBySession,
535
+ status: "errored",
536
+ lastEventKey: eventKey,
537
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
538
+ });
539
+ }
540
+ throw error;
541
+ }
542
+ }
543
+ async function completeLinearSession(input) {
544
+ await input.linearClient.createAgentActivity({
545
+ agentSessionId: input.agentSessionId,
546
+ content: {
547
+ type: import_sdk2.LinearDocument.AgentActivityType.Response,
548
+ body: input.body
549
+ }
550
+ });
551
+ if (!input.store) {
552
+ return;
553
+ }
554
+ const existing = await input.store.getBySessionId(input.agentSessionId);
555
+ if (!existing) {
556
+ return;
557
+ }
558
+ await saveSessionRecord(input.store, {
559
+ ...existing,
560
+ status: "completed",
561
+ lastEventKey: input.lastEventKey ?? existing.lastEventKey ?? null,
562
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
563
+ });
564
+ }
565
+ function normalizeConfig(config) {
566
+ return {
567
+ roomStrategy: config.roomStrategy ?? "issue",
568
+ writebackMode: config.writebackMode ?? "final_only",
569
+ hostAgentHandle: normalizeOptionalHandle(config.hostAgentHandle)
570
+ };
571
+ }
572
+ function normalizeOptionalHandle(handle) {
573
+ if (!handle) {
574
+ return null;
575
+ }
576
+ const normalized = stripHandlePrefix(handle);
577
+ return normalized.length > 0 ? normalized : null;
578
+ }
579
+ async function resolveHostAgentHandle(input) {
580
+ if (input.configuredHostHandle) {
581
+ input.runtime.resolvedHostHandleCache.set(input.thenvoiRest, input.configuredHostHandle);
582
+ return input.configuredHostHandle;
583
+ }
584
+ const cachedAuthenticated = input.runtime.authenticatedHostHandleCache.get(input.thenvoiRest);
585
+ if (cachedAuthenticated) {
586
+ input.runtime.resolvedHostHandleCache.set(input.thenvoiRest, cachedAuthenticated);
587
+ return cachedAuthenticated;
588
+ }
589
+ const cached = input.runtime.resolvedHostHandleCache.get(input.thenvoiRest);
590
+ if (cached) {
591
+ return cached;
592
+ }
593
+ const identity = await input.thenvoiRest.getAgentMe();
594
+ const authenticatedAgentHandle = normalizeOptionalHandle(identity.handle);
595
+ if (authenticatedAgentHandle) {
596
+ input.runtime.authenticatedHostHandleCache.set(input.thenvoiRest, authenticatedAgentHandle);
597
+ input.runtime.resolvedHostHandleCache.set(input.thenvoiRest, authenticatedAgentHandle);
598
+ return authenticatedAgentHandle;
599
+ }
600
+ throw new Error(
601
+ "Linear bridge could not resolve the host agent handle. Set hostAgentHandle or use a REST adapter whose getAgentMe() returns handle."
602
+ );
603
+ }
604
+ function normalizeAction(action) {
605
+ if (!action) {
606
+ return null;
607
+ }
608
+ const normalized = action.trim().toLowerCase();
609
+ if (!SUPPORTED_ACTIONS.has(normalized)) {
610
+ return null;
611
+ }
612
+ return normalized;
613
+ }
614
+ async function handleCanceledAction(input) {
615
+ const existing = await input.deps.store.getBySessionId(input.sessionId);
616
+ if (!existing) {
617
+ input.logger.info("linear_thenvoi_bridge.session_canceled_without_room", {
618
+ sessionId: input.sessionId,
619
+ issueId: input.issueId,
620
+ action: input.payloadAction
621
+ });
622
+ return;
623
+ }
624
+ await saveSessionRecord(input.deps.store, {
625
+ ...existing,
626
+ status: "canceled",
627
+ lastEventKey: input.eventKey,
628
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
629
+ });
630
+ if (!input.skipRoomWrite) {
631
+ await input.deps.thenvoiRest.createChatEvent(existing.thenvoiRoomId, {
632
+ content: "[Linear]: Agent session canceled. Stop in-room execution and await new instructions.",
633
+ messageType: "task",
634
+ metadata: {
635
+ linear_event_action: "canceled",
636
+ linear_session_id: input.sessionId,
637
+ linear_issue_id: input.issueId,
638
+ linear_bridge: "thenvoi"
639
+ }
640
+ });
641
+ }
642
+ input.logger.info("linear_thenvoi_bridge.session_canceled", {
643
+ sessionId: input.sessionId,
644
+ issueId: input.issueId,
645
+ hadRoom: Boolean(existing),
646
+ action: input.payloadAction
647
+ });
648
+ }
649
+ async function handlePromptedAction(input) {
650
+ const existing = await input.deps.store.getBySessionId(input.sessionId);
651
+ if (!existing) {
652
+ input.logger.warn("linear_thenvoi_bridge.prompted_no_room", {
653
+ sessionId: input.sessionId
654
+ });
655
+ return;
656
+ }
657
+ const userResponse = extractPromptedResponseBody(input.payload);
658
+ if (!userResponse) {
659
+ input.logger.warn("linear_thenvoi_bridge.prompted_empty_response", {
660
+ sessionId: input.sessionId
661
+ });
662
+ return;
663
+ }
664
+ const message = `[Linear User Response]: ${userResponse}`;
665
+ const metadata = {
666
+ linear_event_action: "prompted",
667
+ linear_session_id: input.sessionId,
668
+ linear_bridge: "thenvoi"
669
+ };
670
+ const canBootstrapDirectly = input.config.hostAgentHandle !== null ? true : Boolean(normalizeOptionalHandle((await input.deps.thenvoiRest.getAgentMe()).handle));
671
+ if (!input.skipRoomWrite || !canBootstrapDirectly) {
672
+ await input.deps.thenvoiRest.createChatEvent(existing.thenvoiRoomId, {
673
+ content: message,
674
+ messageType: "text",
675
+ metadata
676
+ });
677
+ }
678
+ if (canBootstrapDirectly) {
679
+ const linearActor = resolveLinearActor(input.payload);
680
+ await enqueueBootstrapRequest(input.deps.store, {
681
+ eventKey: input.eventKey,
682
+ linearSessionId: input.sessionId,
683
+ thenvoiRoomId: existing.thenvoiRoomId,
684
+ expectedContent: message,
685
+ messageType: "text",
686
+ senderId: linearActor.id,
687
+ senderName: linearActor.name,
688
+ metadata
689
+ });
690
+ }
691
+ await saveSessionRecord(input.deps.store, {
692
+ ...existing,
693
+ status: "waiting",
694
+ lastEventKey: input.eventKey,
695
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
696
+ });
697
+ input.logger.info("linear_thenvoi_bridge.prompted_forwarded", {
698
+ sessionId: input.sessionId,
699
+ roomId: existing.thenvoiRoomId
700
+ });
701
+ }
702
+ async function enqueueBootstrapRequest(store, request) {
703
+ const now = /* @__PURE__ */ new Date();
704
+ await store.enqueueBootstrapRequest({
705
+ ...request,
706
+ createdAt: now.toISOString(),
707
+ expiresAt: new Date(now.getTime() + 10 * 6e4).toISOString()
708
+ });
709
+ }
710
+ function resolveLinearActor(payload) {
711
+ const creator = payload.agentSession.creator;
712
+ const creatorName = typeof creator?.name === "string" ? creator.name.trim() : "";
713
+ const creatorId = typeof creator?.id === "string" ? creator.id.trim() : "";
714
+ if (creatorName.length > 0) {
715
+ return {
716
+ id: creatorId.length > 0 ? creatorId : payload.agentSession.creatorId ?? payload.appUserId ?? null,
717
+ name: creatorName
718
+ };
719
+ }
720
+ return {
721
+ id: payload.agentSession.creatorId ?? payload.appUserId ?? null,
722
+ name: "Linear User"
723
+ };
724
+ }
725
+ async function resolveRoomRecord(input) {
726
+ const lockScope = input.roomStrategy === "issue" && input.issueId ? `issue:${input.issueId}` : `session:${input.sessionId}`;
727
+ const existing = input.runtime.roomResolutionLocks.get(lockScope);
728
+ if (existing) {
729
+ return existing;
730
+ }
731
+ const promise = resolveRoomRecordImpl(input);
732
+ input.runtime.roomResolutionLocks.set(lockScope, promise);
733
+ try {
734
+ return await promise;
735
+ } finally {
736
+ input.runtime.roomResolutionLocks.delete(lockScope);
737
+ }
738
+ }
739
+ async function forwardBridgeMessage(input) {
740
+ try {
741
+ await input.thenvoiRest.createChatEvent(input.roomRecord.thenvoiRoomId, {
742
+ content: input.message,
743
+ messageType: input.messageType,
744
+ metadata: input.metadata
745
+ });
746
+ return input.roomRecord;
747
+ } catch (error) {
748
+ if (!isRecoverableRoomAccessError(error)) {
749
+ throw error;
750
+ }
751
+ input.logger.warn("linear_thenvoi_bridge.room_forward_recovering_with_fresh_room", {
752
+ sessionId: input.sessionId,
753
+ issueId: input.issueId,
754
+ previousRoomId: input.roomRecord.thenvoiRoomId,
755
+ error: error instanceof Error ? error.message : String(error)
756
+ });
757
+ const recoveredRoom = await createFreshRoomRecord({
758
+ thenvoiRest: input.thenvoiRest,
759
+ store: input.store,
760
+ sessionId: input.sessionId,
761
+ issueId: input.issueId,
762
+ logger: input.logger
763
+ });
764
+ await ensureRoomParticipants({
765
+ thenvoiRest: input.thenvoiRest,
766
+ roomId: recoveredRoom.thenvoiRoomId,
767
+ handles: [input.hostAgentHandle],
768
+ logger: input.logger
769
+ });
770
+ let attempt = 0;
771
+ while (true) {
772
+ try {
773
+ await input.thenvoiRest.createChatEvent(recoveredRoom.thenvoiRoomId, {
774
+ content: input.message,
775
+ messageType: input.messageType,
776
+ metadata: input.metadata
777
+ });
778
+ return recoveredRoom;
779
+ } catch (recoveredError) {
780
+ if (!isRetryableRecoveredRoomEventError(recoveredError) || attempt >= RECOVERED_ROOM_EVENT_RETRY_LIMIT) {
781
+ throw recoveredError;
782
+ }
783
+ attempt += 1;
784
+ const retryBaseDelayMs = input.recoveredRoomRetryBaseDelayMs ?? RECOVERED_ROOM_EVENT_RETRY_BASE_DELAY_MS;
785
+ const delayMs = retryBaseDelayMs * attempt;
786
+ input.logger.warn("linear_thenvoi_bridge.room_recreated_retrying", {
787
+ sessionId: input.sessionId,
788
+ issueId: input.issueId,
789
+ roomId: recoveredRoom.thenvoiRoomId,
790
+ attempt,
791
+ delayMs,
792
+ error: recoveredError instanceof Error ? recoveredError.message : String(recoveredError)
793
+ });
794
+ await sleep(delayMs);
795
+ }
796
+ }
797
+ }
798
+ }
799
+ function isRecoverableRoomAccessError(error) {
800
+ if (!(error instanceof Error)) {
801
+ return false;
802
+ }
803
+ return /\/api\/v1\/agent\/chats\/.+\/events failed \((403|404)/.test(error.message);
804
+ }
805
+ function isRetryableRecoveredRoomEventError(error) {
806
+ if (!(error instanceof Error)) {
807
+ return false;
808
+ }
809
+ return /\/api\/v1\/agent\/chats\/.+\/events failed \((403|404|429)/.test(error.message);
810
+ }
811
+ function isRetryableRateLimitError(error) {
812
+ if (typeof error === "object" && error !== null && "retryable" in error) {
813
+ return error.retryable === true;
814
+ }
815
+ return error instanceof Error && /\b429\b/.test(error.message);
816
+ }
817
+ async function createFreshRoomRecord(input) {
818
+ const now = (/* @__PURE__ */ new Date()).toISOString();
819
+ const created = await input.thenvoiRest.createChat();
820
+ const record = {
821
+ linearSessionId: input.sessionId,
822
+ linearIssueId: input.issueId,
823
+ thenvoiRoomId: created.id,
824
+ status: "active",
825
+ lastEventKey: null,
826
+ createdAt: now,
827
+ updatedAt: now
828
+ };
829
+ await input.store.upsert(record);
830
+ input.logger.info("linear_thenvoi_bridge.room_recreated", {
831
+ sessionId: input.sessionId,
832
+ issueId: input.issueId,
833
+ roomId: created.id
834
+ });
835
+ return record;
836
+ }
837
+ async function resolveRoomRecordImpl(input) {
838
+ const existingBySession = await input.store.getBySessionId(input.sessionId);
839
+ if (existingBySession && existingBySession.status !== "canceled") {
840
+ return existingBySession;
841
+ }
842
+ if (input.roomStrategy === "issue" && input.issueId) {
843
+ const existingByIssue = await input.store.getByIssueId(input.issueId);
844
+ if (existingByIssue && existingByIssue.status !== "canceled") {
845
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
846
+ const linkedRecord = {
847
+ linearSessionId: input.sessionId,
848
+ linearIssueId: input.issueId,
849
+ thenvoiRoomId: existingByIssue.thenvoiRoomId,
850
+ status: "active",
851
+ lastEventKey: existingBySession?.lastEventKey ?? null,
852
+ createdAt: existingBySession?.createdAt ?? now2,
853
+ updatedAt: now2
854
+ };
855
+ await input.store.upsert(linkedRecord);
856
+ return linkedRecord;
857
+ }
858
+ }
859
+ const now = (/* @__PURE__ */ new Date()).toISOString();
860
+ const created = await input.thenvoiRest.createChat();
861
+ const createdRecord = {
862
+ linearSessionId: input.sessionId,
863
+ linearIssueId: input.issueId,
864
+ thenvoiRoomId: created.id,
865
+ status: "active",
866
+ lastEventKey: null,
867
+ createdAt: now,
868
+ updatedAt: now
869
+ };
870
+ await input.store.upsert(createdRecord);
871
+ input.logger.info("linear_thenvoi_bridge.room_created", {
872
+ sessionId: input.sessionId,
873
+ issueId: input.issueId,
874
+ roomId: created.id,
875
+ roomStrategy: input.roomStrategy
876
+ });
877
+ return createdRecord;
878
+ }
879
+ async function ensureRoomParticipants(input) {
880
+ if (input.handles.length === 0) {
881
+ return;
882
+ }
883
+ let existingParticipants;
884
+ try {
885
+ existingParticipants = await input.thenvoiRest.listChatParticipants(input.roomId);
886
+ } catch (error) {
887
+ if (isRetryableRateLimitError(error)) {
888
+ input.logger.warn("linear_thenvoi_bridge.participant_list_rate_limited", {
889
+ roomId: input.roomId,
890
+ error: error instanceof Error ? error.message : String(error)
891
+ });
892
+ return;
893
+ }
894
+ throw error;
895
+ }
896
+ const existingHandles = new Set(
897
+ existingParticipants.map((participant) => stripHandlePrefix(participant.handle ?? participant.name)).filter((value) => value.length > 0)
898
+ );
899
+ const missingHandles = input.handles.filter((handle) => !existingHandles.has(handle));
900
+ if (missingHandles.length === 0) {
901
+ return;
902
+ }
903
+ let peersByHandle;
904
+ try {
905
+ peersByHandle = await lookupPeerIdsByHandle(input.thenvoiRest, input.roomId, missingHandles);
906
+ } catch (error) {
907
+ if (error instanceof UnsupportedFeatureError) {
908
+ input.logger.warn("linear_thenvoi_bridge.peer_lookup_unavailable", {
909
+ roomId: input.roomId,
910
+ missingHandles,
911
+ reason: error.message
912
+ });
913
+ return;
914
+ }
915
+ throw error;
916
+ }
917
+ for (const handle of missingHandles) {
918
+ const participantId = peersByHandle.get(handle);
919
+ if (!participantId) {
920
+ input.logger.warn("linear_thenvoi_bridge.peer_not_found", {
921
+ roomId: input.roomId,
922
+ handle
923
+ });
924
+ continue;
925
+ }
926
+ try {
927
+ await input.thenvoiRest.addChatParticipant(
928
+ input.roomId,
929
+ {
930
+ participantId,
931
+ role: "member"
932
+ }
933
+ );
934
+ } catch (error) {
935
+ input.logger.warn("linear_thenvoi_bridge.add_participant_failed", {
936
+ roomId: input.roomId,
937
+ handle,
938
+ error: error instanceof Error ? error.message : String(error)
939
+ });
940
+ if (!isRetryableRateLimitError(error)) {
941
+ throw error;
942
+ }
943
+ }
944
+ }
945
+ }
946
+ async function lookupPeerIdsByHandle(thenvoiRest, roomId, handles) {
947
+ if (!thenvoiRest.listPeers) {
948
+ throw new UnsupportedFeatureError("Peer listing is not available in current REST adapter");
949
+ }
950
+ const targets = new Set(handles);
951
+ const found = /* @__PURE__ */ new Map();
952
+ for (let page = 1; page <= MAX_PEER_LOOKUP_PAGES && found.size < targets.size; page += 1) {
953
+ const response = await thenvoiRest.listPeers({
954
+ page,
955
+ pageSize: PEER_PAGE_SIZE,
956
+ notInChat: roomId
957
+ });
958
+ for (const peer of response.data) {
959
+ const peerId = peer.id;
960
+ if (typeof peerId !== "string" || peerId.length === 0) {
961
+ continue;
962
+ }
963
+ const handleCandidates = [
964
+ typeof peer.handle === "string" ? stripHandlePrefix(peer.handle) : "",
965
+ typeof peer.name === "string" ? stripHandlePrefix(peer.name) : ""
966
+ ].filter((value) => value.length > 0);
967
+ for (const candidate of handleCandidates) {
968
+ if (!targets.has(candidate)) {
969
+ continue;
970
+ }
971
+ found.set(candidate, peerId);
972
+ }
973
+ }
974
+ if (response.data.length < PEER_PAGE_SIZE) {
975
+ break;
976
+ }
977
+ }
978
+ return found;
979
+ }
980
+ async function selectRelevantPeerHandles(input) {
981
+ const configuredHandles = resolveConfiguredSpecialistHandles(input.intent, {
982
+ planningAgentHandles: input.planningAgentHandles,
983
+ implementationAgentHandles: input.implementationAgentHandles
984
+ }).filter((handle) => handle !== input.hostAgentHandle);
985
+ if (configuredHandles.length > 0) {
986
+ input.logger.info("linear_thenvoi_bridge.peer_prefetch_selected", {
987
+ roomId: input.roomId,
988
+ intent: input.intent,
989
+ handles: configuredHandles,
990
+ source: "configured"
991
+ });
992
+ return configuredHandles;
993
+ }
994
+ if (!input.thenvoiRest.listPeers) {
995
+ return [];
996
+ }
997
+ let peers;
998
+ try {
999
+ const response = await input.thenvoiRest.listPeers({
1000
+ page: 1,
1001
+ pageSize: PEER_PAGE_SIZE,
1002
+ notInChat: input.roomId
1003
+ });
1004
+ peers = response.data;
1005
+ } catch (error) {
1006
+ if (error instanceof UnsupportedFeatureError) {
1007
+ return [];
1008
+ }
1009
+ input.logger.warn("linear_thenvoi_bridge.peer_prefetch_failed", {
1010
+ roomId: input.roomId,
1011
+ intent: input.intent,
1012
+ error: error instanceof Error ? error.message : String(error)
1013
+ });
1014
+ return [];
1015
+ }
1016
+ const ranked = peers.map((peer) => {
1017
+ const handle = normalizeOptionalHandle(typeof peer.handle === "string" ? peer.handle : null);
1018
+ return {
1019
+ handle,
1020
+ score: scorePeerForIntent(peer, input.intent)
1021
+ };
1022
+ }).filter(
1023
+ (candidate) => typeof candidate.handle === "string" && candidate.handle.length > 0 && candidate.handle !== input.hostAgentHandle
1024
+ ).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score);
1025
+ const selected = ranked.slice(0, 2).map((candidate) => candidate.handle);
1026
+ if (selected.length > 0) {
1027
+ input.logger.info("linear_thenvoi_bridge.peer_prefetch_selected", {
1028
+ roomId: input.roomId,
1029
+ intent: input.intent,
1030
+ handles: selected,
1031
+ source: "lookup"
1032
+ });
1033
+ }
1034
+ return selected;
1035
+ }
1036
+ function resolveConfiguredSpecialistHandles(intent, config) {
1037
+ const handles = intent === "implementation" ? config.implementationAgentHandles : config.planningAgentHandles;
1038
+ if (!handles || handles.length === 0) {
1039
+ return [];
1040
+ }
1041
+ const configured = handles.map((value) => normalizeOptionalHandle(value)).filter((value) => Boolean(value));
1042
+ if (intent === "implementation") {
1043
+ return dedupeHandles(configured);
1044
+ }
1045
+ const ranked = configured.map((handle) => ({
1046
+ handle,
1047
+ score: /\bplanner\b/.test(handle) ? 3 : /\breviewer\b|\bcodex\b/.test(handle) ? 2 : 1
1048
+ })).sort((left, right) => right.score - left.score).slice(0, 2).map((entry) => entry.handle);
1049
+ return dedupeHandles(ranked);
1050
+ }
1051
+ function scorePeerForIntent(peer, intent) {
1052
+ const description = typeof peer.description === "string" ? peer.description : "";
1053
+ const haystack = [
1054
+ typeof peer.name === "string" ? peer.name : "",
1055
+ typeof peer.handle === "string" ? peer.handle : "",
1056
+ description
1057
+ ].join(" ").toLowerCase();
1058
+ const weightedTerms = intent === "implementation" ? [
1059
+ { pattern: /\bimplementer\b|\bimplementation\b/, score: 10 },
1060
+ { pattern: /\bengineer\b|\bdeveloper\b|\bcoder\b|\bcoding\b/, score: 8 },
1061
+ { pattern: /\bbuild\b|\bfrontend\b|\bui\b/, score: 5 },
1062
+ { pattern: /\breviewer\b|\breview\b|\bplanner\b|\bplan\b/, score: -4 }
1063
+ ] : [
1064
+ { pattern: /\bplanner\b|\bplanning\b|\bplan\b/, score: 10 },
1065
+ { pattern: /\bclaude\b/, score: 8 },
1066
+ { pattern: /\breviewer\b|\breview\b|\bcodex\b/, score: 7 },
1067
+ { pattern: /\borchestrator\b|\barchitect\b|\bdesign\b|\bscope\b|\bspec\b/, score: 6 },
1068
+ { pattern: /\bimplementer\b|\bimplementation\b/, score: -3 }
1069
+ ];
1070
+ return weightedTerms.reduce((total, term) => term.pattern.test(haystack) ? total + term.score : total, 0);
1071
+ }
1072
+ async function sleep(ms) {
1073
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
1074
+ }
1075
+ async function saveSessionRecord(store, record) {
1076
+ await store.upsert(record);
1077
+ }
1078
+
1079
+ // src/integrations/linear/client.ts
1080
+ var import_sdk3 = require("@linear/sdk");
1081
+ function isLinearApiKey(token) {
1082
+ return token.trim().startsWith("lin_api_");
1083
+ }
1084
+ function createLinearClient(token) {
1085
+ const trimmedToken = token.trim();
1086
+ if (isLinearApiKey(trimmedToken)) {
1087
+ return new import_sdk3.LinearClient({
1088
+ apiKey: trimmedToken
1089
+ });
1090
+ }
1091
+ return new import_sdk3.LinearClient({
1092
+ accessToken: trimmedToken
1093
+ });
1094
+ }
1095
+ function buildLinearAuthorizationHeader(token) {
1096
+ const trimmedToken = token.trim();
1097
+ if (isLinearApiKey(trimmedToken)) {
1098
+ return trimmedToken;
1099
+ }
1100
+ if (trimmedToken.startsWith("Bearer ")) {
1101
+ return trimmedToken;
1102
+ }
1103
+ return `Bearer ${trimmedToken}`;
1104
+ }
1105
+
1106
+ // src/integrations/linear/store.ts
1107
+ var SESSION_STATUSES = /* @__PURE__ */ new Set([
1108
+ "active",
1109
+ "waiting",
1110
+ "completed",
1111
+ "canceled",
1112
+ "errored"
1113
+ ]);
1114
+ var SqliteSessionRoomStore = class {
1115
+ dbPath;
1116
+ dbPromise = null;
1117
+ constructor(dbPath) {
1118
+ this.dbPath = dbPath;
1119
+ }
1120
+ async getBySessionId(sessionId) {
1121
+ const db = await this.getDb();
1122
+ const rawRow = db.prepare(
1123
+ `
1124
+ SELECT
1125
+ linear_session_id,
1126
+ linear_issue_id,
1127
+ thenvoi_room_id,
1128
+ status,
1129
+ last_event_key,
1130
+ created_at,
1131
+ updated_at
1132
+ FROM linear_thenvoi_session_rooms
1133
+ WHERE linear_session_id = ?
1134
+ LIMIT 1
1135
+ `
1136
+ ).get(sessionId);
1137
+ const row = parseSessionRoomRow(rawRow);
1138
+ return row ? this.toRecord(row) : null;
1139
+ }
1140
+ async getByIssueId(issueId) {
1141
+ const db = await this.getDb();
1142
+ const rawRow = db.prepare(
1143
+ `
1144
+ SELECT
1145
+ linear_session_id,
1146
+ linear_issue_id,
1147
+ thenvoi_room_id,
1148
+ status,
1149
+ last_event_key,
1150
+ created_at,
1151
+ updated_at
1152
+ FROM linear_thenvoi_session_rooms
1153
+ WHERE linear_issue_id = ? AND status != 'canceled'
1154
+ ORDER BY updated_at DESC
1155
+ LIMIT 1
1156
+ `
1157
+ ).get(issueId);
1158
+ const row = parseSessionRoomRow(rawRow);
1159
+ return row ? this.toRecord(row) : null;
1160
+ }
1161
+ async upsert(record) {
1162
+ const db = await this.getDb();
1163
+ db.prepare(
1164
+ `
1165
+ INSERT INTO linear_thenvoi_session_rooms (
1166
+ linear_session_id,
1167
+ linear_issue_id,
1168
+ thenvoi_room_id,
1169
+ status,
1170
+ last_event_key,
1171
+ created_at,
1172
+ updated_at
1173
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
1174
+ ON CONFLICT(linear_session_id)
1175
+ DO UPDATE SET
1176
+ linear_issue_id = excluded.linear_issue_id,
1177
+ thenvoi_room_id = excluded.thenvoi_room_id,
1178
+ status = excluded.status,
1179
+ last_event_key = excluded.last_event_key,
1180
+ updated_at = excluded.updated_at
1181
+ `
1182
+ ).run(
1183
+ record.linearSessionId,
1184
+ record.linearIssueId,
1185
+ record.thenvoiRoomId,
1186
+ record.status,
1187
+ record.lastEventKey ?? null,
1188
+ record.createdAt,
1189
+ record.updatedAt
1190
+ );
1191
+ }
1192
+ async markCanceled(sessionId) {
1193
+ const db = await this.getDb();
1194
+ db.prepare(
1195
+ `
1196
+ UPDATE linear_thenvoi_session_rooms
1197
+ SET status = 'canceled', updated_at = ?
1198
+ WHERE linear_session_id = ?
1199
+ `
1200
+ ).run((/* @__PURE__ */ new Date()).toISOString(), sessionId);
1201
+ }
1202
+ async enqueueBootstrapRequest(request) {
1203
+ const db = await this.getDb();
1204
+ db.prepare(
1205
+ `
1206
+ INSERT INTO linear_thenvoi_bootstrap_requests (
1207
+ event_key,
1208
+ linear_session_id,
1209
+ thenvoi_room_id,
1210
+ expected_content,
1211
+ message_type,
1212
+ metadata_json,
1213
+ created_at,
1214
+ expires_at,
1215
+ processed_at
1216
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)
1217
+ ON CONFLICT(event_key)
1218
+ DO UPDATE SET
1219
+ linear_session_id = excluded.linear_session_id,
1220
+ thenvoi_room_id = excluded.thenvoi_room_id,
1221
+ expected_content = excluded.expected_content,
1222
+ message_type = excluded.message_type,
1223
+ metadata_json = excluded.metadata_json,
1224
+ created_at = excluded.created_at,
1225
+ expires_at = excluded.expires_at,
1226
+ processed_at = NULL
1227
+ `
1228
+ ).run(
1229
+ request.eventKey,
1230
+ request.linearSessionId,
1231
+ request.thenvoiRoomId,
1232
+ request.expectedContent,
1233
+ request.messageType,
1234
+ request.metadata ? JSON.stringify(request.metadata) : null,
1235
+ request.createdAt,
1236
+ request.expiresAt
1237
+ );
1238
+ }
1239
+ async listPendingBootstrapRequests(limit = 20) {
1240
+ const db = await this.getDb();
1241
+ const rawRows = db.prepare(
1242
+ `
1243
+ SELECT
1244
+ event_key,
1245
+ linear_session_id,
1246
+ thenvoi_room_id,
1247
+ expected_content,
1248
+ message_type,
1249
+ metadata_json,
1250
+ created_at,
1251
+ expires_at,
1252
+ processed_at
1253
+ FROM linear_thenvoi_bootstrap_requests
1254
+ WHERE processed_at IS NULL AND expires_at > ?
1255
+ ORDER BY created_at ASC
1256
+ LIMIT ?
1257
+ `
1258
+ ).all((/* @__PURE__ */ new Date()).toISOString(), limit);
1259
+ const rows = parseBootstrapRequestRows(rawRows);
1260
+ return rows.map((row) => ({
1261
+ eventKey: row.event_key,
1262
+ linearSessionId: row.linear_session_id,
1263
+ thenvoiRoomId: row.thenvoi_room_id,
1264
+ expectedContent: row.expected_content,
1265
+ messageType: row.message_type,
1266
+ metadata: this.parseMetadata(row.metadata_json),
1267
+ createdAt: row.created_at,
1268
+ expiresAt: row.expires_at
1269
+ }));
1270
+ }
1271
+ async markBootstrapRequestProcessed(eventKey) {
1272
+ const db = await this.getDb();
1273
+ db.prepare(
1274
+ `
1275
+ UPDATE linear_thenvoi_bootstrap_requests
1276
+ SET processed_at = ?
1277
+ WHERE event_key = ? AND processed_at IS NULL
1278
+ `
1279
+ ).run((/* @__PURE__ */ new Date()).toISOString(), eventKey);
1280
+ }
1281
+ async close() {
1282
+ if (!this.dbPromise) {
1283
+ return;
1284
+ }
1285
+ const db = await this.dbPromise;
1286
+ db.close();
1287
+ this.dbPromise = null;
1288
+ }
1289
+ async getDb() {
1290
+ if (this.dbPromise) {
1291
+ return this.dbPromise;
1292
+ }
1293
+ this.dbPromise = this.initialize();
1294
+ return this.dbPromise;
1295
+ }
1296
+ async initialize() {
1297
+ const module2 = await import("sqlite").catch((error) => {
1298
+ throw new UnsupportedFeatureError(
1299
+ `SQLite store requires node:sqlite (Node.js 22+). Original error: ${error instanceof Error ? error.message : String(error)}`
1300
+ );
1301
+ });
1302
+ const db = new module2.DatabaseSync(this.dbPath);
1303
+ db.exec(`
1304
+ CREATE TABLE IF NOT EXISTS linear_thenvoi_session_rooms (
1305
+ linear_session_id TEXT PRIMARY KEY,
1306
+ linear_issue_id TEXT,
1307
+ thenvoi_room_id TEXT NOT NULL,
1308
+ status TEXT NOT NULL,
1309
+ last_event_key TEXT,
1310
+ created_at TEXT NOT NULL,
1311
+ updated_at TEXT NOT NULL
1312
+ );
1313
+
1314
+ CREATE INDEX IF NOT EXISTS idx_linear_thenvoi_session_rooms_issue_active
1315
+ ON linear_thenvoi_session_rooms (linear_issue_id, status, updated_at);
1316
+
1317
+ CREATE TABLE IF NOT EXISTS linear_thenvoi_bootstrap_requests (
1318
+ event_key TEXT PRIMARY KEY,
1319
+ linear_session_id TEXT NOT NULL,
1320
+ thenvoi_room_id TEXT NOT NULL,
1321
+ expected_content TEXT NOT NULL,
1322
+ message_type TEXT NOT NULL,
1323
+ metadata_json TEXT,
1324
+ created_at TEXT NOT NULL,
1325
+ expires_at TEXT NOT NULL,
1326
+ processed_at TEXT
1327
+ );
1328
+
1329
+ CREATE INDEX IF NOT EXISTS idx_linear_thenvoi_bootstrap_requests_pending
1330
+ ON linear_thenvoi_bootstrap_requests (processed_at, expires_at, created_at);
1331
+ `);
1332
+ try {
1333
+ db.exec(`
1334
+ ALTER TABLE linear_thenvoi_session_rooms
1335
+ ADD COLUMN last_event_key TEXT
1336
+ `);
1337
+ } catch (error) {
1338
+ if (!isDuplicateColumnError(error)) {
1339
+ throw error;
1340
+ }
1341
+ }
1342
+ try {
1343
+ db.exec(`
1344
+ ALTER TABLE linear_thenvoi_bootstrap_requests
1345
+ ADD COLUMN message_type TEXT NOT NULL DEFAULT 'task'
1346
+ `);
1347
+ } catch (error) {
1348
+ if (!isDuplicateColumnError(error)) {
1349
+ throw error;
1350
+ }
1351
+ }
1352
+ try {
1353
+ db.exec(`
1354
+ ALTER TABLE linear_thenvoi_bootstrap_requests
1355
+ ADD COLUMN metadata_json TEXT
1356
+ `);
1357
+ } catch (error) {
1358
+ if (!isDuplicateColumnError(error)) {
1359
+ throw error;
1360
+ }
1361
+ }
1362
+ return db;
1363
+ }
1364
+ toRecord(row) {
1365
+ return {
1366
+ linearSessionId: row.linear_session_id,
1367
+ linearIssueId: row.linear_issue_id,
1368
+ thenvoiRoomId: row.thenvoi_room_id,
1369
+ status: row.status,
1370
+ lastEventKey: row.last_event_key,
1371
+ createdAt: row.created_at,
1372
+ updatedAt: row.updated_at
1373
+ };
1374
+ }
1375
+ parseMetadata(metadataJson) {
1376
+ if (!metadataJson) {
1377
+ return void 0;
1378
+ }
1379
+ try {
1380
+ const parsed = JSON.parse(metadataJson);
1381
+ return asRecord(parsed) ?? void 0;
1382
+ } catch (error) {
1383
+ const msg = error instanceof Error ? error.message : String(error);
1384
+ console.warn(`SqliteSessionRoomStore: failed to parse metadata_json: ${msg}`);
1385
+ return void 0;
1386
+ }
1387
+ }
1388
+ };
1389
+ function asRecord(value) {
1390
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
1391
+ return null;
1392
+ }
1393
+ return value;
1394
+ }
1395
+ function isDuplicateColumnError(error) {
1396
+ if (!(error instanceof Error)) {
1397
+ return false;
1398
+ }
1399
+ return /duplicate column name/i.test(error.message);
1400
+ }
1401
+ function asString(value) {
1402
+ return typeof value === "string" ? value : null;
1403
+ }
1404
+ function asNullableString(value) {
1405
+ if (value === null) {
1406
+ return null;
1407
+ }
1408
+ if (value === void 0) {
1409
+ return void 0;
1410
+ }
1411
+ return asString(value);
1412
+ }
1413
+ function isSessionStatus(value) {
1414
+ return SESSION_STATUSES.has(value);
1415
+ }
1416
+ function parseSessionRoomRow(value) {
1417
+ const row = asRecord(value);
1418
+ if (!row) {
1419
+ return null;
1420
+ }
1421
+ const linearSessionId = asString(row.linear_session_id);
1422
+ const linearIssueId = asNullableString(row.linear_issue_id);
1423
+ const thenvoiRoomId = asString(row.thenvoi_room_id);
1424
+ const status = asString(row.status);
1425
+ const lastEventKey = asNullableString(row.last_event_key);
1426
+ const createdAt = asString(row.created_at);
1427
+ const updatedAt = asString(row.updated_at);
1428
+ if (!linearSessionId || linearIssueId === void 0 || !thenvoiRoomId || !status || !isSessionStatus(status) || lastEventKey === void 0 || !createdAt || !updatedAt) {
1429
+ return null;
1430
+ }
1431
+ return {
1432
+ linear_session_id: linearSessionId,
1433
+ linear_issue_id: linearIssueId,
1434
+ thenvoi_room_id: thenvoiRoomId,
1435
+ status,
1436
+ last_event_key: lastEventKey,
1437
+ created_at: createdAt,
1438
+ updated_at: updatedAt
1439
+ };
1440
+ }
1441
+ function parseBootstrapRequestRows(value) {
1442
+ if (!Array.isArray(value)) {
1443
+ return [];
1444
+ }
1445
+ return value.map((entry) => parseBootstrapRequestRow(entry)).filter((entry) => entry !== null);
1446
+ }
1447
+ function parseBootstrapRequestRow(value) {
1448
+ const row = asRecord(value);
1449
+ if (!row) {
1450
+ return null;
1451
+ }
1452
+ const eventKey = asString(row.event_key);
1453
+ const linearSessionId = asString(row.linear_session_id);
1454
+ const thenvoiRoomId = asString(row.thenvoi_room_id);
1455
+ const expectedContent = asString(row.expected_content);
1456
+ const messageType = asString(row.message_type);
1457
+ const metadataJson = asNullableString(row.metadata_json);
1458
+ const createdAt = asString(row.created_at);
1459
+ const expiresAt = asString(row.expires_at);
1460
+ const processedAt = asNullableString(row.processed_at);
1461
+ if (!eventKey || !linearSessionId || !thenvoiRoomId || !expectedContent || !messageType || metadataJson === void 0 || !createdAt || !expiresAt || processedAt === void 0) {
1462
+ return null;
1463
+ }
1464
+ return {
1465
+ event_key: eventKey,
1466
+ linear_session_id: linearSessionId,
1467
+ thenvoi_room_id: thenvoiRoomId,
1468
+ expected_content: expectedContent,
1469
+ message_type: messageType,
1470
+ metadata_json: metadataJson,
1471
+ created_at: createdAt,
1472
+ expires_at: expiresAt,
1473
+ processed_at: processedAt
1474
+ };
1475
+ }
1476
+ function createSqliteSessionRoomStore(dbPath) {
1477
+ return new SqliteSessionRoomStore(dbPath);
1478
+ }
1479
+
1480
+ // src/integrations/linear/tools.ts
1481
+ var import_zod = require("zod");
1482
+ function createLinearTools(options) {
1483
+ const { client, store, enableElicitation = true } = options;
1484
+ const sessionBodySchema = import_zod.z.object({
1485
+ session_id: import_zod.z.string().describe("The Linear agent session ID"),
1486
+ body: import_zod.z.string().describe("The message body in Markdown format")
1487
+ });
1488
+ const issueIdInputSchema = import_zod.z.object({
1489
+ issue_id: import_zod.z.string().optional().describe("The Linear issue ID (UUID) from the session context")
1490
+ }).passthrough();
1491
+ const issueCommentLimitSchema = import_zod.z.number().int().min(1).max(50).optional().describe("Maximum number of recent comments to return");
1492
+ const requiredIssueIdSchema = issueIdInputSchema;
1493
+ const optionalIssueIdSchema = issueIdInputSchema;
1494
+ const tools = [];
1495
+ const addSessionBodyTool = (name, description, handler) => {
1496
+ tools.push({
1497
+ name,
1498
+ description,
1499
+ schema: sessionBodySchema,
1500
+ handler
1501
+ });
1502
+ };
1503
+ addSessionBodyTool(
1504
+ "linear_post_thought",
1505
+ "Post a thought to the Linear agent session, visible to the user as internal reasoning.",
1506
+ async (args) => {
1507
+ await postThought(client, args.session_id, args.body);
1508
+ return { ok: true };
1509
+ }
1510
+ );
1511
+ addSessionBodyTool(
1512
+ "linear_post_action",
1513
+ "Post an action to the Linear agent session, showing the user what step is being taken.",
1514
+ async (args) => {
1515
+ await postAction(client, args.session_id, args.body);
1516
+ return { ok: true };
1517
+ }
1518
+ );
1519
+ addSessionBodyTool(
1520
+ "linear_post_error",
1521
+ "Post an error to the Linear agent session to notify the user of a failure.",
1522
+ async (args) => {
1523
+ await postError(client, args.session_id, args.body);
1524
+ return { ok: true };
1525
+ }
1526
+ );
1527
+ if (enableElicitation) {
1528
+ addSessionBodyTool(
1529
+ "linear_ask_user",
1530
+ "Ask the Linear user a question via an elicitation activity.",
1531
+ async (args) => {
1532
+ await postElicitation(client, args.session_id, args.body);
1533
+ return { ok: true };
1534
+ }
1535
+ );
1536
+ }
1537
+ addSessionBodyTool(
1538
+ "linear_post_response",
1539
+ "Post the final response to the Linear agent session and mark the session completed when a store is available.",
1540
+ async (args) => {
1541
+ await completeLinearSession({
1542
+ linearClient: client,
1543
+ agentSessionId: args.session_id,
1544
+ body: args.body,
1545
+ store
1546
+ });
1547
+ return { ok: true };
1548
+ }
1549
+ );
1550
+ tools.push(
1551
+ {
1552
+ name: "linear_update_plan",
1553
+ description: "Update the plan for the Linear agent session, showing progress on each step.",
1554
+ schema: import_zod.z.object({
1555
+ session_id: import_zod.z.string().describe("The Linear agent session ID"),
1556
+ steps: import_zod.z.array(import_zod.z.object({
1557
+ title: import_zod.z.string().describe("Step title"),
1558
+ status: import_zod.z.enum(["pending", "in_progress", "completed", "failed"]).describe("Step status")
1559
+ })).describe("The plan steps with their current status")
1560
+ }),
1561
+ handler: async (args) => {
1562
+ await updatePlan(
1563
+ client,
1564
+ args.session_id,
1565
+ args.steps
1566
+ );
1567
+ return { ok: true };
1568
+ }
1569
+ }
1570
+ );
1571
+ addIssueTools({
1572
+ tools,
1573
+ client,
1574
+ requiredIssueIdSchema,
1575
+ optionalIssueIdSchema,
1576
+ issueCommentLimitSchema
1577
+ });
1578
+ return tools;
1579
+ }
1580
+ function addIssueTools(input) {
1581
+ const {
1582
+ tools,
1583
+ client,
1584
+ requiredIssueIdSchema,
1585
+ optionalIssueIdSchema,
1586
+ issueCommentLimitSchema
1587
+ } = input;
1588
+ tools.push(
1589
+ {
1590
+ name: "linear_get_issue",
1591
+ description: "Fetch the current Linear issue details using the exact issue UUID from the session context.",
1592
+ schema: requiredIssueIdSchema,
1593
+ handler: async (args) => {
1594
+ const issueId = resolveIssueId("linear_get_issue", args);
1595
+ return readIssue(client, issueId);
1596
+ }
1597
+ },
1598
+ {
1599
+ name: "linear_list_issue_comments",
1600
+ description: "List recent comments for the current Linear issue using the exact issue UUID from the session context.",
1601
+ schema: requiredIssueIdSchema.extend({ limit: issueCommentLimitSchema }),
1602
+ handler: async (args) => {
1603
+ const issueId = resolveIssueId("linear_list_issue_comments", args);
1604
+ return readIssueComments(client, issueId, typeof args.limit === "number" ? args.limit : 20);
1605
+ }
1606
+ },
1607
+ {
1608
+ name: "linear_list_workflow_states",
1609
+ description: "List workflow states for the current issue's team so the bridge can move the issue between Todo, In Progress, In Review, and Done.",
1610
+ schema: optionalIssueIdSchema.extend({
1611
+ team_id: import_zod.z.string().optional().describe("The Linear team ID when already known from the issue context")
1612
+ }),
1613
+ handler: async (args) => {
1614
+ const issueId = resolveOptionalIssueId("linear_list_workflow_states", args);
1615
+ const teamId = typeof args.team_id === "string" && args.team_id.trim().length > 0 ? args.team_id : await readIssueTeamId(client, issueId);
1616
+ if (!teamId) {
1617
+ throw new Error("linear_list_workflow_states requires issue_id or team_id.");
1618
+ }
1619
+ return readWorkflowStates(client, teamId);
1620
+ }
1621
+ },
1622
+ {
1623
+ name: "linear_update_issue",
1624
+ description: "Update a Linear issue (title/description/state/assignee/priority/etc.) from the session workflow.",
1625
+ schema: requiredIssueIdSchema.extend({
1626
+ title: import_zod.z.string().optional().describe("New issue title"),
1627
+ description: import_zod.z.string().optional().describe("New issue description (Markdown)"),
1628
+ priority: import_zod.z.number().int().min(0).max(4).optional().describe("Priority 0-4"),
1629
+ state_id: import_zod.z.string().optional().describe("Workflow state ID"),
1630
+ assignee_id: import_zod.z.string().nullable().optional().describe("Assignee user ID, or null to unassign"),
1631
+ estimate: import_zod.z.number().int().optional().describe("Estimate points"),
1632
+ due_date: import_zod.z.string().optional().describe("Due date ISO string")
1633
+ }),
1634
+ handler: async (args) => {
1635
+ if (typeof client.updateIssue !== "function") {
1636
+ throw new Error("linear_update_issue is unavailable: Linear client does not support updateIssue().");
1637
+ }
1638
+ const issueId = resolveIssueId("linear_update_issue", args);
1639
+ const hasUpdates = args.title !== void 0 || args.description !== void 0 || args.priority !== void 0 || args.state_id !== void 0 || args.assignee_id !== void 0 || args.estimate !== void 0 || args.due_date !== void 0;
1640
+ if (!hasUpdates) {
1641
+ throw new Error("linear_update_issue requires at least one update field.");
1642
+ }
1643
+ await client.updateIssue(
1644
+ issueId,
1645
+ {
1646
+ ...args.title !== void 0 ? { title: args.title } : {},
1647
+ ...args.description !== void 0 ? { description: args.description } : {},
1648
+ ...args.priority !== void 0 ? { priority: args.priority } : {},
1649
+ ...args.state_id !== void 0 ? { stateId: args.state_id } : {},
1650
+ ...args.assignee_id !== void 0 ? { assigneeId: args.assignee_id } : {},
1651
+ ...args.estimate !== void 0 ? { estimate: args.estimate } : {},
1652
+ ...args.due_date !== void 0 ? { dueDate: args.due_date } : {}
1653
+ }
1654
+ );
1655
+ return { ok: true };
1656
+ }
1657
+ },
1658
+ {
1659
+ name: "linear_add_issue_comment",
1660
+ description: "Add a new comment to a Linear issue.",
1661
+ schema: requiredIssueIdSchema.extend({
1662
+ body: import_zod.z.string().describe("Comment body (Markdown)")
1663
+ }),
1664
+ handler: async (args) => {
1665
+ if (typeof client.createComment !== "function") {
1666
+ throw new Error("linear_add_issue_comment is unavailable: Linear client does not support createComment().");
1667
+ }
1668
+ const issueId = resolveIssueId("linear_add_issue_comment", args);
1669
+ await client.createComment({
1670
+ issueId,
1671
+ body: args.body
1672
+ });
1673
+ return { ok: true };
1674
+ }
1675
+ }
1676
+ );
1677
+ }
1678
+ function assertUuid(toolName, value) {
1679
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1680
+ if (!uuidPattern.test(value)) {
1681
+ throw new Error(`${toolName} requires the exact Linear issue UUID from the session context. Received "${value}".`);
1682
+ }
1683
+ }
1684
+ function resolveIssueId(toolName, args) {
1685
+ const issueId = resolveOptionalIssueId(toolName, args);
1686
+ if (!issueId) {
1687
+ throw new Error(`${toolName} requires issue_id.`);
1688
+ }
1689
+ return issueId;
1690
+ }
1691
+ function resolveOptionalIssueId(toolName, args) {
1692
+ if (typeof args.issue_id !== "string" || args.issue_id.length === 0) {
1693
+ return void 0;
1694
+ }
1695
+ assertUuid(toolName, args.issue_id);
1696
+ return args.issue_id;
1697
+ }
1698
+ async function readIssue(client, issueId) {
1699
+ if (typeof client.issue !== "function") {
1700
+ throw new Error("linear_get_issue is unavailable: Linear client does not support issue().");
1701
+ }
1702
+ const issue = await client.issue(issueId);
1703
+ return {
1704
+ issue: {
1705
+ id: issue.id,
1706
+ identifier: issue.identifier ?? null,
1707
+ title: issue.title ?? null,
1708
+ description: issue.description ?? null,
1709
+ url: issue.url ?? null,
1710
+ priority: issue.priority ?? null,
1711
+ estimate: issue.estimate ?? null,
1712
+ due_date: issue.dueDate ?? null,
1713
+ created_at: issue.createdAt ?? null,
1714
+ updated_at: issue.updatedAt ?? null,
1715
+ state: issue.state ? {
1716
+ id: issue.state.id ?? null,
1717
+ name: issue.state.name ?? null,
1718
+ type: issue.state.type ?? null
1719
+ } : null,
1720
+ assignee: issue.assignee ? {
1721
+ id: issue.assignee.id ?? null,
1722
+ name: issue.assignee.displayName ?? issue.assignee.name ?? null
1723
+ } : null,
1724
+ team: issue.team ? {
1725
+ id: issue.team.id ?? null,
1726
+ key: issue.team.key ?? null,
1727
+ name: issue.team.name ?? null
1728
+ } : null
1729
+ }
1730
+ };
1731
+ }
1732
+ async function readIssueComments(client, issueId, limit) {
1733
+ if (typeof client.issue !== "function") {
1734
+ throw new Error("linear_list_issue_comments is unavailable: Linear client does not support issue().");
1735
+ }
1736
+ const issue = await client.issue(issueId);
1737
+ if (typeof issue.comments !== "function") {
1738
+ throw new Error("linear_list_issue_comments is unavailable: issue.comments() is not supported by the Linear client.");
1739
+ }
1740
+ const response = await issue.comments();
1741
+ const comments = (response.nodes ?? []).slice(-limit).map((comment) => ({
1742
+ id: comment.id ?? null,
1743
+ body: comment.body ?? null,
1744
+ created_at: comment.createdAt ?? null,
1745
+ updated_at: comment.updatedAt ?? null,
1746
+ user: comment.user ? {
1747
+ id: comment.user.id ?? null,
1748
+ name: comment.user.displayName ?? comment.user.name ?? null
1749
+ } : null
1750
+ }));
1751
+ return { comments };
1752
+ }
1753
+ async function readIssueTeamId(client, issueId) {
1754
+ if (!issueId) {
1755
+ return null;
1756
+ }
1757
+ if (typeof client.issue !== "function") {
1758
+ throw new Error("linear_list_workflow_states is unavailable: Linear client does not support issue().");
1759
+ }
1760
+ const issue = await client.issue(issueId);
1761
+ return issue.teamId ?? issue.team?.id ?? null;
1762
+ }
1763
+ async function readWorkflowStates(client, teamId) {
1764
+ if (typeof client.workflowStates !== "function") {
1765
+ throw new Error(
1766
+ "linear_list_workflow_states is unavailable: Linear client does not support workflowStates()."
1767
+ );
1768
+ }
1769
+ const response = await client.workflowStates({ teamId });
1770
+ const states = (response.nodes ?? []).map((state) => ({
1771
+ id: state.id ?? null,
1772
+ name: state.name ?? null,
1773
+ type: state.type ?? null,
1774
+ position: state.position ?? null,
1775
+ team_id: state.teamId ?? teamId
1776
+ })).sort((left, right) => (left.position ?? Number.MAX_SAFE_INTEGER) - (right.position ?? Number.MAX_SAFE_INTEGER));
1777
+ return { team_id: teamId, states };
1778
+ }
1779
+
1780
+ // src/integrations/linear/webhook.ts
1781
+ var import_webhooks = require("@linear/sdk/webhooks");
1782
+ var DISPATCH_RETRY_LIMIT = 2;
1783
+ var DISPATCH_RETRY_BASE_DELAY_MS = 1e3;
1784
+ function createInlineLinearBridgeDispatcher(options) {
1785
+ const logger = options?.logger ?? new NoopLogger();
1786
+ const runtime = createLinearBridgeRuntime();
1787
+ return {
1788
+ dispatch: async (job) => {
1789
+ const result = await runDispatchAttempt(async () => {
1790
+ await handleAgentSessionEvent(job.input, {
1791
+ skipAcknowledgment: true,
1792
+ expectedEventKey: job.eventKey,
1793
+ runtime: job.runtime ?? runtime
1794
+ });
1795
+ }, {
1796
+ logger,
1797
+ failureEvent: "linear_thenvoi_bridge.dispatch_failed",
1798
+ retryEvent: "linear_thenvoi_bridge.dispatch_retrying",
1799
+ eventKey: job.eventKey,
1800
+ sessionId: job.input.payload.agentSession.id
1801
+ });
1802
+ if (result.ok) {
1803
+ await markBootstrapHandoffProcessed({
1804
+ eventKey: job.eventKey,
1805
+ store: job.input.deps.store,
1806
+ logger,
1807
+ sessionId: job.input.payload.agentSession.id
1808
+ });
1809
+ return;
1810
+ }
1811
+ const failure = {
1812
+ eventKey: job.eventKey,
1813
+ sessionId: job.input.payload.agentSession.id,
1814
+ attempts: result.attempts,
1815
+ error: result.error
1816
+ };
1817
+ await signalTerminalDispatchFailure({
1818
+ logger,
1819
+ job,
1820
+ failure
1821
+ });
1822
+ throw result.error;
1823
+ }
1824
+ };
1825
+ }
1826
+ function createInProcessLinearBridgeDispatcher(options) {
1827
+ const logger = options?.logger ?? new NoopLogger();
1828
+ const runtime = createLinearBridgeRuntime();
1829
+ const queued = /* @__PURE__ */ new Set();
1830
+ const inFlight = /* @__PURE__ */ new Set();
1831
+ const terminalFailures = [];
1832
+ return {
1833
+ isQueued: (eventKey) => queued.has(eventKey),
1834
+ waitForIdle: async () => {
1835
+ let snapshot = [...inFlight];
1836
+ while (snapshot.length > 0) {
1837
+ await Promise.allSettled(snapshot);
1838
+ snapshot = [...inFlight];
1839
+ }
1840
+ if (terminalFailures.length === 0) {
1841
+ return;
1842
+ }
1843
+ const failures = terminalFailures.splice(0, terminalFailures.length);
1844
+ throw new AggregateError(
1845
+ failures.map((failure) => toDispatchFailureError(failure)),
1846
+ `Linear bridge async dispatch failed for ${failures.length} event(s)`
1847
+ );
1848
+ },
1849
+ dispatch: async (job) => {
1850
+ if (queued.has(job.eventKey)) {
1851
+ return;
1852
+ }
1853
+ queued.add(job.eventKey);
1854
+ const dispatchTask = (async () => {
1855
+ await new Promise((resolveMicrotask) => {
1856
+ queueMicrotask(() => resolveMicrotask());
1857
+ });
1858
+ const result = await runDispatchAttempt(async () => {
1859
+ await handleAgentSessionEvent(job.input, {
1860
+ skipAcknowledgment: true,
1861
+ expectedEventKey: job.eventKey,
1862
+ runtime: job.runtime ?? runtime
1863
+ });
1864
+ }, {
1865
+ logger,
1866
+ failureEvent: "linear_thenvoi_bridge.async_dispatch_failed",
1867
+ retryEvent: "linear_thenvoi_bridge.async_dispatch_retrying",
1868
+ eventKey: job.eventKey,
1869
+ sessionId: job.input.payload.agentSession.id
1870
+ });
1871
+ if (result.ok) {
1872
+ await markBootstrapHandoffProcessed({
1873
+ eventKey: job.eventKey,
1874
+ store: job.input.deps.store,
1875
+ logger,
1876
+ sessionId: job.input.payload.agentSession.id
1877
+ });
1878
+ return;
1879
+ }
1880
+ const failure = {
1881
+ eventKey: job.eventKey,
1882
+ sessionId: job.input.payload.agentSession.id,
1883
+ attempts: result.attempts,
1884
+ error: result.error
1885
+ };
1886
+ terminalFailures.push(failure);
1887
+ await signalTerminalDispatchFailure({
1888
+ logger,
1889
+ job,
1890
+ failure
1891
+ });
1892
+ })();
1893
+ inFlight.add(dispatchTask);
1894
+ void dispatchTask.catch((error) => {
1895
+ logger.error("linear_thenvoi_bridge.async_dispatch_failure_signal_crashed", {
1896
+ eventKey: job.eventKey,
1897
+ sessionId: job.input.payload.agentSession.id,
1898
+ error: serializeError(error)
1899
+ });
1900
+ }).finally(() => {
1901
+ queued.delete(job.eventKey);
1902
+ inFlight.delete(dispatchTask);
1903
+ });
1904
+ await dispatchTask;
1905
+ }
1906
+ };
1907
+ }
1908
+ function createLinearWebhookHandler(options) {
1909
+ const logger = options.deps.logger ?? new NoopLogger();
1910
+ const runtime = createLinearBridgeRuntime();
1911
+ const dispatcher = options.dispatcher ?? createInlineLinearBridgeDispatcher({ logger });
1912
+ const webhookClient = new import_webhooks.LinearWebhookClient(options.config.linearWebhookSecret);
1913
+ const inFlightEventKeys = /* @__PURE__ */ new Set();
1914
+ return async (request, response) => {
1915
+ if (request.method !== "POST") {
1916
+ logger.warn("linear_thenvoi_bridge.webhook_invalid_method", {
1917
+ method: request.method ?? null
1918
+ });
1919
+ sendText(response, 405, "Method not allowed");
1920
+ return;
1921
+ }
1922
+ const signature = getHeaderValue(request, import_webhooks.LINEAR_WEBHOOK_SIGNATURE_HEADER);
1923
+ if (!signature) {
1924
+ logger.warn("linear_thenvoi_bridge.webhook_missing_signature", {});
1925
+ sendText(response, 400, "Missing webhook signature");
1926
+ return;
1927
+ }
1928
+ const timestamp = getHeaderValue(request, import_webhooks.LINEAR_WEBHOOK_TS_HEADER);
1929
+ if (!timestamp) {
1930
+ logger.warn("linear_thenvoi_bridge.webhook_missing_timestamp", {});
1931
+ sendText(response, 400, "Missing webhook timestamp");
1932
+ return;
1933
+ }
1934
+ const rawBody = await readRawBody(request);
1935
+ let payload;
1936
+ try {
1937
+ payload = webhookClient.parseData(
1938
+ rawBody,
1939
+ signature,
1940
+ timestamp
1941
+ );
1942
+ } catch (error) {
1943
+ logger.warn("linear_thenvoi_bridge.webhook_invalid_signature", {
1944
+ error: error instanceof Error ? error.message : String(error)
1945
+ });
1946
+ sendText(response, 400, "Invalid webhook");
1947
+ return;
1948
+ }
1949
+ if (payload.type !== "AgentSessionEvent") {
1950
+ logger.info("linear_thenvoi_bridge.webhook_ignored_event", {
1951
+ type: payload.type
1952
+ });
1953
+ sendText(response, 200, "OK");
1954
+ return;
1955
+ }
1956
+ const eventKey = getAgentSessionEventKey(payload);
1957
+ logger.info("linear_thenvoi_bridge.webhook_received", {
1958
+ sessionId: payload.agentSession.id,
1959
+ issueId: payload.agentSession.issue?.id ?? null,
1960
+ action: payload.action,
1961
+ eventKey
1962
+ });
1963
+ const existing = await options.deps.store.getBySessionId(payload.agentSession.id);
1964
+ const alreadyInFlight = inFlightEventKeys.has(eventKey);
1965
+ if (existing?.lastEventKey === eventKey || alreadyInFlight || dispatcher.isQueued?.(eventKey)) {
1966
+ logger.info("linear_thenvoi_bridge.webhook_duplicate_ignored", {
1967
+ sessionId: payload.agentSession.id,
1968
+ eventKey
1969
+ });
1970
+ sendText(response, 200, "OK");
1971
+ return;
1972
+ }
1973
+ inFlightEventKeys.add(eventKey);
1974
+ try {
1975
+ if (normalizeAction2(payload.action) === "created") {
1976
+ try {
1977
+ await postThought(
1978
+ options.deps.linearClient,
1979
+ payload.agentSession.id,
1980
+ "Received session. Setting up workspace..."
1981
+ );
1982
+ } catch (error) {
1983
+ logger.warn("linear_thenvoi_bridge.webhook_acknowledgment_failed", {
1984
+ sessionId: payload.agentSession.id,
1985
+ error
1986
+ });
1987
+ }
1988
+ }
1989
+ await dispatcher.dispatch({
1990
+ eventKey,
1991
+ runtime,
1992
+ input: {
1993
+ payload,
1994
+ config: options.config,
1995
+ deps: options.deps
1996
+ }
1997
+ });
1998
+ } catch (error) {
1999
+ logger.error("linear_thenvoi_bridge.webhook_dispatch_failed", {
2000
+ sessionId: payload.agentSession.id,
2001
+ issueId: payload.agentSession.issue?.id ?? null,
2002
+ action: payload.action,
2003
+ eventKey,
2004
+ error: serializeError(error)
2005
+ });
2006
+ sendText(response, isRetryableDispatchError(error) ? 503 : 500, "Dispatch failed");
2007
+ return;
2008
+ } finally {
2009
+ inFlightEventKeys.delete(eventKey);
2010
+ }
2011
+ logger.info("linear_thenvoi_bridge.webhook_dispatched", {
2012
+ sessionId: payload.agentSession.id,
2013
+ issueId: payload.agentSession.issue?.id ?? null,
2014
+ action: payload.action,
2015
+ eventKey
2016
+ });
2017
+ sendText(response, 200, "OK");
2018
+ };
2019
+ }
2020
+ function normalizeAction2(action) {
2021
+ return typeof action === "string" ? action.trim().toLowerCase() : "";
2022
+ }
2023
+ function getHeaderValue(request, name) {
2024
+ const value = request.headers[name];
2025
+ if (Array.isArray(value)) {
2026
+ return value[0] ?? null;
2027
+ }
2028
+ return typeof value === "string" ? value : null;
2029
+ }
2030
+ async function readRawBody(request) {
2031
+ if (Buffer.isBuffer(request.body)) {
2032
+ return request.body;
2033
+ }
2034
+ if (typeof request.body === "string") {
2035
+ return Buffer.from(request.body);
2036
+ }
2037
+ if (request.body instanceof Uint8Array) {
2038
+ return Buffer.from(request.body);
2039
+ }
2040
+ const chunks = [];
2041
+ for await (const chunk of request) {
2042
+ if (typeof chunk === "string") {
2043
+ chunks.push(Buffer.from(chunk));
2044
+ } else {
2045
+ chunks.push(Buffer.from(chunk));
2046
+ }
2047
+ }
2048
+ return Buffer.concat(chunks);
2049
+ }
2050
+ function sendText(response, statusCode, body) {
2051
+ if (response.headersSent) {
2052
+ return;
2053
+ }
2054
+ response.statusCode = statusCode;
2055
+ response.end(body);
2056
+ }
2057
+ async function runDispatchAttempt(task, context) {
2058
+ const retryLimit = context.retryLimit ?? DISPATCH_RETRY_LIMIT;
2059
+ const retryBaseDelayMs = context.retryBaseDelayMs ?? DISPATCH_RETRY_BASE_DELAY_MS;
2060
+ let attempt = 0;
2061
+ while (true) {
2062
+ try {
2063
+ await task();
2064
+ return { ok: true };
2065
+ } catch (error) {
2066
+ if (!isRetryableDispatchError(error) || attempt >= retryLimit) {
2067
+ context.logger.error(context.failureEvent, {
2068
+ eventKey: context.eventKey,
2069
+ sessionId: context.sessionId,
2070
+ error: serializeError(error)
2071
+ });
2072
+ return {
2073
+ ok: false,
2074
+ attempts: attempt + 1,
2075
+ error
2076
+ };
2077
+ }
2078
+ attempt += 1;
2079
+ context.logger.warn(context.retryEvent, {
2080
+ eventKey: context.eventKey,
2081
+ sessionId: context.sessionId,
2082
+ attempt,
2083
+ delayMs: retryBaseDelayMs * attempt,
2084
+ error: serializeError(error)
2085
+ });
2086
+ await sleep2(retryBaseDelayMs * attempt);
2087
+ }
2088
+ }
2089
+ }
2090
+ async function markBootstrapHandoffProcessed(input) {
2091
+ try {
2092
+ await input.store.markBootstrapRequestProcessed(input.eventKey);
2093
+ } catch (error) {
2094
+ input.logger.warn("linear_thenvoi_bridge.bootstrap_handoff_mark_processed_failed", {
2095
+ eventKey: input.eventKey,
2096
+ sessionId: input.sessionId,
2097
+ error: serializeError(error)
2098
+ });
2099
+ }
2100
+ }
2101
+ async function signalTerminalDispatchFailure(input) {
2102
+ const { logger, job, failure } = input;
2103
+ try {
2104
+ const existing = await job.input.deps.store.getBySessionId(failure.sessionId);
2105
+ if (existing) {
2106
+ await job.input.deps.store.upsert({
2107
+ ...existing,
2108
+ status: "errored",
2109
+ lastEventKey: job.eventKey,
2110
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2111
+ });
2112
+ logger.warn("linear_thenvoi_bridge.async_dispatch_terminal_failure_signaled", {
2113
+ eventKey: failure.eventKey,
2114
+ sessionId: failure.sessionId,
2115
+ attempts: failure.attempts,
2116
+ signal: "session_store_status_errored"
2117
+ });
2118
+ return;
2119
+ }
2120
+ } catch (storeError) {
2121
+ logger.error("linear_thenvoi_bridge.async_dispatch_terminal_failure_store_signal_failed", {
2122
+ eventKey: failure.eventKey,
2123
+ sessionId: failure.sessionId,
2124
+ attempts: failure.attempts,
2125
+ dispatchError: serializeError(failure.error),
2126
+ signalError: serializeError(storeError)
2127
+ });
2128
+ }
2129
+ try {
2130
+ await postError(
2131
+ job.input.deps.linearClient,
2132
+ failure.sessionId,
2133
+ "Bridge dispatch failed and could not recover automatically. Retry this session event in Linear."
2134
+ );
2135
+ logger.warn("linear_thenvoi_bridge.async_dispatch_terminal_failure_signaled", {
2136
+ eventKey: failure.eventKey,
2137
+ sessionId: failure.sessionId,
2138
+ attempts: failure.attempts,
2139
+ signal: "linear_activity_error"
2140
+ });
2141
+ } catch (signalError) {
2142
+ logger.error("linear_thenvoi_bridge.async_dispatch_terminal_failure_signal_failed", {
2143
+ eventKey: failure.eventKey,
2144
+ sessionId: failure.sessionId,
2145
+ attempts: failure.attempts,
2146
+ dispatchError: serializeError(failure.error),
2147
+ signalError: serializeError(signalError)
2148
+ });
2149
+ }
2150
+ }
2151
+ function isRetryableDispatchError(error) {
2152
+ return typeof error === "object" && error !== null && "retryable" in error && error.retryable === true;
2153
+ }
2154
+ async function sleep2(ms) {
2155
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
2156
+ }
2157
+ function serializeError(error) {
2158
+ if (error instanceof Error) {
2159
+ return {
2160
+ name: error.name,
2161
+ message: error.message,
2162
+ stack: error.stack,
2163
+ ...typeof error.retryable !== "undefined" ? { retryable: error.retryable } : {}
2164
+ };
2165
+ }
2166
+ return {
2167
+ message: String(error)
2168
+ };
2169
+ }
2170
+ function toDispatchFailureError(failure) {
2171
+ return new Error(
2172
+ `Linear bridge async dispatch failed for event ${failure.eventKey} (session ${failure.sessionId})`,
2173
+ { cause: failure.error }
2174
+ );
2175
+ }
2176
+
2177
+ // src/integrations/linear/constants.ts
2178
+ var DEFAULT_STATUS_MAPPING = {
2179
+ active: "active",
2180
+ waiting: "waiting",
2181
+ canceled: "canceled",
2182
+ completed: "completed",
2183
+ errored: "errored"
2184
+ };
2185
+ // Annotate the CommonJS export names for ESM import in node:
2186
+ 0 && (module.exports = {
2187
+ DEFAULT_STATUS_MAPPING,
2188
+ buildLinearAuthorizationHeader,
2189
+ completeLinearSession,
2190
+ createInProcessLinearBridgeDispatcher,
2191
+ createInlineLinearBridgeDispatcher,
2192
+ createLinearBridgeRuntime,
2193
+ createLinearClient,
2194
+ createLinearTools,
2195
+ createLinearWebhookHandler,
2196
+ createSqliteSessionRoomStore,
2197
+ dedupeHandles,
2198
+ handleAgentSessionEvent,
2199
+ isLinearApiKey,
2200
+ postAction,
2201
+ postElicitation,
2202
+ postError,
2203
+ postResponse,
2204
+ postThought,
2205
+ stripHandlePrefix,
2206
+ updatePlan
2207
+ });