@sentry/junior 0.243.0 → 0.245.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.
Files changed (62) hide show
  1. package/dist/{admin-DHOGoI03.js → admin-BEbNsG87.js} +1 -1
  2. package/dist/{agent-hooks-Cqo6ULp6.js → agent-hooks-Caa0Qh_Q.js} +22 -12
  3. package/dist/{agent-invocations-BzRAZ_nj.js → agent-invocations-BO6ddZax.js} +1 -0
  4. package/dist/{agent-runner-BZOjoMlZ.js → agent-runner-BEuPFbbv.js} +199 -57
  5. package/dist/api/schema.d.ts +78 -3
  6. package/dist/api/schema.js +3 -3
  7. package/dist/api.js +316 -246
  8. package/dist/{app-DRLh-8zs.d.ts → app-BYB7szGb.d.ts} +2 -0
  9. package/dist/app.d.ts +1 -1
  10. package/dist/app.js +172 -105
  11. package/dist/{briefs-C4GogKaB.js → briefs-DFAh_jTW.js} +4 -4
  12. package/dist/{catalog-runtime-jazDa-oO.js → catalog-runtime-wmq_fqff.js} +1 -1
  13. package/dist/cli/chat.js +12 -12
  14. package/dist/cli/check.js +3 -3
  15. package/dist/cli/main.js +2 -2
  16. package/dist/cli/plugins.js +4 -4
  17. package/dist/cli/snapshot-create.js +3 -3
  18. package/dist/cli/upgrade.js +3 -3
  19. package/dist/{client-Es79bknZ.js → client-Bn9SRFCi.js} +2 -2
  20. package/dist/{config-gype19qQ.js → config-_XAINZMN.js} +11 -2
  21. package/dist/{conversation-mt3yJ09u.js → conversation-CvBG57Io.js} +15 -2
  22. package/dist/{conversation-privacy-BIplVfmz.js → conversation-privacy-BN5SGxsF.js} +1 -1
  23. package/dist/{credential-sync-BY97dkST.js → credential-sync-BvaUm6HQ.js} +14 -14
  24. package/dist/{executor-C7bsEF-c.js → executor-CFIqoCNJ.js} +3 -3
  25. package/dist/{history-DlsoHsoe.js → history-Nte1oVed.js} +3 -0
  26. package/dist/{images-WmrEAtto.js → images-Ci43I3ek.js} +3 -3
  27. package/dist/instrumentation.js +2 -2
  28. package/dist/{job-queue-DQDesz8a.js → job-queue-CGicV7x6.js} +1 -1
  29. package/dist/{logging-CdZ1LQJ7.js → logging-Bs3dV81s.js} +1 -1
  30. package/dist/nitro.d.ts +1 -1
  31. package/dist/nitro.js +3 -3
  32. package/dist/{oauth-authorization-Di0KlyoJ.js → oauth-authorization-COzWmMjO.js} +1 -1
  33. package/dist/{oauth-callback-server-Cmog7Q3T.js → oauth-callback-server-17c9_39v.js} +2 -2
  34. package/dist/{pending-auth-CNAlGOyl.js → pending-auth-Bnhg7Q9k.js} +2 -2
  35. package/dist/{profile-qqt_2di0.js → profile-BA471c-l.js} +4 -4
  36. package/dist/{prompt-D_cg2iqi.js → prompt-C9Q9cjD3.js} +1 -1
  37. package/dist/{registry-WOJKAIRI.js → registry-BYNOFO6e.js} +1 -1
  38. package/dist/{resolve-C8VvY0lg.js → resolve-rpz8No2i.js} +2 -2
  39. package/dist/{runner-DojxjGmt.js → runner-C8DRk9X6.js} +10 -10
  40. package/dist/{sandbox-egress-signals-B7azPnsm.js → sandbox-egress-signals-B8hurjFt.js} +1 -1
  41. package/dist/{schema-nQRsPWSr.js → schema-WRgR9EL6.js} +11 -6
  42. package/dist/{skills-Zj-9pbSU.js → skills-C4yrD2NR.js} +2 -2
  43. package/dist/{spawn-DDJ5z9IC.js → spawn-CdAaTXrY.js} +4 -3
  44. package/dist/{task-queue-CAi-XM8x.js → task-queue-BBxHzlIB.js} +3 -3
  45. package/dist/{task-runner-CIP2asjP.js → task-runner-BqFapTjs.js} +12 -12
  46. package/dist/{tasks-Qx6Fos_R.js → tasks-Ddd7OAkp.js} +83 -12
  47. package/dist/{thread-state-BN_5S1bx.js → thread-state-DqErwbA8.js} +7 -7
  48. package/dist/{turn-failure-response-B1UW2T7a.js → turn-failure-response-DtL1-fOl.js} +9 -9
  49. package/dist/{turn-lifecycle-BzeE4sVD.js → turn-lifecycle-B5pzyD6W.js} +1 -1
  50. package/dist/{turn-session-routing-DsvU39zW.js → turn-session-routing-D6pTU5uF.js} +1 -1
  51. package/dist/{validation-Dklx_nds.js → validation-BAzlyNIc.js} +1 -1
  52. package/dist/{version-BvG02ZTN.js → version-CiXDH3oe.js} +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/{viewer-B7qDM2DM.js → viewer-wCn8xneI.js} +5 -5
  55. package/dist/{web-authorization-Cqv7bP19.js → web-authorization-d5MtGpfG.js} +6 -6
  56. package/dist/{web-input-CqnqSfzI.js → web-input-BZTiCtvP.js} +71 -48
  57. package/dist/{work-D_BBvd4I.js → work-4QPqTd5_.js} +6 -6
  58. package/dist/{worker-BWdV_5Ys.js → worker-mc62tybz.js} +5 -5
  59. package/migrations/0052_agent_invocation_title.sql +1 -0
  60. package/migrations/meta/0052_snapshot.json +3765 -0
  61. package/migrations/meta/_journal.json +7 -0
  62. package/package.json +5 -5
package/dist/api.js CHANGED
@@ -1,24 +1,24 @@
1
- import { L as editEventAutomation, Mt as getConversation, O as parseSlackThreadId, P as editScheduledAutomation, R as AutomationEditError, St as cancelHumanFacingPendingMessages, T as readLiveAttachment, V as resolveTaskOutcomes, _t as pluginEventCatalog, g as readStats, ht as getEventCatalog, tt as listChildAgents, z as eventAutomationTriggerAvailable } from "./agent-runner-BZOjoMlZ.js";
2
- import { d as logException, p as logWarn } from "./logging-CdZ1LQJ7.js";
1
+ import { $ as getChildAgentLabel, Et as cancelHumanFacingPendingMessages, It as getConversation, L as editEventAutomation, O as parseSlackThreadId, P as editScheduledAutomation, R as AutomationEditError, T as readLiveAttachment, V as resolveTaskOutcomes, bt as automationEventCatalog, g as readStats, gt as getEventCatalog, nt as listChildAgents, z as eventAutomationTriggerAvailable } from "./agent-runner-BEuPFbbv.js";
2
+ import { d as logException, p as logWarn } from "./logging-Bs3dV81s.js";
3
3
  import { d as parseSlackTeamId, g as parseSlackMessageTs, l as parseSlackChannelId } from "./actor-BTtLaeWy.js";
4
- import { E as resolveRootConversationId, T as hasAgentTurnUsage, _ as locationFromRow, b as parseSessionSource, c as getConversationEventStore, d as getDb, f as getSqlExecutor, i as resolveViewerUser, m as withConversationEventLock, o as updateViewerDisplayName, t as findUserByEmail, u as getConversationStore, w as agentTurnUsageSchema } from "./viewer-B7qDM2DM.js";
5
- import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, o as conversationReadColumns, s as juniorConversations } from "./agent-invocations-BzRAZ_nj.js";
6
- import { _ as juniorConversationEvents, b as juniorCodeChanges, f as juniorConversationParticipants, g as juniorConversationMetrics, m as juniorConversationBriefs, v as juniorApiTokens, x as juniorCodeRepositories } from "./executor-C7bsEF-c.js";
7
- import { u as contextProvenance } from "./history-DlsoHsoe.js";
8
- import { D as automationRevision, E as AutomationConflictError, Et as loadTurnProjection, O as requireAutomationRevision, S as automationDefinition, T as sameDefinitionValue, a as readScheduledAutomation, b as saveEventAutomation, g as getEventAutomation, ln as piMessageFromHistoryItem, m as eventAutomationBelongsToUser, o as saveScheduledAutomation, w as readAutomationVersion, x as setEventAutomationStatus } from "./tasks-Qx6Fos_R.js";
9
- import { At as listConversationAnnotations, C as WorkspaceValidationError, M as workspaceRepoCheckoutPath, O as SANDBOX_RUNTIME, Pt as lookupSlackUser, S as updateWorkspace, St as buildSentryConversationUrl, Wt as withSlackRetries, _ as deleteWorkspace, _t as conversationNotArchivedForUser, b as listWorkspaces, bt as readLatestConversationBrief, dt as conversationArchiveTimes, ft as conversationArchivedForEmail, g as createWorkspace, gt as conversationNotArchivedForEmail, ht as conversationHasParticipantUser, jt as listConversationAnnotationsById, lt as createPluginLogger, mt as conversationHasParticipantEmail, pt as conversationArchivedForUser, u as getPlugins, ut as readConversationAccessFromSql, v as getWorkspace, vt as viewerConversationMembership, x as listWorkspacesByRepository, xt as readConversationEventPage, zt as getSlackClient } from "./agent-hooks-Cqo6ULp6.js";
10
- 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, o as cancelConversationPendingMessagesResponseSchema, p as conversationEventPageSchema, r as archiveConversationBodySchema, t as acceptedConversationMessageSchema, u as conversationDetailQuerySchema, x as conversationPendingMessagesReportSchema } from "./conversation-mt3yJ09u.js";
11
- import { n as hash, t as create } from "./profile-qqt_2di0.js";
4
+ import { E as resolveRootConversationId, T as hasAgentTurnUsage, _ as locationFromRow, b as parseSessionSource, c as getConversationEventStore, d as getDb, f as getSqlExecutor, i as resolveViewerUser, m as withConversationEventLock, o as updateViewerDisplayName, t as findUserByEmail, u as getConversationStore, w as agentTurnUsageSchema } from "./viewer-wCn8xneI.js";
5
+ import { c as juniorIdentities, d as juniorDestinations, l as juniorUsers, o as conversationReadColumns, s as juniorConversations } from "./agent-invocations-BO6ddZax.js";
6
+ import { _ as juniorConversationEvents, b as juniorCodeChanges, f as juniorConversationParticipants, g as juniorConversationMetrics, m as juniorConversationBriefs, v as juniorApiTokens, x as juniorCodeRepositories } from "./executor-CFIqoCNJ.js";
7
+ import { u as contextProvenance } from "./history-Nte1oVed.js";
8
+ import { A as requireAutomationRevision, D as sameDefinitionValue, E as readAutomationVersion, O as AutomationConflictError, Ot as loadTurnProjection, a as readScheduledAutomation, b as saveEventAutomation, dn as piMessageFromHistoryItem, g as getEventAutomation, k as automationRevision, m as eventAutomationBelongsToUser, o as saveScheduledAutomation, w as automationDefinition, x as setEventAutomationStatus } from "./tasks-Ddd7OAkp.js";
9
+ import { At as listConversationAnnotations, C as WorkspaceValidationError, M as workspaceRepoCheckoutPath, O as SANDBOX_RUNTIME, Pt as lookupSlackUser, S as updateWorkspace, St as buildSentryConversationUrl, Wt as withSlackRetries, _ as deleteWorkspace, _t as conversationNotArchivedForUser, b as listWorkspaces, bt as readLatestConversationBrief, dt as conversationArchiveTimes, ft as conversationArchivedForEmail, g as createWorkspace, gt as conversationNotArchivedForEmail, ht as conversationHasParticipantUser, jt as listConversationAnnotationsById, lt as createPluginLogger, mt as conversationHasParticipantEmail, pt as conversationArchivedForUser, u as getPlugins, ut as readConversationAccessFromSql, v as getWorkspace, vt as viewerConversationMembership, x as listWorkspacesByRepository, xt as readConversationEventPage, zt as getSlackClient } from "./agent-hooks-Caa0Qh_Q.js";
10
+ import { A as forkConversationBodySchema, F as INPUT_IMAGE_TYPES, O as createConversationBodySchema, P as readMessageAttachments, T as conversationStatsReportSchema, _ as conversationParamsSchema, a as cancelConversationPendingMessagesBodySchema, c as conversationAttachmentParamsSchema, d as conversationDetailReportSchema, g as conversationFeedSchema, h as conversationFeedQuerySchema, i as archiveConversationResponseSchema, j as forkConversationResponseSchema, k as createConversationMessageBodySchema, m as conversationEventsQuerySchema, o as cancelConversationPendingMessagesResponseSchema, p as conversationEventPageSchema, r as archiveConversationBodySchema, t as acceptedConversationMessageSchema, u as conversationDetailQuerySchema, x as conversationPendingMessagesReportSchema } from "./conversation-CvBG57Io.js";
11
+ import { n as hash, t as create } from "./profile-BA471c-l.js";
12
12
  import { t as getFirstRunAtMs } from "./cadence-D00SCzT1.js";
13
- import { t as getCachedSnapshot } from "./resolve-C8VvY0lg.js";
14
- import { a as getVercelConversationWorkQueue } from "./job-queue-DQDesz8a.js";
13
+ import { t as getCachedSnapshot } from "./resolve-rpz8No2i.js";
14
+ import { a as getVercelConversationWorkQueue } from "./job-queue-CGicV7x6.js";
15
15
  import { 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-gznOMaQ2.js";
16
16
  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";
17
- import { H as readHealthReport, L as createVercelAttachmentStorage, M as legacyWebMailboxMetadataSchema, _ as readViewerAutomationRuns, a as recordWebConversationActivity, b as readViewerAutomations, d as resolveSlackConversationContextFromThreadId, g as readViewerAutomationExecutions, h as readConversationSourceTask, i as createConversationId, l as getProductionSlackAdapter, m as deleteViewerTask, o as webActorFromEmail, p as ViewerTaskNotFoundError, r as createAndEnqueueConversation, t as appendAndEnqueueWebMessage, u as formatSlackConversationRedactedLabel, v as readViewerAutomationSummary, w as clearSlackPendingReactions, x as resolveViewerTaskCandidate, y as readViewerAutomationVersions } from "./web-input-CqnqSfzI.js";
18
- import { A as conversationActiveDaysColumn, M as conversationRangeColumns, j as conversationAggregateColumns } from "./task-runner-CIP2asjP.js";
19
- import { r as getWebAuthorization } from "./web-authorization-Cqv7bP19.js";
20
- import { t as decodeInputImages } from "./images-WmrEAtto.js";
21
- import { C as locationParamsSchema, E as activitySixHours$1, L as conversationChildAgentsReportSchema, M as codePersonReportSchema, N as codeRepositoryParamsSchema, P as codeRepositoryReportSchema, R as apiErrorSchema, 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-nQRsPWSr.js";
17
+ import { H as readHealthReport, L as createVercelAttachmentStorage, M as legacyWebMailboxMetadataSchema, _ as readViewerAutomationRuns, a as recordWebConversationActivity, b as readViewerAutomations, d as resolveSlackConversationContextFromThreadId, g as readViewerAutomationExecutions, h as readConversationSourceTask, i as createConversationId, l as getProductionSlackAdapter, m as deleteViewerTask, o as webActorFromEmail, p as ViewerTaskNotFoundError, r as createAndEnqueueConversation, t as appendAndEnqueueWebMessage, u as formatSlackConversationRedactedLabel, v as readViewerAutomationSummary, w as clearSlackPendingReactions, x as resolveViewerTaskCandidate, y as readViewerAutomationVersions } from "./web-input-BZTiCtvP.js";
18
+ import { A as conversationActiveDaysColumn, M as conversationRangeColumns, j as conversationAggregateColumns } from "./task-runner-BqFapTjs.js";
19
+ import { r as getWebAuthorization } from "./web-authorization-d5MtGpfG.js";
20
+ import { t as decodeInputImages } from "./images-Ci43I3ek.js";
21
+ import { C as locationParamsSchema, E as activitySixHours$1, L as conversationChildAgentsReportSchema, M as codePersonReportSchema, N as codeRepositoryParamsSchema, P as codeRepositoryReportSchema, R as apiErrorSchema, 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-WRgR9EL6.js";
22
22
  import { Hono } from "hono";
23
23
  import path from "node:path";
24
24
  import { z } from "zod";
@@ -131,25 +131,256 @@ async function archiveConversation(viewer, conversationId, body) {
131
131
  throwApiError(409, "Conversation received new activity.");
132
132
  }
133
133
  //#endregion
134
+ //#region src/chat/conversations/fork.ts
135
+ /** Event key of the agent note that follows the copied events in a fork. */
136
+ const FORK_NOTE_KEY = "fork:note";
137
+ const forkNote = alias(juniorConversationEvents, "fork_note");
138
+ /**
139
+ * SQL filter for event rows that a fork did not copy from its source. Usage
140
+ * and cost reports use it, so copied model calls count only in the source.
141
+ */
142
+ function notCopiedByFork() {
143
+ return sql`not exists (
144
+ select 1 from ${juniorConversationEvents} as ${sql.identifier("fork_note")}
145
+ where ${forkNote.conversationId} = ${juniorConversationEvents.conversationId}
146
+ and ${forkNote.idempotencyKey} = ${FORK_NOTE_KEY}
147
+ and ${forkNote.seq} > ${juniorConversationEvents.seq}
148
+ )`;
149
+ }
150
+ /**
151
+ * Events that hold credentials, approvals, provider connections, or plugin
152
+ * state of the source actor. A fork does not copy them.
153
+ */
154
+ const UNCOPIED_EVENT_TYPES = /* @__PURE__ */ new Set([
155
+ "authorization_completed",
156
+ "authorization_requested",
157
+ "guardian_action_reviewed",
158
+ "mcp_provider_connected",
159
+ "mcp_provider_connected_unowned",
160
+ "structured_event",
161
+ "unknown"
162
+ ]);
163
+ 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.";
164
+ /** An expected fork request failure, such as a reply without saved history. */
165
+ var ConversationForkError = class extends Error {};
166
+ /**
167
+ * Create a new web root Conversation from the agent history of a source
168
+ * Conversation, through one completed assistant reply.
169
+ *
170
+ * Retries with the same requester and key return the same Conversation.
171
+ */
172
+ async function forkConversation(input) {
173
+ const conversationId = createConversationId({
174
+ actorEmail: input.actorEmail,
175
+ idempotencyKey: JSON.stringify([
176
+ "fork",
177
+ input.sourceConversationId,
178
+ input.idempotencyKey
179
+ ])
180
+ });
181
+ const executor = getSqlExecutor();
182
+ const events = getConversationEventStore();
183
+ const store = getConversationStore();
184
+ return withConversationEventLock(executor, input.sourceConversationId, () => withConversationEventLock(executor, conversationId, async () => {
185
+ if (await store.get({ conversationId })) return { conversationId };
186
+ const source = await store.get({ conversationId: input.sourceConversationId });
187
+ if (!source || source.transcriptPurgedAtMs !== void 0) throw new ConversationForkError("The source history is no longer available.");
188
+ if (source.parentConversationId) throw new ConversationForkError("You cannot fork a child conversation.");
189
+ const boundary = await events.loadByIdempotencyKey(input.sourceConversationId, `message:${input.messageId}:agent`);
190
+ const reply = boundary?.data.type === "assistant_message" ? piMessageFromHistoryItem(boundary.data) : void 0;
191
+ 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.");
192
+ const projection = await loadTurnProjection({
193
+ conversationId: input.sourceConversationId,
194
+ committedSeq: boundary.seq,
195
+ includeTail: false
196
+ });
197
+ if (!projection?.messages.length) throw new ConversationForkError("The selected history is no longer available.");
198
+ if (hasUnfinishedToolCalls(projection.messages)) throw new ConversationForkError("This history has unfinished tool calls. Choose another reply.");
199
+ const nowMs = Date.now();
200
+ await recordWebConversationActivity({
201
+ actor: webActorFromEmail(input.actorEmail, { ...input.actorName ? { fullName: input.actorName } : void 0 }),
202
+ conversationId,
203
+ nowMs,
204
+ rootVisibility: source.visibility === "public" ? "public" : "private"
205
+ });
206
+ await executor.db().update(juniorConversations).set({
207
+ forkedFromConversationId: source.conversationId,
208
+ title: source.title ? `Fork: ${source.title}` : "Forked conversation"
209
+ }).where(eq(juniorConversations.conversationId, conversationId));
210
+ await copyForkEvents({
211
+ sourceConversationId: source.conversationId,
212
+ conversationId,
213
+ events: await events.loadHistory(source.conversationId),
214
+ boundarySeq: boundary.seq,
215
+ messageId: input.messageId
216
+ });
217
+ await events.append(conversationId, [{
218
+ idempotencyKey: FORK_NOTE_KEY,
219
+ createdAtMs: nowMs,
220
+ data: {
221
+ type: "user_message",
222
+ provenance: contextProvenance,
223
+ content: [{
224
+ type: "text",
225
+ text: FORK_NOTE
226
+ }],
227
+ timestamp: nowMs
228
+ }
229
+ }]);
230
+ return { conversationId };
231
+ }));
232
+ }
233
+ /**
234
+ * Copy the source event rows through the selected reply. Copies keep their
235
+ * sequence, author, timestamp, key, and payload, so the fork shows the same
236
+ * transcript and event log, and seq references in compactions stay valid.
237
+ * Later updates of copied Messages and the end of copied Turns are also
238
+ * copied, so that no copied item stays open in the fork.
239
+ */
240
+ async function copyForkEvents(args) {
241
+ const reply = args.events.find((event) => event.data.type === "message" && event.data.role === "assistant" && event.data.messageId === args.messageId);
242
+ const cutoffSeq = Math.max(args.boundarySeq, reply?.seq ?? -1);
243
+ const messageIds = /* @__PURE__ */ new Set();
244
+ const turnIds = /* @__PURE__ */ new Set();
245
+ const seqs = [];
246
+ let historyVersion = 0;
247
+ for (const event of args.events) {
248
+ const data = event.data;
249
+ if (UNCOPIED_EVENT_TYPES.has(data.type)) continue;
250
+ if (event.idempotencyKey === FORK_NOTE_KEY) continue;
251
+ if (event.seq <= cutoffSeq) {
252
+ if (data.type === "message") messageIds.add(data.messageId);
253
+ if (data.type === "turn_started") turnIds.add(data.turnId);
254
+ historyVersion = Math.max(historyVersion, event.historyVersion);
255
+ seqs.push(event.seq);
256
+ } 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);
257
+ }
258
+ if (!seqs.length) return;
259
+ const e = juniorConversationEvents;
260
+ await getSqlExecutor().db().execute(sql`insert into ${e} (${sql.join([
261
+ e.conversationId,
262
+ e.seq,
263
+ e.historyVersion,
264
+ e.schemaVersion,
265
+ e.idempotencyKey,
266
+ e.type,
267
+ e.payload,
268
+ e.actorIdentityId,
269
+ e.createdAt
270
+ ].map((column) => sql.identifier(column.name)), sql`, `)})
271
+ select ${args.conversationId}, ${e.seq}, least(${e.historyVersion}, ${historyVersion}), ${e.schemaVersion}, ${e.idempotencyKey}, ${e.type}, ${e.payload}, ${e.actorIdentityId}, ${e.createdAt}
272
+ from ${e}
273
+ where ${e.conversationId} = ${args.sourceConversationId}
274
+ and ${inArray(e.seq, seqs)}`);
275
+ }
276
+ function hasUnfinishedToolCalls(messages) {
277
+ const pending = /* @__PURE__ */ new Set();
278
+ for (const message of messages) if (message.role === "assistant") {
279
+ for (const part of message.content) if (part.type === "toolCall") pending.add(part.id);
280
+ } else if (message.role === "toolResult") pending.delete(message.toolCallId);
281
+ return pending.size > 0;
282
+ }
283
+ //#endregion
284
+ //#region src/api/conversations/auxiliary-costs.ts
285
+ const auxiliaryRootConversation = alias(juniorConversations, "auxiliary_root_conversation");
286
+ function eventCost() {
287
+ return sql`CASE
288
+ WHEN ${juniorConversationEvents.type} = 'structured_event'
289
+ AND jsonb_typeof(${juniorConversationEvents.payload}->'content'->'costUsd') = 'number'
290
+ THEN (${juniorConversationEvents.payload}->'content'->>'costUsd')::numeric
291
+ WHEN ${juniorConversationEvents.type} IN ('guardian_action_reviewed', 'turn_routed')
292
+ AND jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
293
+ THEN (${juniorConversationEvents.payload}->>'costUsd')::numeric
294
+ ELSE NULL
295
+ END`;
296
+ }
297
+ /** Aggregate additive event costs by namespaced operation for conversations. */
298
+ async function readConversationAuxiliaryCostsFromSql(db, conversationIds, options) {
299
+ if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
300
+ const selectedIds = [...conversationIds];
301
+ const conversationId = options.includeDescendants ? sql`coalesce(
302
+ ${auxiliaryRootConversation.conversationId},
303
+ ${juniorConversations.conversationId}
304
+ )` : sql`${juniorConversations.conversationId}`;
305
+ const namespace = sql`CASE
306
+ WHEN ${juniorConversationEvents.type} = 'structured_event'
307
+ THEN ${juniorConversationEvents.payload}->>'namespace'
308
+ ELSE 'junior'
309
+ END`;
310
+ const name = sql`CASE
311
+ WHEN ${juniorConversationEvents.type} = 'structured_event'
312
+ THEN ${juniorConversationEvents.payload}->>'name'
313
+ ELSE ${juniorConversationEvents.type}
314
+ END`;
315
+ const cost = eventCost();
316
+ const selectedConversation = options.includeDescendants ? or(and(inArray(juniorConversations.rootConversationId, selectedIds), isNotNull(auxiliaryRootConversation.conversationId)), inArray(juniorConversations.conversationId, selectedIds)) : inArray(juniorConversations.conversationId, selectedIds);
317
+ const rows = await db.select({
318
+ conversationId,
319
+ costUsd: sql`round(sum(${cost}), 12)::double precision`.mapWith(Number),
320
+ events: sql`count(*)::integer`.mapWith(Number),
321
+ name,
322
+ namespace
323
+ }).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);
324
+ const result = /* @__PURE__ */ new Map();
325
+ for (const row of rows) {
326
+ const current = result.get(row.conversationId) ?? {
327
+ costUsd: 0,
328
+ operations: []
329
+ };
330
+ current.costUsd = Math.round((current.costUsd + row.costUsd) * 0xe8d4a51000) / 0xe8d4a51000;
331
+ current.operations.push({
332
+ costUsd: row.costUsd,
333
+ events: row.events,
334
+ name: row.name,
335
+ namespace: row.namespace
336
+ });
337
+ result.set(row.conversationId, current);
338
+ }
339
+ return result;
340
+ }
341
+ //#endregion
134
342
  //#region src/api/conversations/child-agents.ts
343
+ /** Read the runtime and usage saved on each child Conversation row. */
344
+ async function readChildConversationMetrics(db, conversationIds) {
345
+ const rows = conversationIds.length === 0 ? [] : await db.select({
346
+ conversationId: juniorConversations.conversationId,
347
+ durationMs: juniorConversations.durationMs,
348
+ usage: juniorConversations.usage
349
+ }).from(juniorConversations).where(inArray(juniorConversations.conversationId, [...conversationIds]));
350
+ return new Map(rows.map((row) => [row.conversationId, {
351
+ cumulativeDurationMs: row.durationMs,
352
+ ...row.usage ? { cumulativeUsage: row.usage } : void 0
353
+ }]));
354
+ }
135
355
  /**
136
356
  * Load the direct child agents of one Conversation, or throw a standard API
137
- * not-found error. Viewers who cannot see private content get only the status
138
- * and time of each child agent.
357
+ * not-found error. Viewers who cannot see private content get only the status,
358
+ * time, and usage of each child agent.
139
359
  */
140
360
  async function requireConversationChildAgents(conversationId, options = {}) {
141
361
  if (!await getConversationStore().get({ conversationId })) throwApiError(404, "Conversation not found.");
142
- const canExposePayload = (await readConversationAccessFromSql(getDb(), [conversationId], options.viewer)).get(conversationId)?.canViewPrivateContent ?? false;
143
- const agents = (await listChildAgents(conversationId)).map((agent) => ({
144
- conversationId: agent.childConversationId,
145
- status: agent.status,
146
- updatedAt: new Date(agent.updatedAtMs).toISOString(),
147
- ...canExposePayload ? {
148
- ...agent.agentName ? { name: agent.agentName } : void 0,
149
- task: agent.task,
150
- ...agent.errorMessage ? { error: agent.errorMessage } : void 0
151
- } : { redacted: true }
152
- }));
362
+ const db = getDb();
363
+ const [accessByConversation, children] = await Promise.all([readConversationAccessFromSql(db, [conversationId], options.viewer), listChildAgents(conversationId)]);
364
+ const canExposePayload = accessByConversation.get(conversationId)?.canViewPrivateContent ?? false;
365
+ const childIds = children.map((agent) => agent.childConversationId);
366
+ const [metricsByChild, auxiliaryCostsByChild] = await Promise.all([readChildConversationMetrics(db, childIds), readConversationAuxiliaryCostsFromSql(db, childIds, { includeDescendants: false })]);
367
+ const agents = children.map((agent) => {
368
+ const auxiliaryCosts = auxiliaryCostsByChild.get(agent.childConversationId);
369
+ return {
370
+ conversationId: agent.childConversationId,
371
+ cumulativeDurationMs: 0,
372
+ ...metricsByChild.get(agent.childConversationId),
373
+ ...auxiliaryCosts ? { auxiliaryCosts } : void 0,
374
+ status: agent.status,
375
+ updatedAt: new Date(agent.updatedAtMs).toISOString(),
376
+ ...canExposePayload ? {
377
+ ...agent.agentName ? { name: agent.agentName } : void 0,
378
+ task: agent.task,
379
+ ...agent.title ? { title: agent.title } : void 0,
380
+ ...agent.errorMessage ? { error: agent.errorMessage } : void 0
381
+ } : { redacted: true }
382
+ };
383
+ });
153
384
  return conversationChildAgentsReportSchema.parse({
154
385
  agents,
155
386
  conversationId
@@ -408,156 +639,6 @@ async function resolveSlackTeamDomains(teamIds) {
408
639
  return new Map(resolved.filter((entry) => entry !== void 0));
409
640
  }
410
641
  //#endregion
411
- //#region src/chat/conversations/fork.ts
412
- /** Event key of the agent note that follows the copied events in a fork. */
413
- const FORK_NOTE_KEY = "fork:note";
414
- const forkNote = alias(juniorConversationEvents, "fork_note");
415
- /**
416
- * SQL filter for event rows that a fork did not copy from its source. Usage
417
- * and cost reports use it, so copied model calls count only in the source.
418
- */
419
- function notCopiedByFork() {
420
- return sql`not exists (
421
- select 1 from ${juniorConversationEvents} as ${sql.identifier("fork_note")}
422
- where ${forkNote.conversationId} = ${juniorConversationEvents.conversationId}
423
- and ${forkNote.idempotencyKey} = ${FORK_NOTE_KEY}
424
- and ${forkNote.seq} > ${juniorConversationEvents.seq}
425
- )`;
426
- }
427
- /**
428
- * Events that hold credentials, approvals, provider connections, or plugin
429
- * state of the source actor. A fork does not copy them.
430
- */
431
- const UNCOPIED_EVENT_TYPES = /* @__PURE__ */ new Set([
432
- "authorization_completed",
433
- "authorization_requested",
434
- "guardian_action_reviewed",
435
- "mcp_provider_connected",
436
- "mcp_provider_connected_unowned",
437
- "structured_event",
438
- "unknown"
439
- ]);
440
- 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.";
441
- /** An expected fork request failure, such as a reply without saved history. */
442
- var ConversationForkError = class extends Error {};
443
- /**
444
- * Create a new web root Conversation from the agent history of a source
445
- * Conversation, through one completed assistant reply.
446
- *
447
- * Retries with the same requester and key return the same Conversation.
448
- */
449
- async function forkConversation(input) {
450
- const conversationId = createConversationId({
451
- actorEmail: input.actorEmail,
452
- idempotencyKey: JSON.stringify([
453
- "fork",
454
- input.sourceConversationId,
455
- input.idempotencyKey
456
- ])
457
- });
458
- const executor = getSqlExecutor();
459
- const events = getConversationEventStore();
460
- const store = getConversationStore();
461
- return withConversationEventLock(executor, input.sourceConversationId, () => withConversationEventLock(executor, conversationId, async () => {
462
- if (await store.get({ conversationId })) return { conversationId };
463
- const source = await store.get({ conversationId: input.sourceConversationId });
464
- if (!source || source.transcriptPurgedAtMs !== void 0) throw new ConversationForkError("The source history is no longer available.");
465
- if (source.parentConversationId) throw new ConversationForkError("You cannot fork a child conversation.");
466
- const boundary = await events.loadByIdempotencyKey(input.sourceConversationId, `message:${input.messageId}:agent`);
467
- const reply = boundary?.data.type === "assistant_message" ? piMessageFromHistoryItem(boundary.data) : void 0;
468
- 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.");
469
- const projection = await loadTurnProjection({
470
- conversationId: input.sourceConversationId,
471
- committedSeq: boundary.seq,
472
- includeTail: false
473
- });
474
- if (!projection?.messages.length) throw new ConversationForkError("The selected history is no longer available.");
475
- if (hasUnfinishedToolCalls(projection.messages)) throw new ConversationForkError("This history has unfinished tool calls. Choose another reply.");
476
- const nowMs = Date.now();
477
- await recordWebConversationActivity({
478
- actor: webActorFromEmail(input.actorEmail, { ...input.actorName ? { fullName: input.actorName } : void 0 }),
479
- conversationId,
480
- nowMs,
481
- rootVisibility: source.visibility === "public" ? "public" : "private"
482
- });
483
- await executor.db().update(juniorConversations).set({
484
- forkedFromConversationId: source.conversationId,
485
- title: source.title ? `Fork: ${source.title}` : "Forked conversation"
486
- }).where(eq(juniorConversations.conversationId, conversationId));
487
- await copyForkEvents({
488
- sourceConversationId: source.conversationId,
489
- conversationId,
490
- events: await events.loadHistory(source.conversationId),
491
- boundarySeq: boundary.seq,
492
- messageId: input.messageId
493
- });
494
- await events.append(conversationId, [{
495
- idempotencyKey: FORK_NOTE_KEY,
496
- createdAtMs: nowMs,
497
- data: {
498
- type: "user_message",
499
- provenance: contextProvenance,
500
- content: [{
501
- type: "text",
502
- text: FORK_NOTE
503
- }],
504
- timestamp: nowMs
505
- }
506
- }]);
507
- return { conversationId };
508
- }));
509
- }
510
- /**
511
- * Copy the source event rows through the selected reply. Copies keep their
512
- * sequence, author, timestamp, key, and payload, so the fork shows the same
513
- * transcript and event log, and seq references in compactions stay valid.
514
- * Later updates of copied Messages and the end of copied Turns are also
515
- * copied, so that no copied item stays open in the fork.
516
- */
517
- async function copyForkEvents(args) {
518
- const reply = args.events.find((event) => event.data.type === "message" && event.data.role === "assistant" && event.data.messageId === args.messageId);
519
- const cutoffSeq = Math.max(args.boundarySeq, reply?.seq ?? -1);
520
- const messageIds = /* @__PURE__ */ new Set();
521
- const turnIds = /* @__PURE__ */ new Set();
522
- const seqs = [];
523
- let historyVersion = 0;
524
- for (const event of args.events) {
525
- const data = event.data;
526
- if (UNCOPIED_EVENT_TYPES.has(data.type)) continue;
527
- if (event.idempotencyKey === FORK_NOTE_KEY) continue;
528
- if (event.seq <= cutoffSeq) {
529
- if (data.type === "message") messageIds.add(data.messageId);
530
- if (data.type === "turn_started") turnIds.add(data.turnId);
531
- historyVersion = Math.max(historyVersion, event.historyVersion);
532
- seqs.push(event.seq);
533
- } 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);
534
- }
535
- if (!seqs.length) return;
536
- const e = juniorConversationEvents;
537
- await getSqlExecutor().db().execute(sql`insert into ${e} (${sql.join([
538
- e.conversationId,
539
- e.seq,
540
- e.historyVersion,
541
- e.schemaVersion,
542
- e.idempotencyKey,
543
- e.type,
544
- e.payload,
545
- e.actorIdentityId,
546
- e.createdAt
547
- ].map((column) => sql.identifier(column.name)), sql`, `)})
548
- select ${args.conversationId}, ${e.seq}, least(${e.historyVersion}, ${historyVersion}), ${e.schemaVersion}, ${e.idempotencyKey}, ${e.type}, ${e.payload}, ${e.actorIdentityId}, ${e.createdAt}
549
- from ${e}
550
- where ${e.conversationId} = ${args.sourceConversationId}
551
- and ${inArray(e.seq, seqs)}`);
552
- }
553
- function hasUnfinishedToolCalls(messages) {
554
- const pending = /* @__PURE__ */ new Set();
555
- for (const message of messages) if (message.role === "assistant") {
556
- for (const part of message.content) if (part.type === "toolCall") pending.add(part.id);
557
- } else if (message.role === "toolResult") pending.delete(message.toolCallId);
558
- return pending.size > 0;
559
- }
560
- //#endregion
561
642
  //#region src/chat/pi/sql-model-usage.ts
562
643
  /** SQL reporting adapter for usage stored on assistant history events. */
563
644
  const message = sql`${juniorConversationEvents.payload}`;
@@ -934,64 +1015,6 @@ async function readRootConversationMetricsFromSql(db, rootConversationIds) {
934
1015
  }));
935
1016
  }
936
1017
  //#endregion
937
- //#region src/api/conversations/auxiliary-costs.ts
938
- const auxiliaryRootConversation = alias(juniorConversations, "auxiliary_root_conversation");
939
- function eventCost() {
940
- return sql`CASE
941
- WHEN ${juniorConversationEvents.type} = 'structured_event'
942
- AND jsonb_typeof(${juniorConversationEvents.payload}->'content'->'costUsd') = 'number'
943
- THEN (${juniorConversationEvents.payload}->'content'->>'costUsd')::numeric
944
- WHEN ${juniorConversationEvents.type} IN ('guardian_action_reviewed', 'turn_routed')
945
- AND jsonb_typeof(${juniorConversationEvents.payload}->'costUsd') = 'number'
946
- THEN (${juniorConversationEvents.payload}->>'costUsd')::numeric
947
- ELSE NULL
948
- END`;
949
- }
950
- /** Aggregate additive event costs by namespaced operation for conversations. */
951
- async function readConversationAuxiliaryCostsFromSql(db, conversationIds, options) {
952
- if (conversationIds.length === 0) return /* @__PURE__ */ new Map();
953
- const selectedIds = [...conversationIds];
954
- const conversationId = options.includeDescendants ? sql`coalesce(
955
- ${auxiliaryRootConversation.conversationId},
956
- ${juniorConversations.conversationId}
957
- )` : sql`${juniorConversations.conversationId}`;
958
- const namespace = sql`CASE
959
- WHEN ${juniorConversationEvents.type} = 'structured_event'
960
- THEN ${juniorConversationEvents.payload}->>'namespace'
961
- ELSE 'junior'
962
- END`;
963
- const name = sql`CASE
964
- WHEN ${juniorConversationEvents.type} = 'structured_event'
965
- THEN ${juniorConversationEvents.payload}->>'name'
966
- ELSE ${juniorConversationEvents.type}
967
- END`;
968
- const cost = eventCost();
969
- const selectedConversation = options.includeDescendants ? or(and(inArray(juniorConversations.rootConversationId, selectedIds), isNotNull(auxiliaryRootConversation.conversationId)), inArray(juniorConversations.conversationId, selectedIds)) : inArray(juniorConversations.conversationId, selectedIds);
970
- const rows = await db.select({
971
- conversationId,
972
- costUsd: sql`round(sum(${cost}), 12)::double precision`.mapWith(Number),
973
- events: sql`count(*)::integer`.mapWith(Number),
974
- name,
975
- namespace
976
- }).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);
977
- const result = /* @__PURE__ */ new Map();
978
- for (const row of rows) {
979
- const current = result.get(row.conversationId) ?? {
980
- costUsd: 0,
981
- operations: []
982
- };
983
- current.costUsd = Math.round((current.costUsd + row.costUsd) * 0xe8d4a51000) / 0xe8d4a51000;
984
- current.operations.push({
985
- costUsd: row.costUsd,
986
- events: row.events,
987
- name: row.name,
988
- namespace: row.namespace
989
- });
990
- result.set(row.conversationId, current);
991
- }
992
- return result;
993
- }
994
- //#endregion
995
1018
  //#region src/chat/plugins/unfinished-work.ts
996
1019
  /**
997
1020
  * Return assigned and unfinished plugin work for the candidate conversations.
@@ -1424,6 +1447,52 @@ async function readConversationForks(conversationId, viewer) {
1424
1447
  return result;
1425
1448
  }
1426
1449
  //#endregion
1450
+ //#region src/api/conversations/agent-tree.ts
1451
+ /** Bound the parent chain that one agent tree reads. */
1452
+ const MAX_AGENT_TREE_DEPTH = 16;
1453
+ const CHILD_LABEL = "Subagent";
1454
+ async function readParentConversationId(conversationId) {
1455
+ const [row] = await getDb().select({ parentId: juniorConversations.parentConversationId }).from(juniorConversations).where(eq(juniorConversations.conversationId, conversationId));
1456
+ return row?.parentId ?? void 0;
1457
+ }
1458
+ /**
1459
+ * Read the agent tree of a child Conversation: its parent chain, root first,
1460
+ * with the child last. Returns undefined for a root Conversation. Each
1461
+ * Conversation has its own access check, so a viewer sees a label only where
1462
+ * they can see private content.
1463
+ */
1464
+ async function readConversationAgentTree(conversationId, viewer) {
1465
+ const ids = [conversationId];
1466
+ let parentId = await readParentConversationId(conversationId);
1467
+ while (parentId && !ids.includes(parentId) && ids.length < MAX_AGENT_TREE_DEPTH) {
1468
+ ids.unshift(parentId);
1469
+ parentId = await readParentConversationId(parentId);
1470
+ }
1471
+ if (ids.length < 2) return;
1472
+ const root = parentId ? void 0 : await getConversationStore().get({ conversationId: ids[0] });
1473
+ const access = await readConversationAccessFromSql(getDb(), ids, viewer);
1474
+ return await Promise.all(ids.map(async (id) => {
1475
+ const itemAccess = access.get(id);
1476
+ if (root?.conversationId === id) return {
1477
+ conversationId: id,
1478
+ label: conversationSummaryFromStoredConversation({
1479
+ access: itemAccess,
1480
+ conversation: root,
1481
+ durationMs: 0
1482
+ }).displayTitle
1483
+ };
1484
+ if (!itemAccess?.canViewPrivateContent) return {
1485
+ conversationId: id,
1486
+ label: CHILD_LABEL
1487
+ };
1488
+ const agent = await getChildAgentLabel(id);
1489
+ return {
1490
+ conversationId: id,
1491
+ label: agent?.title ?? agent?.agentName ?? CHILD_LABEL
1492
+ };
1493
+ }));
1494
+ }
1495
+ //#endregion
1427
1496
  //#region src/api/conversations/detail.ts
1428
1497
  /** Project stored metadata and a bounded event page into a signed history cursor. */
1429
1498
  function projectConversationDetail(args) {
@@ -1523,10 +1592,11 @@ async function readConversationDetail(conversationId, options = {}) {
1523
1592
  limit: options.limit ?? 500
1524
1593
  });
1525
1594
  if (!report) return void 0;
1526
- const forks = await readConversationForks(conversationId, options.viewer);
1595
+ const [forks, agentTree] = await Promise.all([readConversationForks(conversationId, options.viewer), readConversationAgentTree(conversationId, options.viewer)]);
1527
1596
  return conversationDetailReportSchema.parse({
1528
1597
  ...report,
1529
- ...forks
1598
+ ...forks,
1599
+ ...agentTree ? { agentTree } : void 0
1530
1600
  });
1531
1601
  }
1532
1602
  //#endregion
@@ -3387,7 +3457,7 @@ async function readPeoplePluginReports(args) {
3387
3457
  reports: [],
3388
3458
  source: "plugins"
3389
3459
  });
3390
- const { getPluginProfileReports } = await import("./agent-hooks-Cqo6ULp6.js").then((n) => n.t);
3460
+ const { getPluginProfileReports } = await import("./agent-hooks-Caa0Qh_Q.js").then((n) => n.t);
3391
3461
  return pluginOperationalReportFeedSchema.parse({
3392
3462
  generatedAt: new Date(nowMs).toISOString(),
3393
3463
  reports: await getPluginProfileReports({
@@ -4082,7 +4152,7 @@ async function changeViewerAutomationLifecycle(user, kind, id, input) {
4082
4152
  /** Read-only editor support. Core owns schedules; plugins own Event choices. */
4083
4153
  /** Return only Event types that can start a durable Automation. */
4084
4154
  function readAutomationEventCatalog() {
4085
- return Object.entries(pluginEventCatalog(getEventCatalog())).flatMap(([namespace, registration]) => registration.resourceTypes.map((resource) => ({
4155
+ return Object.entries(automationEventCatalog(getEventCatalog())).flatMap(([namespace, registration]) => registration.resourceTypes.map((resource) => ({
4086
4156
  namespace,
4087
4157
  ...resource
4088
4158
  })));
@@ -4355,7 +4425,7 @@ function readDescriptionText(home) {
4355
4425
  }
4356
4426
  /** Read discovered skill names for authenticated runtime diagnostics. */
4357
4427
  async function readSkillReports() {
4358
- const { discoverSkills } = await import("./skills-Zj-9pbSU.js").then((n) => n.o);
4428
+ const { discoverSkills } = await import("./skills-C4yrD2NR.js").then((n) => n.o);
4359
4429
  const skills = await discoverSkills();
4360
4430
  return skillReportsSchema.parse(skills.map((skill) => ({
4361
4431
  name: skill.name,
@@ -4364,7 +4434,7 @@ async function readSkillReports() {
4364
4434
  }
4365
4435
  /** Read safe configured plugin metadata for authenticated runtime diagnostics. */
4366
4436
  async function readPlugins() {
4367
- const { pluginCatalogRuntime } = await import("./catalog-runtime-jazDa-oO.js").then((n) => n.t);
4437
+ const { pluginCatalogRuntime } = await import("./catalog-runtime-wmq_fqff.js").then((n) => n.t);
4368
4438
  return pluginsSchema.parse(pluginCatalogRuntime.getProviders().map((plugin) => ({
4369
4439
  configKeys: plugin.manifest.configKeys ?? [],
4370
4440
  description: plugin.manifest.description,
@@ -4376,7 +4446,7 @@ async function readPlugins() {
4376
4446
  async function readRuntimeInfoReport() {
4377
4447
  const [{ homeDir }, { pluginCatalogRuntime }, plugins, skills] = await Promise.all([
4378
4448
  import("./package-discovery-BVvSnQBB.js").then((n) => n.r),
4379
- import("./catalog-runtime-jazDa-oO.js").then((n) => n.t),
4449
+ import("./catalog-runtime-wmq_fqff.js").then((n) => n.t),
4380
4450
  readPlugins(),
4381
4451
  readSkillReports()
4382
4452
  ]);
@@ -4393,7 +4463,7 @@ async function readRuntimeInfoReport() {
4393
4463
  /** Read sanitized operational summaries contributed by plugins. */
4394
4464
  async function readPluginOperationalReportFeed() {
4395
4465
  const nowMs = Date.now();
4396
- const { getPluginOperationalReports } = await import("./agent-hooks-Cqo6ULp6.js").then((n) => n.t);
4466
+ const { getPluginOperationalReports } = await import("./agent-hooks-Caa0Qh_Q.js").then((n) => n.t);
4397
4467
  return pluginOperationalReportFeedSchema.parse({
4398
4468
  source: "plugins",
4399
4469
  generatedAt: new Date(nowMs).toISOString(),