@sentry/junior 0.234.0 → 0.236.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-BcH6E48y.js → agent-hooks-Da_H0jHv.js} +17 -10
- package/dist/{agent-runner-DUkGlK89.js → agent-runner-uP7QRc4c.js} +484 -162
- package/dist/api/schema.d.ts +288 -2
- package/dist/api/schema.js +5 -4
- package/dist/api.d.ts +8 -2
- package/dist/api.js +524 -83
- package/dist/{app-xk2-Id1T.d.ts → app-MEd7wbF0.d.ts} +83 -49
- package/dist/app.d.ts +3 -3
- package/dist/app.js +164 -119
- package/dist/{types-Du-HbEun.js → automation-B9Kc1gcf.js} +304 -33
- package/dist/{briefs-lyAQbDxJ.js → briefs-DrjBPQFy.js} +4 -4
- package/dist/{catalog-runtime-BSgsPVnr.js → catalog-runtime-C7339Cfm.js} +1 -1
- package/dist/{checkpoint-BNfXlq_1.js → checkpoint-DaD7_Gsb.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-BX5lJRqW.js → client-Ch9Hg9NU.js} +2 -2
- package/dist/{config-DuCzUoCo.js → config-JR9wke1W.js} +57 -17
- package/dist/{conversation-CPjHYOV5.js → conversation-9Fxa1Zbq.js} +14 -1
- package/dist/{conversation-privacy-CSxvdSoV.js → conversation-privacy-au02dyZK.js} +1 -1
- package/dist/{credential-sync-BO4DfH9a.js → credential-sync-BWTA3LnL.js} +14 -14
- package/dist/{executor--jWf-Nnm.js → executor-BW9XWgUQ.js} +70 -6
- package/dist/{images-BaL19wFC.js → images-BdG7zcNf.js} +3 -3
- package/dist/instrumentation.js +2 -2
- package/dist/{job-queue-cwHVOwVZ.js → job-queue-BqXd0ZPV.js} +1 -1
- package/dist/{logging-Cox1Mgcv.js → logging-B4sKwmCG.js} +1 -1
- package/dist/nitro.d.ts +2 -2
- package/dist/nitro.js +5 -5
- package/dist/{oauth-authorization-CC8a1JKP.js → oauth-authorization-BjRN99dc.js} +1 -1
- package/dist/{oauth-callback-server-Z7QSt416.js → oauth-callback-server-B_1p98Mb.js} +2 -2
- package/dist/{pending-auth-DQhC3eSR.js → pending-auth-WqF4Gh8Z.js} +1 -1
- package/dist/{plugins-BT2l4We3.d.ts → plugins-CjfUP5gJ.d.ts} +1 -0
- package/dist/{profile-BdApelHS.js → profile-BamYnmhO.js} +4 -4
- package/dist/{prompt-3D0sSbTC.js → prompt-Ctwy6hSI.js} +1 -1
- package/dist/queue-B5SnqRln.d.ts +25 -0
- package/dist/{registry-DiY6B8CN.js → registry-CXNv9fDX.js} +38 -2
- package/dist/{resolve-VpJoiDpc.js → resolve-C9ETkt4T.js} +2 -2
- package/dist/{runner-BIMegnPE.js → runner-DnBGVRnW.js} +9 -9
- package/dist/{sandbox-egress-signals-Cbtxe492.js → sandbox-egress-signals-DwJ7Boi_.js} +1 -1
- package/dist/{schema-CxwnlTDN.js → schema-CJiqDc5r.js} +26 -3
- package/dist/{skills-WttpPJUy.js → skills-Dd8O9Aav.js} +3 -3
- package/dist/{spawn-DhfMKt4y.js → spawn-DFdRspSD.js} +2 -2
- package/dist/{task-queue-CZqUNJD0.js → task-queue-DGQEOk1A.js} +16 -10
- package/dist/{task-runner-BZaqoUCT.js → task-runner-C5XCIDSF.js} +9 -9
- package/dist/{thread-state-8tXZhu0y.js → thread-state-D3E29wZk.js} +5 -5
- package/dist/{turn-failure-response-DFJ9L9NQ.js → turn-failure-response-TDoj0aZz.js} +8 -8
- package/dist/{turn-lifecycle-WbpTUQ_r.js → turn-lifecycle-DOnU45p7.js} +1 -1
- package/dist/{turn-session-routing--EhI76rH.js → turn-session-routing-DbpZZoin.js} +1 -1
- package/dist/{validation-0F9IgG3b.js → validation-B9lMbisx.js} +1 -1
- package/dist/{version-A7-RRBOE.js → version-CWkIGcLU.js} +1 -1
- package/dist/version.js +1 -1
- package/dist/{web-authorization-BJggXofy.js → web-authorization-PPaoj2ns.js} +6 -6
- package/dist/{web-input-B-siN5Dr.js → web-input-CfNxXtHm.js} +86 -34
- package/dist/web-message-id-xPI_EFSC.js +239 -0
- package/dist/{work-CNc0ejGG.js → work-DNaoFrEe.js} +7 -7
- package/dist/{worker-BHbqpUAu.js → worker-xeJLxYwm.js} +5 -5
- package/migrations/0045_conversation_forks.sql +3 -0
- package/migrations/0046_automation_versions.sql +71 -0
- package/migrations/meta/0045_snapshot.json +3503 -0
- package/migrations/meta/0046_snapshot.json +3707 -0
- package/migrations/meta/_journal.json +15 -1
- package/package.json +5 -5
- 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 {
|
|
2
|
-
import { d as logException, p as logWarn } from "./logging-
|
|
1
|
+
import { $ as resolveTaskOutcomes, Et as getEventCatalog, G as saveScheduledAutomation, M as parseSlackThreadId, Nt as cancelHumanFacingPendingMessages, Ot as pluginEventCatalog, R as editScheduledAutomation, Ut as getConversation, W as readScheduledAutomation, X as AutomationEditError, Y as editEventAutomation, Z as eventAutomationTriggerAvailable, ct as setEventAutomationStatus, dt as readAutomationVersion, ft as sameDefinitionValue, g as readStats, ht as requireAutomationRevision, it as getEventAutomation, k as readLiveAttachment, lt as automationDefinition, mt as automationRevision, nt as eventAutomationBelongsToUser, pt as AutomationConflictError, st as saveEventAutomation } from "./agent-runner-uP7QRc4c.js";
|
|
2
|
+
import { d as logException, p as logWarn } from "./logging-B4sKwmCG.js";
|
|
3
3
|
import { b as parseSlackMessageTs, h as parseSlackTeamId, p as parseSlackChannelId } from "./context-0Et9iVSB.js";
|
|
4
|
-
import {
|
|
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-Da_H0jHv.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-BW9XWgUQ.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-DaD7_Gsb.js";
|
|
9
|
+
import { n as hash, t as create } from "./profile-BamYnmhO.js";
|
|
10
|
+
import { t as getCachedSnapshot } from "./resolve-C9ETkt4T.js";
|
|
11
|
+
import { a as getVercelConversationWorkQueue } from "./job-queue-BqXd0ZPV.js";
|
|
12
|
+
import { F as getFirstRunAtMs, M as compileScheduleIntent, P as weekdaySchema, _ 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-B9Kc1gcf.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, H as readHealthReport, M as legacyWebMailboxMetadataSchema, R as createVercelAttachmentStorage, _ as readViewerAutomationSummary, a as recordWebConversationActivity, b as resolveViewerTaskCandidate, d as resolveSlackConversationContextFromThreadId, f as ViewerTaskNotFoundError, g as readViewerAutomationRuns, h as readViewerAutomationExecutions, i as createConversationId, l as getProductionSlackAdapter, m as readConversationSourceTask, o as webActorFromEmail, p as deleteViewerTask, r as createAndEnqueueConversation, t as appendAndEnqueueWebMessage, u as formatSlackConversationRedactedLabel, v as readViewerAutomationVersions, w as lookupSlackUser, y as readViewerAutomations } from "./web-input-CfNxXtHm.js";
|
|
15
|
+
import { A as conversationAggregateColumns, j as conversationRangeColumns, k as conversationActiveDaysColumn } from "./task-runner-C5XCIDSF.js";
|
|
16
|
+
import { r as getWebAuthorization } from "./web-authorization-PPaoj2ns.js";
|
|
17
|
+
import { t as decodeInputImages } from "./images-BdG7zcNf.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-CJiqDc5r.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) ?? {
|
|
@@ -997,7 +1148,26 @@ function conversationFeedMembershipFilter(status, filter) {
|
|
|
997
1148
|
participantMatch: conversationHasParticipantEmail(filter.email)
|
|
998
1149
|
}), status === "archived" ? conversationArchivedForEmail(filter.email) : status === "all" ? void 0 : conversationNotArchivedForEmail(filter.email));
|
|
999
1150
|
}
|
|
1000
|
-
|
|
1151
|
+
/**
|
|
1152
|
+
* Keep root conversations linked to one code repository. A code change can
|
|
1153
|
+
* link a child conversation, so linked ids resolve to their root. Private
|
|
1154
|
+
* roots stay visible only to their members.
|
|
1155
|
+
*/
|
|
1156
|
+
function conversationCodeRepositoryFilter(repositoryId, viewerUserId) {
|
|
1157
|
+
return and(sql`${juniorConversations.conversationId} IN (
|
|
1158
|
+
SELECT coalesce(linked.root_conversation_id, linked.conversation_id)
|
|
1159
|
+
FROM ${juniorConversations} AS linked
|
|
1160
|
+
WHERE linked.conversation_id IN (
|
|
1161
|
+
SELECT unnest(${juniorCodeChanges.conversationIds})
|
|
1162
|
+
FROM ${juniorCodeChanges}
|
|
1163
|
+
WHERE ${juniorCodeChanges.repositoryId} = ${repositoryId}
|
|
1164
|
+
)
|
|
1165
|
+
)`, or(eq(juniorDestinations.visibility, "public"), viewerUserId ? viewerConversationMembership({
|
|
1166
|
+
actorMatch: eq(juniorIdentities.userId, viewerUserId),
|
|
1167
|
+
participantMatch: conversationHasParticipantUser(viewerUserId)
|
|
1168
|
+
}) : void 0));
|
|
1169
|
+
}
|
|
1170
|
+
async function conversationRows(db, limit, status, filter, query, includePrivateBriefs = false, codeRepository) {
|
|
1001
1171
|
return db.select({
|
|
1002
1172
|
conversation: conversationReadColumns(),
|
|
1003
1173
|
location: juniorDestinations,
|
|
@@ -1008,7 +1178,7 @@ async function conversationRows(db, limit, status, filter, query, includePrivate
|
|
|
1008
1178
|
identitySubjectId: juniorIdentities.providerSubjectId,
|
|
1009
1179
|
identityTenantId: juniorIdentities.providerTenantId,
|
|
1010
1180
|
userDisplayName: juniorUsers.displayName
|
|
1011
|
-
}).from(juniorConversations).leftJoin(juniorDestinations, eq(juniorDestinations.id, juniorConversations.destinationId)).leftJoin(juniorIdentities, eq(juniorIdentities.id, juniorConversations.actorIdentityId)).leftJoin(juniorUsers, eq(juniorUsers.id, juniorIdentities.userId)).where(and(isNull(juniorConversations.parentConversationId), conversationFeedMembershipFilter(status, filter), query ? or(sql`strpos(lower(coalesce(${juniorConversations.title}, '')), ${query}) > 0`, sql`exists (
|
|
1181
|
+
}).from(juniorConversations).leftJoin(juniorDestinations, eq(juniorDestinations.id, juniorConversations.destinationId)).leftJoin(juniorIdentities, eq(juniorIdentities.id, juniorConversations.actorIdentityId)).leftJoin(juniorUsers, eq(juniorUsers.id, juniorIdentities.userId)).where(and(isNull(juniorConversations.parentConversationId), conversationFeedMembershipFilter(status, filter), codeRepository ? conversationCodeRepositoryFilter(codeRepository.id, codeRepository.viewerUserId) : void 0, query ? or(sql`strpos(lower(coalesce(${juniorConversations.title}, '')), ${query}) > 0`, sql`exists (
|
|
1012
1182
|
select 1
|
|
1013
1183
|
from ${juniorConversationBriefs}
|
|
1014
1184
|
where ${juniorConversationBriefs.conversationId} = ${juniorConversations.conversationId}
|
|
@@ -1092,13 +1262,20 @@ function conversationFeedFilter(options) {
|
|
|
1092
1262
|
* Build a bounded dashboard feed. Prefer the viewer user when present; otherwise
|
|
1093
1263
|
* keep only roots linked to actorEmail before applying the limit. Membership is
|
|
1094
1264
|
* root-actor ownership or a materialized participant row for that person.
|
|
1265
|
+
*
|
|
1266
|
+
* `codeRepositoryId` replaces membership with every root linked to that
|
|
1267
|
+
* repository's code changes. Private roots appear only for their members, and
|
|
1268
|
+
* per-person archive state does not hide rows.
|
|
1095
1269
|
*/
|
|
1096
1270
|
async function readConversationFeedFromSql(options = {}) {
|
|
1097
1271
|
const nowMs = Date.now();
|
|
1098
1272
|
const db = getDb();
|
|
1099
|
-
const filter = conversationFeedFilter(options);
|
|
1273
|
+
const filter = options.codeRepositoryId ? void 0 : conversationFeedFilter(options);
|
|
1100
1274
|
const query = options.q?.trim().toLowerCase() || void 0;
|
|
1101
|
-
const rows = await conversationRows(db, options.limit ?? CONVERSATION_FEED_LIMIT, query ? "all" : options.status ?? "active", filter, query, filter?.kind === "viewer"
|
|
1275
|
+
const rows = await conversationRows(db, options.limit ?? CONVERSATION_FEED_LIMIT, query || options.codeRepositoryId ? "all" : options.status ?? "active", filter, query, filter?.kind === "viewer", options.codeRepositoryId ? {
|
|
1276
|
+
id: options.codeRepositoryId,
|
|
1277
|
+
...options.viewer ? { viewerUserId: options.viewer.id } : void 0
|
|
1278
|
+
} : void 0);
|
|
1102
1279
|
const conversations = rows.map((row) => conversationFromRow(row));
|
|
1103
1280
|
const conversationIds = conversations.map((conversation) => conversation.conversationId);
|
|
1104
1281
|
const [accessByConversation, archiveTimesByConversation, annotationsByConversation, auxiliaryCostsByRoot, metricsByRoot, participantsByConversation, teamDomainByTeamId, conversationWork, lastUserMessageAtByConversation, activityPreviewByConversation] = await Promise.all([
|
|
@@ -1166,6 +1343,59 @@ async function readConversationFeed(options = {}) {
|
|
|
1166
1343
|
return conversationFeedSchema.parse(await readConversationFeedFromSql(options));
|
|
1167
1344
|
}
|
|
1168
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
|
|
1169
1399
|
//#region src/api/conversations/detail.ts
|
|
1170
1400
|
/** Project stored metadata and a bounded event page into a signed history cursor. */
|
|
1171
1401
|
function projectConversationDetail(args) {
|
|
@@ -1264,7 +1494,12 @@ async function readConversationDetail(conversationId, options = {}) {
|
|
|
1264
1494
|
...options,
|
|
1265
1495
|
limit: options.limit ?? 500
|
|
1266
1496
|
});
|
|
1267
|
-
|
|
1497
|
+
if (!report) return void 0;
|
|
1498
|
+
const forks = await readConversationForks(conversationId, options.viewer);
|
|
1499
|
+
return conversationDetailReportSchema.parse({
|
|
1500
|
+
...report,
|
|
1501
|
+
...forks
|
|
1502
|
+
});
|
|
1268
1503
|
}
|
|
1269
1504
|
//#endregion
|
|
1270
1505
|
//#region src/api/conversations/event-list.ts
|
|
@@ -1820,10 +2055,11 @@ function createConversationRoutes(options) {
|
|
|
1820
2055
|
}
|
|
1821
2056
|
});
|
|
1822
2057
|
app.get("/", validateRequest("query", conversationFeedQuerySchema, "Invalid query parameters."), async (context) => {
|
|
1823
|
-
const { actorEmail, q, status } = context.req.valid("query");
|
|
2058
|
+
const { actorEmail, codeRepositoryId, q, status } = context.req.valid("query");
|
|
1824
2059
|
const viewer = context.get("viewer");
|
|
1825
2060
|
return jsonResponse(conversationFeedSchema, await readConversationFeed({
|
|
1826
2061
|
...actorEmail ? { actorEmail } : void 0,
|
|
2062
|
+
...codeRepositoryId ? { codeRepositoryId } : void 0,
|
|
1827
2063
|
...q ? { q } : void 0,
|
|
1828
2064
|
status,
|
|
1829
2065
|
...viewer ? { viewer } : void 0
|
|
@@ -1833,14 +2069,15 @@ function createConversationRoutes(options) {
|
|
|
1833
2069
|
app.post("/", requireViewer, messageBodyLimit, validateRequest("json", createConversationBodySchema, "Invalid request body."), async (context) => {
|
|
1834
2070
|
const viewer = context.get("viewer");
|
|
1835
2071
|
const body = context.req.valid("json");
|
|
1836
|
-
return jsonResponse(acceptedConversationMessageSchema, await createConversationForViewer(viewer, body, options
|
|
2072
|
+
return jsonResponse(acceptedConversationMessageSchema, await createConversationForViewer(viewer, body, options));
|
|
1837
2073
|
});
|
|
1838
2074
|
app.post("/:conversationId/messages", requireViewer, messageBodyLimit, validateRequest("param", conversationParamsSchema, "Invalid route parameters."), validateRequest("json", createConversationMessageBodySchema, "Invalid request body."), async (context) => {
|
|
1839
2075
|
const viewer = context.get("viewer");
|
|
1840
2076
|
const { conversationId } = context.req.valid("param");
|
|
1841
2077
|
const body = context.req.valid("json");
|
|
1842
|
-
return jsonResponse(acceptedConversationMessageSchema, await appendConversationMessageForViewer(viewer, conversationId, body, options
|
|
2078
|
+
return jsonResponse(acceptedConversationMessageSchema, await appendConversationMessageForViewer(viewer, conversationId, body, options));
|
|
1843
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"))));
|
|
1844
2081
|
app.patch("/:conversationId/archive", requireViewer, validateRequest("param", conversationParamsSchema, "Invalid route parameters."), validateRequest("json", archiveConversationBodySchema, "Invalid request body."), async (context) => {
|
|
1845
2082
|
const viewer = context.get("viewer");
|
|
1846
2083
|
const { conversationId } = context.req.valid("param");
|
|
@@ -1994,17 +2231,18 @@ function ownedByUserSql(conversationIds, userId) {
|
|
|
1994
2231
|
WHERE identities.user_id = ${userId}
|
|
1995
2232
|
)`;
|
|
1996
2233
|
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
return ownedByUserSql(sql`${juniorCodeChanges.conversationIds}`, userId);
|
|
2234
|
+
/** Limit code changes to one person's conversations or one repository. */
|
|
2235
|
+
function changeScopeFilter(args) {
|
|
2236
|
+
return and(args.userId ? ownedByUserSql(sql`${juniorCodeChanges.conversationIds}`, args.userId) : void 0, args.repositoryId ? eq(juniorCodeChanges.repositoryId, args.repositoryId) : void 0);
|
|
2000
2237
|
}
|
|
2238
|
+
/** Read windowed code summary and activity buckets for an optional scope. */
|
|
2001
2239
|
async function readCodeWindows(args) {
|
|
2002
2240
|
const db = getDb();
|
|
2003
2241
|
const windowEnd = new Date(args.nowMs);
|
|
2004
2242
|
const windowStart = /* @__PURE__ */ new Date(args.nowMs - WINDOW_DAYS * DAY_MS);
|
|
2005
2243
|
const activityStart = startOfUtcDay(args.nowMs - 89 * DAY_MS);
|
|
2006
2244
|
const changes = juniorCodeChanges;
|
|
2007
|
-
const
|
|
2245
|
+
const scope = changeScopeFilter(args);
|
|
2008
2246
|
const conversationTreeCost = conversationTreeCostExpr();
|
|
2009
2247
|
const activityHourEnd = new Date(args.nowMs);
|
|
2010
2248
|
activityHourEnd.setUTCMinutes(0, 0, 0);
|
|
@@ -2020,7 +2258,7 @@ async function readCodeWindows(args) {
|
|
|
2020
2258
|
${changes.state},
|
|
2021
2259
|
${changes.conversationIds} AS conversation_ids
|
|
2022
2260
|
FROM ${changes}
|
|
2023
|
-
${
|
|
2261
|
+
${scope ? sql`WHERE ${scope}` : sql``}
|
|
2024
2262
|
), change_costs AS (
|
|
2025
2263
|
SELECT
|
|
2026
2264
|
recent_changes.id,
|
|
@@ -2086,18 +2324,18 @@ async function readCodeWindows(args) {
|
|
|
2086
2324
|
SELECT ${changes.openedAt} AS event_at, 'created'::text AS kind
|
|
2087
2325
|
FROM ${changes}
|
|
2088
2326
|
WHERE ${changes.openedAt} >= ${activityStart}
|
|
2089
|
-
${
|
|
2327
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2090
2328
|
UNION ALL
|
|
2091
2329
|
SELECT ${changes.mergedAt} AS event_at, 'merged'::text AS kind
|
|
2092
2330
|
FROM ${changes}
|
|
2093
2331
|
WHERE ${changes.mergedAt} >= ${activityStart}
|
|
2094
|
-
${
|
|
2332
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2095
2333
|
UNION ALL
|
|
2096
2334
|
SELECT ${changes.closedAt} AS event_at, 'closed'::text AS kind
|
|
2097
2335
|
FROM ${changes}
|
|
2098
2336
|
WHERE ${changes.state} = 'closed'
|
|
2099
2337
|
AND ${changes.closedAt} >= ${activityStart}
|
|
2100
|
-
${
|
|
2338
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2101
2339
|
) AS events
|
|
2102
2340
|
GROUP BY date_trunc('day', event_at AT TIME ZONE 'UTC')
|
|
2103
2341
|
)
|
|
@@ -2127,18 +2365,18 @@ async function readCodeWindows(args) {
|
|
|
2127
2365
|
SELECT ${changes.openedAt} AS event_at, 'created'::text AS kind
|
|
2128
2366
|
FROM ${changes}
|
|
2129
2367
|
WHERE ${changes.openedAt} >= ${activityHourStart}
|
|
2130
|
-
${
|
|
2368
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2131
2369
|
UNION ALL
|
|
2132
2370
|
SELECT ${changes.mergedAt} AS event_at, 'merged'::text AS kind
|
|
2133
2371
|
FROM ${changes}
|
|
2134
2372
|
WHERE ${changes.mergedAt} >= ${activityHourStart}
|
|
2135
|
-
${
|
|
2373
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2136
2374
|
UNION ALL
|
|
2137
2375
|
SELECT ${changes.closedAt} AS event_at, 'closed'::text AS kind
|
|
2138
2376
|
FROM ${changes}
|
|
2139
2377
|
WHERE ${changes.state} = 'closed'
|
|
2140
2378
|
AND ${changes.closedAt} >= ${activityHourStart}
|
|
2141
|
-
${
|
|
2379
|
+
${scope ? sql`AND ${scope}` : sql``}
|
|
2142
2380
|
) AS events
|
|
2143
2381
|
GROUP BY date_trunc('hour', event_at AT TIME ZONE 'UTC')
|
|
2144
2382
|
)
|
|
@@ -2183,6 +2421,30 @@ async function readCodeWindows(args) {
|
|
|
2183
2421
|
windowStart
|
|
2184
2422
|
};
|
|
2185
2423
|
}
|
|
2424
|
+
/** Columns read for one code change report row. */
|
|
2425
|
+
const codeChangeReportColumns = {
|
|
2426
|
+
closedAt: juniorCodeChanges.closedAt,
|
|
2427
|
+
id: juniorCodeChanges.id,
|
|
2428
|
+
mergedAt: juniorCodeChanges.mergedAt,
|
|
2429
|
+
number: juniorCodeChanges.number,
|
|
2430
|
+
openedAt: juniorCodeChanges.openedAt,
|
|
2431
|
+
provider: juniorCodeChanges.provider,
|
|
2432
|
+
repository: juniorCodeRepositories.name,
|
|
2433
|
+
state: juniorCodeChanges.state,
|
|
2434
|
+
title: juniorCodeChanges.title,
|
|
2435
|
+
url: juniorCodeChanges.url
|
|
2436
|
+
};
|
|
2437
|
+
/** Convert one code change row to its report shape. */
|
|
2438
|
+
function codeChangeReport(change) {
|
|
2439
|
+
return {
|
|
2440
|
+
...change,
|
|
2441
|
+
closedAt: change.closedAt?.toISOString(),
|
|
2442
|
+
mergedAt: change.mergedAt?.toISOString(),
|
|
2443
|
+
openedAt: change.openedAt.toISOString(),
|
|
2444
|
+
title: change.title ?? void 0,
|
|
2445
|
+
url: change.url ?? void 0
|
|
2446
|
+
};
|
|
2447
|
+
}
|
|
2186
2448
|
/** Read code activity created by Junior across installed code plugins. */
|
|
2187
2449
|
async function readCodeOverview(nowMs = Date.now()) {
|
|
2188
2450
|
const db = getDb();
|
|
@@ -2248,30 +2510,12 @@ async function readCodeOverview(nowMs = Date.now()) {
|
|
|
2248
2510
|
GROUP BY ${repositories.id}
|
|
2249
2511
|
ORDER BY "merged" DESC, "created" DESC, ${repositories.name} ASC
|
|
2250
2512
|
LIMIT 25
|
|
2251
|
-
`), db.select(
|
|
2252
|
-
closedAt: changes.closedAt,
|
|
2253
|
-
id: changes.id,
|
|
2254
|
-
mergedAt: changes.mergedAt,
|
|
2255
|
-
number: changes.number,
|
|
2256
|
-
openedAt: changes.openedAt,
|
|
2257
|
-
provider: changes.provider,
|
|
2258
|
-
repository: repositories.name,
|
|
2259
|
-
state: changes.state,
|
|
2260
|
-
title: changes.title,
|
|
2261
|
-
url: changes.url
|
|
2262
|
-
}).from(changes).innerJoin(repositories, eq(changes.repositoryId, repositories.id)).where(or(gte(changes.openedAt, windows.windowStart), gte(changes.mergedAt, windows.windowStart), gte(changes.closedAt, windows.windowStart), eq(changes.state, "open"))).orderBy(desc(changes.updatedAt)).limit(25)]);
|
|
2513
|
+
`), db.select(codeChangeReportColumns).from(changes).innerJoin(repositories, eq(changes.repositoryId, repositories.id)).where(or(gte(changes.openedAt, windows.windowStart), gte(changes.mergedAt, windows.windowStart), gte(changes.closedAt, windows.windowStart), eq(changes.state, "open"))).orderBy(desc(changes.updatedAt)).limit(25)]);
|
|
2263
2514
|
return codeOverviewReportSchema.parse({
|
|
2264
2515
|
activityDays: windows.activityDays,
|
|
2265
2516
|
activityHours: windows.activityHours,
|
|
2266
2517
|
activitySixHours: windows.activitySixHours,
|
|
2267
|
-
changes: recentChanges.map(
|
|
2268
|
-
...change,
|
|
2269
|
-
closedAt: change.closedAt?.toISOString(),
|
|
2270
|
-
mergedAt: change.mergedAt?.toISOString(),
|
|
2271
|
-
openedAt: change.openedAt.toISOString(),
|
|
2272
|
-
title: change.title ?? void 0,
|
|
2273
|
-
url: change.url ?? void 0
|
|
2274
|
-
})),
|
|
2518
|
+
changes: recentChanges.map(codeChangeReport),
|
|
2275
2519
|
generatedAt: windows.windowEnd.toISOString(),
|
|
2276
2520
|
repositories: z.array(repositoryRowSchema).parse(queryRows(repositoryResult)).map((repository) => ({
|
|
2277
2521
|
...repository,
|
|
@@ -2303,11 +2547,61 @@ async function readPersonCodeOverview(args) {
|
|
|
2303
2547
|
});
|
|
2304
2548
|
}
|
|
2305
2549
|
//#endregion
|
|
2550
|
+
//#region src/api/code/repository.ts
|
|
2551
|
+
/** Most recent code changes listed on one repository page. */
|
|
2552
|
+
const REPOSITORY_CHANGE_LIMIT = 100;
|
|
2553
|
+
/**
|
|
2554
|
+
* Read code activity for one repository: windowed summary, activity buckets,
|
|
2555
|
+
* recent code changes, and the Workspaces that include it.
|
|
2556
|
+
*/
|
|
2557
|
+
async function readCodeRepository(repositoryId, nowMs = Date.now()) {
|
|
2558
|
+
const db = getDb();
|
|
2559
|
+
const [repository] = await db.select({
|
|
2560
|
+
id: juniorCodeRepositories.id,
|
|
2561
|
+
name: juniorCodeRepositories.name,
|
|
2562
|
+
provider: juniorCodeRepositories.provider,
|
|
2563
|
+
url: juniorCodeRepositories.url
|
|
2564
|
+
}).from(juniorCodeRepositories).where(eq(juniorCodeRepositories.id, repositoryId)).limit(1);
|
|
2565
|
+
if (!repository) return void 0;
|
|
2566
|
+
const [windows, changes, workspaces] = await Promise.all([
|
|
2567
|
+
readCodeWindows({
|
|
2568
|
+
nowMs,
|
|
2569
|
+
repositoryId
|
|
2570
|
+
}),
|
|
2571
|
+
db.select(codeChangeReportColumns).from(juniorCodeChanges).innerJoin(juniorCodeRepositories, eq(juniorCodeChanges.repositoryId, juniorCodeRepositories.id)).where(eq(juniorCodeChanges.repositoryId, repositoryId)).orderBy(desc(juniorCodeChanges.updatedAt)).limit(REPOSITORY_CHANGE_LIMIT),
|
|
2572
|
+
listWorkspacesByRepository(db, {
|
|
2573
|
+
provider: repository.provider,
|
|
2574
|
+
repo: repository.name
|
|
2575
|
+
})
|
|
2576
|
+
]);
|
|
2577
|
+
return codeRepositoryReportSchema.parse({
|
|
2578
|
+
activityDays: windows.activityDays,
|
|
2579
|
+
activityHours: windows.activityHours,
|
|
2580
|
+
activitySixHours: windows.activitySixHours,
|
|
2581
|
+
changes: changes.map(codeChangeReport),
|
|
2582
|
+
generatedAt: windows.windowEnd.toISOString(),
|
|
2583
|
+
repository: {
|
|
2584
|
+
...repository,
|
|
2585
|
+
url: repository.url ?? void 0
|
|
2586
|
+
},
|
|
2587
|
+
summary: windows.summary,
|
|
2588
|
+
windowEnd: windows.windowEnd.toISOString(),
|
|
2589
|
+
windowStart: windows.windowStart.toISOString(),
|
|
2590
|
+
workspaces
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
//#endregion
|
|
2306
2594
|
//#region src/api/code/routes.ts
|
|
2307
2595
|
/** Create the code analytics API. */
|
|
2308
2596
|
function createCodeRoutes() {
|
|
2309
2597
|
const app = new Hono();
|
|
2310
2598
|
app.get("/", async () => jsonResponse(codeOverviewReportSchema, await readCodeOverview()));
|
|
2599
|
+
app.get("/repositories/:repositoryId", validateRequest("param", codeRepositoryParamsSchema, "Invalid route parameters."), async (context) => {
|
|
2600
|
+
const { repositoryId } = context.req.valid("param");
|
|
2601
|
+
const report = await readCodeRepository(repositoryId);
|
|
2602
|
+
if (!report) throwApiError(404, "Repository not found.");
|
|
2603
|
+
return jsonResponse(codeRepositoryReportSchema, report);
|
|
2604
|
+
});
|
|
2311
2605
|
return app;
|
|
2312
2606
|
}
|
|
2313
2607
|
//#endregion
|
|
@@ -3060,7 +3354,7 @@ async function readPeoplePluginReports(args) {
|
|
|
3060
3354
|
reports: [],
|
|
3061
3355
|
source: "plugins"
|
|
3062
3356
|
});
|
|
3063
|
-
const { getPluginProfileReports } = await import("./agent-hooks-
|
|
3357
|
+
const { getPluginProfileReports } = await import("./agent-hooks-Da_H0jHv.js").then((n) => n.t);
|
|
3064
3358
|
return pluginOperationalReportFeedSchema.parse({
|
|
3065
3359
|
generatedAt: new Date(nowMs).toISOString(),
|
|
3066
3360
|
reports: await getPluginProfileReports({
|
|
@@ -3509,7 +3803,10 @@ function createUserPageRoutes() {
|
|
|
3509
3803
|
}
|
|
3510
3804
|
//#endregion
|
|
3511
3805
|
//#region src/chat/automations/edit.ts
|
|
3512
|
-
/**
|
|
3806
|
+
/**
|
|
3807
|
+
* Web edits. Owners and public Destination readers can edit. Creator-only
|
|
3808
|
+
* rules still apply, and lifecycle actions stay creator-only.
|
|
3809
|
+
*/
|
|
3513
3810
|
async function requireOwnedAutomation(user, kind, id) {
|
|
3514
3811
|
if (kind === "scheduled") {
|
|
3515
3812
|
const task = await readScheduledAutomation(getDb(), id);
|
|
@@ -3526,9 +3823,26 @@ async function requireOwnedAutomation(user, kind, id) {
|
|
|
3526
3823
|
task
|
|
3527
3824
|
};
|
|
3528
3825
|
}
|
|
3529
|
-
|
|
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) {
|
|
3530
3843
|
const { task } = automation;
|
|
3531
3844
|
const common = {
|
|
3845
|
+
ownedByViewer,
|
|
3532
3846
|
id: task.id,
|
|
3533
3847
|
revision: automationRevision(task),
|
|
3534
3848
|
title: task.title ?? null,
|
|
@@ -3559,27 +3873,131 @@ function editView(automation) {
|
|
|
3559
3873
|
}
|
|
3560
3874
|
/** Load exact editable values by ID, independent of list filters. */
|
|
3561
3875
|
async function readViewerAutomationEdit(user, kind, id) {
|
|
3562
|
-
|
|
3876
|
+
const automation = await requireEditableAutomation(user, kind, id);
|
|
3877
|
+
return editView(automation, automation.ownedByViewer);
|
|
3563
3878
|
}
|
|
3564
3879
|
/** Save a partial edit against the read revision, without dispatching work. */
|
|
3565
3880
|
async function updateViewerAutomation(user, id, input) {
|
|
3566
|
-
const current = await
|
|
3881
|
+
const current = await requireEditableAutomation(user, input.kind, id);
|
|
3567
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);
|
|
3568
3889
|
if (input.kind === "scheduled" && current.kind === "scheduled") {
|
|
3569
|
-
const next = await editScheduledAutomation(current.task, input,
|
|
3890
|
+
const next = await editScheduledAutomation(current.task, input, isCreator, Date.now());
|
|
3570
3891
|
return editView({
|
|
3571
3892
|
kind: "scheduled",
|
|
3572
|
-
task: await saveScheduledAutomation(getDb(),
|
|
3573
|
-
|
|
3893
|
+
task: await saveScheduledAutomation(getDb(), restored ? {
|
|
3894
|
+
...next,
|
|
3895
|
+
...restored
|
|
3896
|
+
} : next, input.revision, editedBy)
|
|
3897
|
+
}, isCreator);
|
|
3574
3898
|
}
|
|
3575
3899
|
if (input.kind === "event" && current.kind === "event") {
|
|
3576
|
-
const next = await editEventAutomation(current.task, input,
|
|
3577
|
-
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);
|
|
3578
3905
|
if (!task) throw new ViewerTaskNotFoundError();
|
|
3579
3906
|
return editView({
|
|
3580
3907
|
kind: "event",
|
|
3581
3908
|
task
|
|
3582
|
-
});
|
|
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 });
|
|
3583
4001
|
}
|
|
3584
4002
|
throw new ViewerTaskNotFoundError();
|
|
3585
4003
|
}
|
|
@@ -3589,12 +4007,12 @@ async function changeViewerAutomationLifecycle(user, kind, id, input) {
|
|
|
3589
4007
|
requireAutomationRevision(current.task, input.revision);
|
|
3590
4008
|
const status = current.task.status;
|
|
3591
4009
|
if (status === "completed") throw new AutomationEditError("Completed Automations cannot be restarted.");
|
|
3592
|
-
if (input.action === "pause" && status === "paused") return editView(current);
|
|
4010
|
+
if (input.action === "pause" && status === "paused") return editView(current, true);
|
|
3593
4011
|
if (input.action === "resume" && status !== "paused" && status !== "blocked") throw new AutomationEditError("Only paused or blocked Automations can be resumed.");
|
|
3594
4012
|
if (current.kind === "event") return editView({
|
|
3595
4013
|
kind: "event",
|
|
3596
4014
|
task: await setEventAutomationStatus(getDb(), id, input.action === "pause" ? "paused" : "active", input.revision)
|
|
3597
|
-
});
|
|
4015
|
+
}, true);
|
|
3598
4016
|
const nowMs = Date.now();
|
|
3599
4017
|
const next = {
|
|
3600
4018
|
...current.task,
|
|
@@ -3619,7 +4037,7 @@ async function changeViewerAutomationLifecycle(user, kind, id, input) {
|
|
|
3619
4037
|
return editView({
|
|
3620
4038
|
kind: "scheduled",
|
|
3621
4039
|
task: await saveScheduledAutomation(getDb(), next, input.revision)
|
|
3622
|
-
});
|
|
4040
|
+
}, true);
|
|
3623
4041
|
}
|
|
3624
4042
|
//#endregion
|
|
3625
4043
|
//#region src/chat/automations/editor.ts
|
|
@@ -3698,6 +4116,26 @@ function createAutomationRoutes() {
|
|
|
3698
4116
|
throw error;
|
|
3699
4117
|
}
|
|
3700
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
|
+
});
|
|
3701
4139
|
app.get("/:kind/:id/edit", requireViewer, validateRequest("param", automationParamsSchema, "Invalid Automation."), async (context) => {
|
|
3702
4140
|
const { kind, id } = context.req.valid("param");
|
|
3703
4141
|
try {
|
|
@@ -3879,7 +4317,7 @@ function readDescriptionText(home) {
|
|
|
3879
4317
|
}
|
|
3880
4318
|
/** Read discovered skill names for authenticated runtime diagnostics. */
|
|
3881
4319
|
async function readSkillReports() {
|
|
3882
|
-
const { discoverSkills } = await import("./skills-
|
|
4320
|
+
const { discoverSkills } = await import("./skills-Dd8O9Aav.js").then((n) => n.o);
|
|
3883
4321
|
const skills = await discoverSkills();
|
|
3884
4322
|
return skillReportsSchema.parse(skills.map((skill) => ({
|
|
3885
4323
|
name: skill.name,
|
|
@@ -3888,7 +4326,7 @@ async function readSkillReports() {
|
|
|
3888
4326
|
}
|
|
3889
4327
|
/** Read safe configured plugin metadata for authenticated runtime diagnostics. */
|
|
3890
4328
|
async function readPlugins() {
|
|
3891
|
-
const { pluginCatalogRuntime } = await import("./catalog-runtime-
|
|
4329
|
+
const { pluginCatalogRuntime } = await import("./catalog-runtime-C7339Cfm.js").then((n) => n.t);
|
|
3892
4330
|
return pluginsSchema.parse(pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
3893
4331
|
configKeys: plugin.manifest.configKeys ?? [],
|
|
3894
4332
|
description: plugin.manifest.description,
|
|
@@ -3899,8 +4337,8 @@ async function readPlugins() {
|
|
|
3899
4337
|
/** Read authenticated runtime discovery data. */
|
|
3900
4338
|
async function readRuntimeInfoReport() {
|
|
3901
4339
|
const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
|
|
3902
|
-
import("./package-discovery-
|
|
3903
|
-
import("./catalog-runtime-
|
|
4340
|
+
import("./package-discovery-BVvSnQBB.js").then((n) => n.r),
|
|
4341
|
+
import("./catalog-runtime-C7339Cfm.js").then((n) => n.t),
|
|
3904
4342
|
readPlugins(),
|
|
3905
4343
|
readSkillReports()
|
|
3906
4344
|
]);
|
|
@@ -3917,7 +4355,7 @@ async function readRuntimeInfoReport() {
|
|
|
3917
4355
|
/** Read sanitized operational summaries contributed by plugins. */
|
|
3918
4356
|
async function readPluginOperationalReportFeed() {
|
|
3919
4357
|
const nowMs = Date.now();
|
|
3920
|
-
const { getPluginOperationalReports } = await import("./agent-hooks-
|
|
4358
|
+
const { getPluginOperationalReports } = await import("./agent-hooks-Da_H0jHv.js").then((n) => n.t);
|
|
3921
4359
|
return pluginOperationalReportFeedSchema.parse({
|
|
3922
4360
|
source: "plugins",
|
|
3923
4361
|
generatedAt: new Date(nowMs).toISOString(),
|
|
@@ -3927,7 +4365,7 @@ async function readPluginOperationalReportFeed() {
|
|
|
3927
4365
|
//#endregion
|
|
3928
4366
|
//#region src/api.ts
|
|
3929
4367
|
/** Create Junior's production REST API for authenticated dashboard consumers. */
|
|
3930
|
-
function createJuniorApi() {
|
|
4368
|
+
function createJuniorApi(options = {}) {
|
|
3931
4369
|
const app = new Hono();
|
|
3932
4370
|
app.get("/api/health", async () => jsonResponse(healthReportSchema, readHealthReport()));
|
|
3933
4371
|
app.get("/api/runtime", async () => jsonResponse(runtimeInfoReportSchema, await readRuntimeInfoReport()));
|
|
@@ -3935,7 +4373,10 @@ function createJuniorApi() {
|
|
|
3935
4373
|
app.get("/api/skills", async () => jsonResponse(skillReportsSchema, await readSkillReports()));
|
|
3936
4374
|
app.get("/api/plugin-reports", async () => jsonResponse(pluginOperationalReportFeedSchema, await readPluginOperationalReportFeed()));
|
|
3937
4375
|
app.get("/api/stats", async () => jsonResponse(statsReportSchema, await readStatsReport()));
|
|
3938
|
-
app.route("/api/conversations", createConversationRoutes({
|
|
4376
|
+
app.route("/api/conversations", createConversationRoutes({
|
|
4377
|
+
attachmentStorage: createVercelAttachmentStorage(),
|
|
4378
|
+
queue: options.conversationWorkQueue ?? getVercelConversationWorkQueue()
|
|
4379
|
+
}));
|
|
3939
4380
|
app.route("/api/code", createCodeRoutes());
|
|
3940
4381
|
app.route("/api/personal-tokens", createPersonalTokenRoutes());
|
|
3941
4382
|
app.route("/api/people", createPeopleRoutes());
|