@buildautomaton/cli 0.1.47 → 0.1.49

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/dist/index.js CHANGED
@@ -22733,6 +22733,238 @@ var WebSocketMessageSchema = external_exports.object({
22733
22733
  error: external_exports.string().optional()
22734
22734
  });
22735
22735
 
22736
+ // ../types/src/integrations/link-detection/linear.ts
22737
+ function detectLinearLink(url2) {
22738
+ const linearMatch = url2.match(
22739
+ /https?:\/\/(?:app\.)?linear\.app\/([^/]+)\/issue\/([A-Z]+-\d+)/i
22740
+ );
22741
+ if (linearMatch) {
22742
+ return {
22743
+ url: url2,
22744
+ integrationType: "linear",
22745
+ resourceType: "work-item",
22746
+ resourceId: linearMatch[2],
22747
+ workspaceId: linearMatch[1]
22748
+ };
22749
+ }
22750
+ return null;
22751
+ }
22752
+
22753
+ // ../types/src/integrations/link-detection/github.ts
22754
+ function detectGitHubLink(url2) {
22755
+ const githubIssueMatch = url2.match(
22756
+ /https?:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/i
22757
+ );
22758
+ if (githubIssueMatch) {
22759
+ return {
22760
+ url: url2,
22761
+ integrationType: "github",
22762
+ resourceType: "issue",
22763
+ resourceId: githubIssueMatch[3],
22764
+ owner: githubIssueMatch[1],
22765
+ repo: githubIssueMatch[2]
22766
+ };
22767
+ }
22768
+ const githubPRMatch = url2.match(
22769
+ /https?:\/\/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/i
22770
+ );
22771
+ if (githubPRMatch) {
22772
+ return {
22773
+ url: url2,
22774
+ integrationType: "github",
22775
+ resourceType: "pull-request",
22776
+ resourceId: githubPRMatch[3],
22777
+ owner: githubPRMatch[1],
22778
+ repo: githubPRMatch[2]
22779
+ };
22780
+ }
22781
+ return null;
22782
+ }
22783
+
22784
+ // ../types/src/integrations/link-detection/jira.ts
22785
+ function detectJIRALink(url2) {
22786
+ const jiraMatch = url2.match(
22787
+ /https?:\/\/([^/]+)\.atlassian\.net\/(?:browse|jira\/software\/projects\/[^/]+\/issues)\/([A-Z]+-\d+)/i
22788
+ );
22789
+ if (jiraMatch) {
22790
+ return {
22791
+ url: url2,
22792
+ integrationType: "jira",
22793
+ resourceType: "issue",
22794
+ resourceId: jiraMatch[2],
22795
+ projectKey: jiraMatch[2].split("-")[0]
22796
+ };
22797
+ }
22798
+ return null;
22799
+ }
22800
+
22801
+ // ../types/src/integrations/link-detection/notion.ts
22802
+ function formatNotionPageId(hex) {
22803
+ if (hex.length !== 32) return hex;
22804
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
22805
+ }
22806
+ function detectNotionLink(url2) {
22807
+ const standardMatch = url2.match(
22808
+ /https?:\/\/(?:www\.)?notion\.(?:so|site)\/(?:[^/#?]+\/)?(?:[^/#?-]+-)?([a-f0-9]{32})/i
22809
+ );
22810
+ if (standardMatch) {
22811
+ return {
22812
+ url: url2,
22813
+ integrationType: "notion",
22814
+ resourceType: "page",
22815
+ resourceId: formatNotionPageId(standardMatch[1])
22816
+ };
22817
+ }
22818
+ const siteMatch = url2.match(
22819
+ /https?:\/\/([^.]+)\.notion\.(?:so|site)\/(?:[^/#?-]+-)?([a-f0-9]{32})/i
22820
+ );
22821
+ if (siteMatch) {
22822
+ return {
22823
+ url: url2,
22824
+ integrationType: "notion",
22825
+ resourceType: "page",
22826
+ resourceId: formatNotionPageId(siteMatch[2]),
22827
+ notionWorkspaceId: siteMatch[1]
22828
+ };
22829
+ }
22830
+ return null;
22831
+ }
22832
+
22833
+ // ../types/src/integrations/link-detection/slack.ts
22834
+ function detectSlackLink(url2) {
22835
+ const archiveThreadMatch = url2.match(
22836
+ /https?:\/\/(?:([^./]+)\.)?slack\.com\/archives\/([A-Z0-9]+)\/p(\d+)(?:\?.*)?$/i
22837
+ );
22838
+ if (archiveThreadMatch) {
22839
+ return {
22840
+ url: url2,
22841
+ integrationType: "slack",
22842
+ resourceType: "thread",
22843
+ resourceId: `${archiveThreadMatch[2]}-${archiveThreadMatch[3]}`,
22844
+ isChannel: false
22845
+ };
22846
+ }
22847
+ const appThreadMatch = url2.match(
22848
+ /https?:\/\/app\.slack\.com\/client\/([^/]+)\/([A-Z0-9]+)\/thread\/[A-Z0-9]+-(\d+)/i
22849
+ );
22850
+ if (appThreadMatch) {
22851
+ return {
22852
+ url: url2,
22853
+ integrationType: "slack",
22854
+ resourceType: "thread",
22855
+ resourceId: `${appThreadMatch[2]}-${appThreadMatch[3]}`,
22856
+ isChannel: false
22857
+ };
22858
+ }
22859
+ const channelOnlyMatch = url2.match(
22860
+ /https?:\/\/(?:app\.)?(?:([^./]+)\.)?slack\.com\/archives\/([A-Z0-9]+)\/?(\?.*)?$/i
22861
+ );
22862
+ if (channelOnlyMatch && !url2.includes("/p")) {
22863
+ return {
22864
+ url: url2,
22865
+ integrationType: "slack",
22866
+ resourceType: "channel",
22867
+ resourceId: channelOnlyMatch[2],
22868
+ isChannel: true
22869
+ };
22870
+ }
22871
+ return null;
22872
+ }
22873
+
22874
+ // ../types/src/integrations/link-detection/zoom.ts
22875
+ function detectZoomLink(url2) {
22876
+ try {
22877
+ const u = new URL(url2);
22878
+ const host = u.hostname.toLowerCase();
22879
+ if (!host.endsWith("zoom.us") && !host.endsWith("zoom.com")) return null;
22880
+ const pathParts = u.pathname.split("/").filter(Boolean);
22881
+ if (pathParts.length >= 2 && (pathParts[0] === "j" || pathParts[0] === "s" || pathParts[0] === "my")) {
22882
+ const meetingId = pathParts[1].replace(/\s/g, "");
22883
+ if (meetingId.length >= 9) {
22884
+ return {
22885
+ url: url2,
22886
+ integrationType: "zoom",
22887
+ resourceType: "meeting",
22888
+ resourceId: meetingId,
22889
+ zoomMeetingId: meetingId
22890
+ };
22891
+ }
22892
+ }
22893
+ } catch {
22894
+ }
22895
+ return null;
22896
+ }
22897
+
22898
+ // ../types/src/integrations/link-detection/asana.ts
22899
+ var ASANA_TASK_GID = /\d{10,}/;
22900
+ function parseAsanaTaskGid(segment) {
22901
+ const trimmed2 = segment.trim();
22902
+ if (!ASANA_TASK_GID.test(trimmed2)) return null;
22903
+ return trimmed2;
22904
+ }
22905
+ function detectAsanaLink(url2) {
22906
+ const v1ProjectMatch = url2.match(
22907
+ /https?:\/\/app\.asana\.com\/1\/[^/]+\/project\/([^/?#]+)\/task\/([^/?#]+)/i
22908
+ );
22909
+ if (v1ProjectMatch) {
22910
+ const taskGid = parseAsanaTaskGid(v1ProjectMatch[2]);
22911
+ if (!taskGid) return null;
22912
+ return {
22913
+ url: url2,
22914
+ integrationType: "asana",
22915
+ resourceType: "task",
22916
+ resourceId: taskGid,
22917
+ asanaProjectGid: v1ProjectMatch[1]
22918
+ };
22919
+ }
22920
+ const v1TaskMatch = url2.match(/https?:\/\/app\.asana\.com\/1\/[^/]+(?:\/[^/]+)*\/task\/([^/?#]+)/i);
22921
+ if (v1TaskMatch) {
22922
+ const taskGid = parseAsanaTaskGid(v1TaskMatch[1]);
22923
+ if (!taskGid) return null;
22924
+ return {
22925
+ url: url2,
22926
+ integrationType: "asana",
22927
+ resourceType: "task",
22928
+ resourceId: taskGid
22929
+ };
22930
+ }
22931
+ const legacyMatch = url2.match(/https?:\/\/app\.asana\.com\/0\/([^/?#]+)\/([^/?#]+)/i);
22932
+ if (legacyMatch) {
22933
+ const projectGid = legacyMatch[1];
22934
+ const taskGid = parseAsanaTaskGid(legacyMatch[2]);
22935
+ if (!taskGid) return null;
22936
+ if (projectGid === "inbox" || projectGid === "my_tasks") {
22937
+ return null;
22938
+ }
22939
+ return {
22940
+ url: url2,
22941
+ integrationType: "asana",
22942
+ resourceType: "task",
22943
+ resourceId: taskGid,
22944
+ asanaProjectGid: projectGid === "0" ? void 0 : projectGid
22945
+ };
22946
+ }
22947
+ return null;
22948
+ }
22949
+
22950
+ // ../types/src/integrations/link-detection/index.ts
22951
+ function detectIntegrationLink(url2) {
22952
+ return detectLinearLink(url2) || detectGitHubLink(url2) || detectJIRALink(url2) || detectNotionLink(url2) || detectSlackLink(url2) || detectZoomLink(url2) || detectAsanaLink(url2);
22953
+ }
22954
+ function extractIntegrationLinks(text) {
22955
+ const urlRegex = /https?:\/\/[^\s<>"{}|\\^`[\]]+/gi;
22956
+ const urls = text.match(urlRegex) || [];
22957
+ const links = [];
22958
+ for (const url2 of urls) {
22959
+ const detected = detectIntegrationLink(url2);
22960
+ if (detected) links.push(detected);
22961
+ }
22962
+ return links;
22963
+ }
22964
+ function extractFetchableIntegrationLinks(text) {
22965
+ return extractIntegrationLinks(text).filter((link) => !(link.integrationType === "slack" && link.isChannel));
22966
+ }
22967
+
22736
22968
  // ../types/src/checkpoints.ts
22737
22969
  init_zod();
22738
22970
  var CheckpointKindSchema = external_exports.enum(["daily", "weekly", "overall"]);
@@ -23160,6 +23392,16 @@ var GitRepoMetaSchema = external_exports.object({
23160
23392
  updatedAt: external_exports.string()
23161
23393
  });
23162
23394
 
23395
+ // ../types/src/bridge-mcp-tool-display.ts
23396
+ var GET_INTEGRATION_CONTENT_TOOL = "get_integration_content";
23397
+ var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
23398
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
23399
+ var BRIDGE_MCP_TOOL_LABELS = {
23400
+ [GET_INTEGRATION_CONTENT_TOOL]: "Fetch external ticket content",
23401
+ [LIST_PARENT_SESSION_PROMPTS_TOOL]: "List parent session prompts",
23402
+ [GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL]: "Read parent session transcript"
23403
+ };
23404
+
23163
23405
  // ../types/src/claude-code-permission-mode.ts
23164
23406
  var CLAUDE_CODE_PERMISSION_MODES = [
23165
23407
  "default",
@@ -24469,7 +24711,7 @@ function installBridgeProcessResilience() {
24469
24711
  }
24470
24712
 
24471
24713
  // src/cli-version.ts
24472
- var CLI_VERSION = "0.1.47".length > 0 ? "0.1.47" : "0.0.0-dev";
24714
+ var CLI_VERSION = "0.1.49".length > 0 ? "0.1.49" : "0.0.0-dev";
24473
24715
 
24474
24716
  // src/connection/heartbeat/constants.ts
24475
24717
  var BRIDGE_APP_HEARTBEAT_INTERVAL_MS = 1e4;
@@ -33121,54 +33363,175 @@ function buildForkedSessionAgentPrompt(userPrompt, childSessionId, parentSession
33121
33363
  ].join("\n");
33122
33364
  }
33123
33365
 
33124
- // src/mcp/tools/session-history/fetch-parent-session-cloud.ts
33366
+ // src/mcp/tools/session-history/cloud/internal-api.ts
33125
33367
  function internalApiBase(cloudApiBaseUrl) {
33126
33368
  return cloudApiBaseUrl.replace(/\/+$/, "");
33127
33369
  }
33128
- async function fetchCloudSessionMeta(params) {
33370
+ async function fetchInternalApiJson(params) {
33129
33371
  const token = params.getCloudAccessToken();
33130
33372
  if (!token) return { ok: false, error: "Missing cloud access token." };
33131
- const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.sessionId)}/meta`;
33132
- const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33373
+ const url2 = `${internalApiBase(params.cloudApiBaseUrl)}${params.path}`;
33374
+ const method = params.method ?? "GET";
33375
+ const res = await fetch(url2, {
33376
+ method,
33377
+ headers: {
33378
+ Authorization: `Bearer ${token}`,
33379
+ ...params.body != null ? { "Content-Type": "application/json" } : {}
33380
+ },
33381
+ ...params.body != null ? { body: JSON.stringify(params.body) } : {}
33382
+ });
33133
33383
  if (!res.ok) {
33134
33384
  const t = await res.text().catch(() => "");
33135
- return { ok: false, error: `Session meta fetch failed (${res.status}): ${t.slice(0, 200)}` };
33385
+ return { ok: false, error: `${params.errorLabel} (${res.status}): ${t.slice(0, 200)}` };
33136
33386
  }
33137
33387
  const data = await res.json().catch(() => null);
33138
- if (!data) return { ok: false, error: "Invalid session meta response." };
33388
+ if (!data) return { ok: false, error: `Invalid ${params.errorLabel.toLowerCase()} response.` };
33139
33389
  return { ok: true, data };
33140
33390
  }
33141
- async function fetchCloudParentSessionPrompts(params) {
33142
- const token = params.getCloudAccessToken();
33143
- if (!token) return { ok: false, error: "Missing cloud access token." };
33144
- const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/prompts`;
33145
- const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33146
- if (!res.ok) {
33147
- const t = await res.text().catch(() => "");
33148
- return { ok: false, error: `Parent prompts fetch failed (${res.status}): ${t.slice(0, 200)}` };
33391
+
33392
+ // src/mcp/tools/session-history/cloud/fetch-session-meta.ts
33393
+ async function fetchCloudSessionMeta(params) {
33394
+ return fetchInternalApiJson({
33395
+ ...params,
33396
+ path: `/internal/sessions/${encodeURIComponent(params.sessionId)}/meta`,
33397
+ errorLabel: "Session meta fetch failed"
33398
+ });
33399
+ }
33400
+
33401
+ // src/lib/e2ee/decrypt-stored-e2ee-content.ts
33402
+ function parseJsonObject(raw) {
33403
+ try {
33404
+ const parsed = JSON.parse(raw);
33405
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
33406
+ return parsed;
33407
+ }
33408
+ } catch {
33409
+ return null;
33149
33410
  }
33150
- const data = await res.json().catch(() => null);
33151
- if (!data) return { ok: false, error: "Invalid parent prompts response." };
33152
- return { ok: true, data };
33411
+ return null;
33153
33412
  }
33154
- async function fetchCloudParentTurnTranscript(params) {
33155
- const token = params.getCloudAccessToken();
33156
- if (!token) return { ok: false, error: "Missing cloud access token." };
33157
- const url2 = `${internalApiBase(params.cloudApiBaseUrl)}/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/turns/${encodeURIComponent(params.turnId)}/transcript`;
33158
- const res = await fetch(url2, { headers: { Authorization: `Bearer ${token}` } });
33159
- if (!res.ok) {
33160
- const t = await res.text().catch(() => "");
33161
- return { ok: false, error: `Transcript fetch failed (${res.status}): ${t.slice(0, 200)}` };
33413
+ function storedStringHasE2eeEnvelope(raw) {
33414
+ const record2 = parseJsonObject(raw);
33415
+ return record2 != null && isE2eeEnvelope(record2.ee);
33416
+ }
33417
+ function e2eeKeyIdFromStoredString(raw) {
33418
+ const record2 = parseJsonObject(raw);
33419
+ if (record2 != null && isE2eeEnvelope(record2.ee)) return record2.ee.k;
33420
+ return null;
33421
+ }
33422
+ function decryptStoredE2eeRecord(record2, e2ee) {
33423
+ if (!isE2eeEnvelope(record2.ee)) return record2;
33424
+ if (!e2ee) {
33425
+ throw new Error(`E2EE_REQUIRED:${record2.ee.k}`);
33426
+ }
33427
+ if (record2.ee.k !== e2ee.keyId) {
33428
+ throw new Error(`E2EE_KEY_MISMATCH:encrypted=${record2.ee.k}:cli=${e2ee.keyId}`);
33429
+ }
33430
+ return e2ee.decryptMessage(record2);
33431
+ }
33432
+ function formatCliE2eeDecryptError(error40, e2ee) {
33433
+ const message = error40 instanceof Error ? error40.message : String(error40);
33434
+ const required2 = /^E2EE_REQUIRED:(.+)$/.exec(message.trim());
33435
+ if (required2) {
33436
+ const keyId = required2[1]?.trim() || "unknown";
33437
+ return `Parent session content is encrypted (key ${keyId}) but this bridge was not started with E2EE certificates. Restart the CLI with your E2EE certificate to read fork parent history.`;
33438
+ }
33439
+ const mismatch = /^E2EE_KEY_MISMATCH:encrypted=(.+):cli=(.*)$/.exec(message.trim());
33440
+ if (mismatch) {
33441
+ const encryptedKeyId = mismatch[1]?.trim() || "unknown";
33442
+ const cliKeyId = mismatch[2]?.trim() || e2ee?.keyId || "none";
33443
+ return `Parent session content is encrypted with key ${encryptedKeyId}, but this bridge certificate key is ${cliKeyId}. Use the same E2EE certificate that was used for the parent session.`;
33444
+ }
33445
+ if (message.includes("E2EE key mismatch")) {
33446
+ return `Parent session content could not be decrypted: ${message}`;
33447
+ }
33448
+ return `Parent session content could not be decrypted: ${message}`;
33449
+ }
33450
+ function decryptCliE2eeTranscriptPayload(payload, e2ee) {
33451
+ const record2 = parseJsonObject(payload);
33452
+ if (!record2 || !isE2eeEnvelope(record2.ee)) return payload;
33453
+ const decrypted = decryptStoredE2eeRecord(record2, e2ee);
33454
+ if (typeof decrypted.payload === "string") return decrypted.payload;
33455
+ return JSON.stringify(decrypted.payload ?? decrypted);
33456
+ }
33457
+ function decryptCliE2eeMessageField(payload, field, e2ee) {
33458
+ if (payload == null) return null;
33459
+ const record2 = parseJsonObject(payload);
33460
+ if (!record2 || !isE2eeEnvelope(record2.ee)) return payload;
33461
+ const decrypted = decryptStoredE2eeRecord(record2, e2ee);
33462
+ const value = decrypted[field];
33463
+ if (value == null) return null;
33464
+ return typeof value === "string" ? value : JSON.stringify(value);
33465
+ }
33466
+
33467
+ // src/mcp/tools/session-history/cloud/decrypt-parent-prompt-turns.ts
33468
+ function assertE2eeRuntimeForEnvelope(raw, e2ee) {
33469
+ if (storedStringHasE2eeEnvelope(raw) && !e2ee) {
33470
+ throw new Error(`E2EE_REQUIRED:${e2eeKeyIdFromStoredString(raw) ?? "unknown"}`);
33471
+ }
33472
+ }
33473
+ function decryptParentPromptTurns(turns, e2ee) {
33474
+ return turns.map((turn) => {
33475
+ const promptRaw = typeof turn.promptText === "string" ? turn.promptText : null;
33476
+ const titleRaw = typeof turn.title === "string" ? turn.title : null;
33477
+ if (promptRaw) assertE2eeRuntimeForEnvelope(promptRaw, e2ee);
33478
+ if (titleRaw) assertE2eeRuntimeForEnvelope(titleRaw, e2ee);
33479
+ return {
33480
+ ...turn,
33481
+ promptText: decryptCliE2eeMessageField(promptRaw, "prompt", e2ee),
33482
+ title: decryptCliE2eeMessageField(titleRaw, "title", e2ee)
33483
+ };
33484
+ });
33485
+ }
33486
+
33487
+ // src/mcp/tools/session-history/cloud/fetch-parent-prompts.ts
33488
+ async function fetchCloudParentSessionPrompts(params) {
33489
+ const fetched = await fetchInternalApiJson({
33490
+ ...params,
33491
+ path: `/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/prompts`,
33492
+ errorLabel: "Parent prompts fetch failed"
33493
+ });
33494
+ if (!fetched.ok) return fetched;
33495
+ const turns = Array.isArray(fetched.data.turns) ? fetched.data.turns : [];
33496
+ try {
33497
+ const decryptedTurns = decryptParentPromptTurns(turns, params.e2ee);
33498
+ return { ok: true, data: { ...fetched.data, turns: decryptedTurns } };
33499
+ } catch (error40) {
33500
+ return { ok: false, error: formatCliE2eeDecryptError(error40, params.e2ee) };
33162
33501
  }
33163
- const data = await res.json().catch(() => null);
33164
- const rows = Array.isArray(data?.transcript) ? data.transcript : [];
33502
+ }
33503
+
33504
+ // src/mcp/tools/session-history/cloud/format-parent-transcript-text.ts
33505
+ function formatParentTranscriptText(rows, e2ee) {
33165
33506
  const lines = rows.map((row) => {
33166
33507
  const kind = typeof row.kind === "string" ? row.kind : "event";
33167
- const payload = typeof row.payload === "string" ? row.payload : JSON.stringify(row.payload ?? "");
33508
+ const rawPayload = typeof row.payload === "string" ? row.payload : JSON.stringify(row.payload ?? "");
33509
+ if (storedStringHasE2eeEnvelope(rawPayload) && !e2ee) {
33510
+ throw new Error(`E2EE_REQUIRED:${e2eeKeyIdFromStoredString(rawPayload) ?? "unknown"}`);
33511
+ }
33512
+ const payload = decryptCliE2eeTranscriptPayload(rawPayload, e2ee);
33168
33513
  return `[${kind}] ${payload}`;
33169
33514
  });
33170
- return { ok: true, text: lines.join("\n") };
33515
+ return lines.join("\n");
33516
+ }
33517
+
33518
+ // src/mcp/tools/session-history/cloud/fetch-parent-transcript.ts
33519
+ async function fetchCloudParentTurnTranscript(params) {
33520
+ const fetched = await fetchInternalApiJson({
33521
+ ...params,
33522
+ path: `/internal/sessions/${encodeURIComponent(params.childSessionId)}/parent/turns/${encodeURIComponent(params.turnId)}/transcript`,
33523
+ errorLabel: "Transcript fetch failed"
33524
+ });
33525
+ if (!fetched.ok) return fetched;
33526
+ const rows = Array.isArray(fetched.data.transcript) ? fetched.data.transcript : [];
33527
+ try {
33528
+ return { ok: true, text: formatParentTranscriptText(rows, params.e2ee) };
33529
+ } catch (error40) {
33530
+ return { ok: false, error: formatCliE2eeDecryptError(error40, params.e2ee) };
33531
+ }
33171
33532
  }
33533
+
33534
+ // src/mcp/tools/session-history/cloud/resolve-parent-session-id.ts
33172
33535
  async function resolveParentSessionIdForChild(params) {
33173
33536
  const meta = await fetchCloudSessionMeta({
33174
33537
  sessionId: params.childSessionId,
@@ -33199,6 +33562,39 @@ async function enrichForkedSessionPromptForAgent(params) {
33199
33562
  return buildForkedSessionAgentPrompt(params.promptText, params.sessionId, parentSessionId);
33200
33563
  }
33201
33564
 
33565
+ // src/agents/acp/build-integration-content-agent-prompt-note.ts
33566
+ function buildIntegrationContentAgentPromptNote(sessionId) {
33567
+ const sessionRef = sessionId.trim();
33568
+ return [
33569
+ "The user message includes link(s) to external integration resources (Linear, GitHub, Jira, Notion, Slack thread, Zoom, or Asana).",
33570
+ "Use the get_integration_content MCP tool on the buildautomaton-bridge server to fetch the full content of each link.",
33571
+ `When calling the tool, pass sessionId "${sessionRef}" and the URL as url.`,
33572
+ "By default the tool links the fetched URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false when fetching additional links for context only (after the primary ticket is linked, or when you do not want association).",
33573
+ "If the tool reports unsupported_url, do not retry with the same URL. If it reports temporarily_unavailable, you may retry later."
33574
+ ].join("\n");
33575
+ }
33576
+ function injectIntegrationContentAgentPromptNote(agentPrompt, sessionId) {
33577
+ const note = buildIntegrationContentAgentPromptNote(sessionId);
33578
+ const marker = "\nUser request:\n";
33579
+ const idx = agentPrompt.indexOf(marker);
33580
+ if (idx >= 0) {
33581
+ return `${agentPrompt.slice(0, idx)}
33582
+ ${note}${agentPrompt.slice(idx)}`;
33583
+ }
33584
+ return `${note}
33585
+
33586
+ ${agentPrompt.trim()}`;
33587
+ }
33588
+
33589
+ // src/agents/acp/enrich-integration-content-prompt-for-agent.ts
33590
+ function enrichIntegrationContentPromptForAgent(params) {
33591
+ if (!params.sessionId.trim()) return params.agentPromptText;
33592
+ if (extractFetchableIntegrationLinks(params.originalPromptText).length === 0) {
33593
+ return params.agentPromptText;
33594
+ }
33595
+ return injectIntegrationContentAgentPromptNote(params.agentPromptText, params.sessionId);
33596
+ }
33597
+
33202
33598
  // src/agents/acp/send-prompt-to-agent.ts
33203
33599
  async function sendPromptToAgent(options) {
33204
33600
  const {
@@ -33229,6 +33625,13 @@ async function sendPromptToAgent(options) {
33229
33625
  getCloudAccessToken
33230
33626
  });
33231
33627
  }
33628
+ if (sessionId) {
33629
+ agentPromptText = enrichIntegrationContentPromptForAgent({
33630
+ sessionId,
33631
+ originalPromptText: promptText,
33632
+ agentPromptText
33633
+ });
33634
+ }
33232
33635
  let sendOpts = {};
33233
33636
  if (attachments && attachments.length > 0) {
33234
33637
  if (!sessionId || !cloudApiBaseUrl || !getCloudAccessToken) {
@@ -39200,16 +39603,81 @@ async function warmupAgentCapabilitiesOnConnect(params) {
39200
39603
  })();
39201
39604
  }
39202
39605
 
39203
- // src/mcp/tools/session-history/fork-session-mcp-tools.ts
39204
- var LIST_PARENT_SESSION_PROMPTS_TOOL = "list_parent_session_prompts";
39205
- var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL = "get_parent_session_turn_transcript";
39606
+ // src/mcp/tools/session-history/fork/mcp-text-result.ts
39607
+ function mcpTextResult(text, isError = false) {
39608
+ return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
39609
+ }
39610
+
39611
+ // src/mcp/tools/session-history/fork/call-get-parent-session-turn-transcript.ts
39612
+ async function callGetParentSessionTurnTranscriptTool(ctx, cloud, childSessionId, args) {
39613
+ const turnId = typeof args.turnId === "string" ? args.turnId.trim() : "";
39614
+ if (!turnId) return mcpTextResult("turnId is required.", true);
39615
+ const transcript = await fetchCloudParentTurnTranscript({
39616
+ childSessionId,
39617
+ turnId,
39618
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39619
+ getCloudAccessToken: cloud.getCloudAccessToken,
39620
+ e2ee: ctx.e2ee
39621
+ });
39622
+ if (!transcript.ok) return mcpTextResult(transcript.error, true);
39623
+ return mcpTextResult(transcript.text);
39624
+ }
39625
+
39626
+ // src/mcp/tools/session-history/fork/call-list-parent-session-prompts.ts
39627
+ async function callListParentSessionPromptsTool(ctx, cloud, childSessionId, parentSessionId) {
39628
+ const detail = await fetchCloudParentSessionPrompts({
39629
+ childSessionId,
39630
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39631
+ getCloudAccessToken: cloud.getCloudAccessToken,
39632
+ e2ee: ctx.e2ee
39633
+ });
39634
+ if (!detail.ok) return mcpTextResult(detail.error, true);
39635
+ const turns = Array.isArray(detail.data.turns) ? detail.data.turns : [];
39636
+ const payload = turns.map((t) => ({
39637
+ turnId: typeof t.turnId === "string" ? t.turnId : "",
39638
+ title: typeof t.title === "string" ? t.title : null,
39639
+ status: typeof t.status === "string" ? t.status : null,
39640
+ promptText: typeof t.promptText === "string" ? t.promptText : null
39641
+ }));
39642
+ return mcpTextResult(JSON.stringify({ parentSessionId, turns: payload }, null, 2));
39643
+ }
39644
+
39645
+ // src/mcp/tools/session-history/fork/tool-names.ts
39646
+ var LIST_PARENT_SESSION_PROMPTS_TOOL2 = "list_parent_session_prompts";
39647
+ var GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2 = "get_parent_session_turn_transcript";
39648
+
39649
+ // src/mcp/tools/session-history/fork/call-fork-session-mcp-tool.ts
39650
+ async function callForkSessionMcpTool(ctx, cloud, name, args) {
39651
+ const childSessionId = ctx.sessionId.trim();
39652
+ if (!childSessionId) {
39653
+ return mcpTextResult("sessionId is required.", true);
39654
+ }
39655
+ const parentResolved = await resolveParentSessionIdForChild({
39656
+ childSessionId,
39657
+ cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39658
+ getCloudAccessToken: cloud.getCloudAccessToken
39659
+ });
39660
+ if (!parentResolved.ok) {
39661
+ return mcpTextResult(parentResolved.error, true);
39662
+ }
39663
+ const parentSessionId = parentResolved.parentSessionId;
39664
+ if (name === LIST_PARENT_SESSION_PROMPTS_TOOL2) {
39665
+ return callListParentSessionPromptsTool(ctx, cloud, childSessionId, parentSessionId);
39666
+ }
39667
+ if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2) {
39668
+ return callGetParentSessionTurnTranscriptTool(ctx, cloud, childSessionId, args);
39669
+ }
39670
+ return mcpTextResult(`Unknown fork session tool: ${name}`, true);
39671
+ }
39672
+
39673
+ // src/mcp/tools/session-history/fork/tool-definitions.ts
39206
39674
  var SESSION_ID_SCHEMA = {
39207
39675
  type: "string",
39208
39676
  description: "Id of this forked (child) session \u2014 not the parent session id. Required on every tool call."
39209
39677
  };
39210
39678
  var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39211
39679
  {
39212
- name: LIST_PARENT_SESSION_PROMPTS_TOOL,
39680
+ name: LIST_PARENT_SESSION_PROMPTS_TOOL2,
39213
39681
  description: "List user prompts (main turns) from the parent session a fork was created from. Requires sessionId. Returns an error if the session has no parent history (not a fork).",
39214
39682
  inputSchema: {
39215
39683
  type: "object",
@@ -39219,7 +39687,7 @@ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39219
39687
  }
39220
39688
  },
39221
39689
  {
39222
- name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL,
39690
+ name: GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2,
39223
39691
  description: "Full transcript for one parent-session turn, including tool calls and agent messages. Requires sessionId and turnId. Returns an error if the session has no parent history (not a fork).",
39224
39692
  inputSchema: {
39225
39693
  type: "object",
@@ -39232,56 +39700,133 @@ var FORK_SESSION_MCP_TOOL_DEFINITIONS = [
39232
39700
  }
39233
39701
  }
39234
39702
  ];
39235
- function textResult(text, isError = false) {
39236
- return { content: [{ type: "text", text }], ...isError ? { isError: true } : {} };
39237
- }
39703
+
39704
+ // src/mcp/tools/session-history/fork/is-fork-session-mcp-tool-name.ts
39238
39705
  function isForkSessionMcpToolName(name) {
39239
- return name === LIST_PARENT_SESSION_PROMPTS_TOOL || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL;
39706
+ return name === LIST_PARENT_SESSION_PROMPTS_TOOL2 || name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL2;
39240
39707
  }
39241
- async function callForkSessionMcpTool(ctx, cloud, name, args) {
39242
- const childSessionId = ctx.sessionId.trim();
39243
- if (!childSessionId) {
39244
- return textResult("sessionId is required.", true);
39708
+
39709
+ // src/mcp/tools/integration-content/tool-names.ts
39710
+ var GET_INTEGRATION_CONTENT_TOOL2 = "get_integration_content";
39711
+
39712
+ // src/mcp/tools/integration-content/cloud/fetch-integration-content.ts
39713
+ async function fetchCloudIntegrationContent(params) {
39714
+ return fetchInternalApiJson({
39715
+ ...params,
39716
+ path: `/internal/sessions/${encodeURIComponent(params.sessionId)}/integrations/content`,
39717
+ errorLabel: "Integration content fetch failed",
39718
+ method: "POST",
39719
+ body: {
39720
+ url: params.url,
39721
+ associateAsSessionTicket: params.associateAsSessionTicket !== false
39722
+ }
39723
+ });
39724
+ }
39725
+
39726
+ // src/mcp/tools/integration-content/format-integration-content-for-mcp.ts
39727
+ function formatIntegrationContentForMcp(result) {
39728
+ if (!result.ok) {
39729
+ const lines2 = [`status: ${result.code}`, `message: ${result.message}`];
39730
+ if (result.integrationType) lines2.push(`integrationType: ${result.integrationType}`);
39731
+ if (result.retryable) lines2.push("retryable: true");
39732
+ lines2.push(...formatSessionTicketAssociationLines(result.sessionTicketAssociation));
39733
+ return lines2.join("\n");
39245
39734
  }
39246
- const parentResolved = await resolveParentSessionIdForChild({
39247
- childSessionId,
39735
+ const lines = [
39736
+ formatIntegrationContentBody(result.integrationType, result.content),
39737
+ ...formatSessionTicketAssociationLines(result.sessionTicketAssociation)
39738
+ ];
39739
+ return lines.join("\n");
39740
+ }
39741
+ function formatSessionTicketAssociationLines(association) {
39742
+ if (association.status === "associated") {
39743
+ const title = association.externalTicket.displayTitle?.trim() || association.externalTicket.normalizedUrl;
39744
+ return ["", "--- session ticket ---", "status: associated", `displayTitle: ${title}`];
39745
+ }
39746
+ if (association.status === "failed") {
39747
+ return ["", "--- session ticket ---", "status: failed", `reason: ${association.reason}`];
39748
+ }
39749
+ return ["", "--- session ticket ---", `status: ${association.status}`, `reason: ${association.reason}`];
39750
+ }
39751
+ function formatIntegrationContentBody(integrationType, content) {
39752
+ const lines = [
39753
+ `integrationType: ${integrationType}`,
39754
+ `title: ${content.title}`,
39755
+ `url: ${content.url}`,
39756
+ "",
39757
+ "--- body ---",
39758
+ content.body
39759
+ ];
39760
+ if (content.comments?.length) {
39761
+ lines.push("", "--- comments ---");
39762
+ for (const comment of content.comments) {
39763
+ lines.push(`[${comment.createdAt}] ${comment.author}:`, comment.body, "");
39764
+ }
39765
+ }
39766
+ const threadMessages = content.threadMessages;
39767
+ if (threadMessages?.length) {
39768
+ lines.push("", "--- thread messages ---");
39769
+ for (const msg of threadMessages) {
39770
+ lines.push(`[${msg.ts}] ${msg.displayName}: ${msg.text}`);
39771
+ }
39772
+ }
39773
+ if (content.metadata && Object.keys(content.metadata).length > 0) {
39774
+ lines.push("", "--- metadata ---", JSON.stringify(content.metadata, null, 2));
39775
+ }
39776
+ return lines.join("\n");
39777
+ }
39778
+
39779
+ // src/mcp/tools/integration-content/call-integration-content-mcp-tool.ts
39780
+ async function callIntegrationContentMcpTool(ctx, cloud, name, args) {
39781
+ if (name !== GET_INTEGRATION_CONTENT_TOOL2) {
39782
+ return mcpTextResult(`Unknown integration content tool: ${name}`, true);
39783
+ }
39784
+ const sessionId = ctx.sessionId.trim();
39785
+ if (!sessionId) return mcpTextResult("sessionId is required.", true);
39786
+ const url2 = typeof args.url === "string" ? args.url.trim() : "";
39787
+ if (!url2) return mcpTextResult("url is required.", true);
39788
+ const associateAsSessionTicket = args.associateAsSessionTicket !== false;
39789
+ const fetched = await fetchCloudIntegrationContent({
39790
+ sessionId,
39791
+ url: url2,
39792
+ associateAsSessionTicket,
39248
39793
  cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39249
39794
  getCloudAccessToken: cloud.getCloudAccessToken
39250
39795
  });
39251
- if (!parentResolved.ok) {
39252
- return textResult(parentResolved.error, true);
39253
- }
39254
- const parentSessionId = parentResolved.parentSessionId;
39255
- if (name === LIST_PARENT_SESSION_PROMPTS_TOOL) {
39256
- const detail = await fetchCloudParentSessionPrompts({
39257
- childSessionId,
39258
- cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39259
- getCloudAccessToken: cloud.getCloudAccessToken
39260
- });
39261
- if (!detail.ok) return textResult(detail.error, true);
39262
- const turns = Array.isArray(detail.data.turns) ? detail.data.turns : [];
39263
- const payload = turns.map((t) => ({
39264
- turnId: typeof t.turnId === "string" ? t.turnId : "",
39265
- title: typeof t.title === "string" ? t.title : null,
39266
- status: typeof t.status === "string" ? t.status : null,
39267
- promptText: typeof t.promptText === "string" ? t.promptText : null
39268
- }));
39269
- return textResult(JSON.stringify({ parentSessionId, turns: payload }, null, 2));
39270
- }
39271
- if (name === GET_PARENT_SESSION_TURN_TRANSCRIPT_TOOL) {
39272
- const turnId = typeof args.turnId === "string" ? args.turnId.trim() : "";
39273
- if (!turnId) return textResult("turnId is required.", true);
39274
- const transcript = await fetchCloudParentTurnTranscript({
39275
- childSessionId,
39276
- turnId,
39277
- cloudApiBaseUrl: cloud.cloudApiBaseUrl,
39278
- getCloudAccessToken: cloud.getCloudAccessToken,
39279
- e2ee: ctx.e2ee
39280
- });
39281
- if (!transcript.ok) return textResult(transcript.error, true);
39282
- return textResult(transcript.text);
39796
+ if (!fetched.ok) return mcpTextResult(fetched.error, true);
39797
+ const text = formatIntegrationContentForMcp(fetched.data);
39798
+ const isError = !fetched.data.ok;
39799
+ return mcpTextResult(text, isError);
39800
+ }
39801
+
39802
+ // src/mcp/tools/integration-content/tool-definitions.ts
39803
+ var SESSION_ID_SCHEMA2 = {
39804
+ type: "string",
39805
+ description: "Id of the current session. Required on every tool call so the workspace integration credentials can be resolved."
39806
+ };
39807
+ var INTEGRATION_CONTENT_MCP_TOOL_DEFINITIONS = [
39808
+ {
39809
+ name: GET_INTEGRATION_CONTENT_TOOL2,
39810
+ description: "Fetch the full content of a supported external integration link (Linear, GitHub, Jira, Notion, Slack thread, Zoom meeting, Asana). Requires sessionId and url. By default, links the URL as this session external ticket when the session has none yet. Pass associateAsSessionTicket: false to fetch content only (e.g. additional context links). Returns an error when the URL is unsupported, the integration is not connected, or content is temporarily unavailable (retry may succeed).",
39811
+ inputSchema: {
39812
+ type: "object",
39813
+ properties: {
39814
+ sessionId: SESSION_ID_SCHEMA2,
39815
+ url: { type: "string", description: "External integration URL to fetch" },
39816
+ associateAsSessionTicket: {
39817
+ type: "boolean",
39818
+ description: "Defaults to true. When true and the session has no linked external ticket yet, links this URL as the session external ticket. Pass false when fetching additional links for context only."
39819
+ }
39820
+ },
39821
+ required: ["sessionId", "url"],
39822
+ additionalProperties: false
39823
+ }
39283
39824
  }
39284
- return textResult(`Unknown fork session tool: ${name}`, true);
39825
+ ];
39826
+
39827
+ // src/mcp/tools/integration-content/index.ts
39828
+ function isIntegrationContentMcpToolName(name) {
39829
+ return name === GET_INTEGRATION_CONTENT_TOOL2;
39285
39830
  }
39286
39831
 
39287
39832
  // src/mcp/bridge-mcp-tools.ts
@@ -39294,7 +39839,7 @@ function requireCloud(shared) {
39294
39839
  function createBridgeMcpToolRegistry(shared) {
39295
39840
  const cloud = requireCloud(shared);
39296
39841
  return {
39297
- listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS],
39842
+ listTools: async () => [...FORK_SESSION_MCP_TOOL_DEFINITIONS, ...INTEGRATION_CONTENT_MCP_TOOL_DEFINITIONS],
39298
39843
  callTool: async (name, args) => {
39299
39844
  if (isForkSessionMcpToolName(name)) {
39300
39845
  if (!cloud) {
@@ -39312,6 +39857,22 @@ function createBridgeMcpToolRegistry(shared) {
39312
39857
  }
39313
39858
  return callForkSessionMcpTool({ ...shared, sessionId }, cloud, name, args);
39314
39859
  }
39860
+ if (isIntegrationContentMcpToolName(name)) {
39861
+ if (!cloud) {
39862
+ return {
39863
+ content: [{ type: "text", text: "Bridge MCP tools require cloud API credentials." }],
39864
+ isError: true
39865
+ };
39866
+ }
39867
+ const sessionId = typeof args.sessionId === "string" ? args.sessionId.trim() : "";
39868
+ if (!sessionId) {
39869
+ return {
39870
+ content: [{ type: "text", text: "sessionId is required." }],
39871
+ isError: true
39872
+ };
39873
+ }
39874
+ return callIntegrationContentMcpTool({ ...shared, sessionId }, cloud, name, args);
39875
+ }
39315
39876
  throw new Error(`Unknown bridge MCP tool: ${name}`);
39316
39877
  }
39317
39878
  };