robotrock 0.7.0 → 0.8.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.
@@ -1,613 +0,0 @@
1
- import {
2
- RobotRockError,
3
- createClient,
4
- resolveRobotRockConfig
5
- } from "./chunk-ZG2XVK6Y.js";
6
- import {
7
- assignToSchema,
8
- threadUpdateStatusSchema
9
- } from "./chunk-ZHASQUX6.js";
10
-
11
- // src/ai/context.ts
12
- var APPROVE_BY_HUMAN_ACTIONS = [
13
- { id: "approve", title: "Approve" },
14
- { id: "decline", title: "Decline" }
15
- ];
16
- function isRobotRockClient(value) {
17
- return typeof value === "object" && value !== null && "sendToHuman" in value && typeof value.sendToHuman === "function";
18
- }
19
- function normalizeRobotRockAiContext(clientOrContext) {
20
- if (isRobotRockClient(clientOrContext)) {
21
- return { mode: "polling", client: clientOrContext };
22
- }
23
- if (clientOrContext.mode === "trigger" || clientOrContext.mode === "workflow") {
24
- return clientOrContext;
25
- }
26
- if (clientOrContext.mode === "polling" || clientOrContext.mode === void 0) {
27
- if (!("client" in clientOrContext) || !clientOrContext.client) {
28
- throw new Error("RobotRock AI polling mode requires `client` on the context object.");
29
- }
30
- return { mode: "polling", client: clientOrContext.client };
31
- }
32
- throw new Error(`Unknown RobotRock AI mode: ${String(clientOrContext.mode)}`);
33
- }
34
- async function sendToHumanForAi(context, payload) {
35
- if (context.mode === "trigger") {
36
- const { sendToHumanTask } = await import("./trigger/index.js");
37
- const waitResult = await sendToHumanTask.triggerAndWait({
38
- ...payload,
39
- ...context.app ? { app: context.app } : {}
40
- });
41
- if (!waitResult.ok) {
42
- throw waitResult.error;
43
- }
44
- return waitResult.output;
45
- }
46
- if (context.mode === "workflow") {
47
- const { sendToHumanInWorkflow } = await import("./workflow/index.js");
48
- const result2 = await sendToHumanInWorkflow({
49
- ...payload,
50
- ...context.app ? { app: context.app } : {}
51
- });
52
- return result2;
53
- }
54
- const result = await context.client.sendToHuman(payload);
55
- if (result.mode !== "handled") {
56
- throw new Error(
57
- "RobotRock task was created but not handled. Configure client polling or webhook, or use mode: 'trigger' / 'workflow' for durable waits."
58
- );
59
- }
60
- return {
61
- actionId: result.actionId,
62
- data: result.data,
63
- handledBy: result.handledBy,
64
- handledAt: result.handledAt,
65
- taskId: result.taskId
66
- };
67
- }
68
- async function sendUpdateForAi(context, payload) {
69
- if (context.mode === "workflow") {
70
- const { sendUpdateInWorkflow } = await import("./workflow/index.js");
71
- return sendUpdateInWorkflow({
72
- ...payload,
73
- ...context.app ? { app: context.app } : {}
74
- });
75
- }
76
- if (context.mode === "trigger") {
77
- const client = createClient(
78
- resolveRobotRockConfig(context.app ? { app: context.app } : void 0)
79
- );
80
- return client.sendUpdate(payload);
81
- }
82
- return context.client.sendUpdate(payload);
83
- }
84
- async function approveByHumanForAi(context, payload) {
85
- if (context.mode === "trigger") {
86
- const { approveByHumanTask } = await import("./trigger/index.js");
87
- const waitResult = await approveByHumanTask.triggerAndWait({
88
- ...payload,
89
- ...context.app ? { app: context.app } : {}
90
- });
91
- if (!waitResult.ok) {
92
- throw waitResult.error;
93
- }
94
- return waitResult.output;
95
- }
96
- if (context.mode === "workflow") {
97
- const { approveByHumanInWorkflow } = await import("./workflow/index.js");
98
- return await approveByHumanInWorkflow({
99
- ...payload,
100
- ...context.app ? { app: context.app } : {}
101
- });
102
- }
103
- const result = await context.client.sendToHuman({
104
- ...payload,
105
- actions: APPROVE_BY_HUMAN_ACTIONS
106
- });
107
- if (result.mode !== "handled") {
108
- throw new Error(
109
- "RobotRock approval was not handled. Configure client polling or use mode: 'trigger' / 'workflow' for durable waits."
110
- );
111
- }
112
- return {
113
- actionId: result.actionId,
114
- data: result.data,
115
- handledBy: result.handledBy,
116
- handledAt: result.handledAt,
117
- taskId: result.taskId
118
- };
119
- }
120
-
121
- // src/ai/human-tool-result.ts
122
- var APPROVE_IDS = /* @__PURE__ */ new Set(["approve", "approved"]);
123
- var DECLINE_IDS = /* @__PURE__ */ new Set(["decline", "reject", "deny", "denied"]);
124
- function toHumanToolResult(result) {
125
- const payload = {
126
- taskId: result.taskId,
127
- actionId: result.actionId,
128
- data: result.data,
129
- handledBy: result.handledBy,
130
- handledAt: result.handledAt.toISOString()
131
- };
132
- if (APPROVE_IDS.has(result.actionId)) {
133
- payload.approved = true;
134
- } else if (DECLINE_IDS.has(result.actionId)) {
135
- payload.approved = false;
136
- }
137
- return payload;
138
- }
139
-
140
- // src/ai/approve-by-human-tool.ts
141
- import { tool } from "ai";
142
- import { z } from "zod";
143
- var APPROVE_BY_HUMAN_ACTIONS2 = [
144
- { id: "approve", title: "Approve" },
145
- { id: "decline", title: "Decline" }
146
- ];
147
- var approveByHumanInputSchema = z.object({
148
- type: z.string().optional().describe("Task type slug; defaults to ai-approval"),
149
- name: z.string().describe("Short title for the approval request"),
150
- description: z.string().describe("What needs approval and the consequences of approving or declining"),
151
- contextSummary: z.string().optional().describe("Optional markdown summary shown to the reviewer")
152
- });
153
- function approveByHumanTool(clientOrContext, maybeOptions = {}) {
154
- const isDurable = typeof clientOrContext === "object" && clientOrContext !== null && "mode" in clientOrContext && (clientOrContext.mode === "trigger" || clientOrContext.mode === "workflow");
155
- const aiContext = normalizeRobotRockAiContext(
156
- isDurable ? {
157
- mode: clientOrContext.mode,
158
- app: clientOrContext.app
159
- } : clientOrContext
160
- );
161
- const options = isDurable ? clientOrContext : maybeOptions;
162
- const description = options.description ?? "Request explicit human approval before a sensitive or irreversible step. Returns whether the human approved or declined.";
163
- return tool({
164
- description,
165
- inputSchema: approveByHumanInputSchema,
166
- execute: async (input) => {
167
- const taskContext = input.contextSummary !== void 0 ? {
168
- data: { summary: input.contextSummary },
169
- ui: {
170
- summary: { "ui:widget": "textarea", "ui:options": { rows: 6 } }
171
- }
172
- } : void 0;
173
- const result = await approveByHumanForAi(aiContext, {
174
- type: input.type ?? options.defaultType ?? "ai-approval",
175
- name: input.name,
176
- description: input.description,
177
- context: taskContext
178
- });
179
- return toHumanToolResult(result);
180
- }
181
- });
182
- }
183
-
184
- // src/ai/create-send-to-human-tool.ts
185
- import { tool as tool2 } from "ai";
186
- import { z as z2 } from "zod";
187
- var contextInputSchema = z2.object({
188
- data: z2.record(z2.unknown()).optional(),
189
- ui: z2.record(z2.unknown()).optional()
190
- }).optional();
191
- var sendToHumanToolInputSchema = z2.object({
192
- type: z2.string().describe("Task type slug shown in the RobotRock inbox"),
193
- name: z2.string().describe("Short title for the human reviewer"),
194
- description: z2.string().optional().describe("What you need from the human and why you cannot proceed alone"),
195
- context: contextInputSchema.describe("Optional structured context for the inbox UI"),
196
- validUntil: z2.string().datetime().optional().describe("Optional ISO deadline for the task"),
197
- assignTo: assignToSchema.optional().describe(
198
- "Assign to tenant member emails and/or group slugs; narrows who sees the task in the inbox"
199
- )
200
- });
201
- function createSendToHumanTool(clientOrOptions, maybeOptions) {
202
- const isDurable = typeof clientOrOptions === "object" && clientOrOptions !== null && "mode" in clientOrOptions && (clientOrOptions.mode === "trigger" || clientOrOptions.mode === "workflow");
203
- const aiContext = normalizeRobotRockAiContext(
204
- isDurable ? {
205
- mode: clientOrOptions.mode,
206
- app: clientOrOptions.app
207
- } : clientOrOptions
208
- );
209
- const options = isDurable ? clientOrOptions : maybeOptions;
210
- const description = options.description ?? "Request structured input or a decision from a human in the RobotRock inbox. Use when you lack required information, need policy approval, or must confirm an irreversible step.";
211
- return tool2({
212
- description,
213
- inputSchema: sendToHumanToolInputSchema,
214
- execute: async (input) => {
215
- const taskContext = input.context ? {
216
- data: input.context.data ?? {},
217
- ui: input.context.ui
218
- } : void 0;
219
- const payload = {
220
- type: input.type ?? options.defaultType ?? "ai-human-input",
221
- name: input.name,
222
- description: input.description,
223
- context: taskContext,
224
- validUntil: input.validUntil,
225
- assignTo: input.assignTo,
226
- actions: options.actions,
227
- ...options.threadId ? { threadId: options.threadId } : {}
228
- };
229
- const result = await sendToHumanForAi(aiContext, payload);
230
- return toHumanToolResult(result);
231
- }
232
- });
233
- }
234
-
235
- // src/ai/create-send-update-tool.ts
236
- import { tool as tool3 } from "ai";
237
- import { z as z3 } from "zod";
238
- var sendUpdateToolInputSchema = z3.object({
239
- threadId: z3.string().optional().describe(
240
- "Thread to post the update to. Use the `threadId` returned by a prior send-to-human task. Omit only when a session threadId is configured on the tool."
241
- ),
242
- message: z3.string().describe("Short progress update (1-2 sentences) shown in the inbox status bar"),
243
- status: threadUpdateStatusSchema.optional().describe(
244
- "Lifecycle status driving the status-bar icon/color: info, queued, running, waiting, succeeded, failed, cancelled"
245
- )
246
- });
247
- function createSendUpdateTool(clientOrOptions, maybeOptions = {}) {
248
- const isDurable = typeof clientOrOptions === "object" && clientOrOptions !== null && "mode" in clientOrOptions && (clientOrOptions.mode === "trigger" || clientOrOptions.mode === "workflow");
249
- const aiContext = normalizeRobotRockAiContext(
250
- isDurable ? {
251
- mode: clientOrOptions.mode,
252
- app: clientOrOptions.app
253
- } : clientOrOptions
254
- );
255
- const options = isDurable ? clientOrOptions : maybeOptions;
256
- const description = options.description ?? "Post a short progress update to the RobotRock thread you are working on. Use to report status changes (running, succeeded, failed) so humans can follow along in the inbox.";
257
- return tool3({
258
- description,
259
- inputSchema: sendUpdateToolInputSchema,
260
- execute: async (input) => {
261
- const threadId = input.threadId ?? options.threadId;
262
- if (!threadId) {
263
- throw new Error(
264
- "createSendUpdateTool: no threadId. Pass `threadId` from a prior send-to-human result, or configure a session `threadId` in the tool options."
265
- );
266
- }
267
- try {
268
- const update = await sendUpdateForAi(aiContext, {
269
- threadId,
270
- message: input.message,
271
- status: input.status
272
- });
273
- return { posted: true, threadId, update };
274
- } catch (error) {
275
- if (error instanceof RobotRockError && error.statusCode === 404) {
276
- return {
277
- posted: false,
278
- threadId,
279
- reason: "thread_not_found",
280
- message: `No task exists for thread "${threadId}" yet, so the update was not posted. Create a task on this thread with send-to-human before reporting progress.`
281
- };
282
- }
283
- throw error;
284
- }
285
- }
286
- });
287
- }
288
-
289
- // src/ai/create-ai-tools.ts
290
- function createRobotRockAiTools(options) {
291
- const mode = options.mode ?? (options.client ? "polling" : "trigger");
292
- if (mode === "polling" && !options.client) {
293
- throw new Error("createRobotRockAiTools: polling mode requires `client`.");
294
- }
295
- const context = mode === "trigger" ? { mode: "trigger", app: options.app } : mode === "workflow" ? { mode: "workflow", app: options.app } : { mode: "polling", client: options.client };
296
- const durableContext = context.mode === "trigger" || context.mode === "workflow" ? context : null;
297
- const pollingClient = context.mode === "polling" ? context.client : void 0;
298
- return {
299
- context,
300
- approveByHuman: (toolOptions) => durableContext ? approveByHumanTool({ ...durableContext, ...toolOptions }) : approveByHumanTool(pollingClient, toolOptions),
301
- sendToHuman: (toolOptions) => durableContext ? createSendToHumanTool({
302
- ...durableContext,
303
- ...options.threadId ? { threadId: options.threadId } : {},
304
- ...toolOptions
305
- }) : createSendToHumanTool(pollingClient, {
306
- ...options.threadId ? { threadId: options.threadId } : {},
307
- ...toolOptions
308
- }),
309
- sendUpdate: (toolOptions = {}) => durableContext ? createSendUpdateTool({
310
- ...durableContext,
311
- ...options.threadId ? { threadId: options.threadId } : {},
312
- ...toolOptions
313
- }) : createSendUpdateTool(pollingClient, {
314
- ...options.threadId ? { threadId: options.threadId } : {},
315
- ...toolOptions
316
- })
317
- };
318
- }
319
- function createRobotRockAiTriggerContext(options = {}) {
320
- return { mode: "trigger", ...options };
321
- }
322
- function createRobotRockAiWorkflowContext(options = {}) {
323
- return { mode: "workflow", ...options };
324
- }
325
-
326
- // src/ai/format-tool-approval-task.ts
327
- var DEFAULT_APPROVE_ACTIONS = [
328
- { id: "approve", title: "Approve execution" },
329
- { id: "deny", title: "Deny" }
330
- ];
331
- function defaultFormatToolApprovalTask(toolCall, options = {}) {
332
- const approveId = options.approveActionId ?? "approve";
333
- const denyId = options.denyActionId ?? "deny";
334
- let inputPreview;
335
- try {
336
- inputPreview = JSON.stringify(toolCall.input, null, 2);
337
- } catch {
338
- inputPreview = String(toolCall.input);
339
- }
340
- return {
341
- type: options.type ?? "ai-tool-approval",
342
- name: `Approve: ${toolCall.toolName}`,
343
- description: `Review the proposed \`${toolCall.toolName}\` tool call before it runs.`,
344
- context: {
345
- data: {
346
- toolName: toolCall.toolName,
347
- toolCallId: toolCall.toolCallId,
348
- input: toolCall.input
349
- },
350
- ui: {
351
- toolName: { "ui:widget": "text" },
352
- toolCallId: { "ui:widget": "text" },
353
- input: { "ui:widget": "textarea" }
354
- }
355
- },
356
- actions: [
357
- { id: approveId, title: "Approve execution" },
358
- { id: denyId, title: "Deny" }
359
- ]
360
- };
361
- }
362
-
363
- // src/ai/tool-approval-bridge.ts
364
- function buildToolMatcher(config) {
365
- const names = config.tools ? new Set(config.tools) : null;
366
- return (toolCall) => {
367
- if (config.when?.(toolCall)) {
368
- return true;
369
- }
370
- if (names && names.has(toolCall.toolName)) {
371
- return true;
372
- }
373
- return false;
374
- };
375
- }
376
- function createRobotRockToolApproval(config) {
377
- const matches = buildToolMatcher(config);
378
- const toolApproval = async (options) => {
379
- return matches(options.toolCall) ? "user-approval" : void 0;
380
- };
381
- return toolApproval;
382
- }
383
- function createRobotRockNeedsApproval(config) {
384
- const matches = buildToolMatcher(config);
385
- return async (_input, options) => {
386
- const toolCall = findToolCallInMessages(options.messages, options.toolCallId);
387
- if (!toolCall) {
388
- return false;
389
- }
390
- return matches(toolCall);
391
- };
392
- }
393
- function applyRobotRockToolApprovalToTools(tools, config) {
394
- const names = config.tools ? new Set(config.tools) : null;
395
- const needsApproval = createRobotRockNeedsApproval(config);
396
- const next = { ...tools };
397
- for (const key of Object.keys(tools)) {
398
- const name = String(key);
399
- const shouldApply = names && names.has(name) || names === null && config.when !== void 0;
400
- if (!shouldApply) {
401
- continue;
402
- }
403
- const existing = tools[key];
404
- next[key] = {
405
- ...existing,
406
- needsApproval
407
- };
408
- }
409
- return next;
410
- }
411
- function findToolCallInMessages(messages, toolCallId) {
412
- if (!messages) {
413
- return void 0;
414
- }
415
- for (const message of messages) {
416
- if (typeof message !== "object" || message === null) {
417
- continue;
418
- }
419
- const role = message.role;
420
- if (role !== "assistant") {
421
- continue;
422
- }
423
- const content = message.content;
424
- if (!Array.isArray(content)) {
425
- continue;
426
- }
427
- for (const part of content) {
428
- if (typeof part !== "object" || part === null) {
429
- continue;
430
- }
431
- const p = part;
432
- if (p.type === "tool-call" && p.toolCallId === toolCallId) {
433
- return {
434
- toolName: String(p.toolName ?? ""),
435
- toolCallId,
436
- input: p.input
437
- };
438
- }
439
- }
440
- }
441
- return void 0;
442
- }
443
- function collectApprovalRequests(source) {
444
- const requests = [];
445
- const visit = (value) => {
446
- if (!value) {
447
- return;
448
- }
449
- if (Array.isArray(value)) {
450
- for (const item of value) {
451
- visit(item);
452
- }
453
- return;
454
- }
455
- if (typeof value !== "object") {
456
- return;
457
- }
458
- const obj = value;
459
- if (obj.type === "tool-approval-request" && typeof obj.approvalId === "string") {
460
- if (obj.isAutomatic === true) {
461
- return;
462
- }
463
- const embedded = obj.toolCall;
464
- const toolCallId = typeof obj.toolCallId === "string" ? obj.toolCallId : typeof embedded?.toolCallId === "string" ? embedded.toolCallId : void 0;
465
- let toolCall;
466
- if (embedded && typeof embedded.toolName === "string") {
467
- toolCall = {
468
- toolName: embedded.toolName,
469
- toolCallId: String(embedded.toolCallId ?? toolCallId ?? ""),
470
- input: embedded.input
471
- };
472
- } else if (toolCallId) {
473
- toolCall = findToolCallInMessages(
474
- source.messages,
475
- toolCallId
476
- );
477
- }
478
- requests.push({
479
- type: "tool-approval-request",
480
- approvalId: obj.approvalId,
481
- toolCallId,
482
- toolCall,
483
- isAutomatic: obj.isAutomatic === true
484
- });
485
- return;
486
- }
487
- if (Array.isArray(obj.content)) {
488
- visit(obj.content);
489
- }
490
- if (Array.isArray(obj.steps)) {
491
- visit(obj.steps);
492
- }
493
- if (obj.response && typeof obj.response === "object") {
494
- visit(obj.response.messages);
495
- }
496
- };
497
- visit(source);
498
- if (typeof source === "object" && source !== null && Array.isArray(source.content)) {
499
- visit(source.content);
500
- }
501
- const seen = /* @__PURE__ */ new Set();
502
- return requests.filter((r) => {
503
- if (seen.has(r.approvalId)) {
504
- return false;
505
- }
506
- seen.add(r.approvalId);
507
- return true;
508
- });
509
- }
510
- function resolveToolCallForRequest(request, source, messages) {
511
- if (request.toolCall) {
512
- return request.toolCall;
513
- }
514
- const toolCallId = request.toolCallId;
515
- if (toolCallId) {
516
- const fromMessages = findToolCallInMessages(messages, toolCallId);
517
- if (fromMessages) {
518
- return fromMessages;
519
- }
520
- }
521
- if (typeof source === "object" && source !== null && Array.isArray(source.toolCalls)) {
522
- for (const call of source.toolCalls) {
523
- if (call.toolCallId === toolCallId) {
524
- return {
525
- toolName: String(call.toolName ?? ""),
526
- toolCallId: String(call.toolCallId ?? ""),
527
- input: call.input
528
- };
529
- }
530
- }
531
- }
532
- throw new Error(
533
- `Could not resolve tool call for approval ${request.approvalId}. Pass messages that include the assistant tool-call.`
534
- );
535
- }
536
- async function resolveToolApprovalsViaRobotRock(clientOrContext, source, options = {}) {
537
- const context = normalizeRobotRockAiContext(clientOrContext);
538
- const approveId = options.approveActionId ?? "approve";
539
- const denyId = options.denyActionId ?? "deny";
540
- const formatTask = options.formatTask ?? defaultFormatToolApprovalTask;
541
- const baseMessages = typeof source === "object" && source !== null && Array.isArray(source.messages) ? [...source.messages] : [];
542
- const requests = collectApprovalRequests(source);
543
- const responses = [];
544
- for (const request of requests) {
545
- const toolCall = resolveToolCallForRequest(request, source, baseMessages);
546
- const taskInput = formatTask(toolCall, {
547
- approveActionId: approveId,
548
- denyActionId: denyId
549
- });
550
- const result = await sendToHumanForAi(context, taskInput);
551
- const approved = result.actionId === approveId;
552
- const reason = typeof result.data === "object" && result.data !== null && "reason" in result.data && typeof result.data.reason === "string" ? result.data.reason : approved ? "Approved in RobotRock inbox" : "Denied in RobotRock inbox";
553
- responses.push({
554
- type: "tool-approval-response",
555
- approvalId: request.approvalId,
556
- approved,
557
- reason
558
- });
559
- }
560
- const messages = [...baseMessages];
561
- if (responses.length > 0) {
562
- messages.push({ role: "tool", content: responses });
563
- }
564
- return { responses, messages };
565
- }
566
- async function runWithRobotRockApprovals(options) {
567
- const clientOrContext = options.context ?? (options.client ? options.client : (() => {
568
- throw new Error("runWithRobotRockApprovals requires `client` or `context`.");
569
- })());
570
- const maxRounds = options.maxRounds ?? 20;
571
- let messages = options.messages ? [...options.messages] : [];
572
- let lastResult;
573
- for (let round = 0; round < maxRounds; round++) {
574
- lastResult = await options.generate(messages);
575
- const { responses, messages: nextMessages } = await resolveToolApprovalsViaRobotRock(
576
- clientOrContext,
577
- { ...lastResult, messages },
578
- options.resolveOptions
579
- );
580
- if (responses.length === 0) {
581
- return lastResult;
582
- }
583
- messages = nextMessages;
584
- }
585
- throw new Error(`RobotRock approval loop exceeded maxRounds (${maxRounds})`);
586
- }
587
-
588
- export {
589
- normalizeRobotRockAiContext,
590
- sendToHumanForAi,
591
- sendUpdateForAi,
592
- approveByHumanForAi,
593
- toHumanToolResult,
594
- APPROVE_BY_HUMAN_ACTIONS2 as APPROVE_BY_HUMAN_ACTIONS,
595
- approveByHumanInputSchema,
596
- approveByHumanTool,
597
- sendToHumanToolInputSchema,
598
- createSendToHumanTool,
599
- sendUpdateToolInputSchema,
600
- createSendUpdateTool,
601
- createRobotRockAiTools,
602
- createRobotRockAiTriggerContext,
603
- createRobotRockAiWorkflowContext,
604
- DEFAULT_APPROVE_ACTIONS,
605
- defaultFormatToolApprovalTask,
606
- createRobotRockToolApproval,
607
- createRobotRockNeedsApproval,
608
- applyRobotRockToolApprovalToTools,
609
- collectApprovalRequests,
610
- resolveToolApprovalsViaRobotRock,
611
- runWithRobotRockApprovals
612
- };
613
- //# sourceMappingURL=chunk-55JHCNYX.js.map