robotrock 1.0.0 → 1.3.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/dist/agent-admin.d.ts +82 -0
- package/dist/agent-admin.js +938 -0
- package/dist/agent-admin.js.map +1 -0
- package/dist/ai/index.d.ts +6 -5
- package/dist/ai/index.js +682 -225
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/trigger.d.ts +4 -3
- package/dist/ai/trigger.js +682 -225
- package/dist/ai/trigger.js.map +1 -1
- package/dist/ai/workflow.d.ts +4 -3
- package/dist/ai/workflow.js +679 -222
- package/dist/ai/workflow.js.map +1 -1
- package/dist/auth-headers-qL-ZeEtd.d.ts +13 -0
- package/dist/{client-CzVmjXpz.d.ts → client-YO9Y1rkH.d.ts} +51 -10
- package/dist/eve/agent/index.d.ts +164 -0
- package/dist/eve/agent/index.js +2854 -0
- package/dist/eve/agent/index.js.map +1 -0
- package/dist/eve/index.d.ts +8 -0
- package/dist/eve/index.js +639 -0
- package/dist/eve/index.js.map +1 -0
- package/dist/eve/tools/admin/assign-tasks.d.ts +39 -0
- package/dist/eve/tools/admin/assign-tasks.js +1060 -0
- package/dist/eve/tools/admin/assign-tasks.js.map +1 -0
- package/dist/eve/tools/admin/manage-groups.d.ts +53 -0
- package/dist/eve/tools/admin/manage-groups.js +1218 -0
- package/dist/eve/tools/admin/manage-groups.js.map +1 -0
- package/dist/eve/tools/admin/manage-team-members.d.ts +45 -0
- package/dist/eve/tools/admin/manage-team-members.js +1160 -0
- package/dist/eve/tools/admin/manage-team-members.js.map +1 -0
- package/dist/eve/tools/admin/query-tasks.d.ts +71 -0
- package/dist/eve/tools/admin/query-tasks.js +1161 -0
- package/dist/eve/tools/admin/query-tasks.js.map +1 -0
- package/dist/eve/tools/catalog/search-products.d.ts +57 -0
- package/dist/eve/tools/catalog/search-products.js +94 -0
- package/dist/eve/tools/catalog/search-products.js.map +1 -0
- package/dist/eve/tools/identity/index.d.ts +8 -0
- package/dist/eve/tools/identity/index.js +234 -0
- package/dist/eve/tools/identity/index.js.map +1 -0
- package/dist/eve/tools/identity/my-access.d.ts +18 -0
- package/dist/eve/tools/identity/my-access.js +187 -0
- package/dist/eve/tools/identity/my-access.js.map +1 -0
- package/dist/eve/tools/identity/whoami.d.ts +44 -0
- package/dist/eve/tools/identity/whoami.js +117 -0
- package/dist/eve/tools/identity/whoami.js.map +1 -0
- package/dist/eve/tools/inbox/create-task.d.ts +114 -0
- package/dist/eve/tools/inbox/create-task.js +1785 -0
- package/dist/eve/tools/inbox/create-task.js.map +1 -0
- package/dist/eve/tools/inbox/index.d.ts +6 -0
- package/dist/eve/tools/inbox/index.js +1785 -0
- package/dist/eve/tools/inbox/index.js.map +1 -0
- package/dist/eve/tools/index.d.ts +24 -0
- package/dist/eve/tools/index.js +2848 -0
- package/dist/eve/tools/index.js.map +1 -0
- package/dist/index-DoQN48Bm.d.ts +200 -0
- package/dist/index.d.ts +8 -44
- package/dist/index.js +842 -137
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +6 -1
- package/dist/schemas/index.js +251 -59
- package/dist/schemas/index.js.map +1 -1
- package/dist/tenant-5YKDrdC-.d.ts +23 -0
- package/dist/{tool-approval-bridge-DbwUEBHv.d.ts → tool-approval-bridge-aMA79Z1B.d.ts} +1 -1
- package/dist/tool-reply-guidance-C3qrT1In.d.ts +25 -0
- package/dist/trigger/index.d.ts +2 -1
- package/dist/trigger/index.js +543 -127
- package/dist/trigger/index.js.map +1 -1
- package/dist/{trigger-BCKBbAV7.d.ts → trigger-CXrbKVCL.d.ts} +2 -2
- package/dist/workflow/index.d.ts +2 -1
- package/dist/workflow/index.js +543 -127
- package/dist/workflow/index.js.map +1 -1
- package/package.json +69 -8
|
@@ -0,0 +1,2848 @@
|
|
|
1
|
+
// src/eve/tools/inbox/create-task.ts
|
|
2
|
+
import { defineTool } from "eve/tools";
|
|
3
|
+
import { z as z6 } from "zod";
|
|
4
|
+
|
|
5
|
+
// src/schemas/index.ts
|
|
6
|
+
import { z as z4 } from "zod";
|
|
7
|
+
|
|
8
|
+
// ../core/src/utils/safe-url.ts
|
|
9
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
10
|
+
"localhost",
|
|
11
|
+
"metadata.google.internal",
|
|
12
|
+
"metadata.goog"
|
|
13
|
+
]);
|
|
14
|
+
function normalizeHostname(hostname) {
|
|
15
|
+
return hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
16
|
+
}
|
|
17
|
+
function isBlockedIpv4(host) {
|
|
18
|
+
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
19
|
+
if (!match) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const octets = match.slice(1, 5).map((part) => Number(part));
|
|
23
|
+
if (octets.some((octet) => Number.isNaN(octet) || octet < 0 || octet > 255)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const [a, b, _c, _d] = octets;
|
|
27
|
+
if (a === void 0 || b === void 0) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (a === 127 || a === 0) return true;
|
|
31
|
+
if (a === 10) return true;
|
|
32
|
+
if (a === 169 && b === 254) return true;
|
|
33
|
+
if (a === 192 && b === 168) return true;
|
|
34
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
35
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function ipv4FromNumericHost(host) {
|
|
39
|
+
if (/^\d+$/.test(host)) {
|
|
40
|
+
const num = Number(host);
|
|
41
|
+
if (!Number.isFinite(num) || num < 0 || num > 4294967295) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const a = num >>> 24 & 255;
|
|
45
|
+
const b = num >>> 16 & 255;
|
|
46
|
+
const c = num >>> 8 & 255;
|
|
47
|
+
const d = num & 255;
|
|
48
|
+
return `${a}.${b}.${c}.${d}`;
|
|
49
|
+
}
|
|
50
|
+
const hexMatch = /^0x([0-9a-f]{1,8})$/i.exec(host);
|
|
51
|
+
if (hexMatch) {
|
|
52
|
+
const num = Number.parseInt(hexMatch[1], 16);
|
|
53
|
+
if (!Number.isFinite(num) || num < 0 || num > 4294967295) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const a = num >>> 24 & 255;
|
|
57
|
+
const b = num >>> 16 & 255;
|
|
58
|
+
const c = num >>> 8 & 255;
|
|
59
|
+
const d = num & 255;
|
|
60
|
+
return `${a}.${b}.${c}.${d}`;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function isBlockedIpv4Mapped(host) {
|
|
65
|
+
const mappedDotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(host);
|
|
66
|
+
if (mappedDotted) {
|
|
67
|
+
return isBlockedIpv4(mappedDotted[1]);
|
|
68
|
+
}
|
|
69
|
+
const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(host);
|
|
70
|
+
if (mappedHex) {
|
|
71
|
+
const high = Number.parseInt(mappedHex[1], 16);
|
|
72
|
+
const low = Number.parseInt(mappedHex[2], 16);
|
|
73
|
+
if (Number.isFinite(high) && Number.isFinite(low)) {
|
|
74
|
+
const a = high >> 8 & 255;
|
|
75
|
+
const b = high & 255;
|
|
76
|
+
const c = low >> 8 & 255;
|
|
77
|
+
const d = low & 255;
|
|
78
|
+
return isBlockedIpv4(`${a}.${b}.${c}.${d}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function isBlockedIpv6(host) {
|
|
84
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (host.startsWith("fc") || host.startsWith("fd")) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (host.startsWith("fe80:")) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (isBlockedIpv4Mapped(host)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
function isBlockedHostname(hostname) {
|
|
99
|
+
const host = normalizeHostname(hostname);
|
|
100
|
+
if (!host) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (BLOCKED_HOSTNAMES.has(host)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (host.endsWith(".localhost") || host.endsWith(".local")) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
const numericIpv4 = ipv4FromNumericHost(host);
|
|
110
|
+
if (numericIpv4 && isBlockedIpv4(numericIpv4)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
return isBlockedIpv4(host) || isBlockedIpv6(host);
|
|
114
|
+
}
|
|
115
|
+
function isLoopbackHandlerUrl(urlString) {
|
|
116
|
+
let url;
|
|
117
|
+
try {
|
|
118
|
+
url = new URL(urlString);
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (url.username || url.password) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const host = url.hostname.toLowerCase();
|
|
129
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || host.endsWith(".localhost");
|
|
130
|
+
}
|
|
131
|
+
function isAllowedHandlerUrl(urlString) {
|
|
132
|
+
return isPublicHttpUrl(urlString) || isLoopbackHandlerUrl(urlString);
|
|
133
|
+
}
|
|
134
|
+
function isPublicHttpUrl(urlString) {
|
|
135
|
+
let url;
|
|
136
|
+
try {
|
|
137
|
+
url = new URL(urlString);
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (url.username || url.password) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
return !isBlockedHostname(url.hostname);
|
|
148
|
+
}
|
|
149
|
+
var PUBLIC_HTTP_URL_ERROR = "URL must be a public http:// or https:// address";
|
|
150
|
+
var HANDLER_URL_ERROR = "Handler URL must be a public or loopback http:// or https:// address";
|
|
151
|
+
|
|
152
|
+
// ../core/src/schemas/task.ts
|
|
153
|
+
import { z } from "zod";
|
|
154
|
+
|
|
155
|
+
// ../core/src/schemas/context-urls.ts
|
|
156
|
+
function resolveLinkUrl(value) {
|
|
157
|
+
return value.startsWith("http://") || value.startsWith("https://") ? value : `https://${value}`;
|
|
158
|
+
}
|
|
159
|
+
function widgetType(ui, field) {
|
|
160
|
+
const fieldUi = ui?.[field];
|
|
161
|
+
if (typeof fieldUi !== "object" || fieldUi === null) {
|
|
162
|
+
return void 0;
|
|
163
|
+
}
|
|
164
|
+
const widget = fieldUi["ui:widget"];
|
|
165
|
+
return typeof widget === "string" ? widget : void 0;
|
|
166
|
+
}
|
|
167
|
+
function validateUrlValue(value, widget) {
|
|
168
|
+
if (widget === "image" || widget === "link") {
|
|
169
|
+
if (typeof value !== "string") {
|
|
170
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
171
|
+
}
|
|
172
|
+
const url = widget === "link" ? resolveLinkUrl(value) : value;
|
|
173
|
+
if (!isPublicHttpUrl(url)) {
|
|
174
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (widget === "attachments" && Array.isArray(value)) {
|
|
179
|
+
for (const item of value) {
|
|
180
|
+
if (typeof item !== "object" || item === null) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const url = item.url;
|
|
184
|
+
if (typeof url === "string" && !isPublicHttpUrl(resolveLinkUrl(url))) {
|
|
185
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function validateContextPublicUrls(context) {
|
|
192
|
+
if (!context?.data) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
for (const [field, value] of Object.entries(context.data)) {
|
|
196
|
+
const widget = widgetType(context.ui, field);
|
|
197
|
+
if (!widget) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const error = validateUrlValue(value, widget);
|
|
201
|
+
if (error) {
|
|
202
|
+
return `context.data.${field}: ${error}`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ../core/src/schemas/task.ts
|
|
209
|
+
var safeUrlSchema = z.string().refine((url) => isPublicHttpUrl(url), {
|
|
210
|
+
message: PUBLIC_HTTP_URL_ERROR
|
|
211
|
+
});
|
|
212
|
+
var handlerUrlSchema = z.string().refine((url) => isAllowedHandlerUrl(url), {
|
|
213
|
+
message: HANDLER_URL_ERROR
|
|
214
|
+
});
|
|
215
|
+
var jsonSchema7Schema = z.custom(
|
|
216
|
+
(val) => typeof val === "object" && val !== null,
|
|
217
|
+
{ message: "Must be a valid JSON Schema object" }
|
|
218
|
+
);
|
|
219
|
+
var uiSchemaSchema = z.custom((val) => typeof val === "object" && val !== null, {
|
|
220
|
+
message: "Must be a valid UiSchema object"
|
|
221
|
+
});
|
|
222
|
+
var webhookHandlerSchema = z.object({
|
|
223
|
+
type: z.literal("webhook"),
|
|
224
|
+
url: handlerUrlSchema,
|
|
225
|
+
headers: z.record(z.string(), z.string())
|
|
226
|
+
});
|
|
227
|
+
var triggerHandlerSchema = webhookHandlerSchema.extend({
|
|
228
|
+
type: z.literal("trigger"),
|
|
229
|
+
tokenId: z.string().min(1)
|
|
230
|
+
});
|
|
231
|
+
var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
|
|
232
|
+
var taskActionInputSchema = z.object({
|
|
233
|
+
id: z.string().min(1),
|
|
234
|
+
title: z.string().min(1),
|
|
235
|
+
description: z.string().optional(),
|
|
236
|
+
schema: jsonSchema7Schema.optional(),
|
|
237
|
+
ui: uiSchemaSchema.optional(),
|
|
238
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
239
|
+
});
|
|
240
|
+
var taskActionSchema = taskActionInputSchema.extend({
|
|
241
|
+
// Optional handlers for this action - if present, must have at least 1
|
|
242
|
+
handlers: z.array(handlerSchema).min(1).optional()
|
|
243
|
+
});
|
|
244
|
+
var uiFieldSchemaSchema = z.object({
|
|
245
|
+
"ui:widget": z.string().optional(),
|
|
246
|
+
"ui:title": z.string().optional(),
|
|
247
|
+
"ui:description": z.string().optional(),
|
|
248
|
+
"ui:options": z.record(z.string(), z.unknown()).optional(),
|
|
249
|
+
items: z.lazy(() => z.record(z.string(), uiFieldSchemaSchema)).optional()
|
|
250
|
+
}).passthrough();
|
|
251
|
+
var contextUiSchema = z.record(z.string(), uiFieldSchemaSchema).optional();
|
|
252
|
+
var contextDataSchema = z.object({
|
|
253
|
+
data: z.record(z.string(), z.unknown()),
|
|
254
|
+
ui: contextUiSchema
|
|
255
|
+
}).optional();
|
|
256
|
+
var TASK_CONTEXT_FORMAT_VERSION = 2;
|
|
257
|
+
var taskContextObjectBaseSchema = z.object({
|
|
258
|
+
/** Source application id; omitted tasks are grouped in the default inbox. */
|
|
259
|
+
app: z.string().min(1).optional(),
|
|
260
|
+
type: z.string().min(1),
|
|
261
|
+
name: z.string().min(1),
|
|
262
|
+
description: z.string().optional(),
|
|
263
|
+
validUntil: z.string().optional(),
|
|
264
|
+
context: contextDataSchema,
|
|
265
|
+
/** Task context wire format version. @default 2 */
|
|
266
|
+
contextVersion: z.literal(2).optional(),
|
|
267
|
+
/** @deprecated Use `contextVersion`. Accepted on ingest only. */
|
|
268
|
+
version: z.literal(2).optional(),
|
|
269
|
+
actions: z.array(taskActionSchema).min(1, "At least one action is required")
|
|
270
|
+
});
|
|
271
|
+
function normalizeTaskContextVersion(data) {
|
|
272
|
+
const { version: legacyVersion, contextVersion, ...rest } = data;
|
|
273
|
+
return {
|
|
274
|
+
...rest,
|
|
275
|
+
contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
var taskContextObjectSchema = taskContextObjectBaseSchema.transform(
|
|
279
|
+
normalizeTaskContextVersion
|
|
280
|
+
);
|
|
281
|
+
function refineContextPublicUrls(data, ctx) {
|
|
282
|
+
const error = validateContextPublicUrls(data.context);
|
|
283
|
+
if (error) {
|
|
284
|
+
ctx.addIssue({
|
|
285
|
+
code: z.ZodIssueCode.custom,
|
|
286
|
+
message: error,
|
|
287
|
+
path: ["context"]
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
var taskContextSchema = taskContextObjectSchema.superRefine(
|
|
292
|
+
refineContextPublicUrls
|
|
293
|
+
);
|
|
294
|
+
var assignToSchema = z.object({
|
|
295
|
+
users: z.array(z.string().email()).optional(),
|
|
296
|
+
groups: z.array(z.string().min(1)).optional()
|
|
297
|
+
}).refine(
|
|
298
|
+
(data) => {
|
|
299
|
+
const groups = data.groups ?? [];
|
|
300
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
},
|
|
305
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
306
|
+
);
|
|
307
|
+
var threadUpdateMessageSchema = z.string().min(1).max(500);
|
|
308
|
+
var threadUpdateStatuses = [
|
|
309
|
+
"info",
|
|
310
|
+
"queued",
|
|
311
|
+
"running",
|
|
312
|
+
"waiting",
|
|
313
|
+
"succeeded",
|
|
314
|
+
"failed",
|
|
315
|
+
"cancelled"
|
|
316
|
+
];
|
|
317
|
+
var threadUpdateStatusSchema = z.enum(threadUpdateStatuses);
|
|
318
|
+
var threadUpdateInputSchema = z.object({
|
|
319
|
+
message: threadUpdateMessageSchema,
|
|
320
|
+
status: threadUpdateStatusSchema.optional()
|
|
321
|
+
});
|
|
322
|
+
var taskPriorities = ["low", "normal", "high", "urgent"];
|
|
323
|
+
var taskPrioritySchema = z.enum(taskPriorities);
|
|
324
|
+
var agentTelemetrySchema = z.object({
|
|
325
|
+
/** Agent or app version (semver, git SHA, deploy tag — caller-defined). */
|
|
326
|
+
version: z.string().min(1)
|
|
327
|
+
});
|
|
328
|
+
var createTaskBodySchema = taskContextObjectBaseSchema.extend({
|
|
329
|
+
assignTo: assignToSchema.optional(),
|
|
330
|
+
/**
|
|
331
|
+
* Groups related tasks together. When omitted, the server generates one and
|
|
332
|
+
* returns it so the caller can reuse it on later tasks in the same thread.
|
|
333
|
+
*/
|
|
334
|
+
threadId: z.string().min(1).optional(),
|
|
335
|
+
/**
|
|
336
|
+
* Optional thread priority. When set, applies to the whole thread and
|
|
337
|
+
* overwrites any previous priority. Omit on later tasks to leave unchanged.
|
|
338
|
+
*/
|
|
339
|
+
priority: taskPrioritySchema.optional(),
|
|
340
|
+
/**
|
|
341
|
+
* Optional initial status update logged against the task's thread. Shows in
|
|
342
|
+
* the inbox status bar and the thread update log.
|
|
343
|
+
*/
|
|
344
|
+
update: threadUpdateInputSchema.optional(),
|
|
345
|
+
/** Agent release version — not shown in inbox UI. */
|
|
346
|
+
agent: agentTelemetrySchema.optional()
|
|
347
|
+
}).transform(normalizeTaskContextVersion).superRefine(refineContextPublicUrls);
|
|
348
|
+
var agentChatSeedActionSchema = z.object({
|
|
349
|
+
/** Stable action id echoed back on submit; auto-derived from title when omitted. */
|
|
350
|
+
id: z.string().min(1).optional(),
|
|
351
|
+
title: z.string().min(1),
|
|
352
|
+
description: z.string().optional(),
|
|
353
|
+
/** JSON Schema object for the form fields. */
|
|
354
|
+
schema: z.record(z.string(), z.unknown()).optional(),
|
|
355
|
+
/** RJSF ui schema overrides (ui:widget, ui:enumNames, etc.). */
|
|
356
|
+
ui: z.record(z.string(), z.unknown()).optional(),
|
|
357
|
+
/** Optional default field values. */
|
|
358
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
359
|
+
});
|
|
360
|
+
var agentChatSeedMessageSchema = z.object({
|
|
361
|
+
role: z.enum(["user", "assistant"]),
|
|
362
|
+
text: z.string().min(1),
|
|
363
|
+
/** Optional display-name override for the message sender. */
|
|
364
|
+
senderName: z.string().min(1).optional(),
|
|
365
|
+
/**
|
|
366
|
+
* Optional structured input request rendered as a styled RobotRock action
|
|
367
|
+
* widget below the message text. Use this instead of asking the user to reply
|
|
368
|
+
* in plain text so the request is always a proper action form.
|
|
369
|
+
*/
|
|
370
|
+
requestActionInput: z.object({
|
|
371
|
+
prompt: z.string().optional(),
|
|
372
|
+
action: agentChatSeedActionSchema
|
|
373
|
+
}).optional()
|
|
374
|
+
});
|
|
375
|
+
var eveAgentChatTransportSchema = z.object({
|
|
376
|
+
kind: z.literal("eve"),
|
|
377
|
+
chatId: z.string().min(1),
|
|
378
|
+
baseUrl: z.string().url(),
|
|
379
|
+
sessionId: z.string().optional(),
|
|
380
|
+
continuationToken: z.string().optional(),
|
|
381
|
+
streamIndex: z.number().int().nonnegative().optional(),
|
|
382
|
+
isStreaming: z.boolean().optional()
|
|
383
|
+
});
|
|
384
|
+
var createAgentChatBodySchema = z.object({
|
|
385
|
+
/** Agent id (eve agent name). Required unless `parentChatId` is set. */
|
|
386
|
+
agentIdentifier: z.string().min(1).optional(),
|
|
387
|
+
/** Inherit tenant/connection/agent from an existing chat (agent-spawned chats). */
|
|
388
|
+
parentChatId: z.string().min(1).optional(),
|
|
389
|
+
/** Convex tenantEveConnections id; resolved from the tenant when omitted. */
|
|
390
|
+
eveConnectionId: z.string().min(1).optional(),
|
|
391
|
+
/** Source application id; groups the chat under an inbox section. */
|
|
392
|
+
app: z.string().min(1).optional(),
|
|
393
|
+
/** Email of the user who owns the chat. Required unless `parentChatId` is set. */
|
|
394
|
+
ownerEmail: z.string().email().optional(),
|
|
395
|
+
title: z.string().min(1),
|
|
396
|
+
messages: z.array(agentChatSeedMessageSchema).default([]),
|
|
397
|
+
/** Provenance label stored on the session ("cron" | "agent" | ...). */
|
|
398
|
+
source: z.string().min(1).optional()
|
|
399
|
+
}).refine((data) => Boolean(data.agentIdentifier || data.parentChatId), {
|
|
400
|
+
message: "Provide either agentIdentifier or parentChatId"
|
|
401
|
+
}).refine((data) => Boolean(data.ownerEmail || data.parentChatId), {
|
|
402
|
+
message: "Provide ownerEmail unless parentChatId is set"
|
|
403
|
+
});
|
|
404
|
+
var agentChatAuditToolBodySchema = z.object({
|
|
405
|
+
eveSessionId: z.string().min(1),
|
|
406
|
+
userId: z.string().min(1),
|
|
407
|
+
toolCallId: z.string().min(1),
|
|
408
|
+
toolName: z.string().min(1),
|
|
409
|
+
input: z.record(z.string(), z.unknown()).default({}),
|
|
410
|
+
success: z.boolean(),
|
|
411
|
+
status: z.enum(["completed", "failed", "rejected"]),
|
|
412
|
+
error: z.string().optional(),
|
|
413
|
+
idempotencyKey: z.string().optional()
|
|
414
|
+
});
|
|
415
|
+
var agentChatAuditInputBodySchema = z.object({
|
|
416
|
+
eveSessionId: z.string().min(1),
|
|
417
|
+
userId: z.string().min(1),
|
|
418
|
+
actionId: z.string().min(1),
|
|
419
|
+
actionTitle: z.string().optional(),
|
|
420
|
+
prompt: z.string().optional(),
|
|
421
|
+
data: z.record(z.string(), z.unknown()).optional(),
|
|
422
|
+
idempotencyKey: z.string().optional(),
|
|
423
|
+
/** Eve input request id — used to merge staged HITL metadata. */
|
|
424
|
+
requestId: z.string().optional(),
|
|
425
|
+
/** Eve tool call id — fallback lookup for staged HITL metadata. */
|
|
426
|
+
toolCallId: z.string().optional()
|
|
427
|
+
});
|
|
428
|
+
var eveHitlStagedOptionSchema = z.object({
|
|
429
|
+
id: z.string(),
|
|
430
|
+
label: z.string()
|
|
431
|
+
});
|
|
432
|
+
var agentChatStageHitlBodySchema = z.object({
|
|
433
|
+
eveSessionId: z.string().min(1),
|
|
434
|
+
requests: z.array(
|
|
435
|
+
z.object({
|
|
436
|
+
requestId: z.string().min(1),
|
|
437
|
+
toolCallId: z.string().min(1),
|
|
438
|
+
toolName: z.string().min(1),
|
|
439
|
+
prompt: z.string().min(1),
|
|
440
|
+
display: z.enum(["confirmation", "select", "text"]).optional(),
|
|
441
|
+
allowFreeform: z.boolean().optional(),
|
|
442
|
+
options: z.array(eveHitlStagedOptionSchema).optional(),
|
|
443
|
+
toolInput: z.record(z.string(), z.unknown()).optional()
|
|
444
|
+
})
|
|
445
|
+
)
|
|
446
|
+
});
|
|
447
|
+
var agentChatLinkTaskBodySchema = z.object({
|
|
448
|
+
eveSessionId: z.string().min(1),
|
|
449
|
+
publicTaskId: z.string().min(1),
|
|
450
|
+
toolCallId: z.string().min(1)
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// ../core/src/schemas/tool-result-display.ts
|
|
454
|
+
import { z as z2 } from "zod";
|
|
455
|
+
var toolDisplayMetadataSchema = z2.object({
|
|
456
|
+
widget: z2.string().optional(),
|
|
457
|
+
title: z2.string().optional(),
|
|
458
|
+
description: z2.string().optional()
|
|
459
|
+
});
|
|
460
|
+
var toolDisplayEnvelopeSchema = z2.object({
|
|
461
|
+
display: toolDisplayMetadataSchema.optional(),
|
|
462
|
+
data: z2.record(z2.string(), z2.unknown()),
|
|
463
|
+
ui: z2.record(
|
|
464
|
+
z2.string(),
|
|
465
|
+
z2.object({
|
|
466
|
+
"ui:widget": z2.string().optional(),
|
|
467
|
+
"ui:title": z2.string().optional(),
|
|
468
|
+
"ui:description": z2.string().optional(),
|
|
469
|
+
"ui:options": z2.record(z2.string(), z2.unknown()).optional()
|
|
470
|
+
}).passthrough()
|
|
471
|
+
).optional()
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// ../core/src/schemas/agent-admin.ts
|
|
475
|
+
import { z as z3 } from "zod";
|
|
476
|
+
var tenantRoleSchema = z3.enum(["admin", "member"]);
|
|
477
|
+
var agentAdminUserSchema = z3.object({
|
|
478
|
+
id: z3.string(),
|
|
479
|
+
email: z3.string().email(),
|
|
480
|
+
name: z3.string()
|
|
481
|
+
});
|
|
482
|
+
var agentAdminMemberSchema = z3.object({
|
|
483
|
+
userId: z3.string(),
|
|
484
|
+
role: z3.string(),
|
|
485
|
+
membershipKind: z3.enum(["team", "assignee"]),
|
|
486
|
+
hasLoggedIn: z3.boolean(),
|
|
487
|
+
user: agentAdminUserSchema
|
|
488
|
+
});
|
|
489
|
+
var agentAdminGroupSchema = z3.object({
|
|
490
|
+
id: z3.string(),
|
|
491
|
+
name: z3.string(),
|
|
492
|
+
slug: z3.string(),
|
|
493
|
+
description: z3.string().nullable(),
|
|
494
|
+
memberCount: z3.number().optional()
|
|
495
|
+
});
|
|
496
|
+
var agentAdminGroupDetailSchema = agentAdminGroupSchema.extend({
|
|
497
|
+
members: z3.array(
|
|
498
|
+
z3.object({
|
|
499
|
+
userId: z3.string(),
|
|
500
|
+
user: agentAdminUserSchema
|
|
501
|
+
})
|
|
502
|
+
)
|
|
503
|
+
});
|
|
504
|
+
var agentAdminTaskSummarySchema = z3.object({
|
|
505
|
+
id: z3.string(),
|
|
506
|
+
convexId: z3.string(),
|
|
507
|
+
status: z3.string(),
|
|
508
|
+
type: z3.string().nullable(),
|
|
509
|
+
name: z3.string().nullable(),
|
|
510
|
+
description: z3.string().nullable(),
|
|
511
|
+
validUntil: z3.number(),
|
|
512
|
+
createdAt: z3.number(),
|
|
513
|
+
threadPriority: z3.string().nullable(),
|
|
514
|
+
handledByUserId: z3.string().nullable()
|
|
515
|
+
});
|
|
516
|
+
var inviteMemberBodySchema = z3.object({
|
|
517
|
+
email: z3.string().email(),
|
|
518
|
+
role: tenantRoleSchema.optional()
|
|
519
|
+
});
|
|
520
|
+
var updateMemberRoleBodySchema = z3.object({
|
|
521
|
+
role: tenantRoleSchema
|
|
522
|
+
});
|
|
523
|
+
var createGroupBodySchema = z3.object({
|
|
524
|
+
name: z3.string().min(1),
|
|
525
|
+
description: z3.string().optional()
|
|
526
|
+
});
|
|
527
|
+
var updateGroupBodySchema = z3.object({
|
|
528
|
+
name: z3.string().min(1).optional(),
|
|
529
|
+
description: z3.string().optional()
|
|
530
|
+
});
|
|
531
|
+
var addGroupMemberBodySchema = z3.object({
|
|
532
|
+
userId: z3.string().min(1).describe("Convex user id or member email.")
|
|
533
|
+
});
|
|
534
|
+
var listTasksQuerySchema = z3.object({
|
|
535
|
+
statusFilter: z3.enum(["all", "open", "handled", "expired"]).optional(),
|
|
536
|
+
typeFilter: z3.string().optional(),
|
|
537
|
+
appFilter: z3.string().optional(),
|
|
538
|
+
sortField: z3.enum(["type", "date", "status", "validUntil"]).optional(),
|
|
539
|
+
sortDirection: z3.enum(["asc", "desc"]).optional(),
|
|
540
|
+
limit: z3.coerce.number().int().positive().max(100).optional(),
|
|
541
|
+
cursor: z3.string().optional()
|
|
542
|
+
});
|
|
543
|
+
var searchTasksQuerySchema = z3.object({
|
|
544
|
+
q: z3.string().min(1),
|
|
545
|
+
limit: z3.coerce.number().int().positive().max(100).optional()
|
|
546
|
+
});
|
|
547
|
+
var assignTasksResultSchema = z3.object({
|
|
548
|
+
taskId: z3.string(),
|
|
549
|
+
success: z3.boolean(),
|
|
550
|
+
message: z3.string().optional(),
|
|
551
|
+
name: z3.string().nullable().optional(),
|
|
552
|
+
description: z3.string().nullable().optional(),
|
|
553
|
+
type: z3.string().nullable().optional()
|
|
554
|
+
});
|
|
555
|
+
var assignTasksResponseSchema = z3.object({
|
|
556
|
+
results: z3.array(assignTasksResultSchema)
|
|
557
|
+
});
|
|
558
|
+
var assignTasksBodySchema = z3.object({
|
|
559
|
+
taskIds: z3.array(z3.string().min(1)).min(1).max(100),
|
|
560
|
+
assignTo: assignToSchema.refine(
|
|
561
|
+
(value) => (value.users?.length ?? 0) > 0 || (value.groups?.length ?? 0) > 0,
|
|
562
|
+
{ message: "assignTo needs at least one user email or group slug." }
|
|
563
|
+
)
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// src/schemas/index.ts
|
|
567
|
+
var handlerUrlSchema2 = z4.string().refine((url) => isAllowedHandlerUrl(url), {
|
|
568
|
+
message: HANDLER_URL_ERROR
|
|
569
|
+
});
|
|
570
|
+
var jsonSchema7Schema2 = z4.custom(
|
|
571
|
+
(val) => typeof val === "object" && val !== null,
|
|
572
|
+
{ message: "Must be a valid JSON Schema object" }
|
|
573
|
+
);
|
|
574
|
+
var uiSchemaSchema2 = z4.custom((val) => typeof val === "object" && val !== null, {
|
|
575
|
+
message: "Must be a valid UiSchema object"
|
|
576
|
+
});
|
|
577
|
+
var webhookHandlerSchema2 = z4.object({
|
|
578
|
+
type: z4.literal("webhook"),
|
|
579
|
+
url: handlerUrlSchema2,
|
|
580
|
+
headers: z4.record(z4.string(), z4.string())
|
|
581
|
+
});
|
|
582
|
+
var triggerHandlerSchema2 = webhookHandlerSchema2.extend({
|
|
583
|
+
type: z4.literal("trigger"),
|
|
584
|
+
tokenId: z4.string().min(1)
|
|
585
|
+
});
|
|
586
|
+
var handlerSchema2 = z4.discriminatedUnion("type", [webhookHandlerSchema2, triggerHandlerSchema2]);
|
|
587
|
+
var taskActionInputSchema2 = z4.object({
|
|
588
|
+
id: z4.string().min(1),
|
|
589
|
+
title: z4.string().min(1),
|
|
590
|
+
description: z4.string().optional(),
|
|
591
|
+
schema: jsonSchema7Schema2.optional(),
|
|
592
|
+
ui: uiSchemaSchema2.optional(),
|
|
593
|
+
data: z4.record(z4.string(), z4.unknown()).optional()
|
|
594
|
+
});
|
|
595
|
+
var taskActionSchema2 = taskActionInputSchema2.extend({
|
|
596
|
+
handlers: z4.array(handlerSchema2).min(1).optional()
|
|
597
|
+
});
|
|
598
|
+
var uiFieldSchemaSchema2 = z4.object({
|
|
599
|
+
"ui:widget": z4.string().optional(),
|
|
600
|
+
"ui:title": z4.string().optional(),
|
|
601
|
+
"ui:description": z4.string().optional(),
|
|
602
|
+
"ui:options": z4.record(z4.string(), z4.unknown()).optional(),
|
|
603
|
+
items: z4.lazy(() => z4.record(z4.string(), uiFieldSchemaSchema2)).optional()
|
|
604
|
+
}).passthrough();
|
|
605
|
+
var contextUiSchema2 = z4.record(z4.string(), uiFieldSchemaSchema2).optional();
|
|
606
|
+
var contextDataSchema2 = z4.object({
|
|
607
|
+
data: z4.record(z4.string(), z4.unknown()),
|
|
608
|
+
ui: contextUiSchema2
|
|
609
|
+
}).optional();
|
|
610
|
+
var TASK_CONTEXT_FORMAT_VERSION2 = 2;
|
|
611
|
+
var taskContextObjectBaseSchema2 = z4.object({
|
|
612
|
+
app: z4.string().min(1).optional(),
|
|
613
|
+
type: z4.string().min(1),
|
|
614
|
+
name: z4.string().min(1),
|
|
615
|
+
description: z4.string().optional(),
|
|
616
|
+
validUntil: z4.string().optional(),
|
|
617
|
+
context: contextDataSchema2,
|
|
618
|
+
contextVersion: z4.literal(2).optional(),
|
|
619
|
+
/** @deprecated Use `contextVersion`. Accepted on ingest only. */
|
|
620
|
+
version: z4.literal(2).optional(),
|
|
621
|
+
actions: z4.array(taskActionSchema2).min(1, "At least one action is required")
|
|
622
|
+
});
|
|
623
|
+
function normalizeTaskContextVersion2(data) {
|
|
624
|
+
const { version: legacyVersion, contextVersion, ...rest } = data;
|
|
625
|
+
return {
|
|
626
|
+
...rest,
|
|
627
|
+
contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION2
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
var taskContextObjectSchema2 = taskContextObjectBaseSchema2.transform(
|
|
631
|
+
normalizeTaskContextVersion2
|
|
632
|
+
);
|
|
633
|
+
function refineContextPublicUrls2(data, ctx) {
|
|
634
|
+
const error = validateContextPublicUrls(data.context);
|
|
635
|
+
if (error) {
|
|
636
|
+
ctx.addIssue({
|
|
637
|
+
code: z4.ZodIssueCode.custom,
|
|
638
|
+
message: error,
|
|
639
|
+
path: ["context"]
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
var taskContextSchema2 = taskContextObjectSchema2.superRefine(
|
|
644
|
+
refineContextPublicUrls2
|
|
645
|
+
);
|
|
646
|
+
var assignToSchema2 = z4.object({
|
|
647
|
+
users: z4.array(z4.string().email()).optional(),
|
|
648
|
+
groups: z4.array(z4.string().min(1)).optional()
|
|
649
|
+
}).refine(
|
|
650
|
+
(data) => {
|
|
651
|
+
const groups = data.groups ?? [];
|
|
652
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
},
|
|
657
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
658
|
+
);
|
|
659
|
+
var threadUpdateMessageSchema2 = z4.string().min(1).max(500);
|
|
660
|
+
var threadUpdateStatuses2 = [
|
|
661
|
+
"info",
|
|
662
|
+
"queued",
|
|
663
|
+
"running",
|
|
664
|
+
"waiting",
|
|
665
|
+
"succeeded",
|
|
666
|
+
"failed",
|
|
667
|
+
"cancelled"
|
|
668
|
+
];
|
|
669
|
+
var threadUpdateStatusSchema2 = z4.enum(threadUpdateStatuses2);
|
|
670
|
+
var threadUpdateInputSchema2 = z4.object({
|
|
671
|
+
message: threadUpdateMessageSchema2,
|
|
672
|
+
status: threadUpdateStatusSchema2.optional()
|
|
673
|
+
});
|
|
674
|
+
var taskPriorities2 = ["low", "normal", "high", "urgent"];
|
|
675
|
+
var taskPrioritySchema2 = z4.enum(taskPriorities2);
|
|
676
|
+
var agentTelemetrySchema2 = z4.object({
|
|
677
|
+
version: z4.string().min(1)
|
|
678
|
+
});
|
|
679
|
+
var createTaskBodySchema2 = taskContextObjectBaseSchema2.extend({
|
|
680
|
+
assignTo: assignToSchema2.optional(),
|
|
681
|
+
/**
|
|
682
|
+
* Groups related tasks together. When omitted, the server generates one and
|
|
683
|
+
* returns it so the caller can reuse it on later tasks in the same thread.
|
|
684
|
+
*/
|
|
685
|
+
threadId: z4.string().min(1).optional(),
|
|
686
|
+
/**
|
|
687
|
+
* Optional thread priority. When set, applies to the whole thread and
|
|
688
|
+
* overwrites any previous priority. Omit on later tasks to leave unchanged.
|
|
689
|
+
*/
|
|
690
|
+
priority: taskPrioritySchema2.optional(),
|
|
691
|
+
/**
|
|
692
|
+
* Optional initial status update logged against the task's thread. Shows in
|
|
693
|
+
* the inbox status bar and the thread update log.
|
|
694
|
+
*/
|
|
695
|
+
update: threadUpdateInputSchema2.optional(),
|
|
696
|
+
agent: agentTelemetrySchema2.optional()
|
|
697
|
+
}).transform(normalizeTaskContextVersion2).superRefine(refineContextPublicUrls2);
|
|
698
|
+
var threadUpdateBodySchema = threadUpdateInputSchema2;
|
|
699
|
+
|
|
700
|
+
// ../core/src/handler-retry.ts
|
|
701
|
+
var HANDLER_RETRY_DELAYS_MS = [
|
|
702
|
+
6e4,
|
|
703
|
+
// 1m
|
|
704
|
+
3e5,
|
|
705
|
+
// 5m
|
|
706
|
+
18e5,
|
|
707
|
+
// 30m
|
|
708
|
+
36e5,
|
|
709
|
+
// 1h
|
|
710
|
+
216e5,
|
|
711
|
+
// 6h
|
|
712
|
+
864e5,
|
|
713
|
+
// 24h
|
|
714
|
+
1728e5
|
|
715
|
+
// 48h
|
|
716
|
+
];
|
|
717
|
+
var HANDLER_MAX_ATTEMPTS = HANDLER_RETRY_DELAYS_MS.length + 1;
|
|
718
|
+
|
|
719
|
+
// ../core/src/attribution/index.ts
|
|
720
|
+
import { z as z5 } from "zod";
|
|
721
|
+
|
|
722
|
+
// ../core/src/app-url.ts
|
|
723
|
+
var PRODUCTION_APP_URL = "https://app.robotrock.io";
|
|
724
|
+
var PRODUCTION_API_URL = "https://api.robotrock.io/v1";
|
|
725
|
+
var DEV_API_URL = "http://localhost:4001/v1";
|
|
726
|
+
var APP_SIGNUP_URL = `${PRODUCTION_APP_URL}/sign-up`;
|
|
727
|
+
function normalizeRobotRockApiBaseUrl(baseUrl) {
|
|
728
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
729
|
+
if (trimmed.endsWith("/v1")) {
|
|
730
|
+
return trimmed;
|
|
731
|
+
}
|
|
732
|
+
return `${trimmed}/v1`;
|
|
733
|
+
}
|
|
734
|
+
function getRobotRockApiBaseUrl() {
|
|
735
|
+
const explicit = process.env.ROBOTROCK_BASE_URL?.trim() || process.env.ROBOTROCK_API_URL?.trim();
|
|
736
|
+
if (explicit) {
|
|
737
|
+
return normalizeRobotRockApiBaseUrl(explicit);
|
|
738
|
+
}
|
|
739
|
+
if (process.env.NODE_ENV === "production") {
|
|
740
|
+
return PRODUCTION_API_URL;
|
|
741
|
+
}
|
|
742
|
+
return DEV_API_URL;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ../core/src/attribution/index.ts
|
|
746
|
+
var ATTRIBUTION_COOKIE_MAX_AGE = 60 * 60 * 24 * 90;
|
|
747
|
+
var attributionSchema = z5.object({
|
|
748
|
+
utmSource: z5.string().optional(),
|
|
749
|
+
utmMedium: z5.string().optional(),
|
|
750
|
+
utmCampaign: z5.string().optional(),
|
|
751
|
+
utmContent: z5.string().optional(),
|
|
752
|
+
utmTerm: z5.string().optional(),
|
|
753
|
+
gclid: z5.string().optional(),
|
|
754
|
+
landingUrl: z5.string().optional(),
|
|
755
|
+
referrer: z5.string().optional(),
|
|
756
|
+
capturedAt: z5.number()
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// src/approval-result.ts
|
|
760
|
+
var TaskTimeoutError = class extends Error {
|
|
761
|
+
constructor(message) {
|
|
762
|
+
super(message);
|
|
763
|
+
this.name = "TaskTimeoutError";
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
var TaskExpiredError = class extends Error {
|
|
767
|
+
constructor(message) {
|
|
768
|
+
super(message);
|
|
769
|
+
this.name = "TaskExpiredError";
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
function toDiscriminatedApprovalResult(actions, task) {
|
|
773
|
+
void actions;
|
|
774
|
+
if (!task.handled) {
|
|
775
|
+
throw new Error("Task has no handled result");
|
|
776
|
+
}
|
|
777
|
+
return {
|
|
778
|
+
actionId: task.handled.action.id,
|
|
779
|
+
data: task.handled.action.data,
|
|
780
|
+
handledBy: task.handled.handledBy,
|
|
781
|
+
handledAt: new Date(task.handledAt ?? Date.now()),
|
|
782
|
+
taskId: task.id
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/http.ts
|
|
787
|
+
var RobotRockError = class extends Error {
|
|
788
|
+
constructor(message, statusCode, response) {
|
|
789
|
+
super(message);
|
|
790
|
+
this.statusCode = statusCode;
|
|
791
|
+
this.response = response;
|
|
792
|
+
this.name = "RobotRockError";
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
async function parseResponseBody(response) {
|
|
796
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
797
|
+
const bodyText = await response.text();
|
|
798
|
+
if (!bodyText) {
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
if (contentType.toLowerCase().includes("application/json")) {
|
|
802
|
+
try {
|
|
803
|
+
return JSON.parse(bodyText);
|
|
804
|
+
} catch {
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
try {
|
|
808
|
+
return JSON.parse(bodyText);
|
|
809
|
+
} catch {
|
|
810
|
+
return bodyText;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
function getErrorMessage(data, fallback) {
|
|
814
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
815
|
+
const record = data;
|
|
816
|
+
const maybeMessage = record.message;
|
|
817
|
+
const maybeHint = record.hint;
|
|
818
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim()) {
|
|
819
|
+
if (typeof maybeHint === "string" && maybeHint.trim()) {
|
|
820
|
+
return `${maybeMessage.trim()} ${maybeHint.trim()}`;
|
|
821
|
+
}
|
|
822
|
+
return maybeMessage;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
if (typeof data === "string" && data.trim()) {
|
|
826
|
+
const compact = data.replace(/\s+/g, " ").trim();
|
|
827
|
+
const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
|
|
828
|
+
return `${fallback}. Server returned non-JSON response: ${snippet}`;
|
|
829
|
+
}
|
|
830
|
+
return fallback;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/auth-headers.ts
|
|
834
|
+
var ROBOTROCK_ACTING_USER_ID_HEADER = "x-robotrock-acting-user-id";
|
|
835
|
+
function buildRobotRockAuthHeaders(auth) {
|
|
836
|
+
if (auth.kind === "apiKey") {
|
|
837
|
+
const headers2 = {
|
|
838
|
+
"X-Api-Key": auth.apiKey
|
|
839
|
+
};
|
|
840
|
+
if (auth.actingUserId?.trim()) {
|
|
841
|
+
headers2[ROBOTROCK_ACTING_USER_ID_HEADER] = auth.actingUserId.trim();
|
|
842
|
+
}
|
|
843
|
+
return headers2;
|
|
844
|
+
}
|
|
845
|
+
const headers = {
|
|
846
|
+
Authorization: `Bearer ${auth.token}`,
|
|
847
|
+
"X-RobotRock-Tenant-Slug": auth.tenantSlug,
|
|
848
|
+
"X-RobotRock-Connection-Id": auth.connectionId
|
|
849
|
+
};
|
|
850
|
+
if (auth.actingUserId?.trim()) {
|
|
851
|
+
headers[ROBOTROCK_ACTING_USER_ID_HEADER] = auth.actingUserId.trim();
|
|
852
|
+
}
|
|
853
|
+
return headers;
|
|
854
|
+
}
|
|
855
|
+
function resolveRobotRockAuthConfig(overrides) {
|
|
856
|
+
if (overrides?.agentService) {
|
|
857
|
+
return {
|
|
858
|
+
kind: "agentService",
|
|
859
|
+
...overrides.agentService
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
863
|
+
if (!apiKey) {
|
|
864
|
+
throw new Error(
|
|
865
|
+
"RobotRock auth is required. Set ROBOTROCK_API_KEY, ROBOTROCK_AGENT_SERVICE_TOKEN with tenant context, or pass auth when creating the client."
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
return { kind: "apiKey", apiKey };
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/chats.ts
|
|
872
|
+
function createChatsApi(config) {
|
|
873
|
+
const headers = () => ({
|
|
874
|
+
"Content-Type": "application/json",
|
|
875
|
+
...buildRobotRockAuthHeaders(config.auth)
|
|
876
|
+
});
|
|
877
|
+
return {
|
|
878
|
+
async create(input) {
|
|
879
|
+
const bodyPayload = {
|
|
880
|
+
...input,
|
|
881
|
+
app: input.app ?? config.app,
|
|
882
|
+
messages: input.messages ?? []
|
|
883
|
+
};
|
|
884
|
+
const validation = createAgentChatBodySchema.safeParse(bodyPayload);
|
|
885
|
+
if (!validation.success) {
|
|
886
|
+
throw new RobotRockError(
|
|
887
|
+
`Invalid chat: ${validation.error.issues[0]?.message}`,
|
|
888
|
+
400,
|
|
889
|
+
validation.error.issues
|
|
890
|
+
);
|
|
891
|
+
}
|
|
892
|
+
const response = await fetch(`${config.baseUrl}/agent-chats`, {
|
|
893
|
+
method: "POST",
|
|
894
|
+
headers: headers(),
|
|
895
|
+
body: JSON.stringify(validation.data)
|
|
896
|
+
});
|
|
897
|
+
const data = await parseResponseBody(response);
|
|
898
|
+
if (!response.ok) {
|
|
899
|
+
throw new RobotRockError(
|
|
900
|
+
getErrorMessage(data, "Failed to create chat"),
|
|
901
|
+
response.status,
|
|
902
|
+
data
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
const result = data;
|
|
906
|
+
return { tenantSlug: result.tenantSlug, chats: result.chats };
|
|
907
|
+
},
|
|
908
|
+
async close(chatId, options) {
|
|
909
|
+
if (!chatId) {
|
|
910
|
+
throw new RobotRockError("chatId is required to close a chat", 400);
|
|
911
|
+
}
|
|
912
|
+
const response = await fetch(
|
|
913
|
+
`${config.baseUrl}/agent-chats/${encodeURIComponent(chatId)}/close`,
|
|
914
|
+
{
|
|
915
|
+
method: "POST",
|
|
916
|
+
headers: headers(),
|
|
917
|
+
body: JSON.stringify({ reason: options?.reason })
|
|
918
|
+
}
|
|
919
|
+
);
|
|
920
|
+
if (!response.ok) {
|
|
921
|
+
const data = await parseResponseBody(response);
|
|
922
|
+
throw new RobotRockError(
|
|
923
|
+
getErrorMessage(data, "Failed to close chat"),
|
|
924
|
+
response.status,
|
|
925
|
+
data
|
|
926
|
+
);
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
async stageHitlRequests(input) {
|
|
930
|
+
const validation = agentChatStageHitlBodySchema.safeParse(input);
|
|
931
|
+
if (!validation.success) {
|
|
932
|
+
throw new RobotRockError(
|
|
933
|
+
`Invalid stage HITL input: ${validation.error.issues[0]?.message}`,
|
|
934
|
+
400,
|
|
935
|
+
validation.error.issues
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/stage-hitl`, {
|
|
939
|
+
method: "POST",
|
|
940
|
+
headers: headers(),
|
|
941
|
+
body: JSON.stringify(validation.data)
|
|
942
|
+
});
|
|
943
|
+
if (!response.ok) {
|
|
944
|
+
const data = await parseResponseBody(response);
|
|
945
|
+
throw new RobotRockError(
|
|
946
|
+
getErrorMessage(data, "Failed to stage chat HITL requests"),
|
|
947
|
+
response.status,
|
|
948
|
+
data
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
},
|
|
952
|
+
async getStagedHitlRequests(eveSessionId) {
|
|
953
|
+
const trimmed = eveSessionId.trim();
|
|
954
|
+
if (!trimmed) {
|
|
955
|
+
throw new RobotRockError("eveSessionId is required", 400);
|
|
956
|
+
}
|
|
957
|
+
const url = new URL(`${config.baseUrl}/agent-chats/staged-hitl`);
|
|
958
|
+
url.searchParams.set("eveSessionId", trimmed);
|
|
959
|
+
const response = await fetch(url.toString(), {
|
|
960
|
+
method: "GET",
|
|
961
|
+
headers: headers()
|
|
962
|
+
});
|
|
963
|
+
const data = await parseResponseBody(response);
|
|
964
|
+
if (!response.ok) {
|
|
965
|
+
throw new RobotRockError(
|
|
966
|
+
getErrorMessage(data, "Failed to fetch staged chat HITL requests"),
|
|
967
|
+
response.status,
|
|
968
|
+
data
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
const requests = data.requests;
|
|
972
|
+
return Array.isArray(requests) ? requests : [];
|
|
973
|
+
},
|
|
974
|
+
async logInputSubmission(input) {
|
|
975
|
+
const validation = agentChatAuditInputBodySchema.safeParse(input);
|
|
976
|
+
if (!validation.success) {
|
|
977
|
+
throw new RobotRockError(
|
|
978
|
+
`Invalid audit input: ${validation.error.issues[0]?.message}`,
|
|
979
|
+
400,
|
|
980
|
+
validation.error.issues
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/audit-input`, {
|
|
984
|
+
method: "POST",
|
|
985
|
+
headers: headers(),
|
|
986
|
+
body: JSON.stringify(validation.data)
|
|
987
|
+
});
|
|
988
|
+
if (!response.ok) {
|
|
989
|
+
const data = await parseResponseBody(response);
|
|
990
|
+
throw new RobotRockError(
|
|
991
|
+
getErrorMessage(data, "Failed to log chat input submission"),
|
|
992
|
+
response.status,
|
|
993
|
+
data
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
},
|
|
997
|
+
async logToolExecution(input) {
|
|
998
|
+
const validation = agentChatAuditToolBodySchema.safeParse(input);
|
|
999
|
+
if (!validation.success) {
|
|
1000
|
+
throw new RobotRockError(
|
|
1001
|
+
`Invalid audit tool input: ${validation.error.issues[0]?.message}`,
|
|
1002
|
+
400,
|
|
1003
|
+
validation.error.issues
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/audit-tool`, {
|
|
1007
|
+
method: "POST",
|
|
1008
|
+
headers: headers(),
|
|
1009
|
+
body: JSON.stringify(validation.data)
|
|
1010
|
+
});
|
|
1011
|
+
if (!response.ok) {
|
|
1012
|
+
const data = await parseResponseBody(response);
|
|
1013
|
+
throw new RobotRockError(
|
|
1014
|
+
getErrorMessage(data, "Failed to log chat tool execution"),
|
|
1015
|
+
response.status,
|
|
1016
|
+
data
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
async linkTask(input) {
|
|
1021
|
+
const validation = agentChatLinkTaskBodySchema.safeParse(input);
|
|
1022
|
+
if (!validation.success) {
|
|
1023
|
+
throw new RobotRockError(
|
|
1024
|
+
`Invalid link task input: ${validation.error.issues[0]?.message}`,
|
|
1025
|
+
400,
|
|
1026
|
+
validation.error.issues
|
|
1027
|
+
);
|
|
1028
|
+
}
|
|
1029
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/link-task`, {
|
|
1030
|
+
method: "POST",
|
|
1031
|
+
headers: headers(),
|
|
1032
|
+
body: JSON.stringify(validation.data)
|
|
1033
|
+
});
|
|
1034
|
+
if (!response.ok) {
|
|
1035
|
+
const data = await parseResponseBody(response);
|
|
1036
|
+
throw new RobotRockError(
|
|
1037
|
+
getErrorMessage(data, "Failed to link inbox task to chat"),
|
|
1038
|
+
response.status,
|
|
1039
|
+
data
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// src/client.ts
|
|
1047
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
1048
|
+
var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
1049
|
+
function sleep(ms) {
|
|
1050
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1051
|
+
}
|
|
1052
|
+
function resolveAgentVersionFromEnv() {
|
|
1053
|
+
const fromEnv = process.env.AGENT_VERSION?.trim() || process.env.ROBOTROCK_AGENT_VERSION?.trim();
|
|
1054
|
+
return fromEnv || void 0;
|
|
1055
|
+
}
|
|
1056
|
+
function parseValidUntilMs(value) {
|
|
1057
|
+
if (value === void 0) {
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
if (value instanceof Date) {
|
|
1061
|
+
const ms = value.getTime();
|
|
1062
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
1063
|
+
}
|
|
1064
|
+
if (typeof value === "number") {
|
|
1065
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1066
|
+
}
|
|
1067
|
+
const parsed = Date.parse(value);
|
|
1068
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1069
|
+
}
|
|
1070
|
+
function serializeValidUntil(value) {
|
|
1071
|
+
if (value instanceof Date) {
|
|
1072
|
+
const ms = value.getTime();
|
|
1073
|
+
if (Number.isNaN(ms)) {
|
|
1074
|
+
throw new RobotRockError("Invalid validUntil: Date is invalid", 400);
|
|
1075
|
+
}
|
|
1076
|
+
return value.toISOString();
|
|
1077
|
+
}
|
|
1078
|
+
if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
|
|
1079
|
+
return new Date(value).toISOString();
|
|
1080
|
+
}
|
|
1081
|
+
throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
|
|
1082
|
+
}
|
|
1083
|
+
var RobotRock = class {
|
|
1084
|
+
auth;
|
|
1085
|
+
baseUrl;
|
|
1086
|
+
app;
|
|
1087
|
+
agentVersion;
|
|
1088
|
+
contextVersion;
|
|
1089
|
+
webhook;
|
|
1090
|
+
polling;
|
|
1091
|
+
/** Task CRUD: `create`, `get`, `cancel`, `sendUpdate`. */
|
|
1092
|
+
tasks;
|
|
1093
|
+
/** Chat CRUD: `create`, `close`. */
|
|
1094
|
+
chats;
|
|
1095
|
+
constructor(config) {
|
|
1096
|
+
if (config.webhook && config.polling) {
|
|
1097
|
+
throw new Error(
|
|
1098
|
+
"RobotRock client cannot configure both webhook and polling. Use webhook for callbacks or polling to block until handled."
|
|
1099
|
+
);
|
|
1100
|
+
}
|
|
1101
|
+
const apiKey = config.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
1102
|
+
const agentService = config.agentService;
|
|
1103
|
+
if (apiKey && agentService) {
|
|
1104
|
+
throw new Error(
|
|
1105
|
+
"RobotRock client cannot configure both apiKey and agentService."
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
this.auth = resolveRobotRockAuthConfig({
|
|
1109
|
+
...apiKey ? { apiKey } : {},
|
|
1110
|
+
...agentService ? { agentService } : {}
|
|
1111
|
+
});
|
|
1112
|
+
this.baseUrl = config.baseUrl ?? getRobotRockApiBaseUrl();
|
|
1113
|
+
this.app = config.app;
|
|
1114
|
+
this.agentVersion = config.version ?? resolveAgentVersionFromEnv();
|
|
1115
|
+
this.contextVersion = config.advanced?.contextVersion ?? TASK_CONTEXT_FORMAT_VERSION2;
|
|
1116
|
+
this.webhook = config.webhook;
|
|
1117
|
+
this.polling = config.polling ?? {};
|
|
1118
|
+
this.tasks = {
|
|
1119
|
+
create: (task) => this.createTaskRequest(task),
|
|
1120
|
+
get: (taskId) => this.getTaskById(taskId),
|
|
1121
|
+
cancel: (taskId) => this.cancelTaskRequest(taskId),
|
|
1122
|
+
sendUpdate: (input) => this.sendThreadUpdate(input),
|
|
1123
|
+
list: (input) => this.listTasksRequest(input),
|
|
1124
|
+
search: (input) => this.searchTasksRequest(input)
|
|
1125
|
+
};
|
|
1126
|
+
this.chats = createChatsApi({
|
|
1127
|
+
baseUrl: this.baseUrl,
|
|
1128
|
+
auth: this.auth,
|
|
1129
|
+
app: this.app
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
authHeaders(extra) {
|
|
1133
|
+
return {
|
|
1134
|
+
"Content-Type": "application/json",
|
|
1135
|
+
...buildRobotRockAuthHeaders(this.auth),
|
|
1136
|
+
...extra
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
async createTaskRequest(task) {
|
|
1140
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
1141
|
+
webhook: this.webhook,
|
|
1142
|
+
app: this.app,
|
|
1143
|
+
contextVersion: this.contextVersion,
|
|
1144
|
+
agentVersion: this.agentVersion
|
|
1145
|
+
});
|
|
1146
|
+
const agentVersion = task.version ?? this.agentVersion;
|
|
1147
|
+
const bodyPayload = {
|
|
1148
|
+
...normalizedTask,
|
|
1149
|
+
...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
|
|
1150
|
+
...task.threadId !== void 0 ? { threadId: task.threadId } : {},
|
|
1151
|
+
...task.priority !== void 0 ? { priority: task.priority } : {},
|
|
1152
|
+
...task.update !== void 0 ? { update: task.update } : {},
|
|
1153
|
+
...agentVersion !== void 0 ? { agent: { version: agentVersion } } : {}
|
|
1154
|
+
};
|
|
1155
|
+
const validation = createTaskBodySchema2.safeParse(bodyPayload);
|
|
1156
|
+
if (!validation.success) {
|
|
1157
|
+
throw new RobotRockError(
|
|
1158
|
+
`Invalid task: ${validation.error.issues[0]?.message}`,
|
|
1159
|
+
400,
|
|
1160
|
+
validation.error.issues
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
const headers = this.authHeaders(
|
|
1164
|
+
task.idempotencyKey ? { "Idempotency-Key": task.idempotencyKey } : void 0
|
|
1165
|
+
);
|
|
1166
|
+
const response = await fetch(`${this.baseUrl}/`, {
|
|
1167
|
+
method: "POST",
|
|
1168
|
+
headers,
|
|
1169
|
+
body: JSON.stringify(validation.data)
|
|
1170
|
+
});
|
|
1171
|
+
const data = await parseResponseBody(response);
|
|
1172
|
+
if (!response.ok) {
|
|
1173
|
+
throw new RobotRockError(
|
|
1174
|
+
getErrorMessage(data, "Failed to create task"),
|
|
1175
|
+
response.status,
|
|
1176
|
+
data
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
return data.task;
|
|
1180
|
+
}
|
|
1181
|
+
async sendToHuman(task) {
|
|
1182
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
1183
|
+
webhook: this.webhook,
|
|
1184
|
+
app: this.app,
|
|
1185
|
+
contextVersion: this.contextVersion,
|
|
1186
|
+
agentVersion: this.agentVersion
|
|
1187
|
+
});
|
|
1188
|
+
const createdTaskTask = await this.createTaskRequest(task);
|
|
1189
|
+
const hasHandlers = normalizedTask.actions.some(
|
|
1190
|
+
(action) => Array.isArray(action.handlers) && action.handlers.length > 0
|
|
1191
|
+
);
|
|
1192
|
+
if (hasHandlers) {
|
|
1193
|
+
return {
|
|
1194
|
+
mode: "created",
|
|
1195
|
+
task: createdTaskTask
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
const timeoutMs = this.polling.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1199
|
+
const pollIntervalMs = this.polling.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1200
|
+
const pollingDeadline = Date.now() + timeoutMs;
|
|
1201
|
+
const validUntilMs = parseValidUntilMs(createdTaskTask.validUntil);
|
|
1202
|
+
const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
|
|
1203
|
+
const taskId = createdTaskTask.taskId;
|
|
1204
|
+
while (Date.now() < deadline) {
|
|
1205
|
+
const existing = await this.getTaskById(taskId);
|
|
1206
|
+
if (existing?.status === "handled" && existing.handled) {
|
|
1207
|
+
return {
|
|
1208
|
+
mode: "handled",
|
|
1209
|
+
task: createdTaskTask,
|
|
1210
|
+
...toDiscriminatedApprovalResult(
|
|
1211
|
+
normalizedTask.actions,
|
|
1212
|
+
existing
|
|
1213
|
+
)
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
if (existing?.status === "expired" || existing && Date.now() >= existing.validUntil) {
|
|
1217
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
1218
|
+
}
|
|
1219
|
+
const remainingMs = deadline - Date.now();
|
|
1220
|
+
await sleep(Math.min(pollIntervalMs, Math.max(0, remainingMs)));
|
|
1221
|
+
}
|
|
1222
|
+
if (validUntilMs !== void 0 && Date.now() >= validUntilMs) {
|
|
1223
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
1224
|
+
}
|
|
1225
|
+
throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Create a task via POST /v1 without waiting for a human response.
|
|
1229
|
+
* @deprecated Use `client.tasks.create()` instead.
|
|
1230
|
+
*/
|
|
1231
|
+
async createTask(task) {
|
|
1232
|
+
return this.tasks.create(task);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
|
|
1236
|
+
* @deprecated Use `client.tasks.get()` instead.
|
|
1237
|
+
*/
|
|
1238
|
+
async getTask(taskId) {
|
|
1239
|
+
return this.tasks.get(taskId);
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Log a status update against a thread.
|
|
1243
|
+
* @deprecated Use `client.tasks.sendUpdate()` instead.
|
|
1244
|
+
*/
|
|
1245
|
+
async sendUpdate(input) {
|
|
1246
|
+
return this.tasks.sendUpdate(input);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Cancel a task by public task id.
|
|
1250
|
+
* @deprecated Use `client.tasks.cancel()` instead.
|
|
1251
|
+
*/
|
|
1252
|
+
async cancelTask(taskId) {
|
|
1253
|
+
return this.tasks.cancel(taskId);
|
|
1254
|
+
}
|
|
1255
|
+
async getTaskById(taskId) {
|
|
1256
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
|
|
1257
|
+
method: "GET",
|
|
1258
|
+
headers: this.authHeaders()
|
|
1259
|
+
});
|
|
1260
|
+
if (response.status === 404) {
|
|
1261
|
+
return null;
|
|
1262
|
+
}
|
|
1263
|
+
const data = await parseResponseBody(response);
|
|
1264
|
+
if (!response.ok) {
|
|
1265
|
+
throw new RobotRockError(
|
|
1266
|
+
getErrorMessage(data, "Failed to get task"),
|
|
1267
|
+
response.status,
|
|
1268
|
+
data
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
return data;
|
|
1272
|
+
}
|
|
1273
|
+
async sendThreadUpdate({
|
|
1274
|
+
threadId,
|
|
1275
|
+
message,
|
|
1276
|
+
status
|
|
1277
|
+
}) {
|
|
1278
|
+
if (!threadId) {
|
|
1279
|
+
throw new RobotRockError("threadId is required to send an update", 400);
|
|
1280
|
+
}
|
|
1281
|
+
const validation = threadUpdateBodySchema.safeParse({ message, status });
|
|
1282
|
+
if (!validation.success) {
|
|
1283
|
+
throw new RobotRockError(
|
|
1284
|
+
`Invalid update: ${validation.error.issues[0]?.message}`,
|
|
1285
|
+
400,
|
|
1286
|
+
validation.error.issues
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
const response = await fetch(
|
|
1290
|
+
`${this.baseUrl}/threads/${encodeURIComponent(threadId)}/updates`,
|
|
1291
|
+
{
|
|
1292
|
+
method: "POST",
|
|
1293
|
+
headers: this.authHeaders(),
|
|
1294
|
+
body: JSON.stringify(validation.data)
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
const data = await parseResponseBody(response);
|
|
1298
|
+
if (!response.ok) {
|
|
1299
|
+
throw new RobotRockError(
|
|
1300
|
+
getErrorMessage(data, "Failed to send update"),
|
|
1301
|
+
response.status,
|
|
1302
|
+
data
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
return data.update;
|
|
1306
|
+
}
|
|
1307
|
+
async cancelTaskRequest(taskId) {
|
|
1308
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
|
|
1309
|
+
method: "POST",
|
|
1310
|
+
headers: this.authHeaders()
|
|
1311
|
+
});
|
|
1312
|
+
if (!response.ok) {
|
|
1313
|
+
const data = await parseResponseBody(response);
|
|
1314
|
+
throw new RobotRockError(
|
|
1315
|
+
getErrorMessage(data, "Failed to cancel task"),
|
|
1316
|
+
response.status,
|
|
1317
|
+
data
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
async listTasksRequest(input) {
|
|
1322
|
+
const params = new URLSearchParams();
|
|
1323
|
+
for (const [key, value] of Object.entries(input ?? {})) {
|
|
1324
|
+
if (value !== void 0 && value !== "") {
|
|
1325
|
+
params.set(key, String(value));
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
1329
|
+
const response = await fetch(`${this.baseUrl}/tasks${suffix}`, {
|
|
1330
|
+
method: "GET",
|
|
1331
|
+
headers: this.authHeaders()
|
|
1332
|
+
});
|
|
1333
|
+
const data = await parseResponseBody(response);
|
|
1334
|
+
if (!response.ok) {
|
|
1335
|
+
throw new RobotRockError(
|
|
1336
|
+
getErrorMessage(data, "Failed to list tasks"),
|
|
1337
|
+
response.status,
|
|
1338
|
+
data
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
return data;
|
|
1342
|
+
}
|
|
1343
|
+
async searchTasksRequest(input) {
|
|
1344
|
+
const params = new URLSearchParams({ q: input.q });
|
|
1345
|
+
if (input.limit !== void 0) {
|
|
1346
|
+
params.set("limit", String(input.limit));
|
|
1347
|
+
}
|
|
1348
|
+
const response = await fetch(`${this.baseUrl}/tasks/search?${params.toString()}`, {
|
|
1349
|
+
method: "GET",
|
|
1350
|
+
headers: this.authHeaders()
|
|
1351
|
+
});
|
|
1352
|
+
const data = await parseResponseBody(response);
|
|
1353
|
+
if (!response.ok) {
|
|
1354
|
+
throw new RobotRockError(
|
|
1355
|
+
getErrorMessage(data, "Failed to search tasks"),
|
|
1356
|
+
response.status,
|
|
1357
|
+
data
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
return data;
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
function createClient(config) {
|
|
1364
|
+
return new RobotRock(config);
|
|
1365
|
+
}
|
|
1366
|
+
function attachWebhookToActions(actions, webhook) {
|
|
1367
|
+
return actions.map((action) => ({
|
|
1368
|
+
...action,
|
|
1369
|
+
handlers: webhookToHandlers(webhook)
|
|
1370
|
+
}));
|
|
1371
|
+
}
|
|
1372
|
+
function webhookToHandlers(webhook) {
|
|
1373
|
+
return [
|
|
1374
|
+
{
|
|
1375
|
+
type: "webhook",
|
|
1376
|
+
url: webhook.url,
|
|
1377
|
+
headers: webhook.headers ?? {}
|
|
1378
|
+
}
|
|
1379
|
+
];
|
|
1380
|
+
}
|
|
1381
|
+
function normalizeSendToHumanInput(task, clientDefaults) {
|
|
1382
|
+
const {
|
|
1383
|
+
actions,
|
|
1384
|
+
idempotencyKey: _idempotencyKey,
|
|
1385
|
+
assignTo: _assignTo,
|
|
1386
|
+
threadId: _threadId,
|
|
1387
|
+
priority: _priority,
|
|
1388
|
+
update: _update,
|
|
1389
|
+
version: _version,
|
|
1390
|
+
validUntil,
|
|
1391
|
+
app: taskApp,
|
|
1392
|
+
...rest
|
|
1393
|
+
} = task;
|
|
1394
|
+
const webhook = clientDefaults.webhook;
|
|
1395
|
+
const normalizedActions = webhook ? attachWebhookToActions(actions, webhook) : actions;
|
|
1396
|
+
const app = taskApp ?? clientDefaults.app;
|
|
1397
|
+
return {
|
|
1398
|
+
...rest,
|
|
1399
|
+
contextVersion: clientDefaults.contextVersion,
|
|
1400
|
+
...app ? { app } : {},
|
|
1401
|
+
...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
|
|
1402
|
+
actions: normalizedActions
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/env.ts
|
|
1407
|
+
function resolveRobotRockConfig(overrides) {
|
|
1408
|
+
const agentService = overrides?.agentService;
|
|
1409
|
+
const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
1410
|
+
if (agentService && apiKey) {
|
|
1411
|
+
throw new Error(
|
|
1412
|
+
"RobotRock client cannot configure both apiKey and agentService."
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1415
|
+
const baseUrl = overrides?.baseUrl ?? getRobotRockApiBaseUrl();
|
|
1416
|
+
const app = overrides?.app ?? process.env.ROBOTROCK_APP;
|
|
1417
|
+
if (agentService) {
|
|
1418
|
+
return app ? { agentService, baseUrl, app } : { agentService, baseUrl };
|
|
1419
|
+
}
|
|
1420
|
+
if (!apiKey) {
|
|
1421
|
+
throw new Error(
|
|
1422
|
+
"RobotRock API key is required. Set ROBOTROCK_API_KEY or pass agentService when creating the client."
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
return app ? { apiKey, baseUrl, app } : { apiKey, baseUrl };
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/eve/agent/attributes.ts
|
|
1429
|
+
function readStringAttribute(attributes, key) {
|
|
1430
|
+
const value = attributes?.[key];
|
|
1431
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1432
|
+
return value;
|
|
1433
|
+
}
|
|
1434
|
+
if (Array.isArray(value) && typeof value[0] === "string" && value[0].length > 0) {
|
|
1435
|
+
return value[0];
|
|
1436
|
+
}
|
|
1437
|
+
return void 0;
|
|
1438
|
+
}
|
|
1439
|
+
function readStringArrayAttribute(attributes, key) {
|
|
1440
|
+
const value = attributes?.[key];
|
|
1441
|
+
if (typeof value === "string" && value.length > 0) {
|
|
1442
|
+
return [value];
|
|
1443
|
+
}
|
|
1444
|
+
if (Array.isArray(value)) {
|
|
1445
|
+
return value.filter(
|
|
1446
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
return [];
|
|
1450
|
+
}
|
|
1451
|
+
function parseTenantRole(value) {
|
|
1452
|
+
if (value === "admin" || value === "member") {
|
|
1453
|
+
return value;
|
|
1454
|
+
}
|
|
1455
|
+
return null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// src/eve/agent/tenant.ts
|
|
1459
|
+
function tryResolveTenantCaller(ctx) {
|
|
1460
|
+
const caller = ctx.session.auth.initiator ?? ctx.session.auth.current;
|
|
1461
|
+
if (caller?.principalType !== "user") {
|
|
1462
|
+
return null;
|
|
1463
|
+
}
|
|
1464
|
+
const email = readStringAttribute(caller.attributes, "email");
|
|
1465
|
+
const name = readStringAttribute(caller.attributes, "name");
|
|
1466
|
+
const tenantSlug = readStringAttribute(caller.attributes, "tenantSlug") ?? readStringAttribute(caller.attributes, "tenantId");
|
|
1467
|
+
const role = parseTenantRole(readStringAttribute(caller.attributes, "role"));
|
|
1468
|
+
if (!email || !tenantSlug || !caller.principalId || !role) {
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
const workosUserId = readStringAttribute(caller.attributes, "workosUserId");
|
|
1472
|
+
const connectionId = readStringAttribute(caller.attributes, "connectionId");
|
|
1473
|
+
const groups = readStringArrayAttribute(caller.attributes, "groups");
|
|
1474
|
+
return {
|
|
1475
|
+
userId: caller.principalId,
|
|
1476
|
+
email,
|
|
1477
|
+
name: name ?? email.split("@")[0] ?? email,
|
|
1478
|
+
tenantSlug,
|
|
1479
|
+
...connectionId ? { connectionId } : {},
|
|
1480
|
+
role,
|
|
1481
|
+
isAdmin: role === "admin",
|
|
1482
|
+
groups,
|
|
1483
|
+
...workosUserId ? { workosUserId } : {}
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
function requireTenantCaller(ctx) {
|
|
1487
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1488
|
+
if (!caller) {
|
|
1489
|
+
throw new Error("An authenticated RobotRock tenant user is required.");
|
|
1490
|
+
}
|
|
1491
|
+
return caller;
|
|
1492
|
+
}
|
|
1493
|
+
function requireAdminCaller(ctx) {
|
|
1494
|
+
const caller = requireTenantCaller(ctx);
|
|
1495
|
+
if (!caller.isAdmin) {
|
|
1496
|
+
throw new Error("Tenant admin access is required.");
|
|
1497
|
+
}
|
|
1498
|
+
return caller;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
// src/eve/agent/client-from-session.ts
|
|
1502
|
+
var warnedMissingAuth = false;
|
|
1503
|
+
function tryCreateBoundRobotRockClient(ctx) {
|
|
1504
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1505
|
+
const serviceToken = process.env.ROBOTROCK_AGENT_SERVICE_TOKEN?.trim();
|
|
1506
|
+
if (caller?.connectionId && serviceToken) {
|
|
1507
|
+
return createClient({
|
|
1508
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim(),
|
|
1509
|
+
agentService: {
|
|
1510
|
+
token: serviceToken,
|
|
1511
|
+
tenantSlug: caller.tenantSlug,
|
|
1512
|
+
connectionId: caller.connectionId
|
|
1513
|
+
}
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
const apiKey = process.env.ROBOTROCK_API_KEY?.trim();
|
|
1517
|
+
if (apiKey) {
|
|
1518
|
+
return createClient(
|
|
1519
|
+
resolveRobotRockConfig({
|
|
1520
|
+
apiKey,
|
|
1521
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim()
|
|
1522
|
+
})
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
if (!warnedMissingAuth) {
|
|
1526
|
+
warnedMissingAuth = true;
|
|
1527
|
+
console.warn(
|
|
1528
|
+
"robotrock: set ROBOTROCK_AGENT_SERVICE_TOKEN for hosted multi-tenant auth, or ROBOTROCK_API_KEY for single-tenant deployments."
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
return null;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// src/eve/agent/task-delegation.ts
|
|
1535
|
+
function resolveTaskApp(app) {
|
|
1536
|
+
return app?.trim() || process.env.ROBOTROCK_APP?.trim() || "robotrock-agent";
|
|
1537
|
+
}
|
|
1538
|
+
function resolveWebhookBaseUrl(baseUrl) {
|
|
1539
|
+
return baseUrl?.trim() || process.env.ROBOTROCK_BASE_URL?.trim() || "http://localhost:4001/v1";
|
|
1540
|
+
}
|
|
1541
|
+
function resolveTaskHandledWebhookUrl(baseUrl) {
|
|
1542
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
1543
|
+
const withV1 = trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
1544
|
+
return `${withV1}/agent-chats/task-handled`;
|
|
1545
|
+
}
|
|
1546
|
+
function requireBoundClient(ctx) {
|
|
1547
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
1548
|
+
if (!client) {
|
|
1549
|
+
throw new Error(
|
|
1550
|
+
"RobotRock auth is unset. Configure ROBOTROCK_AGENT_SERVICE_TOKEN for hosted agents or ROBOTROCK_API_KEY for self-hosted deployments."
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
return client;
|
|
1554
|
+
}
|
|
1555
|
+
async function createRobotRockTask(input, ctx) {
|
|
1556
|
+
const client = requireBoundClient(ctx);
|
|
1557
|
+
const { app, ...taskInput } = input;
|
|
1558
|
+
const resolvedApp = resolveTaskApp(app);
|
|
1559
|
+
const webhookUrl = resolveTaskHandledWebhookUrl(
|
|
1560
|
+
resolveWebhookBaseUrl(process.env.ROBOTROCK_BASE_URL)
|
|
1561
|
+
);
|
|
1562
|
+
try {
|
|
1563
|
+
const task = await client.tasks.create({
|
|
1564
|
+
...taskInput,
|
|
1565
|
+
app: resolvedApp,
|
|
1566
|
+
actions: attachWebhookToActions(taskInput.actions, {
|
|
1567
|
+
url: webhookUrl,
|
|
1568
|
+
headers: {}
|
|
1569
|
+
})
|
|
1570
|
+
});
|
|
1571
|
+
return {
|
|
1572
|
+
taskId: task.taskId,
|
|
1573
|
+
threadId: task.threadId,
|
|
1574
|
+
status: task.status,
|
|
1575
|
+
validUntil: task.validUntil,
|
|
1576
|
+
submittedAt: task.submittedAt
|
|
1577
|
+
};
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1580
|
+
if (error instanceof RobotRockError) {
|
|
1581
|
+
const response = error.response && typeof error.response === "object" ? error.response : void 0;
|
|
1582
|
+
console.error("[create_robotrock_task] API request failed", {
|
|
1583
|
+
status: error.statusCode,
|
|
1584
|
+
message: error.message,
|
|
1585
|
+
code: response?.code,
|
|
1586
|
+
hint: response?.hint,
|
|
1587
|
+
tenantSlug: caller?.tenantSlug,
|
|
1588
|
+
connectionId: caller?.connectionId,
|
|
1589
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim() || null
|
|
1590
|
+
});
|
|
1591
|
+
} else {
|
|
1592
|
+
console.error("[create_robotrock_task] unexpected error", error);
|
|
1593
|
+
}
|
|
1594
|
+
throw error;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
function buildRobotRockTaskPayload(input, options) {
|
|
1598
|
+
const contextData = {
|
|
1599
|
+
...input.context?.data ?? {}
|
|
1600
|
+
};
|
|
1601
|
+
if (options?.requestedByEmail) {
|
|
1602
|
+
contextData.requestedBy = options.requestedByEmail;
|
|
1603
|
+
}
|
|
1604
|
+
const hasContext = Object.keys(contextData).length > 0 || input.context?.ui && Object.keys(input.context.ui).length > 0;
|
|
1605
|
+
const payload = {
|
|
1606
|
+
type: input.type,
|
|
1607
|
+
name: input.name,
|
|
1608
|
+
...input.description ? { description: input.description } : {},
|
|
1609
|
+
actions: input.actions.map((action) => ({
|
|
1610
|
+
id: action.id,
|
|
1611
|
+
title: action.title,
|
|
1612
|
+
...action.description ? { description: action.description } : {}
|
|
1613
|
+
})),
|
|
1614
|
+
...input.assignTo ? { assignTo: input.assignTo } : {},
|
|
1615
|
+
...input.priority ? { priority: input.priority } : {},
|
|
1616
|
+
...input.app ? { app: input.app } : {},
|
|
1617
|
+
...input.validUntilHours ? {
|
|
1618
|
+
validUntil: new Date(Date.now() + input.validUntilHours * 60 * 60 * 1e3)
|
|
1619
|
+
} : {},
|
|
1620
|
+
...input.updateMessage ? {
|
|
1621
|
+
update: {
|
|
1622
|
+
message: input.updateMessage,
|
|
1623
|
+
status: "waiting"
|
|
1624
|
+
}
|
|
1625
|
+
} : {}
|
|
1626
|
+
};
|
|
1627
|
+
if (hasContext) {
|
|
1628
|
+
payload.context = {
|
|
1629
|
+
data: contextData,
|
|
1630
|
+
...input.context?.ui ? { ui: input.context.ui } : {}
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
return payload;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// src/eve/agent/chat-audit-api.ts
|
|
1637
|
+
var warnedMissingAuth2 = false;
|
|
1638
|
+
var warnedConnectionRefused = false;
|
|
1639
|
+
function isConnectionRefused(error) {
|
|
1640
|
+
const inspect = (value) => {
|
|
1641
|
+
if (!value || typeof value !== "object") {
|
|
1642
|
+
return false;
|
|
1643
|
+
}
|
|
1644
|
+
if ("code" in value && value.code === "ECONNREFUSED") {
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
if ("cause" in value) {
|
|
1648
|
+
return inspect(value.cause);
|
|
1649
|
+
}
|
|
1650
|
+
if ("errors" in value && Array.isArray(value.errors)) {
|
|
1651
|
+
return value.errors.some((entry) => inspect(entry));
|
|
1652
|
+
}
|
|
1653
|
+
return false;
|
|
1654
|
+
};
|
|
1655
|
+
return inspect(error);
|
|
1656
|
+
}
|
|
1657
|
+
function logAuditFailure(label, error) {
|
|
1658
|
+
if (isConnectionRefused(error)) {
|
|
1659
|
+
if (!warnedConnectionRefused) {
|
|
1660
|
+
warnedConnectionRefused = true;
|
|
1661
|
+
console.warn(
|
|
1662
|
+
"robotrock-chat-audit: RobotRock API is not reachable (connection refused). Audit and task-link calls are skipped until the API is up."
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const hint = error instanceof RobotRockError && error.statusCode === 405 ? " \u2014 set ROBOTROCK_BASE_URL=http://localhost:4001/v1 and run the local API" : "";
|
|
1668
|
+
console.warn(`robotrock-chat-audit: failed to ${label}${hint}`, error);
|
|
1669
|
+
}
|
|
1670
|
+
async function postRobotRockLinkChatTask(ctx, input) {
|
|
1671
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
1672
|
+
if (!client) {
|
|
1673
|
+
if (!warnedMissingAuth2) {
|
|
1674
|
+
warnedMissingAuth2 = true;
|
|
1675
|
+
console.warn(
|
|
1676
|
+
"robotrock-task-link: RobotRock auth is unset; skipping chat task link."
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
try {
|
|
1682
|
+
await client.chats.linkTask(input);
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
logAuditFailure("link task to chat", error);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
// src/eve/tool-reply-guidance.ts
|
|
1689
|
+
var TOOL_REPLY_GUIDANCE_FIELD = "replyGuidance";
|
|
1690
|
+
var WHOAMI_REPLY_GUIDANCE = "The dashboard shows a profile card with name, email, workspace, role, and groups. Do not restate those in your reply. Only add context not in the card (e.g. permissions, refund approval rules, missing group membership).";
|
|
1691
|
+
var MY_ACCESS_REPLY_GUIDANCE = "Access details may render in chat UI. Do not restate role, groups, or admin capabilities if already visible. Only explain what the user can or cannot do for their question (e.g. finance group required for refunds \u2265 $10,000). When access.tenantAdmin is false and the user needs manageTeam, manageGroups, or query-tasks capabilities, explain briefly then call ask_question to offer sending an admin request to tenant admins \u2014 do not tell them to find an admin manually without offering delegation first.";
|
|
1692
|
+
var CREATE_INBOX_TASK_REPLY_GUIDANCE = "A task card shows title, assignee, and inbox status in chat. Do not restate task ID, assignee, or delegation details from the card. Only mention next steps or answer the user's question.";
|
|
1693
|
+
var SEARCH_PRODUCTS_REPLY_GUIDANCE = "Product results render as a list card in chat. Do not restate product names, prices, or SKUs. Only add context not in the list (e.g. no matches, recommendations).";
|
|
1694
|
+
var MANAGE_TEAM_MEMBERS_LIST_REPLY_GUIDANCE = "Team members render as a list card in chat. Do not restate names, emails, or roles from the list. Only add context not shown (e.g. duplicates, next admin actions).";
|
|
1695
|
+
var MANAGE_TEAM_MEMBERS_DETAIL_REPLY_GUIDANCE = "Team member details render in chat UI. Do not restate name, email, or role from the card. Only add next steps or policy context.";
|
|
1696
|
+
var MANAGE_GROUPS_LIST_REPLY_GUIDANCE = "Groups render as a list card in chat. Do not restate group names, slugs, or descriptions from the list. Use slug (not internal id) for get, update, delete, and membership actions. Only add context not shown.";
|
|
1697
|
+
var MANAGE_GROUPS_DETAIL_REPLY_GUIDANCE = "Group details render in chat UI. Do not restate fields already visible in the card. Only add next steps.";
|
|
1698
|
+
var QUERY_TASKS_LIST_REPLY_GUIDANCE = "Tasks render as a list card in chat. Do not restate task names, ids, or statuses from the list. Only add context not shown (e.g. filters applied, recommended next action).";
|
|
1699
|
+
var QUERY_TASKS_DETAIL_REPLY_GUIDANCE = "Task details render in chat UI. Do not restate fields already visible in the card. Only add analysis or next steps.";
|
|
1700
|
+
var ASSIGN_TASKS_REPLY_GUIDANCE = "Reassignment results render in chat UI. Do not restate task ids or assignee emails from the card. Only mention failures, partial success, or next steps.";
|
|
1701
|
+
var ADMIN_MUTATION_REPLY_GUIDANCE = "The action result renders in chat UI. Do not restate fields already visible in the card. Confirm outcome briefly or add next steps only.";
|
|
1702
|
+
function withReplyGuidance(result, guidance) {
|
|
1703
|
+
return {
|
|
1704
|
+
...result,
|
|
1705
|
+
[TOOL_REPLY_GUIDANCE_FIELD]: guidance
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// src/eve/tools/inbox/create-task.ts
|
|
1710
|
+
var CREATE_INBOX_TASK_TOOL_NAME = "create_robotrock_task";
|
|
1711
|
+
var actionSchema = z6.object({
|
|
1712
|
+
id: z6.string().min(1).describe("Stable action id, e.g. approve or reject."),
|
|
1713
|
+
title: z6.string().min(1).describe("Button label shown in the inbox."),
|
|
1714
|
+
description: z6.string().min(1).optional()
|
|
1715
|
+
});
|
|
1716
|
+
var assignToSchema3 = z6.object({
|
|
1717
|
+
users: z6.array(z6.string().email()).optional().describe("Assignee emails \u2014 only these users (plus group members) see the task."),
|
|
1718
|
+
groups: z6.array(z6.string().min(1)).optional().describe(
|
|
1719
|
+
'Group slugs, e.g. "finance" or virtual "admins". Virtual "all" means everyone; omit assignTo for the same effect.'
|
|
1720
|
+
)
|
|
1721
|
+
}).optional().refine(
|
|
1722
|
+
(value) => value === void 0 || (value.users?.length ?? 0) > 0 || (value.groups?.length ?? 0) > 0,
|
|
1723
|
+
{ message: "assignTo needs at least one user email or group slug." }
|
|
1724
|
+
).refine(
|
|
1725
|
+
(value) => {
|
|
1726
|
+
const groups = value?.groups ?? [];
|
|
1727
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
1728
|
+
return false;
|
|
1729
|
+
}
|
|
1730
|
+
return true;
|
|
1731
|
+
},
|
|
1732
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
1733
|
+
);
|
|
1734
|
+
var contextSchema = z6.object({
|
|
1735
|
+
data: z6.record(z6.string(), z6.unknown()).optional().describe("Structured fields shown in the inbox task detail."),
|
|
1736
|
+
ui: z6.record(z6.string(), z6.unknown()).optional().describe(
|
|
1737
|
+
"Optional ui:* hints keyed by context.data field name (ui:widget, ui:title, ui:options). Use currency for amounts, date for ISO timestamps, link/image for URLs. Load the robotrock skill \u2192 context-widgets reference for the full widget catalog."
|
|
1738
|
+
)
|
|
1739
|
+
}).optional();
|
|
1740
|
+
var createInboxTaskInputSchema = z6.object({
|
|
1741
|
+
type: z6.string().min(1).describe("Task category slug, e.g. refund-approval or budget-approval."),
|
|
1742
|
+
name: z6.string().min(1).describe("Short title shown in the inbox list."),
|
|
1743
|
+
description: z6.string().min(1).optional(),
|
|
1744
|
+
actions: z6.array(actionSchema).min(1).describe("At least one reviewer action (approve, reject, etc.)."),
|
|
1745
|
+
assignTo: assignToSchema3.describe(
|
|
1746
|
+
"Limit visibility to specific users and/or groups. Omit to show the task to everyone in the tenant."
|
|
1747
|
+
),
|
|
1748
|
+
context: contextSchema,
|
|
1749
|
+
validUntilHours: z6.number().positive().optional().describe("Optional deadline in hours from now. Defaults to the tenant task TTL."),
|
|
1750
|
+
priority: z6.enum(["low", "normal", "high", "urgent"]).optional(),
|
|
1751
|
+
updateMessage: z6.string().min(1).optional().describe("Optional thread status update logged when the task is created."),
|
|
1752
|
+
delegationReason: z6.string().min(1).optional().describe(
|
|
1753
|
+
"User-facing explanation of why the requester cannot self-approve and the task was routed elsewhere. Required when assignTo excludes the caller."
|
|
1754
|
+
)
|
|
1755
|
+
});
|
|
1756
|
+
function defaultResolveDelegationReason(input) {
|
|
1757
|
+
const assigneeGroups = input.assignTo?.groups?.filter(Boolean) ?? [];
|
|
1758
|
+
if (assigneeGroups.length > 0) {
|
|
1759
|
+
return `This was routed to ${assigneeGroups.join(", ")} for review because you are not in the assignee group.`;
|
|
1760
|
+
}
|
|
1761
|
+
return void 0;
|
|
1762
|
+
}
|
|
1763
|
+
function defineCreateInboxTaskTool(options) {
|
|
1764
|
+
return defineTool({
|
|
1765
|
+
description: "Create a RobotRock inbox task and assign it to users or groups. Does not wait for a response. Only call this after the user explicitly asked to create/send/delegate an inbox task, or after they confirmed via ask_question. Never create a task silently when the user only asked for a preview, dummy example, or explanation. Always set delegationReason when assignTo excludes the caller. For refund approvals include chargeId, amount, currency, and reason in context.data. The dashboard renders a task card \u2014 do not restate task ID, assignee, or delegation details in your reply.",
|
|
1766
|
+
inputSchema: createInboxTaskInputSchema,
|
|
1767
|
+
async execute(input, ctx) {
|
|
1768
|
+
const caller = requireTenantCaller(ctx);
|
|
1769
|
+
const payload = buildRobotRockTaskPayload(input, {
|
|
1770
|
+
requestedByEmail: caller.email
|
|
1771
|
+
});
|
|
1772
|
+
if (options?.defaultApp && !payload.app) {
|
|
1773
|
+
payload.app = options.defaultApp;
|
|
1774
|
+
}
|
|
1775
|
+
const task = await createRobotRockTask(payload, ctx);
|
|
1776
|
+
const delegationReason = options?.resolveDelegationReason?.(input, caller) ?? input.delegationReason?.trim() ?? defaultResolveDelegationReason(input);
|
|
1777
|
+
await postRobotRockLinkChatTask(ctx, {
|
|
1778
|
+
eveSessionId: ctx.session.id,
|
|
1779
|
+
publicTaskId: task.taskId,
|
|
1780
|
+
toolCallId: ctx.callId
|
|
1781
|
+
});
|
|
1782
|
+
return withReplyGuidance(
|
|
1783
|
+
{
|
|
1784
|
+
...task,
|
|
1785
|
+
name: input.name,
|
|
1786
|
+
assignedTo: input.assignTo ?? null,
|
|
1787
|
+
requestedBy: caller.email,
|
|
1788
|
+
delegationReason: delegationReason ?? null,
|
|
1789
|
+
message: delegationReason ? `${delegationReason} This chat updates when the assignee decides.` : "Task created in RobotRock. Assignees can handle it from their inbox; this chat will update when they decide."
|
|
1790
|
+
},
|
|
1791
|
+
CREATE_INBOX_TASK_REPLY_GUIDANCE
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
var createInboxTaskTool = defineCreateInboxTaskTool();
|
|
1797
|
+
|
|
1798
|
+
// src/eve/tools/identity/my-access.ts
|
|
1799
|
+
import { defineTool as defineTool2 } from "eve/tools";
|
|
1800
|
+
import { z as z7 } from "zod";
|
|
1801
|
+
|
|
1802
|
+
// src/eve/tool-result-display.ts
|
|
1803
|
+
function toolDisplayResult(data, options) {
|
|
1804
|
+
const display = options?.widget || options?.title || options?.description ? {
|
|
1805
|
+
...options.widget ? { widget: options.widget } : {},
|
|
1806
|
+
...options.title ? { title: options.title } : {},
|
|
1807
|
+
...options.description ? { description: options.description } : {}
|
|
1808
|
+
} : void 0;
|
|
1809
|
+
return {
|
|
1810
|
+
...display ? { display } : {},
|
|
1811
|
+
data,
|
|
1812
|
+
...options?.ui ? { ui: options.ui } : {}
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/eve/tool-display-format.ts
|
|
1817
|
+
function formatToolObjectResult(data, options) {
|
|
1818
|
+
return withReplyGuidance(
|
|
1819
|
+
toolDisplayResult(data, {
|
|
1820
|
+
title: options.title,
|
|
1821
|
+
description: options.description,
|
|
1822
|
+
ui: options.ui
|
|
1823
|
+
}),
|
|
1824
|
+
options.replyGuidance
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
function formatToolListResult(listKey, items, options) {
|
|
1828
|
+
const listFieldUi = options.itemWidget ? {
|
|
1829
|
+
"ui:widget": "list",
|
|
1830
|
+
items: {
|
|
1831
|
+
"ui:widget": options.itemWidget,
|
|
1832
|
+
...options.itemUi ?? {}
|
|
1833
|
+
}
|
|
1834
|
+
} : {
|
|
1835
|
+
"ui:widget": "table",
|
|
1836
|
+
...options.itemUi ? { items: options.itemUi } : {}
|
|
1837
|
+
};
|
|
1838
|
+
return formatToolObjectResult(
|
|
1839
|
+
{ [listKey]: items },
|
|
1840
|
+
{
|
|
1841
|
+
title: options.title,
|
|
1842
|
+
ui: {
|
|
1843
|
+
[listKey]: listFieldUi
|
|
1844
|
+
},
|
|
1845
|
+
replyGuidance: options.replyGuidance
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// src/eve/tools/identity/format-my-access.ts
|
|
1851
|
+
var ACCESS_FIELD_UI = {
|
|
1852
|
+
workspace: { "ui:title": "Workspace" },
|
|
1853
|
+
role: { "ui:title": "Role" },
|
|
1854
|
+
tenantAdmin: { "ui:title": "Tenant admin", "ui:widget": "boolean" },
|
|
1855
|
+
groups: { "ui:title": "Groups" },
|
|
1856
|
+
manageSettings: { "ui:title": "Manage settings", "ui:widget": "boolean" },
|
|
1857
|
+
manageBilling: { "ui:title": "Manage billing", "ui:widget": "boolean" },
|
|
1858
|
+
manageTeam: { "ui:title": "Manage team", "ui:widget": "boolean" },
|
|
1859
|
+
manageIntegrations: {
|
|
1860
|
+
"ui:title": "Manage integrations",
|
|
1861
|
+
"ui:widget": "boolean"
|
|
1862
|
+
},
|
|
1863
|
+
email: { "ui:title": "Email" }
|
|
1864
|
+
};
|
|
1865
|
+
function formatMyAccessResult(input) {
|
|
1866
|
+
return formatToolObjectResult(
|
|
1867
|
+
{
|
|
1868
|
+
access: {
|
|
1869
|
+
workspace: input.tenantSlug,
|
|
1870
|
+
role: input.tenantRole ?? "member",
|
|
1871
|
+
tenantAdmin: input.isTenantAdmin,
|
|
1872
|
+
groups: input.groupSlugs.join(", ") || "\u2014",
|
|
1873
|
+
manageSettings: input.capabilities.manageSettings,
|
|
1874
|
+
manageBilling: input.capabilities.manageBilling,
|
|
1875
|
+
manageTeam: input.capabilities.manageTeam,
|
|
1876
|
+
manageIntegrations: input.capabilities.manageIntegrations,
|
|
1877
|
+
...input.email ? { email: input.email } : {}
|
|
1878
|
+
}
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
ui: {
|
|
1882
|
+
access: {
|
|
1883
|
+
"ui:widget": "access",
|
|
1884
|
+
"ui:title": "Your access",
|
|
1885
|
+
items: ACCESS_FIELD_UI
|
|
1886
|
+
}
|
|
1887
|
+
},
|
|
1888
|
+
replyGuidance: MY_ACCESS_REPLY_GUIDANCE
|
|
1889
|
+
}
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
// src/eve/tools/identity/my-access.ts
|
|
1894
|
+
var MY_ACCESS_TOOL_NAME = "get_my_access";
|
|
1895
|
+
var myAccessInputSchema = z7.object({});
|
|
1896
|
+
function tenantCapabilities(isAdmin) {
|
|
1897
|
+
return {
|
|
1898
|
+
manageSettings: isAdmin,
|
|
1899
|
+
manageBilling: isAdmin,
|
|
1900
|
+
manageTeam: isAdmin,
|
|
1901
|
+
manageIntegrations: isAdmin
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
function defineMyAccessTool() {
|
|
1905
|
+
return defineTool2({
|
|
1906
|
+
description: "Check the authenticated user's tenant role, group memberships, and admin capabilities in RobotRock. Results render as an access card in chat \u2014 do not restate role, groups, or capabilities in your reply.",
|
|
1907
|
+
inputSchema: myAccessInputSchema,
|
|
1908
|
+
async execute(_input, ctx) {
|
|
1909
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1910
|
+
if (!caller) {
|
|
1911
|
+
return withReplyGuidance(
|
|
1912
|
+
{
|
|
1913
|
+
authenticated: false,
|
|
1914
|
+
message: "No RobotRock user is attached to this session. Dashboard chat supplies user context via the Eve proxy."
|
|
1915
|
+
},
|
|
1916
|
+
"Explain that no user is attached to this session. Do not invent access details."
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
return formatMyAccessResult({
|
|
1920
|
+
tenantSlug: caller.tenantSlug,
|
|
1921
|
+
tenantRole: caller.role,
|
|
1922
|
+
isTenantAdmin: caller.isAdmin,
|
|
1923
|
+
groupSlugs: caller.groups,
|
|
1924
|
+
capabilities: tenantCapabilities(caller.isAdmin),
|
|
1925
|
+
email: caller.email
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
var myAccessTool = defineMyAccessTool();
|
|
1931
|
+
|
|
1932
|
+
// src/eve/tools/identity/whoami.ts
|
|
1933
|
+
import { defineTool as defineTool3 } from "eve/tools";
|
|
1934
|
+
import { z as z8 } from "zod";
|
|
1935
|
+
var WHOAMI_TOOL_NAME = "whoami";
|
|
1936
|
+
var whoamiInputSchema = z8.object({});
|
|
1937
|
+
function defineWhoamiTool() {
|
|
1938
|
+
return defineTool3({
|
|
1939
|
+
description: "Return the authenticated RobotRock user, tenant, role, and group memberships for the current chat session. The dashboard renders a profile card \u2014 do not repeat name, email, workspace, role, or groups in your reply; only add context not shown in the card.",
|
|
1940
|
+
inputSchema: whoamiInputSchema,
|
|
1941
|
+
async execute(_input, ctx) {
|
|
1942
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1943
|
+
if (!caller) {
|
|
1944
|
+
return withReplyGuidance(
|
|
1945
|
+
{
|
|
1946
|
+
authenticated: false,
|
|
1947
|
+
message: "No RobotRock user is attached to this session. Dashboard chat supplies user context via the Eve proxy."
|
|
1948
|
+
},
|
|
1949
|
+
"Explain that no user is attached to this session. Do not invent identity details."
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
return withReplyGuidance(
|
|
1953
|
+
{
|
|
1954
|
+
authenticated: true,
|
|
1955
|
+
userId: caller.userId,
|
|
1956
|
+
name: caller.name,
|
|
1957
|
+
email: caller.email,
|
|
1958
|
+
tenantSlug: caller.tenantSlug,
|
|
1959
|
+
role: caller.role,
|
|
1960
|
+
isAdmin: caller.isAdmin,
|
|
1961
|
+
groups: caller.groups.map((slug) => ({ slug })),
|
|
1962
|
+
...caller.workosUserId ? { workosUserId: caller.workosUserId } : {},
|
|
1963
|
+
sessionId: ctx.session.id
|
|
1964
|
+
},
|
|
1965
|
+
WHOAMI_REPLY_GUIDANCE
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
var whoamiTool = defineWhoamiTool();
|
|
1971
|
+
|
|
1972
|
+
// src/eve/tools/catalog/search-products.ts
|
|
1973
|
+
import { defineTool as defineTool4 } from "eve/tools";
|
|
1974
|
+
import { z as z9 } from "zod";
|
|
1975
|
+
var SEARCH_PRODUCTS_TOOL_NAME = "search_products";
|
|
1976
|
+
var searchProductsInputSchema = z9.object({
|
|
1977
|
+
query: z9.string().min(1).describe("Product name or SKU to search for.")
|
|
1978
|
+
});
|
|
1979
|
+
var DEMO_PRODUCTS = [
|
|
1980
|
+
{
|
|
1981
|
+
id: "sku-1001",
|
|
1982
|
+
name: "Wireless Keyboard",
|
|
1983
|
+
price: 79.99,
|
|
1984
|
+
currency: "USD",
|
|
1985
|
+
inStock: true
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
id: "sku-2042",
|
|
1989
|
+
name: "USB-C Hub",
|
|
1990
|
+
price: 45.99,
|
|
1991
|
+
currency: "USD",
|
|
1992
|
+
inStock: true
|
|
1993
|
+
},
|
|
1994
|
+
{
|
|
1995
|
+
id: "sku-3308",
|
|
1996
|
+
name: "Monitor Stand",
|
|
1997
|
+
price: 129.99,
|
|
1998
|
+
currency: "USD",
|
|
1999
|
+
inStock: false
|
|
2000
|
+
}
|
|
2001
|
+
];
|
|
2002
|
+
function defineSearchProductsTool() {
|
|
2003
|
+
return defineTool4({
|
|
2004
|
+
description: "Search the product catalog and return matching items with rich list UI. Results render as a product list card \u2014 do not restate product names, prices, or SKUs in your reply.",
|
|
2005
|
+
inputSchema: searchProductsInputSchema,
|
|
2006
|
+
async execute({ query }) {
|
|
2007
|
+
const normalized = query.trim().toLowerCase();
|
|
2008
|
+
const items = DEMO_PRODUCTS.filter(
|
|
2009
|
+
(product) => product.name.toLowerCase().includes(normalized) || product.id.toLowerCase().includes(normalized)
|
|
2010
|
+
);
|
|
2011
|
+
return withReplyGuidance(
|
|
2012
|
+
toolDisplayResult(
|
|
2013
|
+
{ items: [...items] },
|
|
2014
|
+
{
|
|
2015
|
+
title: items.length === 1 ? "1 product" : `${items.length} products`,
|
|
2016
|
+
ui: {
|
|
2017
|
+
items: {
|
|
2018
|
+
"ui:widget": "list",
|
|
2019
|
+
"ui:title": "Results",
|
|
2020
|
+
items: {
|
|
2021
|
+
"ui:widget": "productInfo"
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
),
|
|
2027
|
+
SEARCH_PRODUCTS_REPLY_GUIDANCE
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
var searchProductsTool = defineSearchProductsTool();
|
|
2033
|
+
|
|
2034
|
+
// src/agent-admin.ts
|
|
2035
|
+
async function agentAdminFetch(config, path, init) {
|
|
2036
|
+
const baseUrl = (config.baseUrl?.trim() || getRobotRockApiBaseUrl()).replace(
|
|
2037
|
+
/\/+$/,
|
|
2038
|
+
""
|
|
2039
|
+
);
|
|
2040
|
+
const url = `${baseUrl}/agent-admin${path}`;
|
|
2041
|
+
const response = await fetch(url, {
|
|
2042
|
+
...init,
|
|
2043
|
+
headers: {
|
|
2044
|
+
"Content-Type": "application/json",
|
|
2045
|
+
...buildRobotRockAuthHeaders(config.auth),
|
|
2046
|
+
...init?.headers ?? {}
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
const data = await parseResponseBody(response);
|
|
2050
|
+
if (!response.ok) {
|
|
2051
|
+
throw new RobotRockError(
|
|
2052
|
+
getErrorMessage(data, `Agent admin request failed (${response.status})`),
|
|
2053
|
+
response.status,
|
|
2054
|
+
data
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
return data;
|
|
2058
|
+
}
|
|
2059
|
+
function createAgentAdminApi(config) {
|
|
2060
|
+
return {
|
|
2061
|
+
members: {
|
|
2062
|
+
list: () => agentAdminFetch(config, "/members"),
|
|
2063
|
+
get: (memberRef) => agentAdminFetch(config, `/members/${encodeURIComponent(memberRef)}`),
|
|
2064
|
+
invite: (body) => agentAdminFetch(config, "/members", {
|
|
2065
|
+
method: "POST",
|
|
2066
|
+
body: JSON.stringify(body)
|
|
2067
|
+
}),
|
|
2068
|
+
updateRole: (memberRef, role) => agentAdminFetch(
|
|
2069
|
+
config,
|
|
2070
|
+
`/members/${encodeURIComponent(memberRef)}/role`,
|
|
2071
|
+
{
|
|
2072
|
+
method: "PATCH",
|
|
2073
|
+
body: JSON.stringify({ role })
|
|
2074
|
+
}
|
|
2075
|
+
),
|
|
2076
|
+
remove: (memberId) => agentAdminFetch(
|
|
2077
|
+
config,
|
|
2078
|
+
`/members/${encodeURIComponent(memberId)}`,
|
|
2079
|
+
{
|
|
2080
|
+
method: "DELETE"
|
|
2081
|
+
}
|
|
2082
|
+
)
|
|
2083
|
+
},
|
|
2084
|
+
groups: {
|
|
2085
|
+
list: () => agentAdminFetch(config, "/groups"),
|
|
2086
|
+
get: (groupId) => agentAdminFetch(
|
|
2087
|
+
config,
|
|
2088
|
+
`/groups/${encodeURIComponent(groupId)}`
|
|
2089
|
+
),
|
|
2090
|
+
create: (body) => agentAdminFetch(config, "/groups", {
|
|
2091
|
+
method: "POST",
|
|
2092
|
+
body: JSON.stringify(body)
|
|
2093
|
+
}),
|
|
2094
|
+
update: (groupId, body) => agentAdminFetch(
|
|
2095
|
+
config,
|
|
2096
|
+
`/groups/${encodeURIComponent(groupId)}`,
|
|
2097
|
+
{
|
|
2098
|
+
method: "PATCH",
|
|
2099
|
+
body: JSON.stringify(body)
|
|
2100
|
+
}
|
|
2101
|
+
),
|
|
2102
|
+
delete: (groupId) => agentAdminFetch(
|
|
2103
|
+
config,
|
|
2104
|
+
`/groups/${encodeURIComponent(groupId)}`,
|
|
2105
|
+
{
|
|
2106
|
+
method: "DELETE"
|
|
2107
|
+
}
|
|
2108
|
+
),
|
|
2109
|
+
addMember: (groupRef, memberRef) => agentAdminFetch(
|
|
2110
|
+
config,
|
|
2111
|
+
`/groups/${encodeURIComponent(groupRef)}/members`,
|
|
2112
|
+
{
|
|
2113
|
+
method: "POST",
|
|
2114
|
+
body: JSON.stringify({ userId: memberRef })
|
|
2115
|
+
}
|
|
2116
|
+
),
|
|
2117
|
+
removeMember: (groupRef, memberRef) => agentAdminFetch(
|
|
2118
|
+
config,
|
|
2119
|
+
`/groups/${encodeURIComponent(groupRef)}/members/${encodeURIComponent(memberRef)}`,
|
|
2120
|
+
{ method: "DELETE" }
|
|
2121
|
+
)
|
|
2122
|
+
},
|
|
2123
|
+
tasks: {
|
|
2124
|
+
list: (query) => {
|
|
2125
|
+
const params = new URLSearchParams();
|
|
2126
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
2127
|
+
if (value !== void 0 && value !== "") {
|
|
2128
|
+
params.set(key, String(value));
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
const suffix = params.size > 0 ? `?${params.toString()}` : "";
|
|
2132
|
+
return agentAdminFetch(
|
|
2133
|
+
config,
|
|
2134
|
+
`/tasks${suffix}`
|
|
2135
|
+
);
|
|
2136
|
+
},
|
|
2137
|
+
search: (q, limit) => {
|
|
2138
|
+
const params = new URLSearchParams({ q });
|
|
2139
|
+
if (limit !== void 0) {
|
|
2140
|
+
params.set("limit", String(limit));
|
|
2141
|
+
}
|
|
2142
|
+
return agentAdminFetch(
|
|
2143
|
+
config,
|
|
2144
|
+
`/tasks/search?${params.toString()}`
|
|
2145
|
+
);
|
|
2146
|
+
},
|
|
2147
|
+
get: (taskId) => agentAdminFetch(config, `/tasks/${taskId}`),
|
|
2148
|
+
assign: (body) => agentAdminFetch(config, "/tasks/assign", {
|
|
2149
|
+
method: "POST",
|
|
2150
|
+
body: JSON.stringify(body)
|
|
2151
|
+
})
|
|
2152
|
+
}
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
function createBoundAgentAdminClient(ctx) {
|
|
2156
|
+
const caller = requireTenantCaller(ctx);
|
|
2157
|
+
const baseUrl = process.env.ROBOTROCK_BASE_URL?.trim();
|
|
2158
|
+
const serviceToken = process.env.ROBOTROCK_AGENT_SERVICE_TOKEN?.trim();
|
|
2159
|
+
if (caller.connectionId && serviceToken) {
|
|
2160
|
+
return createAgentAdminApi({
|
|
2161
|
+
baseUrl,
|
|
2162
|
+
auth: resolveRobotRockAuthConfig({
|
|
2163
|
+
agentService: {
|
|
2164
|
+
token: serviceToken,
|
|
2165
|
+
tenantSlug: caller.tenantSlug,
|
|
2166
|
+
connectionId: caller.connectionId,
|
|
2167
|
+
actingUserId: caller.userId
|
|
2168
|
+
}
|
|
2169
|
+
})
|
|
2170
|
+
});
|
|
2171
|
+
}
|
|
2172
|
+
const apiKey = process.env.ROBOTROCK_API_KEY?.trim();
|
|
2173
|
+
if (apiKey) {
|
|
2174
|
+
return createAgentAdminApi({
|
|
2175
|
+
baseUrl,
|
|
2176
|
+
auth: {
|
|
2177
|
+
kind: "apiKey",
|
|
2178
|
+
apiKey,
|
|
2179
|
+
actingUserId: caller.userId
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
}
|
|
2183
|
+
throw new Error(
|
|
2184
|
+
"Agent admin auth is unset. Set ROBOTROCK_AGENT_SERVICE_TOKEN for hosted multi-tenant agents, or ROBOTROCK_API_KEY for self-hosted and localhost deployments."
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
function requireBoundAgentAdminClient(ctx) {
|
|
2188
|
+
requireAdminCaller(ctx);
|
|
2189
|
+
return createBoundAgentAdminClient(ctx);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
// src/eve/tools/admin/format-team-members-list.ts
|
|
2193
|
+
var MEMBER_DETAIL_UI = {
|
|
2194
|
+
name: { "ui:title": "Name" },
|
|
2195
|
+
email: { "ui:title": "Email" },
|
|
2196
|
+
role: { "ui:title": "Role" },
|
|
2197
|
+
membershipKind: { "ui:title": "Membership" }
|
|
2198
|
+
};
|
|
2199
|
+
function serializeMemberRow(member) {
|
|
2200
|
+
return {
|
|
2201
|
+
name: member.user.name,
|
|
2202
|
+
email: member.user.email,
|
|
2203
|
+
role: member.role,
|
|
2204
|
+
membershipKind: member.membershipKind
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function formatTeamMembersListResult(members) {
|
|
2208
|
+
const countLabel = members.length === 1 ? "1 team member" : `${members.length} team members`;
|
|
2209
|
+
return formatToolListResult(
|
|
2210
|
+
"members",
|
|
2211
|
+
members.map(serializeMemberRow),
|
|
2212
|
+
{
|
|
2213
|
+
title: countLabel,
|
|
2214
|
+
itemWidget: "tenantMember",
|
|
2215
|
+
replyGuidance: MANAGE_TEAM_MEMBERS_LIST_REPLY_GUIDANCE
|
|
2216
|
+
}
|
|
2217
|
+
);
|
|
2218
|
+
}
|
|
2219
|
+
function formatTeamMemberDetailResult(member, options) {
|
|
2220
|
+
return formatToolObjectResult(serializeMemberRow(member), {
|
|
2221
|
+
title: options?.title ?? member.user.name,
|
|
2222
|
+
ui: MEMBER_DETAIL_UI,
|
|
2223
|
+
replyGuidance: MANAGE_TEAM_MEMBERS_DETAIL_REPLY_GUIDANCE
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// src/eve/tools/admin/manage-team-members.ts
|
|
2228
|
+
import { defineTool as defineTool5 } from "eve/tools";
|
|
2229
|
+
import { z as z10 } from "zod";
|
|
2230
|
+
var MANAGE_TEAM_MEMBERS_TOOL_NAME = "manage_team_members";
|
|
2231
|
+
var manageTeamMembersInputSchema = z10.object({
|
|
2232
|
+
action: z10.enum(["list", "get", "invite", "update_role", "remove"]),
|
|
2233
|
+
userId: z10.string().min(1).optional().describe(
|
|
2234
|
+
"Convex users table id for get, update_role, and remove. Prefer email when the member list only shows email."
|
|
2235
|
+
),
|
|
2236
|
+
email: z10.string().email().optional().describe(
|
|
2237
|
+
"Member email for get, update_role, and remove; invitee email for invite."
|
|
2238
|
+
),
|
|
2239
|
+
role: z10.enum(["admin", "member"]).optional().describe("Role for invite and update_role.")
|
|
2240
|
+
}).superRefine((input, ctx) => {
|
|
2241
|
+
const requireField = (field, message) => {
|
|
2242
|
+
if (!input[field]) {
|
|
2243
|
+
ctx.addIssue({ code: "custom", message, path: [field] });
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
const requireUserIdOrEmail = (message) => {
|
|
2247
|
+
if (!input.userId && !input.email) {
|
|
2248
|
+
ctx.addIssue({ code: "custom", message, path: ["email"] });
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
switch (input.action) {
|
|
2252
|
+
case "get":
|
|
2253
|
+
requireUserIdOrEmail("userId or email is required for this action.");
|
|
2254
|
+
break;
|
|
2255
|
+
case "remove":
|
|
2256
|
+
requireUserIdOrEmail(
|
|
2257
|
+
"userId or email is required when removing a member."
|
|
2258
|
+
);
|
|
2259
|
+
break;
|
|
2260
|
+
case "invite":
|
|
2261
|
+
requireField("email", "email is required when inviting a member.");
|
|
2262
|
+
break;
|
|
2263
|
+
case "update_role":
|
|
2264
|
+
requireUserIdOrEmail(
|
|
2265
|
+
"userId or email is required when updating a role."
|
|
2266
|
+
);
|
|
2267
|
+
requireField("role", "role is required when updating a role.");
|
|
2268
|
+
break;
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
function resolveMemberRef(input) {
|
|
2272
|
+
return input.userId ?? input.email;
|
|
2273
|
+
}
|
|
2274
|
+
function formatMemberMutationResult(payload, title) {
|
|
2275
|
+
return formatToolObjectResult(payload, {
|
|
2276
|
+
title,
|
|
2277
|
+
ui: {
|
|
2278
|
+
success: { "ui:title": "Success", "ui:widget": "boolean" },
|
|
2279
|
+
promoted: { "ui:title": "Promoted existing user", "ui:widget": "boolean" }
|
|
2280
|
+
},
|
|
2281
|
+
replyGuidance: ADMIN_MUTATION_REPLY_GUIDANCE
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
function defineManageTeamMembersTool() {
|
|
2285
|
+
return defineTool5({
|
|
2286
|
+
description: "Tenant-admin tool for workspace team members: list, get, invite, change role, or remove. Requires tenant admin access. Use member email from list results for get, update_role, and remove \u2014 internal ids are optional. Results render as chat UI cards \u2014 do not restate member names, emails, or roles in your reply.",
|
|
2287
|
+
inputSchema: manageTeamMembersInputSchema,
|
|
2288
|
+
async execute(input, ctx) {
|
|
2289
|
+
const api = requireBoundAgentAdminClient(ctx);
|
|
2290
|
+
try {
|
|
2291
|
+
switch (input.action) {
|
|
2292
|
+
case "list": {
|
|
2293
|
+
const result = await api.members.list();
|
|
2294
|
+
return formatTeamMembersListResult(
|
|
2295
|
+
result.members
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
case "get": {
|
|
2299
|
+
const result = await api.members.get(resolveMemberRef(input));
|
|
2300
|
+
return formatTeamMemberDetailResult(result.member);
|
|
2301
|
+
}
|
|
2302
|
+
case "invite": {
|
|
2303
|
+
const result = await api.members.invite({
|
|
2304
|
+
email: input.email,
|
|
2305
|
+
role: input.role
|
|
2306
|
+
});
|
|
2307
|
+
if (result.member) {
|
|
2308
|
+
return formatTeamMemberDetailResult(
|
|
2309
|
+
result.member,
|
|
2310
|
+
{ title: "Member invited" }
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
return formatMemberMutationResult(
|
|
2314
|
+
result,
|
|
2315
|
+
"Invitation sent"
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
case "update_role": {
|
|
2319
|
+
const result = await api.members.updateRole(
|
|
2320
|
+
resolveMemberRef(input),
|
|
2321
|
+
input.role
|
|
2322
|
+
);
|
|
2323
|
+
return formatMemberMutationResult(
|
|
2324
|
+
result,
|
|
2325
|
+
"Member role updated"
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
case "remove": {
|
|
2329
|
+
const memberId = resolveMemberRef(input);
|
|
2330
|
+
const result = await api.members.remove(memberId);
|
|
2331
|
+
return formatMemberMutationResult(
|
|
2332
|
+
result,
|
|
2333
|
+
"Member removed"
|
|
2334
|
+
);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
} catch (error) {
|
|
2338
|
+
if (error instanceof RobotRockError) {
|
|
2339
|
+
return {
|
|
2340
|
+
ok: false,
|
|
2341
|
+
status: error.statusCode,
|
|
2342
|
+
message: error.message,
|
|
2343
|
+
response: error.response
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
throw error;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
var manageTeamMembersTool = defineManageTeamMembersTool();
|
|
2352
|
+
|
|
2353
|
+
// src/eve/tools/admin/manage-groups.ts
|
|
2354
|
+
import { defineTool as defineTool6 } from "eve/tools";
|
|
2355
|
+
import { z as z11 } from "zod";
|
|
2356
|
+
|
|
2357
|
+
// src/eve/tools/admin/format-groups-list.ts
|
|
2358
|
+
var GROUP_ITEM_UI = {
|
|
2359
|
+
name: { "ui:title": "Name" },
|
|
2360
|
+
slug: { "ui:title": "Slug" },
|
|
2361
|
+
description: { "ui:title": "Description" },
|
|
2362
|
+
memberCount: { "ui:title": "Members", "ui:widget": "number" }
|
|
2363
|
+
};
|
|
2364
|
+
function serializeGroupRow(group) {
|
|
2365
|
+
return {
|
|
2366
|
+
name: group.name,
|
|
2367
|
+
slug: group.slug,
|
|
2368
|
+
description: group.description,
|
|
2369
|
+
memberCount: group.memberCount ?? null
|
|
2370
|
+
};
|
|
2371
|
+
}
|
|
2372
|
+
function formatGroupsListResult(groups) {
|
|
2373
|
+
const countLabel = groups.length === 1 ? "1 group" : `${groups.length} groups`;
|
|
2374
|
+
return formatToolListResult(
|
|
2375
|
+
"groups",
|
|
2376
|
+
groups.map(serializeGroupRow),
|
|
2377
|
+
{
|
|
2378
|
+
title: countLabel,
|
|
2379
|
+
itemWidget: "tenantGroup",
|
|
2380
|
+
replyGuidance: MANAGE_GROUPS_LIST_REPLY_GUIDANCE
|
|
2381
|
+
}
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
function formatGroupDetailResult(group) {
|
|
2385
|
+
return formatToolObjectResult(
|
|
2386
|
+
{
|
|
2387
|
+
name: group.name,
|
|
2388
|
+
slug: group.slug,
|
|
2389
|
+
description: group.description,
|
|
2390
|
+
members: group.members.map((member) => ({
|
|
2391
|
+
name: member.user.name,
|
|
2392
|
+
email: member.user.email
|
|
2393
|
+
}))
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
title: group.name,
|
|
2397
|
+
ui: {
|
|
2398
|
+
name: { "ui:title": "Name" },
|
|
2399
|
+
slug: { "ui:title": "Slug" },
|
|
2400
|
+
description: { "ui:title": "Description" },
|
|
2401
|
+
members: {
|
|
2402
|
+
"ui:widget": "list",
|
|
2403
|
+
"ui:title": "Members",
|
|
2404
|
+
items: {
|
|
2405
|
+
name: { "ui:title": "Name" },
|
|
2406
|
+
email: { "ui:title": "Email" }
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
},
|
|
2410
|
+
replyGuidance: MANAGE_GROUPS_DETAIL_REPLY_GUIDANCE
|
|
2411
|
+
}
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
function formatGroupSummaryResult(group) {
|
|
2415
|
+
return formatToolObjectResult(serializeGroupRow(group), {
|
|
2416
|
+
title: group.name,
|
|
2417
|
+
ui: GROUP_ITEM_UI,
|
|
2418
|
+
replyGuidance: MANAGE_GROUPS_DETAIL_REPLY_GUIDANCE
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// src/eve/tools/admin/manage-groups.ts
|
|
2423
|
+
var MANAGE_GROUPS_TOOL_NAME = "manage_groups";
|
|
2424
|
+
var manageGroupsInputSchema = z11.object({
|
|
2425
|
+
action: z11.enum([
|
|
2426
|
+
"list",
|
|
2427
|
+
"get",
|
|
2428
|
+
"create",
|
|
2429
|
+
"update",
|
|
2430
|
+
"delete",
|
|
2431
|
+
"add_member",
|
|
2432
|
+
"remove_member"
|
|
2433
|
+
]),
|
|
2434
|
+
groupId: z11.string().min(1).optional().describe(
|
|
2435
|
+
"Convex group id for get, update, delete, and membership actions. Prefer slug when the group list only shows slug."
|
|
2436
|
+
),
|
|
2437
|
+
slug: z11.string().min(1).optional().describe(
|
|
2438
|
+
"Group slug (e.g. finance) for get, update, delete, and membership actions."
|
|
2439
|
+
),
|
|
2440
|
+
userId: z11.string().min(1).optional().describe(
|
|
2441
|
+
"User id for add_member and remove_member. Prefer email when the member list only shows email."
|
|
2442
|
+
),
|
|
2443
|
+
email: z11.string().email().optional().describe("Member email for add_member and remove_member."),
|
|
2444
|
+
name: z11.string().min(1).optional().describe("Group name for create and update."),
|
|
2445
|
+
description: z11.string().optional().describe("Group description for create and update.")
|
|
2446
|
+
}).superRefine((input, ctx) => {
|
|
2447
|
+
const requireField = (field, message) => {
|
|
2448
|
+
if (!input[field]) {
|
|
2449
|
+
ctx.addIssue({ code: "custom", message, path: [field] });
|
|
2450
|
+
}
|
|
2451
|
+
};
|
|
2452
|
+
const requireGroupIdOrSlug = (message) => {
|
|
2453
|
+
if (!input.groupId && !input.slug) {
|
|
2454
|
+
ctx.addIssue({ code: "custom", message, path: ["slug"] });
|
|
2455
|
+
}
|
|
2456
|
+
};
|
|
2457
|
+
const requireUserIdOrEmail = (message) => {
|
|
2458
|
+
if (!input.userId && !input.email) {
|
|
2459
|
+
ctx.addIssue({ code: "custom", message, path: ["email"] });
|
|
2460
|
+
}
|
|
2461
|
+
};
|
|
2462
|
+
switch (input.action) {
|
|
2463
|
+
case "get":
|
|
2464
|
+
case "delete":
|
|
2465
|
+
requireGroupIdOrSlug(
|
|
2466
|
+
"groupId or slug is required for this action."
|
|
2467
|
+
);
|
|
2468
|
+
break;
|
|
2469
|
+
case "create":
|
|
2470
|
+
requireField("name", "name is required when creating a group.");
|
|
2471
|
+
break;
|
|
2472
|
+
case "update":
|
|
2473
|
+
requireGroupIdOrSlug("groupId or slug is required for update.");
|
|
2474
|
+
break;
|
|
2475
|
+
case "add_member":
|
|
2476
|
+
case "remove_member":
|
|
2477
|
+
requireGroupIdOrSlug(
|
|
2478
|
+
"groupId or slug is required for membership changes."
|
|
2479
|
+
);
|
|
2480
|
+
requireUserIdOrEmail(
|
|
2481
|
+
"userId or email is required for membership changes."
|
|
2482
|
+
);
|
|
2483
|
+
break;
|
|
2484
|
+
}
|
|
2485
|
+
});
|
|
2486
|
+
function formatGroupMutationResult(payload, title) {
|
|
2487
|
+
return formatToolObjectResult(payload, {
|
|
2488
|
+
title,
|
|
2489
|
+
replyGuidance: ADMIN_MUTATION_REPLY_GUIDANCE
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
function resolveGroupRef(input) {
|
|
2493
|
+
return input.groupId ?? input.slug;
|
|
2494
|
+
}
|
|
2495
|
+
function resolveMemberRef2(input) {
|
|
2496
|
+
return input.userId ?? input.email;
|
|
2497
|
+
}
|
|
2498
|
+
function defineManageGroupsTool() {
|
|
2499
|
+
return defineTool6({
|
|
2500
|
+
description: "Tenant-admin tool for workspace groups: list, get, create, update, delete, and manage group membership. Requires tenant admin access. Use group slug (e.g. finance) from list results for get and membership actions \u2014 internal ids are optional. Use member email for add_member and remove_member when user ids are unknown. Results render as chat UI cards \u2014 do not restate group or member fields in your reply.",
|
|
2501
|
+
inputSchema: manageGroupsInputSchema,
|
|
2502
|
+
async execute(input, ctx) {
|
|
2503
|
+
const api = requireBoundAgentAdminClient(ctx);
|
|
2504
|
+
try {
|
|
2505
|
+
switch (input.action) {
|
|
2506
|
+
case "list": {
|
|
2507
|
+
const result = await api.groups.list();
|
|
2508
|
+
return formatGroupsListResult(result.groups);
|
|
2509
|
+
}
|
|
2510
|
+
case "get": {
|
|
2511
|
+
const result = await api.groups.get(resolveGroupRef(input));
|
|
2512
|
+
return formatGroupDetailResult(result.group);
|
|
2513
|
+
}
|
|
2514
|
+
case "create": {
|
|
2515
|
+
const result = await api.groups.create({
|
|
2516
|
+
name: input.name,
|
|
2517
|
+
description: input.description
|
|
2518
|
+
});
|
|
2519
|
+
return formatGroupSummaryResult(result.group);
|
|
2520
|
+
}
|
|
2521
|
+
case "update": {
|
|
2522
|
+
const result = await api.groups.update(resolveGroupRef(input), {
|
|
2523
|
+
name: input.name,
|
|
2524
|
+
description: input.description
|
|
2525
|
+
});
|
|
2526
|
+
return formatGroupSummaryResult(result.group);
|
|
2527
|
+
}
|
|
2528
|
+
case "delete": {
|
|
2529
|
+
const result = await api.groups.delete(resolveGroupRef(input));
|
|
2530
|
+
return formatGroupMutationResult(
|
|
2531
|
+
result,
|
|
2532
|
+
"Group deleted"
|
|
2533
|
+
);
|
|
2534
|
+
}
|
|
2535
|
+
case "add_member": {
|
|
2536
|
+
const result = await api.groups.addMember(
|
|
2537
|
+
resolveGroupRef(input),
|
|
2538
|
+
resolveMemberRef2(input)
|
|
2539
|
+
);
|
|
2540
|
+
return formatGroupMutationResult(
|
|
2541
|
+
result,
|
|
2542
|
+
"Group member added"
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
case "remove_member": {
|
|
2546
|
+
const result = await api.groups.removeMember(
|
|
2547
|
+
resolveGroupRef(input),
|
|
2548
|
+
resolveMemberRef2(input)
|
|
2549
|
+
);
|
|
2550
|
+
return formatGroupMutationResult(
|
|
2551
|
+
result,
|
|
2552
|
+
"Group member removed"
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
} catch (error) {
|
|
2557
|
+
if (error instanceof RobotRockError) {
|
|
2558
|
+
return {
|
|
2559
|
+
ok: false,
|
|
2560
|
+
status: error.statusCode,
|
|
2561
|
+
message: error.message,
|
|
2562
|
+
response: error.response
|
|
2563
|
+
};
|
|
2564
|
+
}
|
|
2565
|
+
throw error;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
var manageGroupsTool = defineManageGroupsTool();
|
|
2571
|
+
|
|
2572
|
+
// src/eve/tools/admin/query-tasks.ts
|
|
2573
|
+
import { defineTool as defineTool7 } from "eve/tools";
|
|
2574
|
+
import { z as z12 } from "zod";
|
|
2575
|
+
|
|
2576
|
+
// src/eve/tools/admin/format-tasks-list.ts
|
|
2577
|
+
function resolveTaskCardLabel(task) {
|
|
2578
|
+
return task.name?.trim() || task.description?.trim() || task.type?.trim() || task.id?.trim() || "Untitled task";
|
|
2579
|
+
}
|
|
2580
|
+
function formatTasksListResult(items, options) {
|
|
2581
|
+
const title = options?.title ?? (items.length === 1 ? "1 task" : `${items.length} tasks`);
|
|
2582
|
+
const result = formatToolListResult(
|
|
2583
|
+
"items",
|
|
2584
|
+
items.map((task) => ({
|
|
2585
|
+
id: task.id,
|
|
2586
|
+
name: resolveTaskCardLabel(task),
|
|
2587
|
+
status: task.status,
|
|
2588
|
+
type: task.type,
|
|
2589
|
+
description: task.description
|
|
2590
|
+
})),
|
|
2591
|
+
{
|
|
2592
|
+
title,
|
|
2593
|
+
itemWidget: "tenantTask",
|
|
2594
|
+
replyGuidance: QUERY_TASKS_LIST_REPLY_GUIDANCE
|
|
2595
|
+
}
|
|
2596
|
+
);
|
|
2597
|
+
if (options?.nextCursor) {
|
|
2598
|
+
return {
|
|
2599
|
+
...result,
|
|
2600
|
+
data: {
|
|
2601
|
+
...result.data,
|
|
2602
|
+
nextCursor: options.nextCursor
|
|
2603
|
+
},
|
|
2604
|
+
display: {
|
|
2605
|
+
...result.display,
|
|
2606
|
+
description: "More tasks available \u2014 pass nextCursor to list again."
|
|
2607
|
+
}
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
return result;
|
|
2611
|
+
}
|
|
2612
|
+
function formatTaskSearchResult(items, total) {
|
|
2613
|
+
return formatTasksListResult(items, {
|
|
2614
|
+
title: total === 1 ? "1 matching task" : `${total} matching tasks`
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
function formatTaskDetailResult(task) {
|
|
2618
|
+
const title = resolveTaskCardLabel({
|
|
2619
|
+
name: typeof task.name === "string" ? task.name : null,
|
|
2620
|
+
description: typeof task.description === "string" ? task.description : null,
|
|
2621
|
+
type: typeof task.type === "string" ? task.type : null,
|
|
2622
|
+
id: typeof task.id === "string" ? task.id : null
|
|
2623
|
+
});
|
|
2624
|
+
return formatToolObjectResult(
|
|
2625
|
+
{
|
|
2626
|
+
id: task.id ?? null,
|
|
2627
|
+
status: task.status ?? null,
|
|
2628
|
+
type: task.type ?? null,
|
|
2629
|
+
name: task.name ?? null,
|
|
2630
|
+
description: task.description ?? null,
|
|
2631
|
+
validUntil: task.validUntil ?? null,
|
|
2632
|
+
createdAt: task.createdAt ?? null
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
title,
|
|
2636
|
+
ui: {
|
|
2637
|
+
id: { "ui:title": "Task ID" },
|
|
2638
|
+
status: { "ui:title": "Status" },
|
|
2639
|
+
type: { "ui:title": "Type" },
|
|
2640
|
+
name: { "ui:title": "Name" },
|
|
2641
|
+
description: { "ui:title": "Description" },
|
|
2642
|
+
validUntil: { "ui:title": "Valid until", "ui:widget": "date" },
|
|
2643
|
+
createdAt: { "ui:title": "Created", "ui:widget": "date" }
|
|
2644
|
+
},
|
|
2645
|
+
replyGuidance: QUERY_TASKS_DETAIL_REPLY_GUIDANCE
|
|
2646
|
+
}
|
|
2647
|
+
);
|
|
2648
|
+
}
|
|
2649
|
+
function formatAssignTasksResult(results, _assignTo) {
|
|
2650
|
+
const succeeded = results.filter((row) => row.success).length;
|
|
2651
|
+
const title = succeeded === results.length ? `${succeeded === 1 ? "1 task" : `${succeeded} tasks`} reassigned` : `${succeeded}/${results.length} tasks reassigned`;
|
|
2652
|
+
return formatToolListResult(
|
|
2653
|
+
"results",
|
|
2654
|
+
results.map((row) => ({
|
|
2655
|
+
id: row.taskId,
|
|
2656
|
+
name: resolveTaskCardLabel({
|
|
2657
|
+
name: row.name,
|
|
2658
|
+
description: row.description,
|
|
2659
|
+
type: row.type,
|
|
2660
|
+
id: row.taskId
|
|
2661
|
+
}),
|
|
2662
|
+
type: row.type ?? null,
|
|
2663
|
+
description: row.description ?? null,
|
|
2664
|
+
status: row.success ? "reassigned" : "failed"
|
|
2665
|
+
})),
|
|
2666
|
+
{
|
|
2667
|
+
title,
|
|
2668
|
+
itemWidget: "tenantTask",
|
|
2669
|
+
replyGuidance: ASSIGN_TASKS_REPLY_GUIDANCE
|
|
2670
|
+
}
|
|
2671
|
+
);
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
// src/eve/tools/admin/query-tasks.ts
|
|
2675
|
+
var QUERY_TASKS_TOOL_NAME = "query_tasks";
|
|
2676
|
+
var queryTasksInputSchema = z12.object({
|
|
2677
|
+
action: z12.enum(["list", "get", "search"]),
|
|
2678
|
+
statusFilter: z12.enum(["all", "open", "handled", "expired"]).optional().describe("Filter by task status for list."),
|
|
2679
|
+
typeFilter: z12.string().optional(),
|
|
2680
|
+
appFilter: z12.string().optional(),
|
|
2681
|
+
sortField: z12.enum(["type", "date", "status", "validUntil"]).optional(),
|
|
2682
|
+
sortDirection: z12.enum(["asc", "desc"]).optional(),
|
|
2683
|
+
limit: z12.number().int().positive().max(100).optional(),
|
|
2684
|
+
cursor: z12.string().optional(),
|
|
2685
|
+
taskId: z12.string().min(1).optional().describe("Public task id or Convex tasks table id for get."),
|
|
2686
|
+
query: z12.string().min(1).optional().describe("Search type, name, description, or id for search.")
|
|
2687
|
+
}).superRefine((input, ctx) => {
|
|
2688
|
+
switch (input.action) {
|
|
2689
|
+
case "get":
|
|
2690
|
+
if (!input.taskId) {
|
|
2691
|
+
ctx.addIssue({
|
|
2692
|
+
code: "custom",
|
|
2693
|
+
message: "taskId is required for get.",
|
|
2694
|
+
path: ["taskId"]
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
break;
|
|
2698
|
+
case "search":
|
|
2699
|
+
if (!input.query) {
|
|
2700
|
+
ctx.addIssue({
|
|
2701
|
+
code: "custom",
|
|
2702
|
+
message: "query is required for search.",
|
|
2703
|
+
path: ["query"]
|
|
2704
|
+
});
|
|
2705
|
+
}
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
});
|
|
2709
|
+
function defineQueryTasksTool() {
|
|
2710
|
+
return defineTool7({
|
|
2711
|
+
description: "Tenant-admin tool to list, fetch detail, or search inbox tasks in the workspace. Requires tenant admin access. Results render as chat UI cards \u2014 do not restate task names, ids, or statuses in your reply.",
|
|
2712
|
+
inputSchema: queryTasksInputSchema,
|
|
2713
|
+
async execute(input, ctx) {
|
|
2714
|
+
const api = requireBoundAgentAdminClient(ctx);
|
|
2715
|
+
try {
|
|
2716
|
+
switch (input.action) {
|
|
2717
|
+
case "list": {
|
|
2718
|
+
const result = await api.tasks.list({
|
|
2719
|
+
statusFilter: input.statusFilter,
|
|
2720
|
+
typeFilter: input.typeFilter,
|
|
2721
|
+
appFilter: input.appFilter,
|
|
2722
|
+
sortField: input.sortField,
|
|
2723
|
+
sortDirection: input.sortDirection,
|
|
2724
|
+
limit: input.limit,
|
|
2725
|
+
cursor: input.cursor
|
|
2726
|
+
});
|
|
2727
|
+
return formatTasksListResult(result.items, {
|
|
2728
|
+
nextCursor: result.nextCursor
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
case "get": {
|
|
2732
|
+
const result = await api.tasks.get(input.taskId);
|
|
2733
|
+
return formatTaskDetailResult(result.task);
|
|
2734
|
+
}
|
|
2735
|
+
case "search": {
|
|
2736
|
+
const result = await api.tasks.search(input.query, input.limit);
|
|
2737
|
+
return formatTaskSearchResult(
|
|
2738
|
+
result.items,
|
|
2739
|
+
result.total
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
} catch (error) {
|
|
2744
|
+
if (error instanceof RobotRockError) {
|
|
2745
|
+
return {
|
|
2746
|
+
ok: false,
|
|
2747
|
+
status: error.statusCode,
|
|
2748
|
+
message: error.message,
|
|
2749
|
+
response: error.response
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
throw error;
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
var queryTasksTool = defineQueryTasksTool();
|
|
2758
|
+
|
|
2759
|
+
// src/eve/tools/admin/assign-tasks.ts
|
|
2760
|
+
import { defineTool as defineTool8 } from "eve/tools";
|
|
2761
|
+
import { z as z13 } from "zod";
|
|
2762
|
+
var ASSIGN_TASKS_TOOL_NAME = "assign_tasks";
|
|
2763
|
+
var assignTasksInputSchema = z13.object({
|
|
2764
|
+
taskIds: z13.array(z13.string().min(1)).min(1).max(100).describe(
|
|
2765
|
+
"Public task ids or Convex tasks table ids to reassign. Use query_tasks search/get to resolve ids first."
|
|
2766
|
+
),
|
|
2767
|
+
assignTo: assignToSchema.refine(
|
|
2768
|
+
(value) => (value.users?.length ?? 0) > 0 || (value.groups?.length ?? 0) > 0,
|
|
2769
|
+
{ message: "assignTo needs at least one user email or group slug." }
|
|
2770
|
+
).describe(
|
|
2771
|
+
'New assignees \u2014 user emails and/or group slugs (e.g. "finance", virtual "admins").'
|
|
2772
|
+
)
|
|
2773
|
+
});
|
|
2774
|
+
function defineAssignTasksTool() {
|
|
2775
|
+
return defineTool8({
|
|
2776
|
+
description: "Reassign existing inbox tasks to users or groups. Requires access to each task (tenant admins can reassign any task; others only tasks they can see). Use query_tasks to find task ids when needed. Results render as chat UI \u2014 do not restate task ids or assignees in your reply.",
|
|
2777
|
+
inputSchema: assignTasksInputSchema,
|
|
2778
|
+
async execute(input, ctx) {
|
|
2779
|
+
const api = createBoundAgentAdminClient(ctx);
|
|
2780
|
+
try {
|
|
2781
|
+
const result = await api.tasks.assign({
|
|
2782
|
+
taskIds: input.taskIds,
|
|
2783
|
+
assignTo: input.assignTo
|
|
2784
|
+
});
|
|
2785
|
+
return formatAssignTasksResult(result.results, input.assignTo);
|
|
2786
|
+
} catch (error) {
|
|
2787
|
+
if (error instanceof RobotRockError) {
|
|
2788
|
+
return {
|
|
2789
|
+
ok: false,
|
|
2790
|
+
status: error.statusCode,
|
|
2791
|
+
message: error.message,
|
|
2792
|
+
response: error.response
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
throw error;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
var assignTasksTool = defineAssignTasksTool();
|
|
2801
|
+
|
|
2802
|
+
// src/eve/tools/index.ts
|
|
2803
|
+
function createRobotrockTools(options) {
|
|
2804
|
+
const tools = {};
|
|
2805
|
+
if (options?.inbox !== false) {
|
|
2806
|
+
tools[CREATE_INBOX_TASK_TOOL_NAME] = defineCreateInboxTaskTool(
|
|
2807
|
+
options?.inbox === void 0 ? void 0 : options.inbox
|
|
2808
|
+
);
|
|
2809
|
+
}
|
|
2810
|
+
if (options?.identity !== false) {
|
|
2811
|
+
tools[WHOAMI_TOOL_NAME] = whoamiTool;
|
|
2812
|
+
tools[MY_ACCESS_TOOL_NAME] = myAccessTool;
|
|
2813
|
+
}
|
|
2814
|
+
return tools;
|
|
2815
|
+
}
|
|
2816
|
+
export {
|
|
2817
|
+
ASSIGN_TASKS_TOOL_NAME,
|
|
2818
|
+
CREATE_INBOX_TASK_TOOL_NAME,
|
|
2819
|
+
MANAGE_GROUPS_TOOL_NAME,
|
|
2820
|
+
MANAGE_TEAM_MEMBERS_TOOL_NAME,
|
|
2821
|
+
MY_ACCESS_TOOL_NAME,
|
|
2822
|
+
QUERY_TASKS_TOOL_NAME,
|
|
2823
|
+
SEARCH_PRODUCTS_TOOL_NAME,
|
|
2824
|
+
WHOAMI_TOOL_NAME,
|
|
2825
|
+
assignTasksInputSchema,
|
|
2826
|
+
assignTasksTool,
|
|
2827
|
+
createInboxTaskInputSchema,
|
|
2828
|
+
createInboxTaskTool,
|
|
2829
|
+
createRobotrockTools,
|
|
2830
|
+
defineAssignTasksTool,
|
|
2831
|
+
defineCreateInboxTaskTool,
|
|
2832
|
+
defineManageGroupsTool,
|
|
2833
|
+
defineManageTeamMembersTool,
|
|
2834
|
+
defineMyAccessTool,
|
|
2835
|
+
defineQueryTasksTool,
|
|
2836
|
+
defineSearchProductsTool,
|
|
2837
|
+
defineWhoamiTool,
|
|
2838
|
+
manageGroupsTool,
|
|
2839
|
+
manageTeamMembersTool,
|
|
2840
|
+
myAccessInputSchema,
|
|
2841
|
+
myAccessTool,
|
|
2842
|
+
queryTasksTool,
|
|
2843
|
+
searchProductsInputSchema,
|
|
2844
|
+
searchProductsTool,
|
|
2845
|
+
whoamiInputSchema,
|
|
2846
|
+
whoamiTool
|
|
2847
|
+
};
|
|
2848
|
+
//# sourceMappingURL=index.js.map
|