robotrock 0.9.0 → 1.1.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/ai/index.d.ts +18 -7
- package/dist/ai/index.js +852 -115
- package/dist/ai/index.js.map +1 -1
- package/dist/ai/trigger.d.ts +4 -3
- package/dist/ai/trigger.js +810 -115
- package/dist/ai/trigger.js.map +1 -1
- package/dist/ai/workflow.d.ts +15 -4
- package/dist/ai/workflow.js +729 -115
- package/dist/ai/workflow.js.map +1 -1
- package/dist/client-XTnFHGFE.d.ts +248 -0
- package/dist/eve/agent/index.d.ts +188 -0
- package/dist/eve/agent/index.js +2322 -0
- package/dist/eve/agent/index.js.map +1 -0
- package/dist/eve/index.d.ts +5 -0
- package/dist/eve/index.js +446 -0
- package/dist/eve/index.js.map +1 -0
- package/dist/eve/tools/identity/index.d.ts +6 -0
- package/dist/eve/tools/identity/index.js +144 -0
- package/dist/eve/tools/identity/index.js.map +1 -0
- package/dist/eve/tools/identity/my-access.d.ts +58 -0
- package/dist/eve/tools/identity/my-access.js +106 -0
- package/dist/eve/tools/identity/my-access.js.map +1 -0
- package/dist/eve/tools/identity/whoami.d.ts +45 -0
- package/dist/eve/tools/identity/whoami.js +101 -0
- package/dist/eve/tools/identity/whoami.js.map +1 -0
- package/dist/eve/tools/inbox/create-task.d.ts +113 -0
- package/dist/eve/tools/inbox/create-task.js +1557 -0
- package/dist/eve/tools/inbox/create-task.js.map +1 -0
- package/dist/eve/tools/inbox/index.d.ts +5 -0
- package/dist/eve/tools/inbox/index.js +1557 -0
- package/dist/eve/tools/inbox/index.js.map +1 -0
- package/dist/eve/tools/index.d.ts +17 -0
- package/dist/eve/tools/index.js +1654 -0
- package/dist/eve/tools/index.js.map +1 -0
- package/dist/index-BL9qKHA8.d.ts +141 -0
- package/dist/index.d.ts +12 -44
- package/dist/index.js +793 -97
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.d.ts +11 -1
- package/dist/schemas/index.js +126 -8
- package/dist/schemas/index.js.map +1 -1
- package/dist/tenant-5YKDrdC-.d.ts +23 -0
- package/dist/{tool-approval-bridge-G765kMJR.d.ts → tool-approval-bridge-C4bTm8vu.d.ts} +93 -2
- package/dist/trigger/index.d.ts +2 -1
- package/dist/trigger/index.js +495 -87
- package/dist/trigger/index.js.map +1 -1
- package/dist/{trigger-D0shjqk0.d.ts → trigger-Dn0DFiyU.d.ts} +64 -3
- package/dist/workflow/index.d.ts +2 -1
- package/dist/workflow/index.js +496 -88
- package/dist/workflow/index.js.map +1 -1
- package/package.json +44 -7
- package/dist/client-Cy7YLxms.d.ts +0 -145
|
@@ -0,0 +1,2322 @@
|
|
|
1
|
+
// ../core/src/eve/user-context-jwt.ts
|
|
2
|
+
import { createHmac, createSign, createVerify, randomBytes } from "crypto";
|
|
3
|
+
var ROBOTROCK_USER_CONTEXT_ISSUER = "https://robotrock.io/eve-user-context";
|
|
4
|
+
var ROBOTROCK_USER_CONTEXT_AUDIENCE = "eve";
|
|
5
|
+
var ROBOTROCK_USER_CONTEXT_TTL_SECONDS = 5 * 60;
|
|
6
|
+
function decodeJwtPart(part) {
|
|
7
|
+
return JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
|
|
8
|
+
}
|
|
9
|
+
function readStringClaim(payload, key) {
|
|
10
|
+
const value = payload[key];
|
|
11
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
12
|
+
}
|
|
13
|
+
function readGroupsClaim(payload) {
|
|
14
|
+
const value = payload.groups;
|
|
15
|
+
if (typeof value === "string" && value.length > 0) {
|
|
16
|
+
return [value];
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.filter(
|
|
20
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
function parseTenantRoleClaim(value) {
|
|
26
|
+
if (value === "admin" || value === "member") {
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function validateStandardClaims(payload, clockSkewSeconds) {
|
|
32
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
33
|
+
const iss = readStringClaim(payload, "iss");
|
|
34
|
+
const aud = readStringClaim(payload, "aud");
|
|
35
|
+
const iat = payload.iat;
|
|
36
|
+
const exp = payload.exp;
|
|
37
|
+
if (iss !== ROBOTROCK_USER_CONTEXT_ISSUER) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (aud !== ROBOTROCK_USER_CONTEXT_AUDIENCE) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (typeof iat !== "number" || typeof exp !== "number") {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (iat > now + clockSkewSeconds) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (exp < now - clockSkewSeconds) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
function verifyRobotRockPlatformUserContextJwt(args) {
|
|
55
|
+
const parts = args.token.split(".");
|
|
56
|
+
if (parts.length !== 3) {
|
|
57
|
+
return { ok: false };
|
|
58
|
+
}
|
|
59
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
60
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
61
|
+
return { ok: false };
|
|
62
|
+
}
|
|
63
|
+
let header;
|
|
64
|
+
let payload;
|
|
65
|
+
try {
|
|
66
|
+
header = decodeJwtPart(encodedHeader);
|
|
67
|
+
payload = decodeJwtPart(encodedPayload);
|
|
68
|
+
} catch {
|
|
69
|
+
return { ok: false };
|
|
70
|
+
}
|
|
71
|
+
if (header.alg !== "RS256") {
|
|
72
|
+
return { ok: false };
|
|
73
|
+
}
|
|
74
|
+
const verifier = createVerify("RSA-SHA256");
|
|
75
|
+
verifier.update(`${encodedHeader}.${encodedPayload}`);
|
|
76
|
+
verifier.end();
|
|
77
|
+
const signature = Buffer.from(encodedSignature, "base64url");
|
|
78
|
+
if (!verifier.verify(args.publicKeyPem, signature)) {
|
|
79
|
+
return { ok: false };
|
|
80
|
+
}
|
|
81
|
+
const clockSkewSeconds = args.clockSkewSeconds ?? 30;
|
|
82
|
+
if (!validateStandardClaims(payload, clockSkewSeconds)) {
|
|
83
|
+
return { ok: false };
|
|
84
|
+
}
|
|
85
|
+
const sub = readStringClaim(payload, "sub");
|
|
86
|
+
const email = readStringClaim(payload, "email");
|
|
87
|
+
const name = readStringClaim(payload, "name");
|
|
88
|
+
const tenantSlug = readStringClaim(payload, "tenantSlug");
|
|
89
|
+
const connectionId = readStringClaim(payload, "connectionId");
|
|
90
|
+
const role = parseTenantRoleClaim(readStringClaim(payload, "role"));
|
|
91
|
+
const workosUserId = readStringClaim(payload, "workosUserId");
|
|
92
|
+
const iat = payload.iat;
|
|
93
|
+
const exp = payload.exp;
|
|
94
|
+
if (!sub || !email || !name || !tenantSlug || !connectionId || !role || typeof iat !== "number" || typeof exp !== "number") {
|
|
95
|
+
return { ok: false };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
claims: {
|
|
100
|
+
iss: ROBOTROCK_USER_CONTEXT_ISSUER,
|
|
101
|
+
aud: ROBOTROCK_USER_CONTEXT_AUDIENCE,
|
|
102
|
+
sub,
|
|
103
|
+
email,
|
|
104
|
+
name,
|
|
105
|
+
tenantSlug,
|
|
106
|
+
connectionId,
|
|
107
|
+
role,
|
|
108
|
+
groups: readGroupsClaim(payload),
|
|
109
|
+
...workosUserId ? { workosUserId } : {},
|
|
110
|
+
iat,
|
|
111
|
+
exp
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ../core/src/eve/platform-user-context-public-key.ts
|
|
117
|
+
var ROBOTROCK_PLATFORM_USER_CONTEXT_PUBLIC_KEY_URL = "https://app.robotrock.io/.well-known/robotrock-user-context-public-key";
|
|
118
|
+
|
|
119
|
+
// src/eve/input-request.ts
|
|
120
|
+
var EVE_INPUT_SUBMIT_ACTION_ID = "submit";
|
|
121
|
+
var EVE_INPUT_OTHER_CHOICE_ID = "__other__";
|
|
122
|
+
var EVE_INPUT_OTHER_CHOICE_LABEL = "Type your own answer";
|
|
123
|
+
var CONFIRMATION_DEFAULT_OPTIONS = [
|
|
124
|
+
{ id: "approve", label: "Approve", style: "primary" },
|
|
125
|
+
{ id: "deny", label: "Deny", style: "danger" }
|
|
126
|
+
];
|
|
127
|
+
var SELECT_PER_OPTION_THRESHOLD = 6;
|
|
128
|
+
function resolveEveInputDisplay(request, toolName) {
|
|
129
|
+
if (request.display) {
|
|
130
|
+
return request.display;
|
|
131
|
+
}
|
|
132
|
+
if (toolName === "ask_question") {
|
|
133
|
+
return request.options && request.options.length > 0 ? "select" : "text";
|
|
134
|
+
}
|
|
135
|
+
if (request.options && request.options.length > 0) {
|
|
136
|
+
return "select";
|
|
137
|
+
}
|
|
138
|
+
return "confirmation";
|
|
139
|
+
}
|
|
140
|
+
function confirmationActions(request) {
|
|
141
|
+
const options = request.options && request.options.length > 0 ? request.options : CONFIRMATION_DEFAULT_OPTIONS;
|
|
142
|
+
return options.map((option) => ({
|
|
143
|
+
id: option.id,
|
|
144
|
+
title: option.label,
|
|
145
|
+
...option.description ? { description: option.description } : {}
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
function textSubmitAction(prompt) {
|
|
149
|
+
return {
|
|
150
|
+
id: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
151
|
+
title: "Submit",
|
|
152
|
+
schema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
required: ["answer"],
|
|
155
|
+
properties: {
|
|
156
|
+
answer: {
|
|
157
|
+
type: "string",
|
|
158
|
+
title: "Your answer"
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
ui: {
|
|
163
|
+
answer: {
|
|
164
|
+
"ui:title": prompt.trim() || "Your answer",
|
|
165
|
+
"ui:widget": "textarea"
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function selectPerOptionActions(options) {
|
|
171
|
+
return options.map((option) => ({
|
|
172
|
+
id: option.id,
|
|
173
|
+
title: option.label,
|
|
174
|
+
...option.description ? { description: option.description } : {}
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
function selectFormAction(request) {
|
|
178
|
+
const options = request.options ?? [];
|
|
179
|
+
const allowFreeform = request.allowFreeform === true;
|
|
180
|
+
const enumValues = options.map((option) => option.id);
|
|
181
|
+
const enumNames = options.map((option) => option.label);
|
|
182
|
+
if (allowFreeform) {
|
|
183
|
+
enumValues.push(EVE_INPUT_OTHER_CHOICE_ID);
|
|
184
|
+
enumNames.push(EVE_INPUT_OTHER_CHOICE_LABEL);
|
|
185
|
+
}
|
|
186
|
+
const properties = {
|
|
187
|
+
choice: {
|
|
188
|
+
type: "string",
|
|
189
|
+
title: "Choose one",
|
|
190
|
+
enum: enumValues
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
if (allowFreeform) {
|
|
194
|
+
properties.other = {
|
|
195
|
+
type: "string",
|
|
196
|
+
title: "Your answer"
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
const schema = {
|
|
200
|
+
type: "object",
|
|
201
|
+
required: ["choice"],
|
|
202
|
+
properties
|
|
203
|
+
};
|
|
204
|
+
if (allowFreeform) {
|
|
205
|
+
schema.allOf = [
|
|
206
|
+
{
|
|
207
|
+
if: {
|
|
208
|
+
properties: {
|
|
209
|
+
choice: { const: EVE_INPUT_OTHER_CHOICE_ID }
|
|
210
|
+
},
|
|
211
|
+
required: ["choice"]
|
|
212
|
+
},
|
|
213
|
+
then: {
|
|
214
|
+
required: ["other"],
|
|
215
|
+
properties: {
|
|
216
|
+
other: {
|
|
217
|
+
type: "string",
|
|
218
|
+
minLength: 1
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
const ui = {
|
|
226
|
+
choice: {
|
|
227
|
+
"ui:widget": "radio",
|
|
228
|
+
"ui:enumNames": enumNames
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
if (allowFreeform) {
|
|
232
|
+
ui.other = {
|
|
233
|
+
"ui:placeholder": "Enter your answer"
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
id: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
238
|
+
title: "Submit",
|
|
239
|
+
schema,
|
|
240
|
+
ui
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function eveInputRequestToActions(request, options) {
|
|
244
|
+
const display = resolveEveInputDisplay(request, options?.toolName);
|
|
245
|
+
switch (display) {
|
|
246
|
+
case "confirmation":
|
|
247
|
+
return confirmationActions(request);
|
|
248
|
+
case "text":
|
|
249
|
+
return [textSubmitAction(request.prompt)];
|
|
250
|
+
case "select": {
|
|
251
|
+
const selectOptions = request.options ?? [];
|
|
252
|
+
if (selectOptions.length === 0) {
|
|
253
|
+
return [textSubmitAction(request.prompt)];
|
|
254
|
+
}
|
|
255
|
+
if (selectOptions.length <= SELECT_PER_OPTION_THRESHOLD && request.allowFreeform !== true) {
|
|
256
|
+
return selectPerOptionActions(selectOptions);
|
|
257
|
+
}
|
|
258
|
+
return [selectFormAction(request)];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/eve/input-audit.ts
|
|
264
|
+
function matchOptionByText(text, options) {
|
|
265
|
+
const normalized = text.trim().toLowerCase();
|
|
266
|
+
if (normalized.length === 0) {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
const byId = options.find((option) => option.id.toLowerCase() === normalized);
|
|
270
|
+
if (byId) {
|
|
271
|
+
return byId;
|
|
272
|
+
}
|
|
273
|
+
const byLabel = options.find((option) => option.label.toLowerCase() === normalized);
|
|
274
|
+
if (byLabel) {
|
|
275
|
+
return byLabel;
|
|
276
|
+
}
|
|
277
|
+
const index = Number(normalized);
|
|
278
|
+
if (Number.isInteger(index) && index > 0 && index <= options.length) {
|
|
279
|
+
return options[index - 1];
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
283
|
+
function resolveEveFreeformTextToInputResponse(request, text) {
|
|
284
|
+
const trimmed = text.trim();
|
|
285
|
+
if (trimmed.length === 0) {
|
|
286
|
+
return void 0;
|
|
287
|
+
}
|
|
288
|
+
const options = request.options ?? [];
|
|
289
|
+
if (options.length > 0) {
|
|
290
|
+
const matched = matchOptionByText(trimmed, options);
|
|
291
|
+
if (matched) {
|
|
292
|
+
return { requestId: request.requestId, optionId: matched.id };
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (request.allowFreeform === true || options.length === 0) {
|
|
296
|
+
return { requestId: request.requestId, text: trimmed };
|
|
297
|
+
}
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
function isEveApprovalInputRequest(request) {
|
|
301
|
+
const options = request.options ?? [];
|
|
302
|
+
return options.length === 2 && options[0]?.id === "approve" && options[1]?.id === "deny";
|
|
303
|
+
}
|
|
304
|
+
function eveInputResponseToActionSubmission(request, response, options) {
|
|
305
|
+
const display = resolveEveInputDisplay(request, options?.toolName);
|
|
306
|
+
const actions = eveInputRequestToActions(request, options);
|
|
307
|
+
if (response.optionId) {
|
|
308
|
+
const action = actions.find((entry) => entry.id === response.optionId);
|
|
309
|
+
if (action) {
|
|
310
|
+
return {
|
|
311
|
+
actionId: response.optionId,
|
|
312
|
+
actionTitle: action.title,
|
|
313
|
+
formData: {}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const option = request.options?.find((entry) => entry.id === response.optionId);
|
|
317
|
+
if (option) {
|
|
318
|
+
return {
|
|
319
|
+
actionId: response.optionId,
|
|
320
|
+
actionTitle: option.label,
|
|
321
|
+
formData: {}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
actionId: response.optionId,
|
|
326
|
+
actionTitle: response.optionId,
|
|
327
|
+
formData: {}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const text = response.text?.trim();
|
|
331
|
+
if (text) {
|
|
332
|
+
if (display === "text") {
|
|
333
|
+
return {
|
|
334
|
+
actionId: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
335
|
+
actionTitle: text,
|
|
336
|
+
formData: { answer: text }
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (display === "select") {
|
|
340
|
+
const option = request.options?.find((entry) => entry.id === text);
|
|
341
|
+
if (option) {
|
|
342
|
+
return {
|
|
343
|
+
actionId: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
344
|
+
actionTitle: option.label,
|
|
345
|
+
formData: { choice: option.id }
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
actionId: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
350
|
+
actionTitle: text,
|
|
351
|
+
formData: { choice: text }
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
actionId: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
356
|
+
actionTitle: text,
|
|
357
|
+
formData: { answer: text }
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
actionId: EVE_INPUT_SUBMIT_ACTION_ID,
|
|
362
|
+
actionTitle: "Responded",
|
|
363
|
+
formData: {}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function buildEveInputAuditSubmissionData(context, formData) {
|
|
367
|
+
return {
|
|
368
|
+
toolCallId: context.toolCallId,
|
|
369
|
+
...context.toolName ? { toolName: context.toolName } : {},
|
|
370
|
+
...context.requestId ? { requestId: context.requestId } : {},
|
|
371
|
+
...context.display ? { display: context.display } : {},
|
|
372
|
+
...context.toolInput !== void 0 ? { toolInput: context.toolInput } : {},
|
|
373
|
+
...formData
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function parseEveAskQuestionToolOutput(output) {
|
|
377
|
+
if (output == null || typeof output !== "object") {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
const record = output;
|
|
381
|
+
const value = record.type === "json" && record.value != null && typeof record.value === "object" ? record.value : record;
|
|
382
|
+
const optionId = typeof value.optionId === "string" ? value.optionId : void 0;
|
|
383
|
+
const text = typeof value.text === "string" ? value.text : void 0;
|
|
384
|
+
if (!optionId && !text) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return { ...optionId ? { optionId } : {}, ...text ? { text } : {} };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/eve/index.ts
|
|
391
|
+
var ROBOTROCK_READY_HOOK_SLUG = "robotrock-ready";
|
|
392
|
+
|
|
393
|
+
// src/eve/agent/constants.ts
|
|
394
|
+
var ROBOTROCK_USER_CONTEXT_HEADER = "x-robotrock-user-token";
|
|
395
|
+
|
|
396
|
+
// src/eve/agent/attributes.ts
|
|
397
|
+
function readStringAttribute(attributes, key) {
|
|
398
|
+
const value = attributes?.[key];
|
|
399
|
+
if (typeof value === "string" && value.length > 0) {
|
|
400
|
+
return value;
|
|
401
|
+
}
|
|
402
|
+
if (Array.isArray(value) && typeof value[0] === "string" && value[0].length > 0) {
|
|
403
|
+
return value[0];
|
|
404
|
+
}
|
|
405
|
+
return void 0;
|
|
406
|
+
}
|
|
407
|
+
function readStringArrayAttribute(attributes, key) {
|
|
408
|
+
const value = attributes?.[key];
|
|
409
|
+
if (typeof value === "string" && value.length > 0) {
|
|
410
|
+
return [value];
|
|
411
|
+
}
|
|
412
|
+
if (Array.isArray(value)) {
|
|
413
|
+
return value.filter(
|
|
414
|
+
(entry) => typeof entry === "string" && entry.length > 0
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return [];
|
|
418
|
+
}
|
|
419
|
+
function parseTenantRole(value) {
|
|
420
|
+
if (value === "admin" || value === "member") {
|
|
421
|
+
return value;
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/eve/agent/tenant.ts
|
|
427
|
+
function tryResolveTenantCaller(ctx) {
|
|
428
|
+
const caller = ctx.session.auth.initiator ?? ctx.session.auth.current;
|
|
429
|
+
if (caller?.principalType !== "user") {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const email = readStringAttribute(caller.attributes, "email");
|
|
433
|
+
const name = readStringAttribute(caller.attributes, "name");
|
|
434
|
+
const tenantSlug = readStringAttribute(caller.attributes, "tenantSlug") ?? readStringAttribute(caller.attributes, "tenantId");
|
|
435
|
+
const role = parseTenantRole(readStringAttribute(caller.attributes, "role"));
|
|
436
|
+
if (!email || !tenantSlug || !caller.principalId || !role) {
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
const workosUserId = readStringAttribute(caller.attributes, "workosUserId");
|
|
440
|
+
const connectionId = readStringAttribute(caller.attributes, "connectionId");
|
|
441
|
+
const groups = readStringArrayAttribute(caller.attributes, "groups");
|
|
442
|
+
return {
|
|
443
|
+
userId: caller.principalId,
|
|
444
|
+
email,
|
|
445
|
+
name: name ?? email.split("@")[0] ?? email,
|
|
446
|
+
tenantSlug,
|
|
447
|
+
...connectionId ? { connectionId } : {},
|
|
448
|
+
role,
|
|
449
|
+
isAdmin: role === "admin",
|
|
450
|
+
groups,
|
|
451
|
+
...workosUserId ? { workosUserId } : {}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function requireTenantCaller(ctx) {
|
|
455
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
456
|
+
if (!caller) {
|
|
457
|
+
throw new Error("An authenticated RobotRock tenant user is required.");
|
|
458
|
+
}
|
|
459
|
+
return caller;
|
|
460
|
+
}
|
|
461
|
+
function isAdminCaller(ctx) {
|
|
462
|
+
return tryResolveTenantCaller(ctx)?.isAdmin === true;
|
|
463
|
+
}
|
|
464
|
+
function requireAdminCaller(ctx) {
|
|
465
|
+
const caller = requireTenantCaller(ctx);
|
|
466
|
+
if (!caller.isAdmin) {
|
|
467
|
+
throw new Error("Tenant admin access is required.");
|
|
468
|
+
}
|
|
469
|
+
return caller;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/eve/agent/auth.ts
|
|
473
|
+
import {
|
|
474
|
+
verifyJwtHmac,
|
|
475
|
+
extractBearerToken
|
|
476
|
+
} from "eve/channels/auth";
|
|
477
|
+
|
|
478
|
+
// src/eve/agent/platform-public-key.ts
|
|
479
|
+
var cachedPublicKeyPem;
|
|
480
|
+
async function resolvePlatformUserContextPublicKeyPem() {
|
|
481
|
+
const envOverride = process.env.ROBOTROCK_USER_CONTEXT_PUBLIC_KEY?.trim();
|
|
482
|
+
if (envOverride) {
|
|
483
|
+
return envOverride;
|
|
484
|
+
}
|
|
485
|
+
if (cachedPublicKeyPem !== void 0) {
|
|
486
|
+
return cachedPublicKeyPem;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
const response = await fetch(ROBOTROCK_PLATFORM_USER_CONTEXT_PUBLIC_KEY_URL, {
|
|
490
|
+
headers: { Accept: "text/plain" }
|
|
491
|
+
});
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
cachedPublicKeyPem = null;
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
const pem = (await response.text()).trim();
|
|
497
|
+
cachedPublicKeyPem = pem.length > 0 ? pem : null;
|
|
498
|
+
return cachedPublicKeyPem;
|
|
499
|
+
} catch {
|
|
500
|
+
cachedPublicKeyPem = null;
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/eve/agent/auth.ts
|
|
506
|
+
function mapVerifiedClaimsToSessionAuth(claims) {
|
|
507
|
+
return {
|
|
508
|
+
authenticator: "robotrock",
|
|
509
|
+
principalId: claims.sub,
|
|
510
|
+
principalType: "user",
|
|
511
|
+
subject: claims.sub,
|
|
512
|
+
issuer: ROBOTROCK_USER_CONTEXT_ISSUER,
|
|
513
|
+
attributes: {
|
|
514
|
+
email: claims.email,
|
|
515
|
+
name: claims.name,
|
|
516
|
+
tenantSlug: claims.tenantSlug,
|
|
517
|
+
tenantId: claims.tenantSlug,
|
|
518
|
+
role: claims.role,
|
|
519
|
+
groups: claims.groups,
|
|
520
|
+
...claims.connectionId ? { connectionId: claims.connectionId } : {},
|
|
521
|
+
...claims.workosUserId ? { workosUserId: claims.workosUserId } : {}
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function robotrockUserContextAuth(options) {
|
|
526
|
+
return async (request) => {
|
|
527
|
+
const token = extractBearerToken(
|
|
528
|
+
request.headers.get(ROBOTROCK_USER_CONTEXT_HEADER)
|
|
529
|
+
);
|
|
530
|
+
if (!token) {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
let publicKeyPem = null;
|
|
534
|
+
if (typeof options?.publicKeyPem === "function") {
|
|
535
|
+
publicKeyPem = await options.publicKeyPem();
|
|
536
|
+
} else if (options?.publicKeyPem) {
|
|
537
|
+
publicKeyPem = options.publicKeyPem;
|
|
538
|
+
} else {
|
|
539
|
+
publicKeyPem = await resolvePlatformUserContextPublicKeyPem();
|
|
540
|
+
}
|
|
541
|
+
if (publicKeyPem) {
|
|
542
|
+
const platformResult = verifyRobotRockPlatformUserContextJwt({
|
|
543
|
+
token,
|
|
544
|
+
publicKeyPem
|
|
545
|
+
});
|
|
546
|
+
if (platformResult.ok) {
|
|
547
|
+
return mapVerifiedClaimsToSessionAuth(platformResult.claims);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const secret = options?.hmacSecret?.trim() ?? process.env.ROBOTROCK_USER_CONTEXT_SECRET?.trim();
|
|
551
|
+
if (!secret) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
const result = await verifyJwtHmac(token, {
|
|
555
|
+
algorithm: "HS256",
|
|
556
|
+
issuer: ROBOTROCK_USER_CONTEXT_ISSUER,
|
|
557
|
+
audiences: [ROBOTROCK_USER_CONTEXT_AUDIENCE],
|
|
558
|
+
secret
|
|
559
|
+
});
|
|
560
|
+
if (!result.ok) {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
const email = readStringAttribute(result.sessionAuth.attributes, "email");
|
|
564
|
+
const name = readStringAttribute(result.sessionAuth.attributes, "name");
|
|
565
|
+
const tenantSlug = readStringAttribute(
|
|
566
|
+
result.sessionAuth.attributes,
|
|
567
|
+
"tenantSlug"
|
|
568
|
+
);
|
|
569
|
+
const connectionId = readStringAttribute(
|
|
570
|
+
result.sessionAuth.attributes,
|
|
571
|
+
"connectionId"
|
|
572
|
+
);
|
|
573
|
+
const role = parseTenantRole(
|
|
574
|
+
readStringAttribute(result.sessionAuth.attributes, "role")
|
|
575
|
+
);
|
|
576
|
+
const groups = readStringArrayAttribute(result.sessionAuth.attributes, "groups");
|
|
577
|
+
const subject = result.sessionAuth.subject;
|
|
578
|
+
if (!email || !tenantSlug || !subject || !role) {
|
|
579
|
+
return null;
|
|
580
|
+
}
|
|
581
|
+
const workosUserId = readStringAttribute(
|
|
582
|
+
result.sessionAuth.attributes,
|
|
583
|
+
"workosUserId"
|
|
584
|
+
);
|
|
585
|
+
return mapVerifiedClaimsToSessionAuth({
|
|
586
|
+
sub: subject,
|
|
587
|
+
email,
|
|
588
|
+
name: name ?? email.split("@")[0] ?? email,
|
|
589
|
+
tenantSlug,
|
|
590
|
+
connectionId,
|
|
591
|
+
role,
|
|
592
|
+
groups,
|
|
593
|
+
workosUserId
|
|
594
|
+
});
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function robotrockAgentServiceAuth() {
|
|
598
|
+
return async (request) => {
|
|
599
|
+
const expected = process.env.ROBOTROCK_AGENT_SERVICE_TOKEN?.trim();
|
|
600
|
+
if (!expected) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
const bearer = extractBearerToken(request.headers.get("authorization"));
|
|
604
|
+
if (!bearer || bearer !== expected) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
return {
|
|
608
|
+
authenticator: "robotrock-agent-service",
|
|
609
|
+
principalId: "robotrock:platform",
|
|
610
|
+
principalType: "runtime",
|
|
611
|
+
attributes: {}
|
|
612
|
+
};
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
function eveSelfServiceAuth() {
|
|
616
|
+
return async (request) => {
|
|
617
|
+
const expected = process.env.EVE_SELF_AUTH_TOKEN?.trim();
|
|
618
|
+
if (!expected) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
const bearer = extractBearerToken(request.headers.get("authorization"));
|
|
622
|
+
if (bearer !== expected) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
authenticator: "eve-self",
|
|
627
|
+
principalId: "eve:runtime",
|
|
628
|
+
principalType: "runtime",
|
|
629
|
+
attributes: {}
|
|
630
|
+
};
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/eve/agent/channel.ts
|
|
635
|
+
import { eveChannel } from "eve/channels/eve";
|
|
636
|
+
import { localDev, vercelOidc } from "eve/channels/auth";
|
|
637
|
+
function shouldEnableLocalDev(explicit) {
|
|
638
|
+
if (explicit !== void 0) {
|
|
639
|
+
return explicit;
|
|
640
|
+
}
|
|
641
|
+
return process.env.NODE_ENV !== "production";
|
|
642
|
+
}
|
|
643
|
+
function robotrockEveChannel(options) {
|
|
644
|
+
const auth = [];
|
|
645
|
+
const includeRobotrock = options?.robotrock !== false;
|
|
646
|
+
if (includeRobotrock) {
|
|
647
|
+
auth.push(robotrockUserContextAuth(options?.userContextAuth));
|
|
648
|
+
}
|
|
649
|
+
if (options?.auth?.length) {
|
|
650
|
+
auth.push(...options.auth);
|
|
651
|
+
}
|
|
652
|
+
if (includeRobotrock) {
|
|
653
|
+
auth.push(robotrockAgentServiceAuth());
|
|
654
|
+
auth.push(eveSelfServiceAuth());
|
|
655
|
+
}
|
|
656
|
+
if (options?.vercelOidc !== false) {
|
|
657
|
+
auth.push(vercelOidc(options?.vercelOidc ?? void 0));
|
|
658
|
+
}
|
|
659
|
+
if (shouldEnableLocalDev(options?.localDev)) {
|
|
660
|
+
auth.push(localDev());
|
|
661
|
+
}
|
|
662
|
+
return eveChannel({ auth });
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/schemas/index.ts
|
|
666
|
+
import { z as z2 } from "zod";
|
|
667
|
+
|
|
668
|
+
// ../core/src/utils/safe-url.ts
|
|
669
|
+
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
670
|
+
"localhost",
|
|
671
|
+
"metadata.google.internal",
|
|
672
|
+
"metadata.goog"
|
|
673
|
+
]);
|
|
674
|
+
function normalizeHostname(hostname) {
|
|
675
|
+
return hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
676
|
+
}
|
|
677
|
+
function isBlockedIpv4(host) {
|
|
678
|
+
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
679
|
+
if (!match) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
const octets = match.slice(1, 5).map((part) => Number(part));
|
|
683
|
+
if (octets.some((octet) => Number.isNaN(octet) || octet < 0 || octet > 255)) {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
const [a, b, _c, _d] = octets;
|
|
687
|
+
if (a === void 0 || b === void 0) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
if (a === 127 || a === 0) return true;
|
|
691
|
+
if (a === 10) return true;
|
|
692
|
+
if (a === 169 && b === 254) return true;
|
|
693
|
+
if (a === 192 && b === 168) return true;
|
|
694
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
695
|
+
if (a === 100 && b >= 64 && b <= 127) return true;
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
function ipv4FromNumericHost(host) {
|
|
699
|
+
if (/^\d+$/.test(host)) {
|
|
700
|
+
const num = Number(host);
|
|
701
|
+
if (!Number.isFinite(num) || num < 0 || num > 4294967295) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
const a = num >>> 24 & 255;
|
|
705
|
+
const b = num >>> 16 & 255;
|
|
706
|
+
const c = num >>> 8 & 255;
|
|
707
|
+
const d = num & 255;
|
|
708
|
+
return `${a}.${b}.${c}.${d}`;
|
|
709
|
+
}
|
|
710
|
+
const hexMatch = /^0x([0-9a-f]{1,8})$/i.exec(host);
|
|
711
|
+
if (hexMatch) {
|
|
712
|
+
const num = Number.parseInt(hexMatch[1], 16);
|
|
713
|
+
if (!Number.isFinite(num) || num < 0 || num > 4294967295) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
const a = num >>> 24 & 255;
|
|
717
|
+
const b = num >>> 16 & 255;
|
|
718
|
+
const c = num >>> 8 & 255;
|
|
719
|
+
const d = num & 255;
|
|
720
|
+
return `${a}.${b}.${c}.${d}`;
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function isBlockedIpv4Mapped(host) {
|
|
725
|
+
const mappedDotted = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/i.exec(host);
|
|
726
|
+
if (mappedDotted) {
|
|
727
|
+
return isBlockedIpv4(mappedDotted[1]);
|
|
728
|
+
}
|
|
729
|
+
const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i.exec(host);
|
|
730
|
+
if (mappedHex) {
|
|
731
|
+
const high = Number.parseInt(mappedHex[1], 16);
|
|
732
|
+
const low = Number.parseInt(mappedHex[2], 16);
|
|
733
|
+
if (Number.isFinite(high) && Number.isFinite(low)) {
|
|
734
|
+
const a = high >> 8 & 255;
|
|
735
|
+
const b = high & 255;
|
|
736
|
+
const c = low >> 8 & 255;
|
|
737
|
+
const d = low & 255;
|
|
738
|
+
return isBlockedIpv4(`${a}.${b}.${c}.${d}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return false;
|
|
742
|
+
}
|
|
743
|
+
function isBlockedIpv6(host) {
|
|
744
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") {
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
if (host.startsWith("fc") || host.startsWith("fd")) {
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
if (host.startsWith("fe80:")) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
if (isBlockedIpv4Mapped(host)) {
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
return false;
|
|
757
|
+
}
|
|
758
|
+
function isBlockedHostname(hostname) {
|
|
759
|
+
const host = normalizeHostname(hostname);
|
|
760
|
+
if (!host) {
|
|
761
|
+
return true;
|
|
762
|
+
}
|
|
763
|
+
if (BLOCKED_HOSTNAMES.has(host)) {
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
if (host.endsWith(".localhost") || host.endsWith(".local")) {
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
const numericIpv4 = ipv4FromNumericHost(host);
|
|
770
|
+
if (numericIpv4 && isBlockedIpv4(numericIpv4)) {
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
return isBlockedIpv4(host) || isBlockedIpv6(host);
|
|
774
|
+
}
|
|
775
|
+
function isLoopbackHandlerUrl(urlString) {
|
|
776
|
+
let url;
|
|
777
|
+
try {
|
|
778
|
+
url = new URL(urlString);
|
|
779
|
+
} catch {
|
|
780
|
+
return false;
|
|
781
|
+
}
|
|
782
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
if (url.username || url.password) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
const host = url.hostname.toLowerCase();
|
|
789
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || host.endsWith(".localhost");
|
|
790
|
+
}
|
|
791
|
+
function isAllowedHandlerUrl(urlString) {
|
|
792
|
+
return isPublicHttpUrl(urlString) || isLoopbackHandlerUrl(urlString);
|
|
793
|
+
}
|
|
794
|
+
function isPublicHttpUrl(urlString) {
|
|
795
|
+
let url;
|
|
796
|
+
try {
|
|
797
|
+
url = new URL(urlString);
|
|
798
|
+
} catch {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
if (url.username || url.password) {
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
return !isBlockedHostname(url.hostname);
|
|
808
|
+
}
|
|
809
|
+
var PUBLIC_HTTP_URL_ERROR = "URL must be a public http:// or https:// address";
|
|
810
|
+
var HANDLER_URL_ERROR = "Handler URL must be a public or loopback http:// or https:// address";
|
|
811
|
+
|
|
812
|
+
// ../core/src/schemas/task.ts
|
|
813
|
+
import { z } from "zod";
|
|
814
|
+
|
|
815
|
+
// ../core/src/schemas/context-urls.ts
|
|
816
|
+
function resolveLinkUrl(value) {
|
|
817
|
+
return value.startsWith("http://") || value.startsWith("https://") ? value : `https://${value}`;
|
|
818
|
+
}
|
|
819
|
+
function widgetType(ui, field) {
|
|
820
|
+
const fieldUi = ui?.[field];
|
|
821
|
+
if (typeof fieldUi !== "object" || fieldUi === null) {
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
const widget = fieldUi["ui:widget"];
|
|
825
|
+
return typeof widget === "string" ? widget : void 0;
|
|
826
|
+
}
|
|
827
|
+
function validateUrlValue(value, widget) {
|
|
828
|
+
if (widget === "image" || widget === "link") {
|
|
829
|
+
if (typeof value !== "string") {
|
|
830
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
831
|
+
}
|
|
832
|
+
const url = widget === "link" ? resolveLinkUrl(value) : value;
|
|
833
|
+
if (!isPublicHttpUrl(url)) {
|
|
834
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
if (widget === "attachments" && Array.isArray(value)) {
|
|
839
|
+
for (const item of value) {
|
|
840
|
+
if (typeof item !== "object" || item === null) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const url = item.url;
|
|
844
|
+
if (typeof url === "string" && !isPublicHttpUrl(resolveLinkUrl(url))) {
|
|
845
|
+
return PUBLIC_HTTP_URL_ERROR;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
function validateContextPublicUrls(context) {
|
|
852
|
+
if (!context?.data) {
|
|
853
|
+
return null;
|
|
854
|
+
}
|
|
855
|
+
for (const [field, value] of Object.entries(context.data)) {
|
|
856
|
+
const widget = widgetType(context.ui, field);
|
|
857
|
+
if (!widget) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
const error = validateUrlValue(value, widget);
|
|
861
|
+
if (error) {
|
|
862
|
+
return `context.data.${field}: ${error}`;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// ../core/src/schemas/task.ts
|
|
869
|
+
var safeUrlSchema = z.string().refine((url) => isPublicHttpUrl(url), {
|
|
870
|
+
message: PUBLIC_HTTP_URL_ERROR
|
|
871
|
+
});
|
|
872
|
+
var handlerUrlSchema = z.string().refine((url) => isAllowedHandlerUrl(url), {
|
|
873
|
+
message: HANDLER_URL_ERROR
|
|
874
|
+
});
|
|
875
|
+
var jsonSchema7Schema = z.custom(
|
|
876
|
+
(val) => typeof val === "object" && val !== null,
|
|
877
|
+
{ message: "Must be a valid JSON Schema object" }
|
|
878
|
+
);
|
|
879
|
+
var uiSchemaSchema = z.custom((val) => typeof val === "object" && val !== null, {
|
|
880
|
+
message: "Must be a valid UiSchema object"
|
|
881
|
+
});
|
|
882
|
+
var webhookHandlerSchema = z.object({
|
|
883
|
+
type: z.literal("webhook"),
|
|
884
|
+
url: handlerUrlSchema,
|
|
885
|
+
headers: z.record(z.string(), z.string())
|
|
886
|
+
});
|
|
887
|
+
var triggerHandlerSchema = webhookHandlerSchema.extend({
|
|
888
|
+
type: z.literal("trigger"),
|
|
889
|
+
tokenId: z.string().min(1)
|
|
890
|
+
});
|
|
891
|
+
var handlerSchema = z.discriminatedUnion("type", [webhookHandlerSchema, triggerHandlerSchema]);
|
|
892
|
+
var taskActionInputSchema = z.object({
|
|
893
|
+
id: z.string().min(1),
|
|
894
|
+
title: z.string().min(1),
|
|
895
|
+
description: z.string().optional(),
|
|
896
|
+
schema: jsonSchema7Schema.optional(),
|
|
897
|
+
ui: uiSchemaSchema.optional(),
|
|
898
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
899
|
+
});
|
|
900
|
+
var taskActionSchema = taskActionInputSchema.extend({
|
|
901
|
+
// Optional handlers for this action - if present, must have at least 1
|
|
902
|
+
handlers: z.array(handlerSchema).min(1).optional()
|
|
903
|
+
});
|
|
904
|
+
var uiFieldSchemaSchema = z.object({
|
|
905
|
+
"ui:widget": z.string().optional(),
|
|
906
|
+
"ui:title": z.string().optional(),
|
|
907
|
+
"ui:description": z.string().optional(),
|
|
908
|
+
"ui:options": z.record(z.string(), z.unknown()).optional(),
|
|
909
|
+
items: z.lazy(() => z.record(z.string(), uiFieldSchemaSchema)).optional()
|
|
910
|
+
}).passthrough();
|
|
911
|
+
var contextUiSchema = z.record(z.string(), uiFieldSchemaSchema).optional();
|
|
912
|
+
var contextDataSchema = z.object({
|
|
913
|
+
data: z.record(z.string(), z.unknown()),
|
|
914
|
+
ui: contextUiSchema
|
|
915
|
+
}).optional();
|
|
916
|
+
var TASK_CONTEXT_FORMAT_VERSION = 2;
|
|
917
|
+
var taskContextObjectBaseSchema = z.object({
|
|
918
|
+
/** Source application id; omitted tasks are grouped in the default inbox. */
|
|
919
|
+
app: z.string().min(1).optional(),
|
|
920
|
+
type: z.string().min(1),
|
|
921
|
+
name: z.string().min(1),
|
|
922
|
+
description: z.string().optional(),
|
|
923
|
+
validUntil: z.string().optional(),
|
|
924
|
+
context: contextDataSchema,
|
|
925
|
+
/** Task context wire format version. @default 2 */
|
|
926
|
+
contextVersion: z.literal(2).optional(),
|
|
927
|
+
/** @deprecated Use `contextVersion`. Accepted on ingest only. */
|
|
928
|
+
version: z.literal(2).optional(),
|
|
929
|
+
actions: z.array(taskActionSchema).min(1, "At least one action is required")
|
|
930
|
+
});
|
|
931
|
+
function normalizeTaskContextVersion(data) {
|
|
932
|
+
const { version: legacyVersion, contextVersion, ...rest } = data;
|
|
933
|
+
return {
|
|
934
|
+
...rest,
|
|
935
|
+
contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
var taskContextObjectSchema = taskContextObjectBaseSchema.transform(
|
|
939
|
+
normalizeTaskContextVersion
|
|
940
|
+
);
|
|
941
|
+
function refineContextPublicUrls(data, ctx) {
|
|
942
|
+
const error = validateContextPublicUrls(data.context);
|
|
943
|
+
if (error) {
|
|
944
|
+
ctx.addIssue({
|
|
945
|
+
code: z.ZodIssueCode.custom,
|
|
946
|
+
message: error,
|
|
947
|
+
path: ["context"]
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
var taskContextSchema = taskContextObjectSchema.superRefine(
|
|
952
|
+
refineContextPublicUrls
|
|
953
|
+
);
|
|
954
|
+
var assignToSchema = z.object({
|
|
955
|
+
users: z.array(z.string().email()).optional(),
|
|
956
|
+
groups: z.array(z.string().min(1)).optional()
|
|
957
|
+
}).refine(
|
|
958
|
+
(data) => {
|
|
959
|
+
const groups = data.groups ?? [];
|
|
960
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
961
|
+
return false;
|
|
962
|
+
}
|
|
963
|
+
return true;
|
|
964
|
+
},
|
|
965
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
966
|
+
);
|
|
967
|
+
var threadUpdateMessageSchema = z.string().min(1).max(500);
|
|
968
|
+
var threadUpdateStatuses = [
|
|
969
|
+
"info",
|
|
970
|
+
"queued",
|
|
971
|
+
"running",
|
|
972
|
+
"waiting",
|
|
973
|
+
"succeeded",
|
|
974
|
+
"failed",
|
|
975
|
+
"cancelled"
|
|
976
|
+
];
|
|
977
|
+
var threadUpdateStatusSchema = z.enum(threadUpdateStatuses);
|
|
978
|
+
var threadUpdateInputSchema = z.object({
|
|
979
|
+
message: threadUpdateMessageSchema,
|
|
980
|
+
status: threadUpdateStatusSchema.optional()
|
|
981
|
+
});
|
|
982
|
+
var taskPriorities = ["low", "normal", "high", "urgent"];
|
|
983
|
+
var taskPrioritySchema = z.enum(taskPriorities);
|
|
984
|
+
var agentTelemetrySchema = z.object({
|
|
985
|
+
/** Agent or app version (semver, git SHA, deploy tag — caller-defined). */
|
|
986
|
+
version: z.string().min(1)
|
|
987
|
+
});
|
|
988
|
+
var createTaskBodySchema = taskContextObjectBaseSchema.extend({
|
|
989
|
+
assignTo: assignToSchema.optional(),
|
|
990
|
+
/**
|
|
991
|
+
* Groups related tasks together. When omitted, the server generates one and
|
|
992
|
+
* returns it so the caller can reuse it on later tasks in the same thread.
|
|
993
|
+
*/
|
|
994
|
+
threadId: z.string().min(1).optional(),
|
|
995
|
+
/**
|
|
996
|
+
* Optional thread priority. When set, applies to the whole thread and
|
|
997
|
+
* overwrites any previous priority. Omit on later tasks to leave unchanged.
|
|
998
|
+
*/
|
|
999
|
+
priority: taskPrioritySchema.optional(),
|
|
1000
|
+
/**
|
|
1001
|
+
* Optional initial status update logged against the task's thread. Shows in
|
|
1002
|
+
* the inbox status bar and the thread update log.
|
|
1003
|
+
*/
|
|
1004
|
+
update: threadUpdateInputSchema.optional(),
|
|
1005
|
+
/** Agent release version — not shown in inbox UI. */
|
|
1006
|
+
agent: agentTelemetrySchema.optional()
|
|
1007
|
+
}).transform(normalizeTaskContextVersion).superRefine(refineContextPublicUrls);
|
|
1008
|
+
var agentChatSeedActionSchema = z.object({
|
|
1009
|
+
/** Stable action id echoed back on submit; auto-derived from title when omitted. */
|
|
1010
|
+
id: z.string().min(1).optional(),
|
|
1011
|
+
title: z.string().min(1),
|
|
1012
|
+
description: z.string().optional(),
|
|
1013
|
+
/** JSON Schema object for the form fields. */
|
|
1014
|
+
schema: z.record(z.string(), z.unknown()).optional(),
|
|
1015
|
+
/** RJSF ui schema overrides (ui:widget, ui:enumNames, etc.). */
|
|
1016
|
+
ui: z.record(z.string(), z.unknown()).optional(),
|
|
1017
|
+
/** Optional default field values. */
|
|
1018
|
+
data: z.record(z.string(), z.unknown()).optional()
|
|
1019
|
+
});
|
|
1020
|
+
var agentChatSeedMessageSchema = z.object({
|
|
1021
|
+
role: z.enum(["user", "assistant"]),
|
|
1022
|
+
text: z.string().min(1),
|
|
1023
|
+
/** Optional display-name override for the message sender. */
|
|
1024
|
+
senderName: z.string().min(1).optional(),
|
|
1025
|
+
/**
|
|
1026
|
+
* Optional structured input request rendered as a styled RobotRock action
|
|
1027
|
+
* widget below the message text. Use this instead of asking the user to reply
|
|
1028
|
+
* in plain text so the request is always a proper action form.
|
|
1029
|
+
*/
|
|
1030
|
+
requestActionInput: z.object({
|
|
1031
|
+
prompt: z.string().optional(),
|
|
1032
|
+
action: agentChatSeedActionSchema
|
|
1033
|
+
}).optional()
|
|
1034
|
+
});
|
|
1035
|
+
var eveAgentChatTransportSchema = z.object({
|
|
1036
|
+
kind: z.literal("eve"),
|
|
1037
|
+
chatId: z.string().min(1),
|
|
1038
|
+
baseUrl: z.string().url(),
|
|
1039
|
+
sessionId: z.string().optional(),
|
|
1040
|
+
continuationToken: z.string().optional(),
|
|
1041
|
+
streamIndex: z.number().int().nonnegative().optional(),
|
|
1042
|
+
isStreaming: z.boolean().optional()
|
|
1043
|
+
});
|
|
1044
|
+
var createAgentChatBodySchema = z.object({
|
|
1045
|
+
/** Agent id (eve agent name). Required unless `parentChatId` is set. */
|
|
1046
|
+
agentIdentifier: z.string().min(1).optional(),
|
|
1047
|
+
/** Inherit tenant/connection/agent from an existing chat (agent-spawned chats). */
|
|
1048
|
+
parentChatId: z.string().min(1).optional(),
|
|
1049
|
+
/** Convex tenantEveConnections id; resolved from the tenant when omitted. */
|
|
1050
|
+
eveConnectionId: z.string().min(1).optional(),
|
|
1051
|
+
/** Source application id; groups the chat under an inbox section. */
|
|
1052
|
+
app: z.string().min(1).optional(),
|
|
1053
|
+
assignTo: assignToSchema.optional(),
|
|
1054
|
+
title: z.string().min(1),
|
|
1055
|
+
messages: z.array(agentChatSeedMessageSchema).default([]),
|
|
1056
|
+
/** Provenance label stored on the session ("cron" | "agent" | ...). */
|
|
1057
|
+
source: z.string().min(1).optional()
|
|
1058
|
+
}).refine((data) => Boolean(data.agentIdentifier || data.parentChatId), {
|
|
1059
|
+
message: "Provide either agentIdentifier or parentChatId"
|
|
1060
|
+
});
|
|
1061
|
+
var agentChatAuditInputBodySchema = z.object({
|
|
1062
|
+
eveSessionId: z.string().min(1),
|
|
1063
|
+
userId: z.string().min(1),
|
|
1064
|
+
actionId: z.string().min(1),
|
|
1065
|
+
actionTitle: z.string().optional(),
|
|
1066
|
+
prompt: z.string().optional(),
|
|
1067
|
+
data: z.record(z.string(), z.unknown()).optional(),
|
|
1068
|
+
idempotencyKey: z.string().optional(),
|
|
1069
|
+
/** Eve input request id — used to merge staged HITL metadata. */
|
|
1070
|
+
requestId: z.string().optional(),
|
|
1071
|
+
/** Eve tool call id — fallback lookup for staged HITL metadata. */
|
|
1072
|
+
toolCallId: z.string().optional()
|
|
1073
|
+
});
|
|
1074
|
+
var eveHitlStagedOptionSchema = z.object({
|
|
1075
|
+
id: z.string(),
|
|
1076
|
+
label: z.string()
|
|
1077
|
+
});
|
|
1078
|
+
var agentChatStageHitlBodySchema = z.object({
|
|
1079
|
+
eveSessionId: z.string().min(1),
|
|
1080
|
+
requests: z.array(
|
|
1081
|
+
z.object({
|
|
1082
|
+
requestId: z.string().min(1),
|
|
1083
|
+
toolCallId: z.string().min(1),
|
|
1084
|
+
toolName: z.string().min(1),
|
|
1085
|
+
prompt: z.string().min(1),
|
|
1086
|
+
display: z.enum(["confirmation", "select", "text"]).optional(),
|
|
1087
|
+
allowFreeform: z.boolean().optional(),
|
|
1088
|
+
options: z.array(eveHitlStagedOptionSchema).optional(),
|
|
1089
|
+
toolInput: z.record(z.string(), z.unknown()).optional()
|
|
1090
|
+
})
|
|
1091
|
+
)
|
|
1092
|
+
});
|
|
1093
|
+
var agentChatLinkTaskBodySchema = z.object({
|
|
1094
|
+
eveSessionId: z.string().min(1),
|
|
1095
|
+
publicTaskId: z.string().min(1),
|
|
1096
|
+
toolCallId: z.string().min(1)
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
// src/schemas/index.ts
|
|
1100
|
+
var handlerUrlSchema2 = z2.string().refine((url) => isAllowedHandlerUrl(url), {
|
|
1101
|
+
message: HANDLER_URL_ERROR
|
|
1102
|
+
});
|
|
1103
|
+
var jsonSchema7Schema2 = z2.custom(
|
|
1104
|
+
(val) => typeof val === "object" && val !== null,
|
|
1105
|
+
{ message: "Must be a valid JSON Schema object" }
|
|
1106
|
+
);
|
|
1107
|
+
var uiSchemaSchema2 = z2.custom((val) => typeof val === "object" && val !== null, {
|
|
1108
|
+
message: "Must be a valid UiSchema object"
|
|
1109
|
+
});
|
|
1110
|
+
var webhookHandlerSchema2 = z2.object({
|
|
1111
|
+
type: z2.literal("webhook"),
|
|
1112
|
+
url: handlerUrlSchema2,
|
|
1113
|
+
headers: z2.record(z2.string(), z2.string())
|
|
1114
|
+
});
|
|
1115
|
+
var triggerHandlerSchema2 = webhookHandlerSchema2.extend({
|
|
1116
|
+
type: z2.literal("trigger"),
|
|
1117
|
+
tokenId: z2.string().min(1)
|
|
1118
|
+
});
|
|
1119
|
+
var handlerSchema2 = z2.discriminatedUnion("type", [webhookHandlerSchema2, triggerHandlerSchema2]);
|
|
1120
|
+
var taskActionInputSchema2 = z2.object({
|
|
1121
|
+
id: z2.string().min(1),
|
|
1122
|
+
title: z2.string().min(1),
|
|
1123
|
+
description: z2.string().optional(),
|
|
1124
|
+
schema: jsonSchema7Schema2.optional(),
|
|
1125
|
+
ui: uiSchemaSchema2.optional(),
|
|
1126
|
+
data: z2.record(z2.string(), z2.unknown()).optional()
|
|
1127
|
+
});
|
|
1128
|
+
var taskActionSchema2 = taskActionInputSchema2.extend({
|
|
1129
|
+
handlers: z2.array(handlerSchema2).min(1).optional()
|
|
1130
|
+
});
|
|
1131
|
+
var uiFieldSchemaSchema2 = z2.object({
|
|
1132
|
+
"ui:widget": z2.string().optional(),
|
|
1133
|
+
"ui:title": z2.string().optional(),
|
|
1134
|
+
"ui:description": z2.string().optional(),
|
|
1135
|
+
"ui:options": z2.record(z2.string(), z2.unknown()).optional(),
|
|
1136
|
+
items: z2.lazy(() => z2.record(z2.string(), uiFieldSchemaSchema2)).optional()
|
|
1137
|
+
}).passthrough();
|
|
1138
|
+
var contextUiSchema2 = z2.record(z2.string(), uiFieldSchemaSchema2).optional();
|
|
1139
|
+
var contextDataSchema2 = z2.object({
|
|
1140
|
+
data: z2.record(z2.string(), z2.unknown()),
|
|
1141
|
+
ui: contextUiSchema2
|
|
1142
|
+
}).optional();
|
|
1143
|
+
var TASK_CONTEXT_FORMAT_VERSION2 = 2;
|
|
1144
|
+
var taskContextObjectBaseSchema2 = z2.object({
|
|
1145
|
+
app: z2.string().min(1).optional(),
|
|
1146
|
+
type: z2.string().min(1),
|
|
1147
|
+
name: z2.string().min(1),
|
|
1148
|
+
description: z2.string().optional(),
|
|
1149
|
+
validUntil: z2.string().optional(),
|
|
1150
|
+
context: contextDataSchema2,
|
|
1151
|
+
contextVersion: z2.literal(2).optional(),
|
|
1152
|
+
/** @deprecated Use `contextVersion`. Accepted on ingest only. */
|
|
1153
|
+
version: z2.literal(2).optional(),
|
|
1154
|
+
actions: z2.array(taskActionSchema2).min(1, "At least one action is required")
|
|
1155
|
+
});
|
|
1156
|
+
function normalizeTaskContextVersion2(data) {
|
|
1157
|
+
const { version: legacyVersion, contextVersion, ...rest } = data;
|
|
1158
|
+
return {
|
|
1159
|
+
...rest,
|
|
1160
|
+
contextVersion: contextVersion ?? legacyVersion ?? TASK_CONTEXT_FORMAT_VERSION2
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
var taskContextObjectSchema2 = taskContextObjectBaseSchema2.transform(
|
|
1164
|
+
normalizeTaskContextVersion2
|
|
1165
|
+
);
|
|
1166
|
+
function refineContextPublicUrls2(data, ctx) {
|
|
1167
|
+
const error = validateContextPublicUrls(data.context);
|
|
1168
|
+
if (error) {
|
|
1169
|
+
ctx.addIssue({
|
|
1170
|
+
code: z2.ZodIssueCode.custom,
|
|
1171
|
+
message: error,
|
|
1172
|
+
path: ["context"]
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
var taskContextSchema2 = taskContextObjectSchema2.superRefine(
|
|
1177
|
+
refineContextPublicUrls2
|
|
1178
|
+
);
|
|
1179
|
+
var assignToSchema2 = z2.object({
|
|
1180
|
+
users: z2.array(z2.string().email()).optional(),
|
|
1181
|
+
groups: z2.array(z2.string().min(1)).optional()
|
|
1182
|
+
}).refine(
|
|
1183
|
+
(data) => {
|
|
1184
|
+
const groups = data.groups ?? [];
|
|
1185
|
+
if (groups.includes("all") && groups.length > 1) {
|
|
1186
|
+
return false;
|
|
1187
|
+
}
|
|
1188
|
+
return true;
|
|
1189
|
+
},
|
|
1190
|
+
{ message: 'Cannot combine "all" with other group slugs' }
|
|
1191
|
+
);
|
|
1192
|
+
var threadUpdateMessageSchema2 = z2.string().min(1).max(500);
|
|
1193
|
+
var threadUpdateStatuses2 = [
|
|
1194
|
+
"info",
|
|
1195
|
+
"queued",
|
|
1196
|
+
"running",
|
|
1197
|
+
"waiting",
|
|
1198
|
+
"succeeded",
|
|
1199
|
+
"failed",
|
|
1200
|
+
"cancelled"
|
|
1201
|
+
];
|
|
1202
|
+
var threadUpdateStatusSchema2 = z2.enum(threadUpdateStatuses2);
|
|
1203
|
+
var threadUpdateInputSchema2 = z2.object({
|
|
1204
|
+
message: threadUpdateMessageSchema2,
|
|
1205
|
+
status: threadUpdateStatusSchema2.optional()
|
|
1206
|
+
});
|
|
1207
|
+
var taskPriorities2 = ["low", "normal", "high", "urgent"];
|
|
1208
|
+
var taskPrioritySchema2 = z2.enum(taskPriorities2);
|
|
1209
|
+
var agentTelemetrySchema2 = z2.object({
|
|
1210
|
+
version: z2.string().min(1)
|
|
1211
|
+
});
|
|
1212
|
+
var createTaskBodySchema2 = taskContextObjectBaseSchema2.extend({
|
|
1213
|
+
assignTo: assignToSchema2.optional(),
|
|
1214
|
+
/**
|
|
1215
|
+
* Groups related tasks together. When omitted, the server generates one and
|
|
1216
|
+
* returns it so the caller can reuse it on later tasks in the same thread.
|
|
1217
|
+
*/
|
|
1218
|
+
threadId: z2.string().min(1).optional(),
|
|
1219
|
+
/**
|
|
1220
|
+
* Optional thread priority. When set, applies to the whole thread and
|
|
1221
|
+
* overwrites any previous priority. Omit on later tasks to leave unchanged.
|
|
1222
|
+
*/
|
|
1223
|
+
priority: taskPrioritySchema2.optional(),
|
|
1224
|
+
/**
|
|
1225
|
+
* Optional initial status update logged against the task's thread. Shows in
|
|
1226
|
+
* the inbox status bar and the thread update log.
|
|
1227
|
+
*/
|
|
1228
|
+
update: threadUpdateInputSchema2.optional(),
|
|
1229
|
+
agent: agentTelemetrySchema2.optional()
|
|
1230
|
+
}).transform(normalizeTaskContextVersion2).superRefine(refineContextPublicUrls2);
|
|
1231
|
+
var threadUpdateBodySchema = threadUpdateInputSchema2;
|
|
1232
|
+
|
|
1233
|
+
// src/approval-result.ts
|
|
1234
|
+
var TaskTimeoutError = class extends Error {
|
|
1235
|
+
constructor(message) {
|
|
1236
|
+
super(message);
|
|
1237
|
+
this.name = "TaskTimeoutError";
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
var TaskExpiredError = class extends Error {
|
|
1241
|
+
constructor(message) {
|
|
1242
|
+
super(message);
|
|
1243
|
+
this.name = "TaskExpiredError";
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
1246
|
+
function toDiscriminatedApprovalResult(actions, task) {
|
|
1247
|
+
void actions;
|
|
1248
|
+
if (!task.handled) {
|
|
1249
|
+
throw new Error("Task has no handled result");
|
|
1250
|
+
}
|
|
1251
|
+
return {
|
|
1252
|
+
actionId: task.handled.action.id,
|
|
1253
|
+
data: task.handled.action.data,
|
|
1254
|
+
handledBy: task.handled.handledBy,
|
|
1255
|
+
handledAt: new Date(task.handledAt ?? Date.now()),
|
|
1256
|
+
taskId: task.id
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// src/http.ts
|
|
1261
|
+
var RobotRockError = class extends Error {
|
|
1262
|
+
constructor(message, statusCode, response) {
|
|
1263
|
+
super(message);
|
|
1264
|
+
this.statusCode = statusCode;
|
|
1265
|
+
this.response = response;
|
|
1266
|
+
this.name = "RobotRockError";
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
async function parseResponseBody(response) {
|
|
1270
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
1271
|
+
const bodyText = await response.text();
|
|
1272
|
+
if (!bodyText) {
|
|
1273
|
+
return null;
|
|
1274
|
+
}
|
|
1275
|
+
if (contentType.toLowerCase().includes("application/json")) {
|
|
1276
|
+
try {
|
|
1277
|
+
return JSON.parse(bodyText);
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
try {
|
|
1282
|
+
return JSON.parse(bodyText);
|
|
1283
|
+
} catch {
|
|
1284
|
+
return bodyText;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
function getErrorMessage(data, fallback) {
|
|
1288
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
1289
|
+
const record = data;
|
|
1290
|
+
const maybeMessage = record.message;
|
|
1291
|
+
const maybeHint = record.hint;
|
|
1292
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim()) {
|
|
1293
|
+
if (typeof maybeHint === "string" && maybeHint.trim()) {
|
|
1294
|
+
return `${maybeMessage.trim()} ${maybeHint.trim()}`;
|
|
1295
|
+
}
|
|
1296
|
+
return maybeMessage;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (typeof data === "string" && data.trim()) {
|
|
1300
|
+
const compact = data.replace(/\s+/g, " ").trim();
|
|
1301
|
+
const snippet = compact.length > 180 ? `${compact.slice(0, 180)}...` : compact;
|
|
1302
|
+
return `${fallback}. Server returned non-JSON response: ${snippet}`;
|
|
1303
|
+
}
|
|
1304
|
+
return fallback;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// ../core/src/handler-retry.ts
|
|
1308
|
+
var HANDLER_RETRY_DELAYS_MS = [
|
|
1309
|
+
6e4,
|
|
1310
|
+
// 1m
|
|
1311
|
+
3e5,
|
|
1312
|
+
// 5m
|
|
1313
|
+
18e5,
|
|
1314
|
+
// 30m
|
|
1315
|
+
36e5,
|
|
1316
|
+
// 1h
|
|
1317
|
+
216e5,
|
|
1318
|
+
// 6h
|
|
1319
|
+
864e5,
|
|
1320
|
+
// 24h
|
|
1321
|
+
1728e5
|
|
1322
|
+
// 48h
|
|
1323
|
+
];
|
|
1324
|
+
var HANDLER_MAX_ATTEMPTS = HANDLER_RETRY_DELAYS_MS.length + 1;
|
|
1325
|
+
|
|
1326
|
+
// ../core/src/attribution/index.ts
|
|
1327
|
+
import { z as z3 } from "zod";
|
|
1328
|
+
|
|
1329
|
+
// ../core/src/app-url.ts
|
|
1330
|
+
var PRODUCTION_APP_URL = "https://app.robotrock.io";
|
|
1331
|
+
var PRODUCTION_API_URL = "https://api.robotrock.io/v1";
|
|
1332
|
+
var DEV_API_URL = "http://localhost:4001/v1";
|
|
1333
|
+
var APP_SIGNUP_URL = `${PRODUCTION_APP_URL}/sign-up`;
|
|
1334
|
+
function normalizeRobotRockApiBaseUrl(baseUrl) {
|
|
1335
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
1336
|
+
if (trimmed.endsWith("/v1")) {
|
|
1337
|
+
return trimmed;
|
|
1338
|
+
}
|
|
1339
|
+
return `${trimmed}/v1`;
|
|
1340
|
+
}
|
|
1341
|
+
function getRobotRockApiBaseUrl() {
|
|
1342
|
+
const explicit = process.env.ROBOTROCK_BASE_URL?.trim() || process.env.ROBOTROCK_API_URL?.trim();
|
|
1343
|
+
if (explicit) {
|
|
1344
|
+
return normalizeRobotRockApiBaseUrl(explicit);
|
|
1345
|
+
}
|
|
1346
|
+
if (process.env.NODE_ENV === "production") {
|
|
1347
|
+
return PRODUCTION_API_URL;
|
|
1348
|
+
}
|
|
1349
|
+
return DEV_API_URL;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// ../core/src/attribution/index.ts
|
|
1353
|
+
var ATTRIBUTION_COOKIE_MAX_AGE = 60 * 60 * 24 * 90;
|
|
1354
|
+
var attributionSchema = z3.object({
|
|
1355
|
+
utmSource: z3.string().optional(),
|
|
1356
|
+
utmMedium: z3.string().optional(),
|
|
1357
|
+
utmCampaign: z3.string().optional(),
|
|
1358
|
+
utmContent: z3.string().optional(),
|
|
1359
|
+
utmTerm: z3.string().optional(),
|
|
1360
|
+
gclid: z3.string().optional(),
|
|
1361
|
+
landingUrl: z3.string().optional(),
|
|
1362
|
+
referrer: z3.string().optional(),
|
|
1363
|
+
capturedAt: z3.number()
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
// src/auth-headers.ts
|
|
1367
|
+
function buildRobotRockAuthHeaders(auth) {
|
|
1368
|
+
if (auth.kind === "apiKey") {
|
|
1369
|
+
return {
|
|
1370
|
+
"X-Api-Key": auth.apiKey
|
|
1371
|
+
};
|
|
1372
|
+
}
|
|
1373
|
+
return {
|
|
1374
|
+
Authorization: `Bearer ${auth.token}`,
|
|
1375
|
+
"X-RobotRock-Tenant-Slug": auth.tenantSlug,
|
|
1376
|
+
"X-RobotRock-Connection-Id": auth.connectionId
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function resolveRobotRockAuthConfig(overrides) {
|
|
1380
|
+
if (overrides?.agentService) {
|
|
1381
|
+
return {
|
|
1382
|
+
kind: "agentService",
|
|
1383
|
+
...overrides.agentService
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
1387
|
+
if (!apiKey) {
|
|
1388
|
+
throw new Error(
|
|
1389
|
+
"RobotRock auth is required. Set ROBOTROCK_API_KEY, ROBOTROCK_AGENT_SERVICE_TOKEN with tenant context, or pass auth when creating the client."
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
return { kind: "apiKey", apiKey };
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// src/chats.ts
|
|
1396
|
+
function createChatsApi(config) {
|
|
1397
|
+
const headers = () => ({
|
|
1398
|
+
"Content-Type": "application/json",
|
|
1399
|
+
...buildRobotRockAuthHeaders(config.auth)
|
|
1400
|
+
});
|
|
1401
|
+
return {
|
|
1402
|
+
async create(input) {
|
|
1403
|
+
const bodyPayload = {
|
|
1404
|
+
...input,
|
|
1405
|
+
app: input.app ?? config.app,
|
|
1406
|
+
messages: input.messages ?? []
|
|
1407
|
+
};
|
|
1408
|
+
const validation = createAgentChatBodySchema.safeParse(bodyPayload);
|
|
1409
|
+
if (!validation.success) {
|
|
1410
|
+
throw new RobotRockError(
|
|
1411
|
+
`Invalid chat: ${validation.error.issues[0]?.message}`,
|
|
1412
|
+
400,
|
|
1413
|
+
validation.error.issues
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
const response = await fetch(`${config.baseUrl}/agent-chats`, {
|
|
1417
|
+
method: "POST",
|
|
1418
|
+
headers: headers(),
|
|
1419
|
+
body: JSON.stringify(validation.data)
|
|
1420
|
+
});
|
|
1421
|
+
const data = await parseResponseBody(response);
|
|
1422
|
+
if (!response.ok) {
|
|
1423
|
+
throw new RobotRockError(
|
|
1424
|
+
getErrorMessage(data, "Failed to create chat"),
|
|
1425
|
+
response.status,
|
|
1426
|
+
data
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
const result = data;
|
|
1430
|
+
return { tenantSlug: result.tenantSlug, chats: result.chats };
|
|
1431
|
+
},
|
|
1432
|
+
async close(chatId, options) {
|
|
1433
|
+
if (!chatId) {
|
|
1434
|
+
throw new RobotRockError("chatId is required to close a chat", 400);
|
|
1435
|
+
}
|
|
1436
|
+
const response = await fetch(
|
|
1437
|
+
`${config.baseUrl}/agent-chats/${encodeURIComponent(chatId)}/close`,
|
|
1438
|
+
{
|
|
1439
|
+
method: "POST",
|
|
1440
|
+
headers: headers(),
|
|
1441
|
+
body: JSON.stringify({ reason: options?.reason })
|
|
1442
|
+
}
|
|
1443
|
+
);
|
|
1444
|
+
if (!response.ok) {
|
|
1445
|
+
const data = await parseResponseBody(response);
|
|
1446
|
+
throw new RobotRockError(
|
|
1447
|
+
getErrorMessage(data, "Failed to close chat"),
|
|
1448
|
+
response.status,
|
|
1449
|
+
data
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
},
|
|
1453
|
+
async stageHitlRequests(input) {
|
|
1454
|
+
const validation = agentChatStageHitlBodySchema.safeParse(input);
|
|
1455
|
+
if (!validation.success) {
|
|
1456
|
+
throw new RobotRockError(
|
|
1457
|
+
`Invalid stage HITL input: ${validation.error.issues[0]?.message}`,
|
|
1458
|
+
400,
|
|
1459
|
+
validation.error.issues
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/stage-hitl`, {
|
|
1463
|
+
method: "POST",
|
|
1464
|
+
headers: headers(),
|
|
1465
|
+
body: JSON.stringify(validation.data)
|
|
1466
|
+
});
|
|
1467
|
+
if (!response.ok) {
|
|
1468
|
+
const data = await parseResponseBody(response);
|
|
1469
|
+
throw new RobotRockError(
|
|
1470
|
+
getErrorMessage(data, "Failed to stage chat HITL requests"),
|
|
1471
|
+
response.status,
|
|
1472
|
+
data
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
},
|
|
1476
|
+
async getStagedHitlRequests(eveSessionId) {
|
|
1477
|
+
const trimmed = eveSessionId.trim();
|
|
1478
|
+
if (!trimmed) {
|
|
1479
|
+
throw new RobotRockError("eveSessionId is required", 400);
|
|
1480
|
+
}
|
|
1481
|
+
const url = new URL(`${config.baseUrl}/agent-chats/staged-hitl`);
|
|
1482
|
+
url.searchParams.set("eveSessionId", trimmed);
|
|
1483
|
+
const response = await fetch(url.toString(), {
|
|
1484
|
+
method: "GET",
|
|
1485
|
+
headers: headers()
|
|
1486
|
+
});
|
|
1487
|
+
const data = await parseResponseBody(response);
|
|
1488
|
+
if (!response.ok) {
|
|
1489
|
+
throw new RobotRockError(
|
|
1490
|
+
getErrorMessage(data, "Failed to fetch staged chat HITL requests"),
|
|
1491
|
+
response.status,
|
|
1492
|
+
data
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
const requests = data.requests;
|
|
1496
|
+
return Array.isArray(requests) ? requests : [];
|
|
1497
|
+
},
|
|
1498
|
+
async logInputSubmission(input) {
|
|
1499
|
+
const validation = agentChatAuditInputBodySchema.safeParse(input);
|
|
1500
|
+
if (!validation.success) {
|
|
1501
|
+
throw new RobotRockError(
|
|
1502
|
+
`Invalid audit input: ${validation.error.issues[0]?.message}`,
|
|
1503
|
+
400,
|
|
1504
|
+
validation.error.issues
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/audit-input`, {
|
|
1508
|
+
method: "POST",
|
|
1509
|
+
headers: headers(),
|
|
1510
|
+
body: JSON.stringify(validation.data)
|
|
1511
|
+
});
|
|
1512
|
+
if (!response.ok) {
|
|
1513
|
+
const data = await parseResponseBody(response);
|
|
1514
|
+
throw new RobotRockError(
|
|
1515
|
+
getErrorMessage(data, "Failed to log chat input submission"),
|
|
1516
|
+
response.status,
|
|
1517
|
+
data
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
async linkTask(input) {
|
|
1522
|
+
const validation = agentChatLinkTaskBodySchema.safeParse(input);
|
|
1523
|
+
if (!validation.success) {
|
|
1524
|
+
throw new RobotRockError(
|
|
1525
|
+
`Invalid link task input: ${validation.error.issues[0]?.message}`,
|
|
1526
|
+
400,
|
|
1527
|
+
validation.error.issues
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
const response = await fetch(`${config.baseUrl}/agent-chats/link-task`, {
|
|
1531
|
+
method: "POST",
|
|
1532
|
+
headers: headers(),
|
|
1533
|
+
body: JSON.stringify(validation.data)
|
|
1534
|
+
});
|
|
1535
|
+
if (!response.ok) {
|
|
1536
|
+
const data = await parseResponseBody(response);
|
|
1537
|
+
throw new RobotRockError(
|
|
1538
|
+
getErrorMessage(data, "Failed to link inbox task to chat"),
|
|
1539
|
+
response.status,
|
|
1540
|
+
data
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// src/client.ts
|
|
1548
|
+
var DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
1549
|
+
var DEFAULT_TIMEOUT_MS = 24 * 60 * 60 * 1e3;
|
|
1550
|
+
function sleep(ms) {
|
|
1551
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1552
|
+
}
|
|
1553
|
+
function resolveAgentVersionFromEnv() {
|
|
1554
|
+
const fromEnv = process.env.AGENT_VERSION?.trim() || process.env.ROBOTROCK_AGENT_VERSION?.trim();
|
|
1555
|
+
return fromEnv || void 0;
|
|
1556
|
+
}
|
|
1557
|
+
function parseValidUntilMs(value) {
|
|
1558
|
+
if (value === void 0) {
|
|
1559
|
+
return void 0;
|
|
1560
|
+
}
|
|
1561
|
+
if (value instanceof Date) {
|
|
1562
|
+
const ms = value.getTime();
|
|
1563
|
+
return Number.isNaN(ms) ? void 0 : ms;
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof value === "number") {
|
|
1566
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1567
|
+
}
|
|
1568
|
+
const parsed = Date.parse(value);
|
|
1569
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1570
|
+
}
|
|
1571
|
+
function serializeValidUntil(value) {
|
|
1572
|
+
if (value instanceof Date) {
|
|
1573
|
+
const ms = value.getTime();
|
|
1574
|
+
if (Number.isNaN(ms)) {
|
|
1575
|
+
throw new RobotRockError("Invalid validUntil: Date is invalid", 400);
|
|
1576
|
+
}
|
|
1577
|
+
return value.toISOString();
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof value === "string" && !Number.isNaN(Date.parse(value))) {
|
|
1580
|
+
return new Date(value).toISOString();
|
|
1581
|
+
}
|
|
1582
|
+
throw new RobotRockError("Invalid validUntil: expected a Date or parseable date string", 400);
|
|
1583
|
+
}
|
|
1584
|
+
var RobotRock = class {
|
|
1585
|
+
auth;
|
|
1586
|
+
baseUrl;
|
|
1587
|
+
app;
|
|
1588
|
+
agentVersion;
|
|
1589
|
+
contextVersion;
|
|
1590
|
+
webhook;
|
|
1591
|
+
polling;
|
|
1592
|
+
/** Task CRUD: `create`, `get`, `cancel`, `sendUpdate`. */
|
|
1593
|
+
tasks;
|
|
1594
|
+
/** Chat CRUD: `create`, `close`. */
|
|
1595
|
+
chats;
|
|
1596
|
+
constructor(config) {
|
|
1597
|
+
if (config.webhook && config.polling) {
|
|
1598
|
+
throw new Error(
|
|
1599
|
+
"RobotRock client cannot configure both webhook and polling. Use webhook for callbacks or polling to block until handled."
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
const apiKey = config.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
1603
|
+
const agentService = config.agentService;
|
|
1604
|
+
if (apiKey && agentService) {
|
|
1605
|
+
throw new Error(
|
|
1606
|
+
"RobotRock client cannot configure both apiKey and agentService."
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
this.auth = resolveRobotRockAuthConfig({
|
|
1610
|
+
...apiKey ? { apiKey } : {},
|
|
1611
|
+
...agentService ? { agentService } : {}
|
|
1612
|
+
});
|
|
1613
|
+
this.baseUrl = config.baseUrl ?? getRobotRockApiBaseUrl();
|
|
1614
|
+
this.app = config.app;
|
|
1615
|
+
this.agentVersion = config.version ?? resolveAgentVersionFromEnv();
|
|
1616
|
+
this.contextVersion = config.advanced?.contextVersion ?? TASK_CONTEXT_FORMAT_VERSION2;
|
|
1617
|
+
this.webhook = config.webhook;
|
|
1618
|
+
this.polling = config.polling ?? {};
|
|
1619
|
+
this.tasks = {
|
|
1620
|
+
create: (task) => this.createTaskRequest(task),
|
|
1621
|
+
get: (taskId) => this.getTaskById(taskId),
|
|
1622
|
+
cancel: (taskId) => this.cancelTaskRequest(taskId),
|
|
1623
|
+
sendUpdate: (input) => this.sendThreadUpdate(input)
|
|
1624
|
+
};
|
|
1625
|
+
this.chats = createChatsApi({
|
|
1626
|
+
baseUrl: this.baseUrl,
|
|
1627
|
+
auth: this.auth,
|
|
1628
|
+
app: this.app
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
authHeaders(extra) {
|
|
1632
|
+
return {
|
|
1633
|
+
"Content-Type": "application/json",
|
|
1634
|
+
...buildRobotRockAuthHeaders(this.auth),
|
|
1635
|
+
...extra
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1638
|
+
async createTaskRequest(task) {
|
|
1639
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
1640
|
+
webhook: this.webhook,
|
|
1641
|
+
app: this.app,
|
|
1642
|
+
contextVersion: this.contextVersion,
|
|
1643
|
+
agentVersion: this.agentVersion
|
|
1644
|
+
});
|
|
1645
|
+
const agentVersion = task.version ?? this.agentVersion;
|
|
1646
|
+
const bodyPayload = {
|
|
1647
|
+
...normalizedTask,
|
|
1648
|
+
...task.assignTo !== void 0 ? { assignTo: task.assignTo } : {},
|
|
1649
|
+
...task.threadId !== void 0 ? { threadId: task.threadId } : {},
|
|
1650
|
+
...task.priority !== void 0 ? { priority: task.priority } : {},
|
|
1651
|
+
...task.update !== void 0 ? { update: task.update } : {},
|
|
1652
|
+
...agentVersion !== void 0 ? { agent: { version: agentVersion } } : {}
|
|
1653
|
+
};
|
|
1654
|
+
const validation = createTaskBodySchema2.safeParse(bodyPayload);
|
|
1655
|
+
if (!validation.success) {
|
|
1656
|
+
throw new RobotRockError(
|
|
1657
|
+
`Invalid task: ${validation.error.issues[0]?.message}`,
|
|
1658
|
+
400,
|
|
1659
|
+
validation.error.issues
|
|
1660
|
+
);
|
|
1661
|
+
}
|
|
1662
|
+
const headers = this.authHeaders(
|
|
1663
|
+
task.idempotencyKey ? { "Idempotency-Key": task.idempotencyKey } : void 0
|
|
1664
|
+
);
|
|
1665
|
+
const response = await fetch(`${this.baseUrl}/`, {
|
|
1666
|
+
method: "POST",
|
|
1667
|
+
headers,
|
|
1668
|
+
body: JSON.stringify(validation.data)
|
|
1669
|
+
});
|
|
1670
|
+
const data = await parseResponseBody(response);
|
|
1671
|
+
if (!response.ok) {
|
|
1672
|
+
throw new RobotRockError(
|
|
1673
|
+
getErrorMessage(data, "Failed to create task"),
|
|
1674
|
+
response.status,
|
|
1675
|
+
data
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
return data.task;
|
|
1679
|
+
}
|
|
1680
|
+
async sendToHuman(task) {
|
|
1681
|
+
const normalizedTask = normalizeSendToHumanInput(task, {
|
|
1682
|
+
webhook: this.webhook,
|
|
1683
|
+
app: this.app,
|
|
1684
|
+
contextVersion: this.contextVersion,
|
|
1685
|
+
agentVersion: this.agentVersion
|
|
1686
|
+
});
|
|
1687
|
+
const createdTaskTask = await this.createTaskRequest(task);
|
|
1688
|
+
const hasHandlers = normalizedTask.actions.some(
|
|
1689
|
+
(action) => Array.isArray(action.handlers) && action.handlers.length > 0
|
|
1690
|
+
);
|
|
1691
|
+
if (hasHandlers) {
|
|
1692
|
+
return {
|
|
1693
|
+
mode: "created",
|
|
1694
|
+
task: createdTaskTask
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
const timeoutMs = this.polling.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1698
|
+
const pollIntervalMs = this.polling.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1699
|
+
const pollingDeadline = Date.now() + timeoutMs;
|
|
1700
|
+
const validUntilMs = parseValidUntilMs(createdTaskTask.validUntil);
|
|
1701
|
+
const deadline = validUntilMs !== void 0 ? Math.min(pollingDeadline, validUntilMs) : pollingDeadline;
|
|
1702
|
+
const taskId = createdTaskTask.taskId;
|
|
1703
|
+
while (Date.now() < deadline) {
|
|
1704
|
+
const existing = await this.getTaskById(taskId);
|
|
1705
|
+
if (existing?.status === "handled" && existing.handled) {
|
|
1706
|
+
return {
|
|
1707
|
+
mode: "handled",
|
|
1708
|
+
task: createdTaskTask,
|
|
1709
|
+
...toDiscriminatedApprovalResult(
|
|
1710
|
+
normalizedTask.actions,
|
|
1711
|
+
existing
|
|
1712
|
+
)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
if (existing?.status === "expired" || existing && Date.now() >= existing.validUntil) {
|
|
1716
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
1717
|
+
}
|
|
1718
|
+
const remainingMs = deadline - Date.now();
|
|
1719
|
+
await sleep(Math.min(pollIntervalMs, Math.max(0, remainingMs)));
|
|
1720
|
+
}
|
|
1721
|
+
if (validUntilMs !== void 0 && Date.now() >= validUntilMs) {
|
|
1722
|
+
throw new TaskExpiredError("Task reached validUntil before a human completed it");
|
|
1723
|
+
}
|
|
1724
|
+
throw new TaskTimeoutError(`No human response within ${timeoutMs}ms`);
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Create a task via POST /v1 without waiting for a human response.
|
|
1728
|
+
* @deprecated Use `client.tasks.create()` instead.
|
|
1729
|
+
*/
|
|
1730
|
+
async createTask(task) {
|
|
1731
|
+
return this.tasks.create(task);
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Get a task by public task id (returned as `task.taskId` from {@link sendToHuman}).
|
|
1735
|
+
* @deprecated Use `client.tasks.get()` instead.
|
|
1736
|
+
*/
|
|
1737
|
+
async getTask(taskId) {
|
|
1738
|
+
return this.tasks.get(taskId);
|
|
1739
|
+
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Log a status update against a thread.
|
|
1742
|
+
* @deprecated Use `client.tasks.sendUpdate()` instead.
|
|
1743
|
+
*/
|
|
1744
|
+
async sendUpdate(input) {
|
|
1745
|
+
return this.tasks.sendUpdate(input);
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Cancel a task by public task id.
|
|
1749
|
+
* @deprecated Use `client.tasks.cancel()` instead.
|
|
1750
|
+
*/
|
|
1751
|
+
async cancelTask(taskId) {
|
|
1752
|
+
return this.tasks.cancel(taskId);
|
|
1753
|
+
}
|
|
1754
|
+
async getTaskById(taskId) {
|
|
1755
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}`, {
|
|
1756
|
+
method: "GET",
|
|
1757
|
+
headers: this.authHeaders()
|
|
1758
|
+
});
|
|
1759
|
+
if (response.status === 404) {
|
|
1760
|
+
return null;
|
|
1761
|
+
}
|
|
1762
|
+
const data = await parseResponseBody(response);
|
|
1763
|
+
if (!response.ok) {
|
|
1764
|
+
throw new RobotRockError(
|
|
1765
|
+
getErrorMessage(data, "Failed to get task"),
|
|
1766
|
+
response.status,
|
|
1767
|
+
data
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
return data;
|
|
1771
|
+
}
|
|
1772
|
+
async sendThreadUpdate({
|
|
1773
|
+
threadId,
|
|
1774
|
+
message,
|
|
1775
|
+
status
|
|
1776
|
+
}) {
|
|
1777
|
+
if (!threadId) {
|
|
1778
|
+
throw new RobotRockError("threadId is required to send an update", 400);
|
|
1779
|
+
}
|
|
1780
|
+
const validation = threadUpdateBodySchema.safeParse({ message, status });
|
|
1781
|
+
if (!validation.success) {
|
|
1782
|
+
throw new RobotRockError(
|
|
1783
|
+
`Invalid update: ${validation.error.issues[0]?.message}`,
|
|
1784
|
+
400,
|
|
1785
|
+
validation.error.issues
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
const response = await fetch(
|
|
1789
|
+
`${this.baseUrl}/threads/${encodeURIComponent(threadId)}/updates`,
|
|
1790
|
+
{
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
headers: this.authHeaders(),
|
|
1793
|
+
body: JSON.stringify(validation.data)
|
|
1794
|
+
}
|
|
1795
|
+
);
|
|
1796
|
+
const data = await parseResponseBody(response);
|
|
1797
|
+
if (!response.ok) {
|
|
1798
|
+
throw new RobotRockError(
|
|
1799
|
+
getErrorMessage(data, "Failed to send update"),
|
|
1800
|
+
response.status,
|
|
1801
|
+
data
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
return data.update;
|
|
1805
|
+
}
|
|
1806
|
+
async cancelTaskRequest(taskId) {
|
|
1807
|
+
const response = await fetch(`${this.baseUrl}/tasks/${taskId}/cancel`, {
|
|
1808
|
+
method: "POST",
|
|
1809
|
+
headers: this.authHeaders()
|
|
1810
|
+
});
|
|
1811
|
+
if (!response.ok) {
|
|
1812
|
+
const data = await parseResponseBody(response);
|
|
1813
|
+
throw new RobotRockError(
|
|
1814
|
+
getErrorMessage(data, "Failed to cancel task"),
|
|
1815
|
+
response.status,
|
|
1816
|
+
data
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1821
|
+
function createClient(config) {
|
|
1822
|
+
return new RobotRock(config);
|
|
1823
|
+
}
|
|
1824
|
+
function attachWebhookToActions(actions, webhook) {
|
|
1825
|
+
return actions.map((action) => ({
|
|
1826
|
+
...action,
|
|
1827
|
+
handlers: webhookToHandlers(webhook)
|
|
1828
|
+
}));
|
|
1829
|
+
}
|
|
1830
|
+
function webhookToHandlers(webhook) {
|
|
1831
|
+
return [
|
|
1832
|
+
{
|
|
1833
|
+
type: "webhook",
|
|
1834
|
+
url: webhook.url,
|
|
1835
|
+
headers: webhook.headers ?? {}
|
|
1836
|
+
}
|
|
1837
|
+
];
|
|
1838
|
+
}
|
|
1839
|
+
function normalizeSendToHumanInput(task, clientDefaults) {
|
|
1840
|
+
const {
|
|
1841
|
+
actions,
|
|
1842
|
+
idempotencyKey: _idempotencyKey,
|
|
1843
|
+
assignTo: _assignTo,
|
|
1844
|
+
threadId: _threadId,
|
|
1845
|
+
priority: _priority,
|
|
1846
|
+
update: _update,
|
|
1847
|
+
version: _version,
|
|
1848
|
+
validUntil,
|
|
1849
|
+
app: taskApp,
|
|
1850
|
+
...rest
|
|
1851
|
+
} = task;
|
|
1852
|
+
const webhook = clientDefaults.webhook;
|
|
1853
|
+
const normalizedActions = webhook ? attachWebhookToActions(actions, webhook) : actions;
|
|
1854
|
+
const app = taskApp ?? clientDefaults.app;
|
|
1855
|
+
return {
|
|
1856
|
+
...rest,
|
|
1857
|
+
contextVersion: clientDefaults.contextVersion,
|
|
1858
|
+
...app ? { app } : {},
|
|
1859
|
+
...validUntil !== void 0 ? { validUntil: serializeValidUntil(validUntil) } : {},
|
|
1860
|
+
actions: normalizedActions
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// src/env.ts
|
|
1865
|
+
function resolveRobotRockConfig(overrides) {
|
|
1866
|
+
const agentService = overrides?.agentService;
|
|
1867
|
+
const apiKey = overrides?.apiKey ?? process.env.ROBOTROCK_API_KEY;
|
|
1868
|
+
if (agentService && apiKey) {
|
|
1869
|
+
throw new Error(
|
|
1870
|
+
"RobotRock client cannot configure both apiKey and agentService."
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
const baseUrl = overrides?.baseUrl ?? getRobotRockApiBaseUrl();
|
|
1874
|
+
const app = overrides?.app ?? process.env.ROBOTROCK_APP;
|
|
1875
|
+
if (agentService) {
|
|
1876
|
+
return app ? { agentService, baseUrl, app } : { agentService, baseUrl };
|
|
1877
|
+
}
|
|
1878
|
+
if (!apiKey) {
|
|
1879
|
+
throw new Error(
|
|
1880
|
+
"RobotRock API key is required. Set ROBOTROCK_API_KEY or pass agentService when creating the client."
|
|
1881
|
+
);
|
|
1882
|
+
}
|
|
1883
|
+
return app ? { apiKey, baseUrl, app } : { apiKey, baseUrl };
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// src/eve/agent/client-from-session.ts
|
|
1887
|
+
var warnedMissingAuth = false;
|
|
1888
|
+
function tryCreateBoundRobotRockClient(ctx) {
|
|
1889
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
1890
|
+
const serviceToken = process.env.ROBOTROCK_AGENT_SERVICE_TOKEN?.trim();
|
|
1891
|
+
if (caller?.connectionId && serviceToken) {
|
|
1892
|
+
return createClient({
|
|
1893
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim(),
|
|
1894
|
+
agentService: {
|
|
1895
|
+
token: serviceToken,
|
|
1896
|
+
tenantSlug: caller.tenantSlug,
|
|
1897
|
+
connectionId: caller.connectionId
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
const apiKey = process.env.ROBOTROCK_API_KEY?.trim();
|
|
1902
|
+
if (apiKey) {
|
|
1903
|
+
return createClient(
|
|
1904
|
+
resolveRobotRockConfig({
|
|
1905
|
+
apiKey,
|
|
1906
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim()
|
|
1907
|
+
})
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
if (!warnedMissingAuth) {
|
|
1911
|
+
warnedMissingAuth = true;
|
|
1912
|
+
console.warn(
|
|
1913
|
+
"robotrock: set ROBOTROCK_AGENT_SERVICE_TOKEN for hosted multi-tenant auth, or ROBOTROCK_API_KEY for single-tenant deployments."
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1918
|
+
function resetBoundClientAuthWarning() {
|
|
1919
|
+
warnedMissingAuth = false;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
// src/eve/agent/chat-audit-api.ts
|
|
1923
|
+
var warnedMissingAuth2 = false;
|
|
1924
|
+
var warnedConnectionRefused = false;
|
|
1925
|
+
function isConnectionRefused(error) {
|
|
1926
|
+
const inspect = (value) => {
|
|
1927
|
+
if (!value || typeof value !== "object") {
|
|
1928
|
+
return false;
|
|
1929
|
+
}
|
|
1930
|
+
if ("code" in value && value.code === "ECONNREFUSED") {
|
|
1931
|
+
return true;
|
|
1932
|
+
}
|
|
1933
|
+
if ("cause" in value) {
|
|
1934
|
+
return inspect(value.cause);
|
|
1935
|
+
}
|
|
1936
|
+
if ("errors" in value && Array.isArray(value.errors)) {
|
|
1937
|
+
return value.errors.some((entry) => inspect(entry));
|
|
1938
|
+
}
|
|
1939
|
+
return false;
|
|
1940
|
+
};
|
|
1941
|
+
return inspect(error);
|
|
1942
|
+
}
|
|
1943
|
+
function logAuditFailure(label, error) {
|
|
1944
|
+
if (isConnectionRefused(error)) {
|
|
1945
|
+
if (!warnedConnectionRefused) {
|
|
1946
|
+
warnedConnectionRefused = true;
|
|
1947
|
+
console.warn(
|
|
1948
|
+
"robotrock-chat-audit: RobotRock API is not reachable (connection refused). Audit and task-link calls are skipped until the API is up."
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
const hint = error instanceof RobotRockError && error.statusCode === 405 ? " \u2014 set ROBOTROCK_BASE_URL=http://localhost:4001/v1 and run the local API" : "";
|
|
1954
|
+
console.warn(`robotrock-chat-audit: failed to ${label}${hint}`, error);
|
|
1955
|
+
}
|
|
1956
|
+
async function postRobotRockChatInputAudit(ctx, payload) {
|
|
1957
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
1958
|
+
if (!client) {
|
|
1959
|
+
if (!warnedMissingAuth2) {
|
|
1960
|
+
warnedMissingAuth2 = true;
|
|
1961
|
+
console.warn(
|
|
1962
|
+
"robotrock-chat-audit: RobotRock auth is unset; skipping HITL audit logging."
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
try {
|
|
1968
|
+
await client.chats.logInputSubmission(payload);
|
|
1969
|
+
} catch (error) {
|
|
1970
|
+
logAuditFailure("log input submission", error);
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
async function postRobotRockStageHitl(ctx, input) {
|
|
1974
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
1975
|
+
if (!client) {
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
if (input.requests.length === 0) {
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
try {
|
|
1982
|
+
await client.chats.stageHitlRequests(input);
|
|
1983
|
+
} catch (error) {
|
|
1984
|
+
logAuditFailure("stage HITL requests", error);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
async function fetchRobotRockStagedHitl(ctx, eveSessionId) {
|
|
1988
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
1989
|
+
if (!client) {
|
|
1990
|
+
return [];
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
return await client.chats.getStagedHitlRequests(eveSessionId);
|
|
1994
|
+
} catch (error) {
|
|
1995
|
+
logAuditFailure("fetch staged HITL requests", error);
|
|
1996
|
+
return [];
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
async function postRobotRockLinkChatTask(ctx, input) {
|
|
2000
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
2001
|
+
if (!client) {
|
|
2002
|
+
if (!warnedMissingAuth2) {
|
|
2003
|
+
warnedMissingAuth2 = true;
|
|
2004
|
+
console.warn(
|
|
2005
|
+
"robotrock-task-link: RobotRock auth is unset; skipping chat task link."
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
try {
|
|
2011
|
+
await client.chats.linkTask(input);
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
logAuditFailure("link task to chat", error);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
function resetChatAuditWarnings() {
|
|
2017
|
+
warnedMissingAuth2 = false;
|
|
2018
|
+
warnedConnectionRefused = false;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// src/eve/agent/task-delegation.ts
|
|
2022
|
+
function resolveTaskApp(app) {
|
|
2023
|
+
return app?.trim() || process.env.ROBOTROCK_APP?.trim() || "robotrock-agent";
|
|
2024
|
+
}
|
|
2025
|
+
function resolveWebhookBaseUrl(baseUrl) {
|
|
2026
|
+
return baseUrl?.trim() || process.env.ROBOTROCK_BASE_URL?.trim() || "http://localhost:4001/v1";
|
|
2027
|
+
}
|
|
2028
|
+
function resolveTaskHandledWebhookUrl(baseUrl) {
|
|
2029
|
+
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
2030
|
+
const withV1 = trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
2031
|
+
return `${withV1}/agent-chats/task-handled`;
|
|
2032
|
+
}
|
|
2033
|
+
function requireBoundClient(ctx) {
|
|
2034
|
+
const client = tryCreateBoundRobotRockClient(ctx);
|
|
2035
|
+
if (!client) {
|
|
2036
|
+
throw new Error(
|
|
2037
|
+
"RobotRock auth is unset. Configure ROBOTROCK_AGENT_SERVICE_TOKEN for hosted agents or ROBOTROCK_API_KEY for self-hosted deployments."
|
|
2038
|
+
);
|
|
2039
|
+
}
|
|
2040
|
+
return client;
|
|
2041
|
+
}
|
|
2042
|
+
async function createRobotRockTask(input, ctx) {
|
|
2043
|
+
const client = requireBoundClient(ctx);
|
|
2044
|
+
const { app, ...taskInput } = input;
|
|
2045
|
+
const resolvedApp = resolveTaskApp(app);
|
|
2046
|
+
const webhookUrl = resolveTaskHandledWebhookUrl(
|
|
2047
|
+
resolveWebhookBaseUrl(process.env.ROBOTROCK_BASE_URL)
|
|
2048
|
+
);
|
|
2049
|
+
try {
|
|
2050
|
+
const task = await client.tasks.create({
|
|
2051
|
+
...taskInput,
|
|
2052
|
+
app: resolvedApp,
|
|
2053
|
+
actions: attachWebhookToActions(taskInput.actions, {
|
|
2054
|
+
url: webhookUrl,
|
|
2055
|
+
headers: {}
|
|
2056
|
+
})
|
|
2057
|
+
});
|
|
2058
|
+
return {
|
|
2059
|
+
taskId: task.taskId,
|
|
2060
|
+
threadId: task.threadId,
|
|
2061
|
+
status: task.status,
|
|
2062
|
+
validUntil: task.validUntil,
|
|
2063
|
+
submittedAt: task.submittedAt
|
|
2064
|
+
};
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
2067
|
+
if (error instanceof RobotRockError) {
|
|
2068
|
+
const response = error.response && typeof error.response === "object" ? error.response : void 0;
|
|
2069
|
+
console.error("[create_robotrock_task] API request failed", {
|
|
2070
|
+
status: error.statusCode,
|
|
2071
|
+
message: error.message,
|
|
2072
|
+
code: response?.code,
|
|
2073
|
+
hint: response?.hint,
|
|
2074
|
+
tenantSlug: caller?.tenantSlug,
|
|
2075
|
+
connectionId: caller?.connectionId,
|
|
2076
|
+
baseUrl: process.env.ROBOTROCK_BASE_URL?.trim() || null
|
|
2077
|
+
});
|
|
2078
|
+
} else {
|
|
2079
|
+
console.error("[create_robotrock_task] unexpected error", error);
|
|
2080
|
+
}
|
|
2081
|
+
throw error;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
function buildRobotRockTaskPayload(input, options) {
|
|
2085
|
+
const contextData = {
|
|
2086
|
+
...input.context?.data ?? {}
|
|
2087
|
+
};
|
|
2088
|
+
if (options?.requestedByEmail) {
|
|
2089
|
+
contextData.requestedBy = options.requestedByEmail;
|
|
2090
|
+
}
|
|
2091
|
+
const hasContext = Object.keys(contextData).length > 0 || input.context?.ui && Object.keys(input.context.ui).length > 0;
|
|
2092
|
+
const payload = {
|
|
2093
|
+
type: input.type,
|
|
2094
|
+
name: input.name,
|
|
2095
|
+
...input.description ? { description: input.description } : {},
|
|
2096
|
+
actions: input.actions.map((action) => ({
|
|
2097
|
+
id: action.id,
|
|
2098
|
+
title: action.title,
|
|
2099
|
+
...action.description ? { description: action.description } : {}
|
|
2100
|
+
})),
|
|
2101
|
+
...input.assignTo ? { assignTo: input.assignTo } : {},
|
|
2102
|
+
...input.priority ? { priority: input.priority } : {},
|
|
2103
|
+
...input.app ? { app: input.app } : {},
|
|
2104
|
+
...input.validUntilHours ? {
|
|
2105
|
+
validUntil: new Date(Date.now() + input.validUntilHours * 60 * 60 * 1e3)
|
|
2106
|
+
} : {},
|
|
2107
|
+
...input.updateMessage ? {
|
|
2108
|
+
update: {
|
|
2109
|
+
message: input.updateMessage,
|
|
2110
|
+
status: "waiting"
|
|
2111
|
+
}
|
|
2112
|
+
} : {}
|
|
2113
|
+
};
|
|
2114
|
+
if (hasContext) {
|
|
2115
|
+
payload.context = {
|
|
2116
|
+
data: contextData,
|
|
2117
|
+
...input.context?.ui ? { ui: input.context.ui } : {}
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
return payload;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// src/eve/agent/cron-chat.ts
|
|
2124
|
+
function resolveCronAgentIdentifier(agentIdentifier) {
|
|
2125
|
+
return agentIdentifier?.trim() || process.env.ROBOTROCK_CRON_AGENT_IDENTIFIER?.trim() || "robotrock-agent";
|
|
2126
|
+
}
|
|
2127
|
+
async function createRobotRockCronChat(input) {
|
|
2128
|
+
const apiKey = process.env.ROBOTROCK_API_KEY?.trim();
|
|
2129
|
+
if (!apiKey) {
|
|
2130
|
+
throw new Error("ROBOTROCK_API_KEY is unset");
|
|
2131
|
+
}
|
|
2132
|
+
const client = createClient(resolveRobotRockConfig({ apiKey }));
|
|
2133
|
+
return client.chats.create({
|
|
2134
|
+
agentIdentifier: resolveCronAgentIdentifier(input.agentIdentifier),
|
|
2135
|
+
title: input.title,
|
|
2136
|
+
messages: input.messages,
|
|
2137
|
+
source: "cron",
|
|
2138
|
+
assignTo: input.assignTo
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// src/eve/agent/hooks/ready.ts
|
|
2143
|
+
import { defineHook } from "eve/hooks";
|
|
2144
|
+
var robotrockReadyHook = defineHook({});
|
|
2145
|
+
function defineRobotRockReadyHook() {
|
|
2146
|
+
return defineHook({});
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/eve/agent/hooks/chat-audit.ts
|
|
2150
|
+
import { defineHook as defineHook2 } from "eve/hooks";
|
|
2151
|
+
var loggedIdempotencyKeys = /* @__PURE__ */ new Set();
|
|
2152
|
+
function toEveInputRequest(request) {
|
|
2153
|
+
return {
|
|
2154
|
+
requestId: request.requestId,
|
|
2155
|
+
prompt: request.prompt,
|
|
2156
|
+
...request.display ? { display: request.display } : {},
|
|
2157
|
+
...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
|
|
2158
|
+
...request.options ? { options: request.options } : {}
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
async function logPendingInputResponse(ctx, pending, response) {
|
|
2162
|
+
const caller = tryResolveTenantCaller(ctx);
|
|
2163
|
+
if (!caller) {
|
|
2164
|
+
return;
|
|
2165
|
+
}
|
|
2166
|
+
const idempotencyKey = `${ctx.session.id}:${pending.requestId}`;
|
|
2167
|
+
if (loggedIdempotencyKeys.has(idempotencyKey)) {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
const eveRequest = toEveInputRequest(pending);
|
|
2171
|
+
const submission = eveInputResponseToActionSubmission(eveRequest, response, {
|
|
2172
|
+
toolName: pending.toolName
|
|
2173
|
+
});
|
|
2174
|
+
const toolInput = pending.display === "confirmation" ? pending.toolInput : void 0;
|
|
2175
|
+
await postRobotRockChatInputAudit(ctx, {
|
|
2176
|
+
eveSessionId: ctx.session.id,
|
|
2177
|
+
userId: caller.userId,
|
|
2178
|
+
actionId: submission.actionId,
|
|
2179
|
+
actionTitle: submission.actionTitle,
|
|
2180
|
+
prompt: pending.prompt,
|
|
2181
|
+
requestId: pending.requestId,
|
|
2182
|
+
toolCallId: pending.toolCallId,
|
|
2183
|
+
data: buildEveInputAuditSubmissionData(
|
|
2184
|
+
{
|
|
2185
|
+
toolCallId: pending.toolCallId,
|
|
2186
|
+
toolName: pending.toolName,
|
|
2187
|
+
requestId: pending.requestId,
|
|
2188
|
+
display: pending.display,
|
|
2189
|
+
toolInput
|
|
2190
|
+
},
|
|
2191
|
+
submission.formData
|
|
2192
|
+
),
|
|
2193
|
+
idempotencyKey
|
|
2194
|
+
});
|
|
2195
|
+
loggedIdempotencyKeys.add(idempotencyKey);
|
|
2196
|
+
}
|
|
2197
|
+
function resetRobotrockChatAuditIdempotencyKeys() {
|
|
2198
|
+
loggedIdempotencyKeys.clear();
|
|
2199
|
+
}
|
|
2200
|
+
var robotrockChatAuditHook = defineHook2({
|
|
2201
|
+
events: {
|
|
2202
|
+
async "input.requested"(event, ctx) {
|
|
2203
|
+
const requests = [];
|
|
2204
|
+
for (const request of event.data.requests) {
|
|
2205
|
+
if (request.action.kind !== "tool-call") {
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
requests.push({
|
|
2209
|
+
requestId: request.requestId,
|
|
2210
|
+
prompt: request.prompt,
|
|
2211
|
+
toolName: request.action.toolName,
|
|
2212
|
+
toolCallId: request.action.callId,
|
|
2213
|
+
...request.display ? { display: request.display } : {},
|
|
2214
|
+
...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
|
|
2215
|
+
...request.options ? { options: request.options } : {},
|
|
2216
|
+
...request.action.input !== void 0 ? {
|
|
2217
|
+
toolInput: request.action.input != null && typeof request.action.input === "object" && !Array.isArray(request.action.input) ? request.action.input : void 0
|
|
2218
|
+
} : {}
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
await postRobotRockStageHitl(ctx, {
|
|
2222
|
+
eveSessionId: ctx.session.id,
|
|
2223
|
+
requests
|
|
2224
|
+
});
|
|
2225
|
+
},
|
|
2226
|
+
async "message.received"(event, ctx) {
|
|
2227
|
+
const message = event.data.message?.trim();
|
|
2228
|
+
if (!message) {
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const staged = await fetchRobotRockStagedHitl(ctx, ctx.session.id);
|
|
2232
|
+
for (const pending of staged) {
|
|
2233
|
+
const response = resolveEveFreeformTextToInputResponse(
|
|
2234
|
+
toEveInputRequest(pending),
|
|
2235
|
+
message
|
|
2236
|
+
);
|
|
2237
|
+
if (!response) {
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
await logPendingInputResponse(ctx, pending, response);
|
|
2241
|
+
}
|
|
2242
|
+
},
|
|
2243
|
+
async "action.result"(event, ctx) {
|
|
2244
|
+
const result = event.data.result;
|
|
2245
|
+
if (result.kind !== "tool-result") {
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const staged = await fetchRobotRockStagedHitl(ctx, ctx.session.id);
|
|
2249
|
+
const pending = staged.find((entry) => entry.toolCallId === result.callId);
|
|
2250
|
+
if (!pending) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
const idempotencyKey = `${ctx.session.id}:${pending.requestId}`;
|
|
2254
|
+
if (loggedIdempotencyKeys.has(idempotencyKey)) {
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
if (pending.toolName === "ask_question") {
|
|
2258
|
+
const parsed = parseEveAskQuestionToolOutput(result.output);
|
|
2259
|
+
if (!parsed) {
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
await logPendingInputResponse(ctx, pending, {
|
|
2263
|
+
requestId: pending.requestId,
|
|
2264
|
+
...parsed
|
|
2265
|
+
});
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
if (!isEveApprovalInputRequest(toEveInputRequest(pending))) {
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
if (event.data.status === "rejected") {
|
|
2272
|
+
await logPendingInputResponse(ctx, pending, {
|
|
2273
|
+
requestId: pending.requestId,
|
|
2274
|
+
optionId: "deny"
|
|
2275
|
+
});
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
if (event.data.status === "completed") {
|
|
2279
|
+
await logPendingInputResponse(ctx, pending, {
|
|
2280
|
+
requestId: pending.requestId,
|
|
2281
|
+
optionId: "approve"
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
function defineRobotRockChatAuditHook() {
|
|
2288
|
+
return robotrockChatAuditHook;
|
|
2289
|
+
}
|
|
2290
|
+
export {
|
|
2291
|
+
ROBOTROCK_PLATFORM_USER_CONTEXT_PUBLIC_KEY_URL,
|
|
2292
|
+
ROBOTROCK_READY_HOOK_SLUG,
|
|
2293
|
+
ROBOTROCK_USER_CONTEXT_AUDIENCE,
|
|
2294
|
+
ROBOTROCK_USER_CONTEXT_HEADER,
|
|
2295
|
+
ROBOTROCK_USER_CONTEXT_ISSUER,
|
|
2296
|
+
buildRobotRockTaskPayload,
|
|
2297
|
+
createRobotRockCronChat,
|
|
2298
|
+
createRobotRockTask,
|
|
2299
|
+
defineRobotRockChatAuditHook,
|
|
2300
|
+
defineRobotRockReadyHook,
|
|
2301
|
+
eveSelfServiceAuth,
|
|
2302
|
+
fetchRobotRockStagedHitl,
|
|
2303
|
+
isAdminCaller,
|
|
2304
|
+
postRobotRockChatInputAudit,
|
|
2305
|
+
postRobotRockLinkChatTask,
|
|
2306
|
+
postRobotRockStageHitl,
|
|
2307
|
+
requireAdminCaller,
|
|
2308
|
+
requireTenantCaller,
|
|
2309
|
+
resetBoundClientAuthWarning,
|
|
2310
|
+
resetChatAuditWarnings,
|
|
2311
|
+
resetRobotrockChatAuditIdempotencyKeys,
|
|
2312
|
+
resolvePlatformUserContextPublicKeyPem,
|
|
2313
|
+
resolveTaskHandledWebhookUrl,
|
|
2314
|
+
robotrockAgentServiceAuth,
|
|
2315
|
+
robotrockChatAuditHook,
|
|
2316
|
+
robotrockEveChannel,
|
|
2317
|
+
robotrockReadyHook,
|
|
2318
|
+
robotrockUserContextAuth,
|
|
2319
|
+
tryCreateBoundRobotRockClient,
|
|
2320
|
+
tryResolveTenantCaller
|
|
2321
|
+
};
|
|
2322
|
+
//# sourceMappingURL=index.js.map
|