@builder.io/ai-utils 0.32.0 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/claw.d.ts +33 -2
- package/src/claw.js +97 -7
- package/src/claw.spec.d.ts +1 -0
- package/src/claw.spec.js +200 -0
- package/src/codegen.d.ts +79 -1
- package/src/events.d.ts +52 -2
- package/src/events.js +8 -0
- package/src/organization.d.ts +5 -0
- package/src/projects.d.ts +3 -0
- package/src/projects.js +1 -0
package/package.json
CHANGED
package/src/claw.d.ts
CHANGED
|
@@ -44,7 +44,39 @@ export interface ParsedChannelId {
|
|
|
44
44
|
* "inbox/user/builder-user-id" → { platform: "inbox", type: "user", ids: ["builder-user-id"] }
|
|
45
45
|
*/
|
|
46
46
|
export declare function parseChannelId(channelId: string): ParsedChannelId;
|
|
47
|
+
/**
|
|
48
|
+
* Converts a Builder channel_id URI to a clickable URL for the
|
|
49
|
+
* corresponding platform (Slack, Jira, etc.).
|
|
50
|
+
*
|
|
51
|
+
* Supported formats:
|
|
52
|
+
*
|
|
53
|
+
* Slack (uses app_redirect URLs - works for all workspaces):
|
|
54
|
+
* slack/thread/TEAM_ID/CHANNEL_ID/THREAD_TS
|
|
55
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=CHANNEL_ID&message_ts=THREAD_TS
|
|
56
|
+
* slack/channel/TEAM_ID/CHANNEL_ID
|
|
57
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=CHANNEL_ID
|
|
58
|
+
* slack/dm/TEAM_ID/USER_ID
|
|
59
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=USER_ID
|
|
60
|
+
*
|
|
61
|
+
* Jira:
|
|
62
|
+
* Returns null - needs integration.baseUrl from database
|
|
63
|
+
* (Future: jira/comment/CLOUD_ID/ISSUE_KEY → {baseUrl}/browse/ISSUE_KEY)
|
|
64
|
+
*
|
|
65
|
+
* Returns null for unsupported platforms or malformed channel IDs.
|
|
66
|
+
*
|
|
67
|
+
* TODO: Accept optional integration metadata parameter to construct proper
|
|
68
|
+
* workspace-specific URLs (Slack teamDomain, Jira baseUrl).
|
|
69
|
+
*/
|
|
70
|
+
export declare function convertChannelIdToUrl(channelId: string): string | null;
|
|
47
71
|
export interface WorkerReportOptions {
|
|
72
|
+
/** The original user's channel that triggered this work. */
|
|
73
|
+
originChannelId?: string;
|
|
74
|
+
/** The report content. */
|
|
75
|
+
content: string;
|
|
76
|
+
/** Agent/tool ID (for sub-agent reports). */
|
|
77
|
+
agentId: string;
|
|
78
|
+
}
|
|
79
|
+
export interface WorkerMessageOptions {
|
|
48
80
|
/** The original user's channel that triggered this work. */
|
|
49
81
|
originChannelId?: string;
|
|
50
82
|
/** The report content. */
|
|
@@ -53,8 +85,6 @@ export interface WorkerReportOptions {
|
|
|
53
85
|
projectId?: string;
|
|
54
86
|
/** Branch name (for branch reports). */
|
|
55
87
|
branchName?: string;
|
|
56
|
-
/** Agent/tool ID (for sub-agent reports). */
|
|
57
|
-
agentId?: string;
|
|
58
88
|
}
|
|
59
89
|
/**
|
|
60
90
|
* Formats a `<worker_report>` message for the org-agent.
|
|
@@ -64,6 +94,7 @@ export interface WorkerReportOptions {
|
|
|
64
94
|
* correct user channel.
|
|
65
95
|
*/
|
|
66
96
|
export declare function formatWorkerReport(opts: WorkerReportOptions): string;
|
|
97
|
+
export declare function formatWorkerMessage(opts: WorkerMessageOptions): string;
|
|
67
98
|
export interface IncomingMessageOptions {
|
|
68
99
|
/** The source channel (e.g. slack/thread/TEAM/CHANNEL/TS). */
|
|
69
100
|
channelId: string;
|
package/src/claw.js
CHANGED
|
@@ -16,6 +16,78 @@ export function parseChannelId(channelId) {
|
|
|
16
16
|
const [platform, type, ...ids] = parts;
|
|
17
17
|
return { platform, type, ids };
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Converts a Builder channel_id URI to a clickable URL for the
|
|
21
|
+
* corresponding platform (Slack, Jira, etc.).
|
|
22
|
+
*
|
|
23
|
+
* Supported formats:
|
|
24
|
+
*
|
|
25
|
+
* Slack (uses app_redirect URLs - works for all workspaces):
|
|
26
|
+
* slack/thread/TEAM_ID/CHANNEL_ID/THREAD_TS
|
|
27
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=CHANNEL_ID&message_ts=THREAD_TS
|
|
28
|
+
* slack/channel/TEAM_ID/CHANNEL_ID
|
|
29
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=CHANNEL_ID
|
|
30
|
+
* slack/dm/TEAM_ID/USER_ID
|
|
31
|
+
* → https://slack.com/app_redirect?team=TEAM_ID&channel=USER_ID
|
|
32
|
+
*
|
|
33
|
+
* Jira:
|
|
34
|
+
* Returns null - needs integration.baseUrl from database
|
|
35
|
+
* (Future: jira/comment/CLOUD_ID/ISSUE_KEY → {baseUrl}/browse/ISSUE_KEY)
|
|
36
|
+
*
|
|
37
|
+
* Returns null for unsupported platforms or malformed channel IDs.
|
|
38
|
+
*
|
|
39
|
+
* TODO: Accept optional integration metadata parameter to construct proper
|
|
40
|
+
* workspace-specific URLs (Slack teamDomain, Jira baseUrl).
|
|
41
|
+
*/
|
|
42
|
+
export function convertChannelIdToUrl(channelId) {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = parseChannelId(channelId);
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const { platform, type, ids } = parsed;
|
|
51
|
+
if (platform === "slack") {
|
|
52
|
+
return slackChannelIdToUrl(type, ids);
|
|
53
|
+
}
|
|
54
|
+
// Jira URLs need integration.baseUrl from database - not available here
|
|
55
|
+
// TODO: Add integration metadata parameter to support Jira URLs
|
|
56
|
+
if (platform === "jira") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function slackChannelIdToUrl(type, ids) {
|
|
62
|
+
// Use app_redirect URLs for all Slack types - they work universally across
|
|
63
|
+
// all workspaces without needing to know the workspace domain.
|
|
64
|
+
// These URLs open in the Slack app or web client automatically.
|
|
65
|
+
if (type === "thread") {
|
|
66
|
+
// slack/thread/TEAM_ID/CHANNEL_ID/THREAD_TS
|
|
67
|
+
const [teamId, slackChannelId, threadTs] = ids;
|
|
68
|
+
if (!teamId || !slackChannelId || !threadTs) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return `https://slack.com/app_redirect?team=${teamId}&channel=${slackChannelId}&message_ts=${threadTs}`;
|
|
72
|
+
}
|
|
73
|
+
if (type === "channel") {
|
|
74
|
+
// slack/channel/TEAM_ID/CHANNEL_ID
|
|
75
|
+
const [teamId, slackChannelId] = ids;
|
|
76
|
+
if (!teamId || !slackChannelId) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return `https://slack.com/app_redirect?team=${teamId}&channel=${slackChannelId}`;
|
|
80
|
+
}
|
|
81
|
+
if (type === "dm") {
|
|
82
|
+
// slack/dm/TEAM_ID/USER_ID
|
|
83
|
+
const [teamId, userId] = ids;
|
|
84
|
+
if (!teamId || !userId) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return `https://slack.com/app_redirect?team=${teamId}&channel=${userId}`;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
19
91
|
// ── Org-agent message formatting ──
|
|
20
92
|
//
|
|
21
93
|
// The org-agent receives two kinds of messages in its conversation:
|
|
@@ -36,19 +108,33 @@ export function formatWorkerReport(opts) {
|
|
|
36
108
|
let xml = `<worker_report>\n`;
|
|
37
109
|
if (opts.originChannelId) {
|
|
38
110
|
xml += `<origin_channel_id>${opts.originChannelId}</origin_channel_id>\n`;
|
|
111
|
+
const url = convertChannelIdToUrl(opts.originChannelId);
|
|
112
|
+
if (url) {
|
|
113
|
+
xml += `<origin_channel_url>${url}</origin_channel_url>\n`;
|
|
114
|
+
}
|
|
39
115
|
}
|
|
40
|
-
|
|
41
|
-
|
|
116
|
+
xml += `<agent_id>${opts.agentId}</agent_id>\n`;
|
|
117
|
+
xml += `<content>${opts.content}</content>\n`;
|
|
118
|
+
xml += `</worker_report>\n`;
|
|
119
|
+
xml += WORKER_REPORT_TRAILER;
|
|
120
|
+
return xml;
|
|
121
|
+
}
|
|
122
|
+
export function formatWorkerMessage(opts) {
|
|
123
|
+
let xml = `<worker_message>\n`;
|
|
124
|
+
if (opts.originChannelId) {
|
|
125
|
+
xml += `<origin_channel_id>${opts.originChannelId}</origin_channel_id>\n`;
|
|
126
|
+
const url = convertChannelIdToUrl(opts.originChannelId);
|
|
127
|
+
if (url) {
|
|
128
|
+
xml += `<origin_channel_url>${url}</origin_channel_url>\n`;
|
|
129
|
+
}
|
|
42
130
|
}
|
|
43
|
-
if (opts.
|
|
131
|
+
if (opts.projectId && opts.branchName) {
|
|
132
|
+
xml += `<project_id>${opts.projectId}</project_id>\n`;
|
|
44
133
|
xml += `<branch_name>${opts.branchName}</branch_name>\n`;
|
|
45
134
|
xml += `<channel_id>builder/branch/${opts.projectId}/${opts.branchName}</channel_id>\n`;
|
|
46
135
|
}
|
|
47
|
-
if (opts.agentId) {
|
|
48
|
-
xml += `<agent_id>${opts.agentId}</agent_id>\n`;
|
|
49
|
-
}
|
|
50
136
|
xml += `<content>${opts.content}</content>\n`;
|
|
51
|
-
xml += `</
|
|
137
|
+
xml += `</worker_message>\n`;
|
|
52
138
|
xml += WORKER_REPORT_TRAILER;
|
|
53
139
|
return xml;
|
|
54
140
|
}
|
|
@@ -65,6 +151,10 @@ export function formatIncomingMessage(opts) {
|
|
|
65
151
|
}
|
|
66
152
|
result += `<incoming_message>\n`;
|
|
67
153
|
result += `<channel_id>${opts.channelId}</channel_id>\n`;
|
|
154
|
+
const channelUrl = convertChannelIdToUrl(opts.channelId);
|
|
155
|
+
if (channelUrl) {
|
|
156
|
+
result += `<channel_url>${channelUrl}</channel_url>\n`;
|
|
157
|
+
}
|
|
68
158
|
if (opts.dmId) {
|
|
69
159
|
result += `<dm_id>${opts.dmId}</dm_id>\n`;
|
|
70
160
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/src/claw.spec.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { convertChannelIdToUrl, formatIncomingMessage, formatWorkerMessage, formatWorkerReport, } from "./claw";
|
|
3
|
+
describe("convertChannelIdToUrl", () => {
|
|
4
|
+
describe("slack/thread format", () => {
|
|
5
|
+
it("converts a thread channel ID to a Slack app_redirect URL", () => {
|
|
6
|
+
const url = convertChannelIdToUrl("slack/thread/T01ABC123/C01DEF456/1700000000.123456");
|
|
7
|
+
expect(url).toBe("https://slack.com/app_redirect?team=T01ABC123&channel=C01DEF456&message_ts=1700000000.123456");
|
|
8
|
+
});
|
|
9
|
+
it("handles timestamps with dots correctly", () => {
|
|
10
|
+
const url = convertChannelIdToUrl("slack/thread/TTEAM/CCHAN/1234567890.000100");
|
|
11
|
+
expect(url).toBe("https://slack.com/app_redirect?team=TTEAM&channel=CCHAN&message_ts=1234567890.000100");
|
|
12
|
+
});
|
|
13
|
+
it("returns null when teamId is missing", () => {
|
|
14
|
+
expect(convertChannelIdToUrl("slack/thread/CCHAN/1234567890.000100")).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
it("returns null when channelId is missing", () => {
|
|
17
|
+
expect(convertChannelIdToUrl("slack/thread/TTEAM/1234567890.000100")).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
it("returns null when threadTs is missing", () => {
|
|
20
|
+
expect(convertChannelIdToUrl("slack/thread/TTEAM/CCHAN")).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("slack/channel format", () => {
|
|
24
|
+
it("converts a channel ID to a Slack app_redirect URL", () => {
|
|
25
|
+
const url = convertChannelIdToUrl("slack/channel/T01ABC123/C01DEF456");
|
|
26
|
+
expect(url).toBe("https://slack.com/app_redirect?team=T01ABC123&channel=C01DEF456");
|
|
27
|
+
});
|
|
28
|
+
it("returns null when teamId is missing", () => {
|
|
29
|
+
expect(convertChannelIdToUrl("slack/channel/C01DEF456")).toBeNull();
|
|
30
|
+
});
|
|
31
|
+
it("returns null when channelId is missing", () => {
|
|
32
|
+
expect(convertChannelIdToUrl("slack/channel/TTEAM")).toBeNull();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe("slack/dm format", () => {
|
|
36
|
+
it("converts a DM channel ID to a Slack app_redirect URL", () => {
|
|
37
|
+
const url = convertChannelIdToUrl("slack/dm/T01ABC123/U01XYZ789");
|
|
38
|
+
expect(url).toBe("https://slack.com/app_redirect?team=T01ABC123&channel=U01XYZ789");
|
|
39
|
+
});
|
|
40
|
+
it("returns null when teamId is missing", () => {
|
|
41
|
+
expect(convertChannelIdToUrl("slack/dm/U01XYZ789")).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it("returns null when userId is missing", () => {
|
|
44
|
+
expect(convertChannelIdToUrl("slack/dm/TTEAM")).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("jira/comment format", () => {
|
|
48
|
+
it("returns null (needs integration.baseUrl from database)", () => {
|
|
49
|
+
const url = convertChannelIdToUrl("jira/comment/cloud-id/PROJ-123");
|
|
50
|
+
expect(url).toBeNull();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("unsupported platforms", () => {
|
|
54
|
+
it("returns null for telegram channel IDs", () => {
|
|
55
|
+
expect(convertChannelIdToUrl("telegram/chat/123456")).toBeNull();
|
|
56
|
+
});
|
|
57
|
+
it("returns null for builder branch channel IDs", () => {
|
|
58
|
+
expect(convertChannelIdToUrl("builder/branch/proj-id/my-branch")).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
it("returns null for inbox channel IDs", () => {
|
|
61
|
+
expect(convertChannelIdToUrl("inbox/user/user-id")).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
it("returns null for internal channel IDs", () => {
|
|
64
|
+
expect(convertChannelIdToUrl("internal/support")).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
it("returns null for completely invalid input", () => {
|
|
67
|
+
expect(convertChannelIdToUrl("not-a-channel-id")).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
it("returns null for empty string", () => {
|
|
70
|
+
expect(convertChannelIdToUrl("")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("formatIncomingMessage", () => {
|
|
75
|
+
it("includes channel_url for Slack thread channel IDs", () => {
|
|
76
|
+
const result = formatIncomingMessage({
|
|
77
|
+
channelId: "slack/thread/TTEAM/CCHAN/1234567890.000100",
|
|
78
|
+
sender: "Alice",
|
|
79
|
+
timestamp: "Monday, January 1, 2024 at 10:00 AM PST",
|
|
80
|
+
content: "Hello!",
|
|
81
|
+
});
|
|
82
|
+
expect(result).toContain("<channel_url>https://slack.com/app_redirect?team=TTEAM&channel=CCHAN&message_ts=1234567890.000100</channel_url>");
|
|
83
|
+
});
|
|
84
|
+
it("includes channel_url for Slack channel IDs", () => {
|
|
85
|
+
const result = formatIncomingMessage({
|
|
86
|
+
channelId: "slack/channel/TTEAM/CCHAN",
|
|
87
|
+
sender: "Bob",
|
|
88
|
+
timestamp: "Tuesday, January 2, 2024 at 11:00 AM PST",
|
|
89
|
+
content: "Hey there",
|
|
90
|
+
});
|
|
91
|
+
expect(result).toContain("<channel_url>https://slack.com/app_redirect?team=TTEAM&channel=CCHAN</channel_url>");
|
|
92
|
+
});
|
|
93
|
+
it("does not include channel_url for Jira (needs integration.baseUrl)", () => {
|
|
94
|
+
const result = formatIncomingMessage({
|
|
95
|
+
channelId: "jira/comment/cloud-id/PROJ-123",
|
|
96
|
+
sender: "Charlie",
|
|
97
|
+
timestamp: "Wednesday, January 3, 2024 at 12:00 PM PST",
|
|
98
|
+
content: "Jira comment",
|
|
99
|
+
});
|
|
100
|
+
expect(result).not.toContain("<channel_url>");
|
|
101
|
+
});
|
|
102
|
+
it("does not include channel_url for unsupported platforms", () => {
|
|
103
|
+
const result = formatIncomingMessage({
|
|
104
|
+
channelId: "telegram/chat/123456",
|
|
105
|
+
sender: "Charlie",
|
|
106
|
+
timestamp: "Wednesday, January 3, 2024 at 12:00 PM PST",
|
|
107
|
+
content: "Telegram message",
|
|
108
|
+
});
|
|
109
|
+
expect(result).not.toContain("<channel_url>");
|
|
110
|
+
});
|
|
111
|
+
it("includes dm_id without generating dm_url", () => {
|
|
112
|
+
const result = formatIncomingMessage({
|
|
113
|
+
channelId: "slack/thread/TTEAM/CCHAN/1234567890.000100",
|
|
114
|
+
dmId: "slack/dm/TTEAM/UUSER",
|
|
115
|
+
sender: "Dave",
|
|
116
|
+
timestamp: "Thursday, January 4, 2024 at 1:00 PM PST",
|
|
117
|
+
content: "DM context",
|
|
118
|
+
});
|
|
119
|
+
expect(result).toContain("<dm_id>slack/dm/TTEAM/UUSER</dm_id>");
|
|
120
|
+
expect(result).not.toContain("<dm_url>");
|
|
121
|
+
});
|
|
122
|
+
it("preserves all other fields unchanged", () => {
|
|
123
|
+
const result = formatIncomingMessage({
|
|
124
|
+
channelId: "slack/thread/TTEAM/CCHAN/1234567890.000100",
|
|
125
|
+
sender: "Frank",
|
|
126
|
+
timestamp: "Saturday, January 6, 2024 at 3:00 PM PST",
|
|
127
|
+
content: "Test message",
|
|
128
|
+
messageContext: "Prior context",
|
|
129
|
+
});
|
|
130
|
+
expect(result).toContain("<channel_id>slack/thread/TTEAM/CCHAN/1234567890.000100</channel_id>");
|
|
131
|
+
expect(result).toContain("<sender>Frank</sender>");
|
|
132
|
+
expect(result).toContain("Prior context");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe("formatWorkerMessage", () => {
|
|
136
|
+
it("includes origin_channel_url for Slack originChannelId", () => {
|
|
137
|
+
const result = formatWorkerMessage({
|
|
138
|
+
originChannelId: "slack/thread/TTEAM/CCHAN/1234567890.000100",
|
|
139
|
+
content: "Worker result",
|
|
140
|
+
projectId: "proj-123",
|
|
141
|
+
branchName: "feat/my-feature",
|
|
142
|
+
});
|
|
143
|
+
expect(result).toContain("<origin_channel_url>https://slack.com/app_redirect?team=TTEAM&channel=CCHAN&message_ts=1234567890.000100</origin_channel_url>");
|
|
144
|
+
});
|
|
145
|
+
it("does not include origin_channel_url for Jira (needs integration.baseUrl)", () => {
|
|
146
|
+
const result = formatWorkerMessage({
|
|
147
|
+
originChannelId: "jira/comment/cloud-id/PROJ-456",
|
|
148
|
+
content: "Worker result",
|
|
149
|
+
});
|
|
150
|
+
expect(result).not.toContain("<origin_channel_url>");
|
|
151
|
+
});
|
|
152
|
+
it("does not include origin_channel_url for unsupported platforms", () => {
|
|
153
|
+
const result = formatWorkerMessage({
|
|
154
|
+
originChannelId: "builder/branch/proj-id/my-branch",
|
|
155
|
+
content: "Worker result",
|
|
156
|
+
});
|
|
157
|
+
expect(result).not.toContain("<origin_channel_url>");
|
|
158
|
+
});
|
|
159
|
+
it("omits origin_channel_url when originChannelId is absent", () => {
|
|
160
|
+
const result = formatWorkerMessage({
|
|
161
|
+
content: "Worker result without origin",
|
|
162
|
+
});
|
|
163
|
+
expect(result).not.toContain("<origin_channel_url>");
|
|
164
|
+
expect(result).not.toContain("<origin_channel_id>");
|
|
165
|
+
});
|
|
166
|
+
it("uses matching XML tags (worker_message, not worker_report)", () => {
|
|
167
|
+
const result = formatWorkerMessage({
|
|
168
|
+
content: "Test content",
|
|
169
|
+
});
|
|
170
|
+
expect(result).toContain("<worker_message>");
|
|
171
|
+
expect(result).toContain("</worker_message>");
|
|
172
|
+
expect(result).not.toContain("</worker_report>");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe("formatWorkerReport", () => {
|
|
176
|
+
it("includes origin_channel_url for Slack originChannelId", () => {
|
|
177
|
+
const result = formatWorkerReport({
|
|
178
|
+
originChannelId: "slack/channel/TTEAM/CCHAN",
|
|
179
|
+
content: "Report content",
|
|
180
|
+
agentId: "agent-abc",
|
|
181
|
+
});
|
|
182
|
+
expect(result).toContain("<origin_channel_url>https://slack.com/app_redirect?team=TTEAM&channel=CCHAN</origin_channel_url>");
|
|
183
|
+
});
|
|
184
|
+
it("does not include origin_channel_url for Jira (needs integration.baseUrl)", () => {
|
|
185
|
+
const result = formatWorkerReport({
|
|
186
|
+
originChannelId: "jira/comment/cloud-id/PROJ-789",
|
|
187
|
+
content: "Report content",
|
|
188
|
+
agentId: "agent-xyz",
|
|
189
|
+
});
|
|
190
|
+
expect(result).not.toContain("<origin_channel_url>");
|
|
191
|
+
});
|
|
192
|
+
it("does not include origin_channel_url for unsupported platforms", () => {
|
|
193
|
+
const result = formatWorkerReport({
|
|
194
|
+
originChannelId: "telegram/chat/123456",
|
|
195
|
+
content: "Report content",
|
|
196
|
+
agentId: "agent-xyz",
|
|
197
|
+
});
|
|
198
|
+
expect(result).not.toContain("<origin_channel_url>");
|
|
199
|
+
});
|
|
200
|
+
});
|
package/src/codegen.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ export interface CustomAgentDefinition {
|
|
|
45
45
|
systemPrompt?: string;
|
|
46
46
|
tools?: string[];
|
|
47
47
|
model?: string;
|
|
48
|
+
roundRobinModels?: string[];
|
|
48
49
|
mode?: CodeGenMode;
|
|
49
50
|
position?: string;
|
|
50
51
|
needDevServer?: boolean;
|
|
@@ -224,6 +225,8 @@ export interface WebFetchToolInput {
|
|
|
224
225
|
export interface ExplorationMetadataToolInput {
|
|
225
226
|
category?: "reusable_knowledge" | "one_off" | "bad_quality";
|
|
226
227
|
gif_id?: string;
|
|
228
|
+
timeline_id?: string;
|
|
229
|
+
recording_id?: string;
|
|
227
230
|
important_files: {
|
|
228
231
|
file_path: string;
|
|
229
232
|
relevance?: "high" | "medium" | "low";
|
|
@@ -244,6 +247,58 @@ export interface ReadMcpResourceToolInput {
|
|
|
244
247
|
export interface RecordFrameToolInput {
|
|
245
248
|
title: string;
|
|
246
249
|
frame: "last-image";
|
|
250
|
+
category?: TimelineEventCategory;
|
|
251
|
+
description?: string;
|
|
252
|
+
}
|
|
253
|
+
export type TestOutcome = "succeeded" | "couldnt_verify" | "failed" | "other";
|
|
254
|
+
export interface ReportTestOutcomeToolInput {
|
|
255
|
+
outcome: TestOutcome;
|
|
256
|
+
summary: string;
|
|
257
|
+
details?: string;
|
|
258
|
+
}
|
|
259
|
+
export type TimelineEventCategory = "navigation" | "interaction" | "assertion" | "error" | "milestone" | "code-change" | "observation";
|
|
260
|
+
export interface TimelineEvent {
|
|
261
|
+
id: number;
|
|
262
|
+
timestamp: number;
|
|
263
|
+
label: string;
|
|
264
|
+
category: TimelineEventCategory;
|
|
265
|
+
description?: string;
|
|
266
|
+
toolName?: string;
|
|
267
|
+
frameIndex?: number;
|
|
268
|
+
durationMs?: number;
|
|
269
|
+
thinking?: string;
|
|
270
|
+
}
|
|
271
|
+
export interface TimelineFrameMetadata {
|
|
272
|
+
index: number;
|
|
273
|
+
timestamp: number;
|
|
274
|
+
title: string;
|
|
275
|
+
category: TimelineEventCategory;
|
|
276
|
+
eventId: number;
|
|
277
|
+
explicit: boolean;
|
|
278
|
+
displayDurationMs: number;
|
|
279
|
+
fileName?: string;
|
|
280
|
+
image_url?: string;
|
|
281
|
+
cursorX?: number | null;
|
|
282
|
+
cursorY?: number | null;
|
|
283
|
+
viewportWidth?: number;
|
|
284
|
+
viewportHeight?: number;
|
|
285
|
+
}
|
|
286
|
+
export interface TimelineRecording {
|
|
287
|
+
version: 1;
|
|
288
|
+
sessionId: string;
|
|
289
|
+
startTime: number;
|
|
290
|
+
endTime: number;
|
|
291
|
+
totalFrames: number;
|
|
292
|
+
events: TimelineEvent[];
|
|
293
|
+
frames: TimelineFrameMetadata[];
|
|
294
|
+
testOutcome?: TestOutcome;
|
|
295
|
+
testSummary?: string;
|
|
296
|
+
testDetails?: string;
|
|
297
|
+
uiIssues?: Array<{
|
|
298
|
+
title: string;
|
|
299
|
+
description: string;
|
|
300
|
+
debugInfo?: string;
|
|
301
|
+
}>;
|
|
247
302
|
}
|
|
248
303
|
/**
|
|
249
304
|
* Configuration values proposed by the setup analyzer agent
|
|
@@ -500,6 +555,11 @@ export interface SendMessageToolInput {
|
|
|
500
555
|
markdown: string;
|
|
501
556
|
status: "starting" | "question" | "will-follow-up" | "done:success" | "done:error";
|
|
502
557
|
loadingMessage?: string;
|
|
558
|
+
/**
|
|
559
|
+
* When true, send the response as a voice message using text-to-speech.
|
|
560
|
+
* Only supported for Telegram channels.
|
|
561
|
+
*/
|
|
562
|
+
voiceResponse?: boolean;
|
|
503
563
|
}
|
|
504
564
|
export interface SpawnBranchToolInput {
|
|
505
565
|
projectId: string;
|
|
@@ -536,6 +596,7 @@ export interface SubmitPRReviewToolInput {
|
|
|
536
596
|
/** SubmitRecording - Visual verification with recording (posted as separate review) */
|
|
537
597
|
export interface SubmitRecordingToolInput {
|
|
538
598
|
gif_id: string;
|
|
599
|
+
timeline_id?: string;
|
|
539
600
|
recording_caption: string;
|
|
540
601
|
summary?: string;
|
|
541
602
|
comments?: PRReviewComment[];
|
|
@@ -612,6 +673,7 @@ export interface CodeGenToolMap {
|
|
|
612
673
|
ResolveQAComments: ResolveQACommentsToolInput;
|
|
613
674
|
ReportUIIssue: ReportUIIssueToolInput;
|
|
614
675
|
ReportIssue: ReportIssueToolInput;
|
|
676
|
+
ReportTestOutcome: ReportTestOutcomeToolInput;
|
|
615
677
|
VerifySetupCommand: VerifySetupCommandToolInput;
|
|
616
678
|
VerifyDevCommand: VerifyDevCommandToolInput;
|
|
617
679
|
VerifyDevServer: VerifyDevServerToolInput;
|
|
@@ -726,6 +788,17 @@ export interface CodeGenInputOptions {
|
|
|
726
788
|
projectId?: string;
|
|
727
789
|
branchName?: string;
|
|
728
790
|
repoHash?: string;
|
|
791
|
+
/**
|
|
792
|
+
* Server-side branch.agentType cached from middleware Firestore lookup.
|
|
793
|
+
* Used to avoid redundant getBranch calls for billing exemption checks.
|
|
794
|
+
* @internal - Set by middleware, not by clients
|
|
795
|
+
*/
|
|
796
|
+
branchAgentType?: string | null;
|
|
797
|
+
/**
|
|
798
|
+
* True when middleware performed a Firestore branch lookup; do not use source-based exemption.
|
|
799
|
+
* @internal - Set by middleware, not by clients
|
|
800
|
+
*/
|
|
801
|
+
branchAgentTypeChecked?: boolean;
|
|
729
802
|
/** @deprecated */
|
|
730
803
|
prevId?: string;
|
|
731
804
|
/** @deprecated */
|
|
@@ -1121,6 +1194,8 @@ export interface GenerateCompletionStepToolCallRequest {
|
|
|
1121
1194
|
id: string;
|
|
1122
1195
|
name: string;
|
|
1123
1196
|
input: any;
|
|
1197
|
+
/** When true, this tool call can be resolved by a user message if no client handler exists */
|
|
1198
|
+
messageValid?: boolean;
|
|
1124
1199
|
}
|
|
1125
1200
|
export interface GenerateCompletionStepTerminals {
|
|
1126
1201
|
type: "terminals";
|
|
@@ -1130,6 +1205,8 @@ export interface GenerateCompletionStepProposeConfig {
|
|
|
1130
1205
|
type: "propose_config";
|
|
1131
1206
|
config: SetupAnalysisValues;
|
|
1132
1207
|
message?: string;
|
|
1208
|
+
/** When true, the project requires no dependency installation. The UI should not show an Apply dialog for dependencies. */
|
|
1209
|
+
noDependencies?: boolean;
|
|
1133
1210
|
}
|
|
1134
1211
|
export interface GenerateCompletionStepUpdateSetupValue {
|
|
1135
1212
|
type: "update_setup_value";
|
|
@@ -1165,10 +1242,11 @@ export interface UserSourceBase {
|
|
|
1165
1242
|
principals?: string[];
|
|
1166
1243
|
jobs?: string[];
|
|
1167
1244
|
permissions?: UserSourcePermission[];
|
|
1245
|
+
channelId?: string;
|
|
1168
1246
|
}
|
|
1169
1247
|
/** All integration sources: Slack, Teams, Jira, Linear, and git providers (GitHub, GitLab, Azure, Bitbucket). */
|
|
1170
1248
|
export interface UserSourceOther extends UserSourceBase {
|
|
1171
|
-
source: "slack" | "telegram" | "teams" | "jira" | "linear" | "github" | "gitlab" | "azure" | "bitbucket";
|
|
1249
|
+
source: "slack" | "telegram" | "whatsapp" | "teams" | "jira" | "linear" | "github" | "gitlab" | "azure" | "bitbucket";
|
|
1172
1250
|
/** User ID from the external platform (Slack user ID, GitHub user id, etc.) */
|
|
1173
1251
|
userId?: string;
|
|
1174
1252
|
userName?: string;
|
package/src/events.d.ts
CHANGED
|
@@ -530,7 +530,6 @@ export declare const ForceSetupAgentV1: {
|
|
|
530
530
|
};
|
|
531
531
|
export type ClawMessageSentV1 = FusionEventVariant<"claw.message.sent", {
|
|
532
532
|
branchName: string;
|
|
533
|
-
messageType: "text" | "task-result" | "cron-trigger";
|
|
534
533
|
content: string;
|
|
535
534
|
senderType: "user" | "sub-agent" | "system";
|
|
536
535
|
senderId?: string;
|
|
@@ -538,6 +537,8 @@ export type ClawMessageSentV1 = FusionEventVariant<"claw.message.sent", {
|
|
|
538
537
|
channelId?: string;
|
|
539
538
|
dmId?: string;
|
|
540
539
|
senderDisplayName?: string;
|
|
540
|
+
agentBranchName?: string;
|
|
541
|
+
agentProjectId?: string;
|
|
541
542
|
userSource?: UserSource;
|
|
542
543
|
/** Optional context to include alongside the message (e.g. Slack thread history). */
|
|
543
544
|
messageContext?: string;
|
|
@@ -548,13 +549,62 @@ export declare const ClawMessageSentV1: {
|
|
|
548
549
|
eventName: "claw.message.sent";
|
|
549
550
|
version: "1";
|
|
550
551
|
};
|
|
552
|
+
export type CodegenCompletionV1 = FusionEventVariant<"codegen.completion", {
|
|
553
|
+
completionId: string;
|
|
554
|
+
role?: string;
|
|
555
|
+
projectId?: string;
|
|
556
|
+
branchName?: string;
|
|
557
|
+
sessionId: string;
|
|
558
|
+
userSourceUserId?: string;
|
|
559
|
+
model?: string;
|
|
560
|
+
compact?: boolean;
|
|
561
|
+
usedTokens?: number;
|
|
562
|
+
category?: string;
|
|
563
|
+
userPrompt?: string;
|
|
564
|
+
}, {
|
|
565
|
+
projectId?: string;
|
|
566
|
+
branchName?: string;
|
|
567
|
+
sessionId: string;
|
|
568
|
+
}, 1>;
|
|
569
|
+
export declare const CodegenCompletionV1: {
|
|
570
|
+
eventName: "codegen.completion";
|
|
571
|
+
version: "1";
|
|
572
|
+
};
|
|
573
|
+
export type CodegenUserPromptV1 = FusionEventVariant<"codegen.user.prompt", {
|
|
574
|
+
completionId: string;
|
|
575
|
+
role?: string;
|
|
576
|
+
projectId?: string;
|
|
577
|
+
branchName?: string;
|
|
578
|
+
sessionId: string;
|
|
579
|
+
userSourceUserId?: string;
|
|
580
|
+
model?: string;
|
|
581
|
+
compact?: boolean;
|
|
582
|
+
usedTokens?: number;
|
|
583
|
+
category?: string;
|
|
584
|
+
userPrompt: string;
|
|
585
|
+
}, {
|
|
586
|
+
projectId?: string;
|
|
587
|
+
branchName?: string;
|
|
588
|
+
sessionId: string;
|
|
589
|
+
}, 1>;
|
|
590
|
+
export declare const CodegenUserPromptV1: {
|
|
591
|
+
eventName: "codegen.user.prompt";
|
|
592
|
+
version: "1";
|
|
593
|
+
};
|
|
551
594
|
export interface SendMessageToOrgAgentInput {
|
|
595
|
+
agentBranchName?: string;
|
|
596
|
+
agentProjectId?: string;
|
|
552
597
|
content: string;
|
|
598
|
+
senderType?: "user" | "sub-agent" | "system";
|
|
553
599
|
channelId?: string;
|
|
554
600
|
senderDisplayName?: string;
|
|
555
601
|
messageContext?: string;
|
|
602
|
+
senderId?: string;
|
|
603
|
+
dmId?: string;
|
|
604
|
+
userSource?: UserSource;
|
|
605
|
+
attachments?: FileUpload[];
|
|
556
606
|
}
|
|
557
|
-
export type FusionEvent = AiTaskCompletedEvent | AiTaskFailedEvent | GitPrCreatedEvent | ClientDevtoolsSessionStartedEvent | ClientDevtoolsSessionIdleEventV1 | FusionProjectCreatedV1 | SetupAgentCompletedV1 | ForceSetupAgentV1 | ClawMessageSentV1;
|
|
607
|
+
export type FusionEvent = AiTaskCompletedEvent | AiTaskFailedEvent | GitPrCreatedEvent | ClientDevtoolsSessionStartedEvent | ClientDevtoolsSessionIdleEventV1 | FusionProjectCreatedV1 | SetupAgentCompletedV1 | ForceSetupAgentV1 | ClawMessageSentV1 | CodegenCompletionV1 | CodegenUserPromptV1;
|
|
558
608
|
export interface ModelPermissionRequiredEvent {
|
|
559
609
|
type: "assistant.model.permission.required";
|
|
560
610
|
data: {
|
package/src/events.js
CHANGED
|
@@ -34,3 +34,11 @@ export const ClawMessageSentV1 = {
|
|
|
34
34
|
eventName: "claw.message.sent",
|
|
35
35
|
version: "1",
|
|
36
36
|
};
|
|
37
|
+
export const CodegenCompletionV1 = {
|
|
38
|
+
eventName: "codegen.completion",
|
|
39
|
+
version: "1",
|
|
40
|
+
};
|
|
41
|
+
export const CodegenUserPromptV1 = {
|
|
42
|
+
eventName: "codegen.user.prompt",
|
|
43
|
+
version: "1",
|
|
44
|
+
};
|
package/src/organization.d.ts
CHANGED
|
@@ -84,6 +84,10 @@ interface OrganizationSettings {
|
|
|
84
84
|
enabled?: boolean;
|
|
85
85
|
};
|
|
86
86
|
disableFigmaImageUpload?: boolean;
|
|
87
|
+
prReviewer?: {
|
|
88
|
+
instructions?: string;
|
|
89
|
+
model?: string;
|
|
90
|
+
};
|
|
87
91
|
enableTicketAssessment?: boolean;
|
|
88
92
|
ticketAssessmentPrompt?: string;
|
|
89
93
|
ticketAssessmentModel?: string;
|
|
@@ -292,6 +296,7 @@ export interface FeatureMap {
|
|
|
292
296
|
enterpriseGitProviders?: boolean;
|
|
293
297
|
agentCreditsRollover?: boolean;
|
|
294
298
|
selfHostedGitProviders?: boolean;
|
|
299
|
+
reviewAgent?: boolean;
|
|
295
300
|
}
|
|
296
301
|
export interface SubscriptionInfo {
|
|
297
302
|
price?: number;
|
package/src/projects.d.ts
CHANGED
|
@@ -569,6 +569,7 @@ export interface Project {
|
|
|
569
569
|
/** MIGRATION: accepts both string and number during migration period */
|
|
570
570
|
updatedAt: InMigrationDate;
|
|
571
571
|
pinned?: boolean;
|
|
572
|
+
pinOrder?: number;
|
|
572
573
|
archived?: boolean;
|
|
573
574
|
createdBy: string;
|
|
574
575
|
lastUpdateBy?: string;
|
|
@@ -644,6 +645,7 @@ export interface Project {
|
|
|
644
645
|
prReviewer?: {
|
|
645
646
|
enabled: boolean;
|
|
646
647
|
instructions?: string;
|
|
648
|
+
model?: string;
|
|
647
649
|
};
|
|
648
650
|
enableSnapshots?: boolean;
|
|
649
651
|
postMergeMemories?: boolean;
|
|
@@ -867,6 +869,7 @@ export interface SendMessageOptions {
|
|
|
867
869
|
featureFlags?: Record<string, boolean>;
|
|
868
870
|
webhook?: WebhookConfig;
|
|
869
871
|
fireAndForget?: boolean;
|
|
872
|
+
skipPromptAnalysis?: boolean;
|
|
870
873
|
canHandleTools?: (keyof CodeGenToolMap)[];
|
|
871
874
|
}
|
|
872
875
|
export interface MemoryData {
|
package/src/projects.js
CHANGED
|
@@ -30,6 +30,7 @@ export const EXAMPLE_REPOS = [
|
|
|
30
30
|
"BuilderIO/fusion-angular-tailwind-starter",
|
|
31
31
|
"BuilderIO/fusion-svelte-tailwind-starter",
|
|
32
32
|
"BuilderIO/fusion-vue-tailwind-starter",
|
|
33
|
+
"BuilderIO/org-agent-starter",
|
|
33
34
|
];
|
|
34
35
|
export const STARTER_REPO = "BuilderIO/fusion-starter";
|
|
35
36
|
export const EXAMPLE_OR_STARTER_REPOS = [...EXAMPLE_REPOS, STARTER_REPO];
|