@sentry/junior 0.235.0 → 0.237.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-hooks-DS7k0ZWY.js → agent-hooks-otHGbhBx.js} +20 -9
- package/dist/{agent-runner-BlDI1zV2.js → agent-runner-DMAsnPb9.js} +456 -120
- package/dist/api/schema.d.ts +223 -2
- package/dist/api/schema.js +5 -4
- package/dist/api.d.ts +8 -2
- package/dist/api.js +404 -47
- package/dist/{app-pGsR__aT.d.ts → app-MEd7wbF0.d.ts} +82 -48
- package/dist/app.d.ts +3 -3
- package/dist/app.js +162 -119
- package/dist/{types-Du-HbEun.js → automation-CrQGQ2MT.js} +306 -34
- package/dist/{briefs-C3jhszOz.js → briefs-DDA1ugXk.js} +4 -4
- package/dist/{catalog-runtime-B9u_3-GW.js → catalog-runtime-DlXuhrmk.js} +1 -1
- package/dist/{checkpoint-DLcJ5mKL.js → checkpoint-BlIIiHG5.js} +16 -9
- package/dist/cli/chat.d.ts +1 -1
- package/dist/cli/chat.js +14 -14
- package/dist/cli/check.js +4 -4
- package/dist/cli/main.js +1 -1
- package/dist/cli/plugins.d.ts +1 -1
- package/dist/cli/plugins.js +4 -4
- package/dist/cli/snapshot-create.js +3 -3
- package/dist/cli/upgrade.d.ts +1 -1
- package/dist/cli/upgrade.js +5 -5
- package/dist/{client-BSnNHO0R.js → client-CPMbTmJU.js} +2 -2
- package/dist/{config-Bmw4xTPK.js → config-CxpPG6nR.js} +57 -17
- package/dist/{conversation-C_aYrISI.js → conversation-9Fxa1Zbq.js} +13 -1
- package/dist/{conversation-privacy-BV2dtba2.js → conversation-privacy-Dbkdvozu.js} +1 -1
- package/dist/{credential-sync-CukzQJ1x.js → credential-sync-D9yGg3q5.js} +14 -14
- package/dist/{executor-B9ksWZCv.js → executor-BNxfDS1V.js} +39 -6
- package/dist/{images-CEXcQ0Id.js → images-D6Y3FJDQ.js} +3 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-CY1L2YRn.js → job-queue-Dq5_zt_l.js} +1 -1
- package/dist/{logging-F--48Q7Q.js → logging-rB_ngE_R.js} +1 -1
- package/dist/nitro.d.ts +2 -2
- package/dist/nitro.js +5 -5
- package/dist/{oauth-authorization-LasSG3Qc.js → oauth-authorization-5T1S1v_1.js} +1 -1
- package/dist/{oauth-callback-server-D07ZzVMs.js → oauth-callback-server-Csw7RssK.js} +2 -2
- package/dist/{pending-auth-DrG9Mkje.js → pending-auth-moCA2mTc.js} +1 -1
- package/dist/{plugins-BT2l4We3.d.ts → plugins-CjfUP5gJ.d.ts} +1 -0
- package/dist/{profile-DZ2DUdd1.js → profile-D6apIG2N.js} +4 -4
- package/dist/{prompt-BM_5mAU3.js → prompt-Ctwy6hSI.js} +1 -1
- package/dist/queue-B5SnqRln.d.ts +25 -0
- package/dist/{registry-_JLY0x7b.js → registry-zfTzTHe6.js} +38 -2
- package/dist/{resolve-Cuoj3MML.js → resolve-sJtTzuBO.js} +2 -2
- package/dist/{runner-BfelmbMv.js → runner-DkCk8cbc.js} +9 -9
- package/dist/{sandbox-egress-signals-D5QUIO7Z.js → sandbox-egress-signals-tSQ8OEcw.js} +1 -1
- package/dist/{schema-CrdDxGbx.js → schema-C5bgOpRg.js} +3 -2
- package/dist/{skills-DAegjk5Y.js → skills-BVpr_Gnv.js} +3 -3
- package/dist/{spawn-DCfV7zGK.js → spawn-CJhmyE4E.js} +2 -2
- package/dist/{task-queue-GcuFtzor.js → task-queue-BAiZ7_CF.js} +16 -10
- package/dist/{task-runner-B0-P_3aq.js → task-runner-Bh8XONXT.js} +9 -9
- package/dist/{thread-state-TezcEhuH.js → thread-state-BnQln4jO.js} +5 -5
- package/dist/{turn-failure-response-Drhj_-t4.js → turn-failure-response-Ba7_kItn.js} +8 -8
- package/dist/{turn-lifecycle-BpIW3zOx.js → turn-lifecycle-D6CTbkb9.js} +1 -1
- package/dist/{turn-session-routing-Dg8GDgyc.js → turn-session-routing-DNevs9H7.js} +1 -1
- package/dist/{validation-DAkYeKDT.js → validation-BNPhuAvt.js} +2 -3
- package/dist/{version-9CB4Z-J4.js → version-EQ4MWuUJ.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-Cu33voNg.js → web-authorization-DDt4FZld.js} +6 -6
- package/dist/{web-input-B3k0J7WB.js → web-input-C56ivZuH.js} +86 -102
- package/dist/web-message-id-xPI_EFSC.js +239 -0
- package/dist/{work-PmkvcY9l.js → work-DJ7hb9Ul.js} +7 -7
- package/dist/{worker-COcaq2J6.js → worker-DiiV8Lba.js} +5 -5
- package/migrations/0045_conversation_forks.sql +3 -0
- package/migrations/0046_automation_versions.sql +71 -0
- package/migrations/0047_user_timezone.sql +1 -0
- package/migrations/meta/0045_snapshot.json +3503 -0
- package/migrations/meta/0046_snapshot.json +3707 -0
- package/migrations/meta/0047_snapshot.json +3713 -0
- package/migrations/meta/_journal.json +22 -1
- package/package.json +4 -4
- package/dist/web-message-id-CVVCTWUb.js +0 -464
- package/dist/{package-discovery-7a9YqLhE.js → package-discovery-BVvSnQBB.js} +1 -1
- package/dist/{plugin-module-BKGOXca2.js → plugin-module-CVvEUSMH.js} +1 -1
package/dist/api.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { Dt as
|
|
2
|
-
import { d as logException, p as logWarn } from "./logging-
|
|
1
|
+
import { Dt as getEventCatalog, G as readScheduledAutomation, K as saveScheduledAutomation, M as parseSlackThreadId, Pt as cancelHumanFacingPendingMessages, Q as eventAutomationTriggerAvailable, R as editScheduledAutomation, Wt as getConversation, X as editEventAutomation, Z as AutomationEditError, at as getEventAutomation, ct as saveEventAutomation, et as resolveTaskOutcomes, ft as readAutomationVersion, g as readStats, gt as requireAutomationRevision, ht as automationRevision, k as readLiveAttachment, kt as pluginEventCatalog, lt as setEventAutomationStatus, mt as AutomationConflictError, pt as sameDefinitionValue, rt as eventAutomationBelongsToUser, ut as automationDefinition, z as lookupSlackUser } from "./agent-runner-DMAsnPb9.js";
|
|
2
|
+
import { d as logException, p as logWarn } from "./logging-rB_ngE_R.js";
|
|
3
3
|
import { b as parseSlackMessageTs, h as parseSlackTeamId, p as parseSlackChannelId } from "./context-0Et9iVSB.js";
|
|
4
|
-
import { At as conversationHasParticipantUser, C as WorkspaceValidationError, Dt as conversationArchivedForEmail, Et as conversationArchiveTimes, Ft as readLatestConversationBrief, Gt as listConversationAnnotations, It as readConversationEventPage, Kt as listConversationAnnotationsById, Lt as buildSentryConversationUrl, M as workspaceRepoCheckoutPath, Mt as conversationNotArchivedForUser, Nt as viewerConversationMembership, O as SANDBOX_RUNTIME, Ot as conversationArchivedForUser, Q as findUserByEmail, S as updateWorkspace, Tt as readConversationAccessFromSql, W as getSlackClient, Y as withSlackRetries, _ as deleteWorkspace, b as listWorkspaces, cn as parseSessionSource, en as getConversationStore, et as resolveViewerUser, fn as agentTurnUsageSchema, g as createWorkspace, jt as conversationNotArchivedForEmail, kt as conversationHasParticipantEmail, mn as resolveRootConversationId, nn as getSqlExecutor, on as locationFromRow, pn as hasAgentTurnUsage, tn as getDb, tt as updateViewerDisplayName, u as getPlugins, v as getWorkspace, wt as createPluginLogger, x as listWorkspacesByRepository } from "./agent-hooks-
|
|
4
|
+
import { At as conversationHasParticipantUser, C as WorkspaceValidationError, Dt as conversationArchivedForEmail, Et as conversationArchiveTimes, Ft as readLatestConversationBrief, Gt as listConversationAnnotations, It as readConversationEventPage, Kt as listConversationAnnotationsById, Lt as buildSentryConversationUrl, M as workspaceRepoCheckoutPath, Mt as conversationNotArchivedForUser, Nt as viewerConversationMembership, O as SANDBOX_RUNTIME, Ot as conversationArchivedForUser, Q as findUserByEmail, Qt as getConversationEventStore, S as updateWorkspace, Tt as readConversationAccessFromSql, W as getSlackClient, Y as withSlackRetries, _ as deleteWorkspace, b as listWorkspaces, cn as parseSessionSource, en as getConversationStore, et as resolveViewerUser, fn as agentTurnUsageSchema, g as createWorkspace, in as withConversationEventLock, jt as conversationNotArchivedForEmail, kt as conversationHasParticipantEmail, mn as resolveRootConversationId, nn as getSqlExecutor, on as locationFromRow, pn as hasAgentTurnUsage, tn as getDb, tt as updateViewerDisplayName, u as getPlugins, v as getWorkspace, wt as createPluginLogger, x as listWorkspacesByRepository } from "./agent-hooks-otHGbhBx.js";
|
|
5
5
|
import { n as juniorDestinations } from "./destinations-BeBiJeEM.js";
|
|
6
|
-
import { C as
|
|
7
|
-
import { D as createConversationBodySchema,
|
|
8
|
-
import {
|
|
9
|
-
import { t as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
6
|
+
import { C as juniorCodeChanges, D as juniorConversations, E as conversationReadColumns, O as juniorIdentities, _ as juniorConversationBriefs, b as juniorConversationEvents, h as juniorConversationParticipants, k as juniorUsers, w as juniorCodeRepositories, x as juniorApiTokens, y as juniorConversationMetrics } from "./executor-BNxfDS1V.js";
|
|
7
|
+
import { A as forkConversationResponseSchema, D as createConversationBodySchema, N as readMessageAttachments, O as createConversationMessageBodySchema, P as INPUT_IMAGE_TYPES, T as conversationStatsReportSchema, _ as conversationParamsSchema, a as cancelConversationPendingMessagesBodySchema, c as conversationAttachmentParamsSchema, d as conversationDetailReportSchema, g as conversationFeedSchema, h as conversationFeedQuerySchema, i as archiveConversationResponseSchema, k as forkConversationBodySchema, m as conversationEventsQuerySchema, nt as contextProvenance, o as cancelConversationPendingMessagesResponseSchema, p as conversationEventPageSchema, r as archiveConversationBodySchema, t as acceptedConversationMessageSchema, u as conversationDetailQuerySchema, x as conversationPendingMessagesReportSchema } from "./conversation-9Fxa1Zbq.js";
|
|
8
|
+
import { F as loadTurnProjection, vt as piMessageFromHistoryItem } from "./checkpoint-BlIIiHG5.js";
|
|
9
|
+
import { n as hash, t as create } from "./profile-D6apIG2N.js";
|
|
10
|
+
import { t as getCachedSnapshot } from "./resolve-sJtTzuBO.js";
|
|
11
|
+
import { a as getVercelConversationWorkQueue } from "./job-queue-Dq5_zt_l.js";
|
|
12
|
+
import { F as weekdaySchema, I as getFirstRunAtMs, M as compileScheduleIntent, _ as automationSummarySchema, b as automationVersionListSchema, c as automationLifecycleSchema, d as automationParamsSchema, f as automationRunListSchema, g as automationSchedulePreviewSchema, h as automationScheduleIntentSchema, i as automationExecutionListSchema, j as ScheduleIntentError, l as automationListQuerySchema, n as automationEditSchema, r as automationEventCatalogSchema, t as automationEditErrorSchema, u as automationListSchema, v as automationUpdateSchema, x as automationVersionParamsSchema, y as automationVersionActivateSchema } from "./automation-CrQGQ2MT.js";
|
|
13
|
+
import { C as skillReportsSchema, a as fillUtcHours, b as pluginsSchema, c as sumUtcHoursIntoSixHours, d as trailingUtcSixHourWindow, f as utcDayKey, h as pluginOperationalReportFeedSchema, i as fillUtcDays, l as trailingUtcDayWindow, m as healthReportSchema, n as DAY_MS$1, o as fillUtcSixHours, p as utcHourKey, r as HOUR_MS$1, s as startOfUtcHour, u as trailingUtcHourWindow, x as runtimeInfoReportSchema } from "./web-message-id-xPI_EFSC.js";
|
|
14
|
+
import { C as clearSlackPendingReactions, L as createVercelAttachmentStorage, V as readHealthReport, _ as readViewerAutomationSummary, a as recordWebConversationActivity, b as resolveViewerTaskCandidate, d as resolveSlackConversationContextFromThreadId, f as ViewerTaskNotFoundError, g as readViewerAutomationRuns, h as readViewerAutomationExecutions, i as createConversationId, j as legacyWebMailboxMetadataSchema, l as getProductionSlackAdapter, m as readConversationSourceTask, o as webActorFromEmail, p as deleteViewerTask, r as createAndEnqueueConversation, t as appendAndEnqueueWebMessage, u as formatSlackConversationRedactedLabel, v as readViewerAutomationVersions, y as readViewerAutomations } from "./web-input-C56ivZuH.js";
|
|
15
|
+
import { A as conversationAggregateColumns, j as conversationRangeColumns, k as conversationActiveDaysColumn } from "./task-runner-Bh8XONXT.js";
|
|
16
|
+
import { r as getWebAuthorization } from "./web-authorization-DDt4FZld.js";
|
|
17
|
+
import { t as decodeInputImages } from "./images-D6Y3FJDQ.js";
|
|
18
|
+
import { C as locationParamsSchema, E as activitySixHours$1, I as apiErrorSchema, M as codePersonReportSchema, N as codeRepositoryParamsSchema, P as codeRepositoryReportSchema, S as locationDirectoryReportSchema, T as activityHours$1, _ as actorProfileReportSchema, a as workspaceBodySchema, d as createdPersonalTokenSchema, f as personalTokenListSchema, g as actorDirectoryReportSchema, h as revokePersonalTokenResponseSchema, i as deleteWorkspaceResponseSchema, j as codeOverviewReportSchema, l as workspaceSchema, m as personalTokenParamsSchema, n as statsReportSchema, o as workspaceListSchema, s as workspaceParamsSchema, u as createPersonalTokenBodySchema, v as personParamsSchema, w as activityDays$1, x as locationDetailReportSchema, y as personalSpendReportSchema } from "./schema-C5bgOpRg.js";
|
|
18
19
|
import { Hono } from "hono";
|
|
19
20
|
import path from "node:path";
|
|
20
21
|
import { z } from "zod";
|
|
@@ -186,7 +187,7 @@ function actorFromViewer(viewer) {
|
|
|
186
187
|
});
|
|
187
188
|
}
|
|
188
189
|
/** Create a dashboard root conversation and enqueue its first message. */
|
|
189
|
-
async function createConversationForViewer(viewer, body,
|
|
190
|
+
async function createConversationForViewer(viewer, body, services) {
|
|
190
191
|
const images = parseImages(body.images);
|
|
191
192
|
try {
|
|
192
193
|
return await createAndEnqueueConversation({
|
|
@@ -197,15 +198,15 @@ async function createConversationForViewer(viewer, body, attachmentStorage) {
|
|
|
197
198
|
...body.visibility ? { visibility: body.visibility } : void 0
|
|
198
199
|
}, {
|
|
199
200
|
conversationStore: getConversationStore(),
|
|
200
|
-
attachmentStorage,
|
|
201
|
-
queue:
|
|
201
|
+
attachmentStorage: services.attachmentStorage,
|
|
202
|
+
queue: services.queue
|
|
202
203
|
});
|
|
203
204
|
} catch (error) {
|
|
204
205
|
throwApiError(500, "Unable to create conversation.", error);
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
/** Append one dashboard message to an existing conversation. */
|
|
208
|
-
async function appendConversationMessageForViewer(viewer, conversationId, body,
|
|
209
|
+
async function appendConversationMessageForViewer(viewer, conversationId, body, services) {
|
|
209
210
|
const conversation = await getConversationStore().get({ conversationId });
|
|
210
211
|
if (!conversation) throwApiError(404, "Conversation not found.");
|
|
211
212
|
const destinationPlatform = conversation.destination?.platform;
|
|
@@ -221,8 +222,8 @@ async function appendConversationMessageForViewer(viewer, conversationId, body,
|
|
|
221
222
|
images
|
|
222
223
|
}, {
|
|
223
224
|
conversationStore: getConversationStore(),
|
|
224
|
-
attachmentStorage,
|
|
225
|
-
queue:
|
|
225
|
+
attachmentStorage: services.attachmentStorage,
|
|
226
|
+
queue: services.queue
|
|
226
227
|
});
|
|
227
228
|
} catch (error) {
|
|
228
229
|
throwApiError(500, "Unable to append message.", error);
|
|
@@ -379,6 +380,156 @@ async function resolveSlackTeamDomains(teamIds) {
|
|
|
379
380
|
return new Map(resolved.filter((entry) => entry !== void 0));
|
|
380
381
|
}
|
|
381
382
|
//#endregion
|
|
383
|
+
//#region src/chat/conversations/fork.ts
|
|
384
|
+
/** Event key of the agent note that follows the copied events in a fork. */
|
|
385
|
+
const FORK_NOTE_KEY = "fork:note";
|
|
386
|
+
const forkNote = alias(juniorConversationEvents, "fork_note");
|
|
387
|
+
/**
|
|
388
|
+
* SQL filter for event rows that a fork did not copy from its source. Usage
|
|
389
|
+
* and cost reports use it, so copied model calls count only in the source.
|
|
390
|
+
*/
|
|
391
|
+
function notCopiedByFork() {
|
|
392
|
+
return sql`not exists (
|
|
393
|
+
select 1 from ${juniorConversationEvents} as ${sql.identifier("fork_note")}
|
|
394
|
+
where ${forkNote.conversationId} = ${juniorConversationEvents.conversationId}
|
|
395
|
+
and ${forkNote.idempotencyKey} = ${FORK_NOTE_KEY}
|
|
396
|
+
and ${forkNote.seq} > ${juniorConversationEvents.seq}
|
|
397
|
+
)`;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Events that hold credentials, approvals, provider connections, or plugin
|
|
401
|
+
* state of the source actor. A fork does not copy them.
|
|
402
|
+
*/
|
|
403
|
+
const UNCOPIED_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
404
|
+
"authorization_completed",
|
|
405
|
+
"authorization_requested",
|
|
406
|
+
"guardian_action_reviewed",
|
|
407
|
+
"mcp_provider_connected",
|
|
408
|
+
"mcp_provider_connected_unowned",
|
|
409
|
+
"structured_event",
|
|
410
|
+
"unknown"
|
|
411
|
+
]);
|
|
412
|
+
const FORK_NOTE = "This Conversation is a fork. The earlier messages are copied from another Conversation. Sandbox files, active work, approvals, credentials, Watches, Automations, and the delivery location were not copied. Earlier file paths and runtime context belong to the source Conversation. Verify files in the current Sandbox before you use them.";
|
|
413
|
+
/** An expected fork request failure, such as a reply without saved history. */
|
|
414
|
+
var ConversationForkError = class extends Error {};
|
|
415
|
+
/**
|
|
416
|
+
* Create a new web root Conversation from the agent history of a source
|
|
417
|
+
* Conversation, through one completed assistant reply.
|
|
418
|
+
*
|
|
419
|
+
* Retries with the same requester and key return the same Conversation.
|
|
420
|
+
*/
|
|
421
|
+
async function forkConversation(input) {
|
|
422
|
+
const conversationId = createConversationId({
|
|
423
|
+
actorEmail: input.actorEmail,
|
|
424
|
+
idempotencyKey: JSON.stringify([
|
|
425
|
+
"fork",
|
|
426
|
+
input.sourceConversationId,
|
|
427
|
+
input.idempotencyKey
|
|
428
|
+
])
|
|
429
|
+
});
|
|
430
|
+
const executor = getSqlExecutor();
|
|
431
|
+
const events = getConversationEventStore();
|
|
432
|
+
const store = getConversationStore();
|
|
433
|
+
return withConversationEventLock(executor, input.sourceConversationId, () => withConversationEventLock(executor, conversationId, async () => {
|
|
434
|
+
if (await store.get({ conversationId })) return { conversationId };
|
|
435
|
+
const source = await store.get({ conversationId: input.sourceConversationId });
|
|
436
|
+
if (!source || source.transcriptPurgedAtMs !== void 0) throw new ConversationForkError("The source history is no longer available.");
|
|
437
|
+
if (source.parentConversationId) throw new ConversationForkError("You cannot fork a child conversation.");
|
|
438
|
+
const boundary = await events.loadByIdempotencyKey(input.sourceConversationId, `message:${input.messageId}:agent`);
|
|
439
|
+
const reply = boundary?.data.type === "assistant_message" ? piMessageFromHistoryItem(boundary.data) : void 0;
|
|
440
|
+
if (!boundary || reply?.role !== "assistant" || reply.stopReason !== "stop" || reply.content.some((part) => part.type === "toolCall")) throw new ConversationForkError("Choose a completed assistant reply with saved agent history.");
|
|
441
|
+
const projection = await loadTurnProjection({
|
|
442
|
+
conversationId: input.sourceConversationId,
|
|
443
|
+
committedSeq: boundary.seq,
|
|
444
|
+
includeTail: false
|
|
445
|
+
});
|
|
446
|
+
if (!projection?.messages.length) throw new ConversationForkError("The selected history is no longer available.");
|
|
447
|
+
if (hasUnfinishedToolCalls(projection.messages)) throw new ConversationForkError("This history has unfinished tool calls. Choose another reply.");
|
|
448
|
+
const nowMs = Date.now();
|
|
449
|
+
await recordWebConversationActivity({
|
|
450
|
+
actor: webActorFromEmail(input.actorEmail, { ...input.actorName ? { fullName: input.actorName } : void 0 }),
|
|
451
|
+
conversationId,
|
|
452
|
+
nowMs,
|
|
453
|
+
rootVisibility: source.visibility === "public" ? "public" : "private"
|
|
454
|
+
});
|
|
455
|
+
await executor.db().update(juniorConversations).set({
|
|
456
|
+
forkedFromConversationId: source.conversationId,
|
|
457
|
+
title: source.title ? `Fork: ${source.title}` : "Forked conversation"
|
|
458
|
+
}).where(eq(juniorConversations.conversationId, conversationId));
|
|
459
|
+
await copyForkEvents({
|
|
460
|
+
sourceConversationId: source.conversationId,
|
|
461
|
+
conversationId,
|
|
462
|
+
events: await events.loadHistory(source.conversationId),
|
|
463
|
+
boundarySeq: boundary.seq,
|
|
464
|
+
messageId: input.messageId
|
|
465
|
+
});
|
|
466
|
+
await events.append(conversationId, [{
|
|
467
|
+
idempotencyKey: FORK_NOTE_KEY,
|
|
468
|
+
createdAtMs: nowMs,
|
|
469
|
+
data: {
|
|
470
|
+
type: "user_message",
|
|
471
|
+
provenance: contextProvenance,
|
|
472
|
+
content: [{
|
|
473
|
+
type: "text",
|
|
474
|
+
text: FORK_NOTE
|
|
475
|
+
}],
|
|
476
|
+
timestamp: nowMs
|
|
477
|
+
}
|
|
478
|
+
}]);
|
|
479
|
+
return { conversationId };
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Copy the source event rows through the selected reply. Copies keep their
|
|
484
|
+
* sequence, author, timestamp, key, and payload, so the fork shows the same
|
|
485
|
+
* transcript and event log, and seq references in compactions stay valid.
|
|
486
|
+
* Later updates of copied Messages and the end of copied Turns are also
|
|
487
|
+
* copied, so that no copied item stays open in the fork.
|
|
488
|
+
*/
|
|
489
|
+
async function copyForkEvents(args) {
|
|
490
|
+
const reply = args.events.find((event) => event.data.type === "message" && event.data.role === "assistant" && event.data.messageId === args.messageId);
|
|
491
|
+
const cutoffSeq = Math.max(args.boundarySeq, reply?.seq ?? -1);
|
|
492
|
+
const messageIds = /* @__PURE__ */ new Set();
|
|
493
|
+
const turnIds = /* @__PURE__ */ new Set();
|
|
494
|
+
const seqs = [];
|
|
495
|
+
let historyVersion = 0;
|
|
496
|
+
for (const event of args.events) {
|
|
497
|
+
const data = event.data;
|
|
498
|
+
if (UNCOPIED_EVENT_TYPES.has(data.type)) continue;
|
|
499
|
+
if (event.idempotencyKey === FORK_NOTE_KEY) continue;
|
|
500
|
+
if (event.seq <= cutoffSeq) {
|
|
501
|
+
if (data.type === "message") messageIds.add(data.messageId);
|
|
502
|
+
if (data.type === "turn_started") turnIds.add(data.turnId);
|
|
503
|
+
historyVersion = Math.max(historyVersion, event.historyVersion);
|
|
504
|
+
seqs.push(event.seq);
|
|
505
|
+
} else if ((data.type === "message_updated" || data.type === "message_handled") && messageIds.has(data.messageId) || (data.type === "turn_completed" || data.type === "turn_failed") && turnIds.has(data.turnId)) seqs.push(event.seq);
|
|
506
|
+
}
|
|
507
|
+
if (!seqs.length) return;
|
|
508
|
+
const e = juniorConversationEvents;
|
|
509
|
+
await getSqlExecutor().db().execute(sql`insert into ${e} (${sql.join([
|
|
510
|
+
e.conversationId,
|
|
511
|
+
e.seq,
|
|
512
|
+
e.historyVersion,
|
|
513
|
+
e.schemaVersion,
|
|
514
|
+
e.idempotencyKey,
|
|
515
|
+
e.type,
|
|
516
|
+
e.payload,
|
|
517
|
+
e.actorIdentityId,
|
|
518
|
+
e.createdAt
|
|
519
|
+
].map((column) => sql.identifier(column.name)), sql`, `)})
|
|
520
|
+
select ${args.conversationId}, ${e.seq}, least(${e.historyVersion}, ${historyVersion}), ${e.schemaVersion}, ${e.idempotencyKey}, ${e.type}, ${e.payload}, ${e.actorIdentityId}, ${e.createdAt}
|
|
521
|
+
from ${e}
|
|
522
|
+
where ${e.conversationId} = ${args.sourceConversationId}
|
|
523
|
+
and ${inArray(e.seq, seqs)}`);
|
|
524
|
+
}
|
|
525
|
+
function hasUnfinishedToolCalls(messages) {
|
|
526
|
+
const pending = /* @__PURE__ */ new Set();
|
|
527
|
+
for (const message of messages) if (message.role === "assistant") {
|
|
528
|
+
for (const part of message.content) if (part.type === "toolCall") pending.add(part.id);
|
|
529
|
+
} else if (message.role === "toolResult") pending.delete(message.toolCallId);
|
|
530
|
+
return pending.size > 0;
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
382
533
|
//#region src/chat/pi/sql-model-usage.ts
|
|
383
534
|
/** SQL reporting adapter for usage stored on assistant history events. */
|
|
384
535
|
const message = sql`${juniorConversationEvents.payload}`;
|
|
@@ -474,7 +625,7 @@ async function readConversationModelUsageFromSql(executor, options) {
|
|
|
474
625
|
costCacheRead: summedCost(nonnegativeNumber(cost, "cacheRead")),
|
|
475
626
|
costCacheWrite: summedCost(nonnegativeNumber(cost, "cacheWrite")),
|
|
476
627
|
costTotal: summedCost(nonnegativeNumber(cost, "total"))
|
|
477
|
-
}).from(juniorConversationEvents).innerJoin(juniorConversations, eq(juniorConversations.conversationId, juniorConversationEvents.conversationId)).where(and(options.includeDescendants ? eq(juniorConversations.rootConversationId, options.conversationId) : eq(juniorConversationEvents.conversationId, options.conversationId), eq(juniorConversationEvents.type, "assistant_message"), sql`jsonb_typeof(${message}->'provider') = 'string'`, sql`jsonb_typeof(${message}->'model') = 'string'`, sql`coalesce(${message}->>'provider', '') <> ''`, sql`coalesce(${message}->>'model', '') <> ''`)).groupBy(modelId).orderBy(modelId)).flatMap((row) => {
|
|
628
|
+
}).from(juniorConversationEvents).innerJoin(juniorConversations, eq(juniorConversations.conversationId, juniorConversationEvents.conversationId)).where(and(options.includeDescendants ? eq(juniorConversations.rootConversationId, options.conversationId) : eq(juniorConversationEvents.conversationId, options.conversationId), eq(juniorConversationEvents.type, "assistant_message"), notCopiedByFork(), sql`jsonb_typeof(${message}->'provider') = 'string'`, sql`jsonb_typeof(${message}->'model') = 'string'`, sql`coalesce(${message}->>'provider', '') <> ''`, sql`coalesce(${message}->>'model', '') <> ''`)).groupBy(modelId).orderBy(modelId)).flatMap((row) => {
|
|
478
629
|
const modelUsage = usageFromRow(row);
|
|
479
630
|
return modelUsage ? [{
|
|
480
631
|
modelId: row.modelId,
|
|
@@ -794,7 +945,7 @@ async function readConversationAuxiliaryCostsFromSql(db, conversationIds, option
|
|
|
794
945
|
events: sql`count(*)::integer`.mapWith(Number),
|
|
795
946
|
name,
|
|
796
947
|
namespace
|
|
797
|
-
}).from(juniorConversationEvents).innerJoin(juniorConversations, eq(juniorConversations.conversationId, juniorConversationEvents.conversationId)).leftJoin(auxiliaryRootConversation, and(eq(auxiliaryRootConversation.conversationId, juniorConversations.rootConversationId), isNull(auxiliaryRootConversation.parentConversationId), eq(auxiliaryRootConversation.rootConversationId, auxiliaryRootConversation.conversationId))).where(and(selectedConversation, or(eq(juniorConversationEvents.type, "structured_event"), inArray(juniorConversationEvents.type, ["guardian_action_reviewed", "turn_routed"])), sql`${cost} >= 0
|
|
948
|
+
}).from(juniorConversationEvents).innerJoin(juniorConversations, eq(juniorConversations.conversationId, juniorConversationEvents.conversationId)).leftJoin(auxiliaryRootConversation, and(eq(auxiliaryRootConversation.conversationId, juniorConversations.rootConversationId), isNull(auxiliaryRootConversation.parentConversationId), eq(auxiliaryRootConversation.rootConversationId, auxiliaryRootConversation.conversationId))).where(and(selectedConversation, or(eq(juniorConversationEvents.type, "structured_event"), inArray(juniorConversationEvents.type, ["guardian_action_reviewed", "turn_routed"])), sql`${cost} >= 0`, notCopiedByFork())).groupBy(conversationId, namespace, name).orderBy(conversationId, namespace, name);
|
|
798
949
|
const result = /* @__PURE__ */ new Map();
|
|
799
950
|
for (const row of rows) {
|
|
800
951
|
const current = result.get(row.conversationId) ?? {
|
|
@@ -1192,6 +1343,59 @@ async function readConversationFeed(options = {}) {
|
|
|
1192
1343
|
return conversationFeedSchema.parse(await readConversationFeedFromSql(options));
|
|
1193
1344
|
}
|
|
1194
1345
|
//#endregion
|
|
1346
|
+
//#region src/api/conversations/fork.ts
|
|
1347
|
+
/** Fork a Conversation that the viewer can read into a new root they own. */
|
|
1348
|
+
async function forkConversationForViewer(viewer, sourceConversationId, body) {
|
|
1349
|
+
if (!(await readConversationAccessFromSql(getDb(), [sourceConversationId], viewer)).get(sourceConversationId)?.canViewPrivateContent) throwApiError(403, "You do not have access to this conversation.");
|
|
1350
|
+
try {
|
|
1351
|
+
return await forkConversation({
|
|
1352
|
+
sourceConversationId,
|
|
1353
|
+
...body,
|
|
1354
|
+
actorEmail: viewer.email,
|
|
1355
|
+
...viewer.displayName ? { actorName: viewer.displayName } : void 0
|
|
1356
|
+
});
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
if (error instanceof ConversationForkError) throwApiError(409, error.message);
|
|
1359
|
+
throw error;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Read fork state for the viewer: whether they can fork, and the source and
|
|
1364
|
+
* fork links that they can open.
|
|
1365
|
+
*/
|
|
1366
|
+
async function readConversationForks(conversationId, viewer) {
|
|
1367
|
+
const db = getDb();
|
|
1368
|
+
const [row] = await db.select({
|
|
1369
|
+
forkedFrom: juniorConversations.forkedFromConversationId,
|
|
1370
|
+
parent: juniorConversations.parentConversationId
|
|
1371
|
+
}).from(juniorConversations).where(eq(juniorConversations.conversationId, conversationId));
|
|
1372
|
+
const forks = await db.select({ conversationId: juniorConversations.conversationId }).from(juniorConversations).where(eq(juniorConversations.forkedFromConversationId, conversationId)).orderBy(asc(juniorConversations.createdAt)).limit(50);
|
|
1373
|
+
const forkedFrom = row?.forkedFrom ?? void 0;
|
|
1374
|
+
const ids = forks.map((fork) => fork.conversationId);
|
|
1375
|
+
const access = await readConversationAccessFromSql(db, [
|
|
1376
|
+
conversationId,
|
|
1377
|
+
...ids,
|
|
1378
|
+
...forkedFrom ? [forkedFrom] : []
|
|
1379
|
+
], viewer);
|
|
1380
|
+
const canOpen = (id) => access.get(id)?.canViewPrivateContent;
|
|
1381
|
+
if (!canOpen(conversationId)) return {
|
|
1382
|
+
canFork: false,
|
|
1383
|
+
forks: []
|
|
1384
|
+
};
|
|
1385
|
+
const result = {
|
|
1386
|
+
canFork: !row?.parent,
|
|
1387
|
+
forks: ids.filter(canOpen)
|
|
1388
|
+
};
|
|
1389
|
+
if (forkedFrom && canOpen(forkedFrom)) {
|
|
1390
|
+
const [source] = await db.select({ title: juniorConversations.title }).from(juniorConversations).where(eq(juniorConversations.conversationId, forkedFrom));
|
|
1391
|
+
if (source) {
|
|
1392
|
+
result.forkedFromConversationId = forkedFrom;
|
|
1393
|
+
if (source.title) result.forkedFromTitle = source.title;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
//#endregion
|
|
1195
1399
|
//#region src/api/conversations/detail.ts
|
|
1196
1400
|
/** Project stored metadata and a bounded event page into a signed history cursor. */
|
|
1197
1401
|
function projectConversationDetail(args) {
|
|
@@ -1290,7 +1494,12 @@ async function readConversationDetail(conversationId, options = {}) {
|
|
|
1290
1494
|
...options,
|
|
1291
1495
|
limit: options.limit ?? 500
|
|
1292
1496
|
});
|
|
1293
|
-
|
|
1497
|
+
if (!report) return void 0;
|
|
1498
|
+
const forks = await readConversationForks(conversationId, options.viewer);
|
|
1499
|
+
return conversationDetailReportSchema.parse({
|
|
1500
|
+
...report,
|
|
1501
|
+
...forks
|
|
1502
|
+
});
|
|
1294
1503
|
}
|
|
1295
1504
|
//#endregion
|
|
1296
1505
|
//#region src/api/conversations/event-list.ts
|
|
@@ -1860,14 +2069,15 @@ function createConversationRoutes(options) {
|
|
|
1860
2069
|
app.post("/", requireViewer, messageBodyLimit, validateRequest("json", createConversationBodySchema, "Invalid request body."), async (context) => {
|
|
1861
2070
|
const viewer = context.get("viewer");
|
|
1862
2071
|
const body = context.req.valid("json");
|
|
1863
|
-
return jsonResponse(acceptedConversationMessageSchema, await createConversationForViewer(viewer, body, options
|
|
2072
|
+
return jsonResponse(acceptedConversationMessageSchema, await createConversationForViewer(viewer, body, options));
|
|
1864
2073
|
});
|
|
1865
2074
|
app.post("/:conversationId/messages", requireViewer, messageBodyLimit, validateRequest("param", conversationParamsSchema, "Invalid route parameters."), validateRequest("json", createConversationMessageBodySchema, "Invalid request body."), async (context) => {
|
|
1866
2075
|
const viewer = context.get("viewer");
|
|
1867
2076
|
const { conversationId } = context.req.valid("param");
|
|
1868
2077
|
const body = context.req.valid("json");
|
|
1869
|
-
return jsonResponse(acceptedConversationMessageSchema, await appendConversationMessageForViewer(viewer, conversationId, body, options
|
|
2078
|
+
return jsonResponse(acceptedConversationMessageSchema, await appendConversationMessageForViewer(viewer, conversationId, body, options));
|
|
1870
2079
|
});
|
|
2080
|
+
app.post("/:conversationId/forks", requireViewer, bodyLimit({ maxSize: 4096 }), validateRequest("param", conversationParamsSchema, "Invalid route parameters."), validateRequest("json", forkConversationBodySchema, "Invalid request body."), async (context) => jsonResponse(forkConversationResponseSchema, await forkConversationForViewer(context.get("viewer"), context.req.valid("param").conversationId, context.req.valid("json"))));
|
|
1871
2081
|
app.patch("/:conversationId/archive", requireViewer, validateRequest("param", conversationParamsSchema, "Invalid route parameters."), validateRequest("json", archiveConversationBodySchema, "Invalid request body."), async (context) => {
|
|
1872
2082
|
const viewer = context.get("viewer");
|
|
1873
2083
|
const { conversationId } = context.req.valid("param");
|
|
@@ -3144,7 +3354,7 @@ async function readPeoplePluginReports(args) {
|
|
|
3144
3354
|
reports: [],
|
|
3145
3355
|
source: "plugins"
|
|
3146
3356
|
});
|
|
3147
|
-
const { getPluginProfileReports } = await import("./agent-hooks-
|
|
3357
|
+
const { getPluginProfileReports } = await import("./agent-hooks-otHGbhBx.js").then((n) => n.t);
|
|
3148
3358
|
return pluginOperationalReportFeedSchema.parse({
|
|
3149
3359
|
generatedAt: new Date(nowMs).toISOString(),
|
|
3150
3360
|
reports: await getPluginProfileReports({
|
|
@@ -3593,7 +3803,10 @@ function createUserPageRoutes() {
|
|
|
3593
3803
|
}
|
|
3594
3804
|
//#endregion
|
|
3595
3805
|
//#region src/chat/automations/edit.ts
|
|
3596
|
-
/**
|
|
3806
|
+
/**
|
|
3807
|
+
* Web edits. Owners and public Destination readers can edit. Creator-only
|
|
3808
|
+
* rules still apply, and lifecycle actions stay creator-only.
|
|
3809
|
+
*/
|
|
3597
3810
|
async function requireOwnedAutomation(user, kind, id) {
|
|
3598
3811
|
if (kind === "scheduled") {
|
|
3599
3812
|
const task = await readScheduledAutomation(getDb(), id);
|
|
@@ -3610,9 +3823,26 @@ async function requireOwnedAutomation(user, kind, id) {
|
|
|
3610
3823
|
task
|
|
3611
3824
|
};
|
|
3612
3825
|
}
|
|
3613
|
-
|
|
3826
|
+
/** Owners and public Destination readers can edit. Others get not found. */
|
|
3827
|
+
async function requireEditableAutomation(user, kind, id) {
|
|
3828
|
+
const candidate = await resolveViewerTaskCandidate(user, kind, id);
|
|
3829
|
+
if (!candidate) throw new ViewerTaskNotFoundError();
|
|
3830
|
+
return candidate;
|
|
3831
|
+
}
|
|
3832
|
+
/** Record the viewer's Slack identity in the Automation workspace as the editor. */
|
|
3833
|
+
function viewerEditor(user, automation) {
|
|
3834
|
+
if (automation.ownedByViewer) return automation.task.createdBy;
|
|
3835
|
+
const identity = user.identities.find((candidate) => candidate.provider === "slack" && candidate.providerTenantId === automation.task.destination.teamId);
|
|
3836
|
+
if (!identity) return void 0;
|
|
3837
|
+
const editor = { slackUserId: identity.providerSubjectId };
|
|
3838
|
+
if (identity.displayName) editor.fullName = identity.displayName;
|
|
3839
|
+
if (identity.handle) editor.userName = identity.handle;
|
|
3840
|
+
return editor;
|
|
3841
|
+
}
|
|
3842
|
+
function editView(automation, ownedByViewer) {
|
|
3614
3843
|
const { task } = automation;
|
|
3615
3844
|
const common = {
|
|
3845
|
+
ownedByViewer,
|
|
3616
3846
|
id: task.id,
|
|
3617
3847
|
revision: automationRevision(task),
|
|
3618
3848
|
title: task.title ?? null,
|
|
@@ -3643,27 +3873,131 @@ function editView(automation) {
|
|
|
3643
3873
|
}
|
|
3644
3874
|
/** Load exact editable values by ID, independent of list filters. */
|
|
3645
3875
|
async function readViewerAutomationEdit(user, kind, id) {
|
|
3646
|
-
|
|
3876
|
+
const automation = await requireEditableAutomation(user, kind, id);
|
|
3877
|
+
return editView(automation, automation.ownedByViewer);
|
|
3647
3878
|
}
|
|
3648
3879
|
/** Save a partial edit against the read revision, without dispatching work. */
|
|
3649
3880
|
async function updateViewerAutomation(user, id, input) {
|
|
3650
|
-
const current = await
|
|
3881
|
+
const current = await requireEditableAutomation(user, input.kind, id);
|
|
3651
3882
|
requireAutomationRevision(current.task, input.revision);
|
|
3883
|
+
return saveViewerEdit(user, current, input);
|
|
3884
|
+
}
|
|
3885
|
+
/** `restored` carries version values that edit input cannot express, such as a cleared title. */
|
|
3886
|
+
async function saveViewerEdit(user, current, input, restored) {
|
|
3887
|
+
const isCreator = current.ownedByViewer;
|
|
3888
|
+
const editedBy = viewerEditor(user, current);
|
|
3652
3889
|
if (input.kind === "scheduled" && current.kind === "scheduled") {
|
|
3653
|
-
const next = await editScheduledAutomation(current.task, input,
|
|
3890
|
+
const next = await editScheduledAutomation(current.task, input, isCreator, Date.now());
|
|
3654
3891
|
return editView({
|
|
3655
3892
|
kind: "scheduled",
|
|
3656
|
-
task: await saveScheduledAutomation(getDb(),
|
|
3657
|
-
|
|
3893
|
+
task: await saveScheduledAutomation(getDb(), restored ? {
|
|
3894
|
+
...next,
|
|
3895
|
+
...restored
|
|
3896
|
+
} : next, input.revision, editedBy)
|
|
3897
|
+
}, isCreator);
|
|
3658
3898
|
}
|
|
3659
3899
|
if (input.kind === "event" && current.kind === "event") {
|
|
3660
|
-
const next = await editEventAutomation(current.task, input,
|
|
3661
|
-
const task = await saveEventAutomation(getDb(),
|
|
3900
|
+
const next = await editEventAutomation(current.task, input, isCreator, getEventCatalog());
|
|
3901
|
+
const task = await saveEventAutomation(getDb(), restored ? {
|
|
3902
|
+
...next,
|
|
3903
|
+
title: restored.title
|
|
3904
|
+
} : next, input.revision, editedBy);
|
|
3662
3905
|
if (!task) throw new ViewerTaskNotFoundError();
|
|
3663
3906
|
return editView({
|
|
3664
3907
|
kind: "event",
|
|
3665
3908
|
task
|
|
3666
|
-
});
|
|
3909
|
+
}, isCreator);
|
|
3910
|
+
}
|
|
3911
|
+
throw new ViewerTaskNotFoundError();
|
|
3912
|
+
}
|
|
3913
|
+
function scheduleIntent(schedule) {
|
|
3914
|
+
const recurrence = schedule.recurrence;
|
|
3915
|
+
if (schedule.kind !== "recurring" || !recurrence) throw new AutomationEditError("A one-time Schedule cannot be restored. Set a new time in the editor.", "schedule");
|
|
3916
|
+
return {
|
|
3917
|
+
kind: "recurring",
|
|
3918
|
+
frequency: recurrence.frequency,
|
|
3919
|
+
interval: recurrence.interval,
|
|
3920
|
+
time: `${String(recurrence.time.hour).padStart(2, "0")}:${String(recurrence.time.minute).padStart(2, "0")}`,
|
|
3921
|
+
weekdays: recurrence.weekdays?.map((day) => weekdaySchema.options[day]),
|
|
3922
|
+
dayOfMonth: recurrence.dayOfMonth,
|
|
3923
|
+
month: recurrence.month,
|
|
3924
|
+
startDate: recurrence.startDate,
|
|
3925
|
+
timezone: schedule.timezone
|
|
3926
|
+
};
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Turn saved outcomes back into edit input. Messages to the Automation
|
|
3930
|
+
* Destination or the creator DM are always allowed again. Other stored
|
|
3931
|
+
* Destinations must still be on the Automation.
|
|
3932
|
+
*/
|
|
3933
|
+
async function restorableOutcomes(current, outcomes) {
|
|
3934
|
+
const { task } = current;
|
|
3935
|
+
let creatorDm;
|
|
3936
|
+
const restored = [];
|
|
3937
|
+
for (const outcome of outcomes) {
|
|
3938
|
+
if (sameDefinitionValue(outcome.destination, task.destination)) {
|
|
3939
|
+
restored.push({
|
|
3940
|
+
action: outcome.action,
|
|
3941
|
+
destination: "current_conversation"
|
|
3942
|
+
});
|
|
3943
|
+
continue;
|
|
3944
|
+
}
|
|
3945
|
+
if (current.ownedByViewer && !task.outcomes.some((stored) => sameDefinitionValue(stored, outcome))) {
|
|
3946
|
+
creatorDm ??= (await resolveTaskOutcomes([{
|
|
3947
|
+
action: "send_message",
|
|
3948
|
+
destination: "task_creator"
|
|
3949
|
+
}], task.destination, task.createdBy.slackUserId))[0].destination;
|
|
3950
|
+
if (sameDefinitionValue(outcome.destination, creatorDm)) {
|
|
3951
|
+
restored.push({
|
|
3952
|
+
action: outcome.action,
|
|
3953
|
+
destination: "task_creator"
|
|
3954
|
+
});
|
|
3955
|
+
continue;
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
restored.push(outcome);
|
|
3959
|
+
}
|
|
3960
|
+
return restored;
|
|
3961
|
+
}
|
|
3962
|
+
/**
|
|
3963
|
+
* Make a saved version active. This saves its definition as a new version
|
|
3964
|
+
* through the same edit rules, so history is never rewritten.
|
|
3965
|
+
*/
|
|
3966
|
+
async function activateViewerAutomationVersion(user, kind, id, input) {
|
|
3967
|
+
const current = await requireEditableAutomation(user, kind, id);
|
|
3968
|
+
requireAutomationRevision(current.task, input.revision);
|
|
3969
|
+
const saved = await readAutomationVersion(getDb(), kind, id, input.version);
|
|
3970
|
+
if (!saved) throw new AutomationEditError("This version does not exist.", "version");
|
|
3971
|
+
const { definition } = saved;
|
|
3972
|
+
if (sameDefinitionValue(definition, automationDefinition(current.task))) return editView(current, current.ownedByViewer);
|
|
3973
|
+
if (!sameDefinitionValue(definition.destination, current.task.destination)) throw new AutomationEditError("This version delivers to another Destination. Move the automation from Slack instead.", "destination");
|
|
3974
|
+
const fields = {};
|
|
3975
|
+
if (definition.instruction !== current.task.task.text) fields.instruction = definition.instruction;
|
|
3976
|
+
if (definition.credentialMode !== current.task.credentialMode) fields.credentialMode = definition.credentialMode;
|
|
3977
|
+
if (!sameDefinitionValue(definition.outcomes, current.task.outcomes)) fields.outcomes = await restorableOutcomes(current, definition.outcomes);
|
|
3978
|
+
const title = definition.title ?? void 0;
|
|
3979
|
+
if (saved.kind === "scheduled" && current.kind === "scheduled") {
|
|
3980
|
+
const update = {
|
|
3981
|
+
kind: "scheduled",
|
|
3982
|
+
revision: input.revision,
|
|
3983
|
+
...fields
|
|
3984
|
+
};
|
|
3985
|
+
const restored = { title };
|
|
3986
|
+
const { schedule } = saved.definition;
|
|
3987
|
+
if (!sameDefinitionValue(schedule, current.task.schedule)) {
|
|
3988
|
+
update.schedule = scheduleIntent(schedule);
|
|
3989
|
+
restored.schedule = schedule;
|
|
3990
|
+
}
|
|
3991
|
+
return saveViewerEdit(user, current, update, restored);
|
|
3992
|
+
}
|
|
3993
|
+
if (saved.kind === "event" && current.kind === "event") {
|
|
3994
|
+
const update = {
|
|
3995
|
+
kind: "event",
|
|
3996
|
+
revision: input.revision,
|
|
3997
|
+
...fields
|
|
3998
|
+
};
|
|
3999
|
+
if (!sameDefinitionValue(saved.definition.trigger, current.task.trigger)) update.trigger = saved.definition.trigger;
|
|
4000
|
+
return saveViewerEdit(user, current, update, { title });
|
|
3667
4001
|
}
|
|
3668
4002
|
throw new ViewerTaskNotFoundError();
|
|
3669
4003
|
}
|
|
@@ -3673,12 +4007,12 @@ async function changeViewerAutomationLifecycle(user, kind, id, input) {
|
|
|
3673
4007
|
requireAutomationRevision(current.task, input.revision);
|
|
3674
4008
|
const status = current.task.status;
|
|
3675
4009
|
if (status === "completed") throw new AutomationEditError("Completed Automations cannot be restarted.");
|
|
3676
|
-
if (input.action === "pause" && status === "paused") return editView(current);
|
|
4010
|
+
if (input.action === "pause" && status === "paused") return editView(current, true);
|
|
3677
4011
|
if (input.action === "resume" && status !== "paused" && status !== "blocked") throw new AutomationEditError("Only paused or blocked Automations can be resumed.");
|
|
3678
4012
|
if (current.kind === "event") return editView({
|
|
3679
4013
|
kind: "event",
|
|
3680
4014
|
task: await setEventAutomationStatus(getDb(), id, input.action === "pause" ? "paused" : "active", input.revision)
|
|
3681
|
-
});
|
|
4015
|
+
}, true);
|
|
3682
4016
|
const nowMs = Date.now();
|
|
3683
4017
|
const next = {
|
|
3684
4018
|
...current.task,
|
|
@@ -3703,7 +4037,7 @@ async function changeViewerAutomationLifecycle(user, kind, id, input) {
|
|
|
3703
4037
|
return editView({
|
|
3704
4038
|
kind: "scheduled",
|
|
3705
4039
|
task: await saveScheduledAutomation(getDb(), next, input.revision)
|
|
3706
|
-
});
|
|
4040
|
+
}, true);
|
|
3707
4041
|
}
|
|
3708
4042
|
//#endregion
|
|
3709
4043
|
//#region src/chat/automations/editor.ts
|
|
@@ -3782,6 +4116,26 @@ function createAutomationRoutes() {
|
|
|
3782
4116
|
throw error;
|
|
3783
4117
|
}
|
|
3784
4118
|
});
|
|
4119
|
+
app.get("/:kind/:id/versions", requireViewer, validateRequest("param", automationParamsSchema, "Invalid Automation."), async (context) => {
|
|
4120
|
+
const { kind, id } = context.req.valid("param");
|
|
4121
|
+
try {
|
|
4122
|
+
return jsonResponse(automationVersionListSchema, await readViewerAutomationVersions(context.get("viewer"), kind, id));
|
|
4123
|
+
} catch (error) {
|
|
4124
|
+
if (error instanceof ViewerTaskNotFoundError) return jsonResponse(apiErrorSchema, { error: error.message }, { status: 404 });
|
|
4125
|
+
throw error;
|
|
4126
|
+
}
|
|
4127
|
+
});
|
|
4128
|
+
app.post("/:kind/:id/versions/:version/activate", requireViewer, validateRequest("param", automationVersionParamsSchema, "Invalid Automation version."), validateRequest("json", automationVersionActivateSchema, "Invalid version activation."), async (context) => {
|
|
4129
|
+
const { kind, id, version } = context.req.valid("param");
|
|
4130
|
+
try {
|
|
4131
|
+
return jsonResponse(automationEditSchema, await activateViewerAutomationVersion(context.get("viewer"), kind, id, {
|
|
4132
|
+
version,
|
|
4133
|
+
revision: context.req.valid("json").revision
|
|
4134
|
+
}));
|
|
4135
|
+
} catch (error) {
|
|
4136
|
+
return editErrorResponse(error);
|
|
4137
|
+
}
|
|
4138
|
+
});
|
|
3785
4139
|
app.get("/:kind/:id/edit", requireViewer, validateRequest("param", automationParamsSchema, "Invalid Automation."), async (context) => {
|
|
3786
4140
|
const { kind, id } = context.req.valid("param");
|
|
3787
4141
|
try {
|
|
@@ -3963,7 +4317,7 @@ function readDescriptionText(home) {
|
|
|
3963
4317
|
}
|
|
3964
4318
|
/** Read discovered skill names for authenticated runtime diagnostics. */
|
|
3965
4319
|
async function readSkillReports() {
|
|
3966
|
-
const { discoverSkills } = await import("./skills-
|
|
4320
|
+
const { discoverSkills } = await import("./skills-BVpr_Gnv.js").then((n) => n.o);
|
|
3967
4321
|
const skills = await discoverSkills();
|
|
3968
4322
|
return skillReportsSchema.parse(skills.map((skill) => ({
|
|
3969
4323
|
name: skill.name,
|
|
@@ -3972,7 +4326,7 @@ async function readSkillReports() {
|
|
|
3972
4326
|
}
|
|
3973
4327
|
/** Read safe configured plugin metadata for authenticated runtime diagnostics. */
|
|
3974
4328
|
async function readPlugins() {
|
|
3975
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-
|
|
4329
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-DlXuhrmk.js").then((n) => n.t);
|
|
3976
4330
|
return pluginsSchema.parse(pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
3977
4331
|
configKeys: plugin.manifest.configKeys ?? [],
|
|
3978
4332
|
description: plugin.manifest.description,
|
|
@@ -3983,8 +4337,8 @@ async function readPlugins() {
|
|
|
3983
4337
|
/** Read authenticated runtime discovery data. */
|
|
3984
4338
|
async function readRuntimeInfoReport() {
|
|
3985
4339
|
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
3986
|
-
import("./package-discovery-
|
|
3987
|
-
import("./catalog-runtime-
|
|
4340
|
+
import("./package-discovery-BVvSnQBB.js").then((n) => n.r),
|
|
4341
|
+
import("./catalog-runtime-DlXuhrmk.js").then((n) => n.t),
|
|
3988
4342
|
readPlugins(),
|
|
3989
4343
|
readSkillReports()
|
|
3990
4344
|
]);
|
|
@@ -4001,7 +4355,7 @@ async function readRuntimeInfoReport() {
|
|
|
4001
4355
|
/** Read sanitized operational summaries contributed by plugins. */
|
|
4002
4356
|
async function readPluginOperationalReportFeed() {
|
|
4003
4357
|
const nowMs = Date.now();
|
|
4004
|
-
const { getPluginOperationalReports } = await import("./agent-hooks-
|
|
4358
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-otHGbhBx.js").then((n) => n.t);
|
|
4005
4359
|
return pluginOperationalReportFeedSchema.parse({
|
|
4006
4360
|
source: "plugins",
|
|
4007
4361
|
generatedAt: new Date(nowMs).toISOString(),
|
|
@@ -4011,7 +4365,7 @@ async function readPluginOperationalReportFeed() {
|
|
|
4011
4365
|
//#endregion
|
|
4012
4366
|
//#region src/api.ts
|
|
4013
4367
|
/** Create Junior's production REST API for authenticated dashboard consumers. */
|
|
4014
|
-
function createJuniorApi() {
|
|
4368
|
+
function createJuniorApi(options = {}) {
|
|
4015
4369
|
const app = new Hono();
|
|
4016
4370
|
app.get("/api/health", async () => jsonResponse(healthReportSchema, readHealthReport()));
|
|
4017
4371
|
app.get("/api/runtime", async () => jsonResponse(runtimeInfoReportSchema, await readRuntimeInfoReport()));
|
|
@@ -4019,7 +4373,10 @@ function createJuniorApi() {
|
|
|
4019
4373
|
app.get("/api/skills", async () => jsonResponse(skillReportsSchema, await readSkillReports()));
|
|
4020
4374
|
app.get("/api/plugin-reports", async () => jsonResponse(pluginOperationalReportFeedSchema, await readPluginOperationalReportFeed()));
|
|
4021
4375
|
app.get("/api/stats", async () => jsonResponse(statsReportSchema, await readStatsReport()));
|
|
4022
|
-
app.route("/api/conversations", createConversationRoutes({
|
|
4376
|
+
app.route("/api/conversations", createConversationRoutes({
|
|
4377
|
+
attachmentStorage: createVercelAttachmentStorage(),
|
|
4378
|
+
queue: options.conversationWorkQueue ?? getVercelConversationWorkQueue()
|
|
4379
|
+
}));
|
|
4023
4380
|
app.route("/api/code", createCodeRoutes());
|
|
4024
4381
|
app.route("/api/personal-tokens", createPersonalTokenRoutes());
|
|
4025
4382
|
app.route("/api/people", createPeopleRoutes());
|