@tea-agent/loop-agent 0.41.0 → 0.42.0-next.1

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 (162) hide show
  1. package/CHANGELOG.md +102 -14
  2. package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
  3. package/dist/adapters/context-transfer/pi-session.js +61 -0
  4. package/dist/build-stamp.json +3 -3
  5. package/dist/cli/command-definitions.js +12 -0
  6. package/dist/cli/program.js +23 -1
  7. package/dist/commands/dag-artifact.js +284 -0
  8. package/dist/commands/dag-context.js +184 -0
  9. package/dist/commands/dag-rerun.js +206 -1
  10. package/dist/executors/dag-pi-executor.js +39 -12
  11. package/dist/executors/pi-sdk-executor.js +3 -0
  12. package/dist/{worker → infrastructure}/console/app-data.js +4 -0
  13. package/dist/infrastructure/console/artifact-revision-store.js +430 -0
  14. package/dist/infrastructure/console/context-export-store.js +160 -0
  15. package/dist/infrastructure/console/dir-lock.js +132 -0
  16. package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
  17. package/dist/infrastructure/harness/artifact-store.js +10 -1
  18. package/dist/infrastructure/harness/atomic-write.js +12 -2
  19. package/dist/shared/context-transfer/artifact-revision.js +172 -0
  20. package/dist/shared/context-transfer.js +418 -0
  21. package/dist/shared/operator/capabilities.js +14 -0
  22. package/dist/shared/operator/safe-run-summary.js +1 -0
  23. package/dist/shared/path-safety.js +93 -0
  24. package/dist/shared/preview.js +28 -4
  25. package/dist/task/config-types.js +2 -1
  26. package/dist/task/task-demand-routing.js +3 -0
  27. package/dist/worker/console/chat/chat-event-store.js +2 -2
  28. package/dist/worker/console/chat/resource-preferences-store.js +1 -1
  29. package/dist/worker/console/chat/routes.js +1 -1
  30. package/dist/worker/console/chat/session-store.js +1 -1
  31. package/dist/worker/console/chat/user-questions.js +1 -1
  32. package/dist/worker/console/console-update-runtime.js +1 -1
  33. package/dist/worker/console/context-transfer-diagnostics.js +198 -0
  34. package/dist/worker/console/dag-confirmation.js +1 -1
  35. package/dist/worker/console/dag-execution-receipt.js +1 -1
  36. package/dist/worker/console/doctor.js +1 -1
  37. package/dist/worker/console/draft-store.js +1 -1
  38. package/dist/worker/console/human-gate-token.js +1 -1
  39. package/dist/worker/console/index.js +3 -3
  40. package/dist/worker/console/interview/assessment.js +1 -1
  41. package/dist/worker/console/interview/session.js +1 -1
  42. package/dist/worker/console/operation-runner.js +45 -4
  43. package/dist/worker/console/operation-sse.js +1 -1
  44. package/dist/worker/console/operation-wait.js +1 -1
  45. package/dist/worker/console/operator-actions.js +104 -3
  46. package/dist/worker/console/operator-surface-health.js +1 -1
  47. package/dist/worker/console/operator-user-error.js +169 -0
  48. package/dist/worker/console/pi-plugins.js +57 -0
  49. package/dist/worker/console/routes.js +637 -8
  50. package/dist/worker/console/security.js +35 -0
  51. package/dist/worker/console/server.js +6 -3
  52. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DwVJxUxz.js → abnfDiagram-N423BO3Z-DZZ8m3PO.js} +1 -1
  53. package/dist/worker/console/static/assets/{arc-DqfCcXdu.js → arc-D6PvaVd-.js} +1 -1
  54. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-qMVtggRv.js → architectureDiagram-T3A2C74G-B_OTOiI8.js} +1 -1
  55. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-ECrV38dh.js → blockDiagram-VBNYF7ZC-Bv6rqHBg.js} +1 -1
  56. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DBaaLqQv.js → c4Diagram-5PPSVZJV-B8eHr0oz.js} +1 -1
  57. package/dist/worker/console/static/assets/channel-BU5gOilw.js +1 -0
  58. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-BvfNHkAz.js → chunk-2GRJ4B5K-DYwR0im2.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-C5StiIbc.js → chunk-2Q5K7J3B-D2WPGqXt.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-HabGiJkS.js → chunk-5RXB4S5H-CQISJ_I7.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BiTg127i.js → chunk-5VM5RSS4-C0o2Du1e.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-CZsXBPuR.js → chunk-6Q2QTUOP-4f8kr-U7.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D-r1MxLK.js → chunk-GF5L2VYU-D_OWvXzX.js} +1 -1
  64. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-DQxb9plr.js → chunk-JWPE2WC7-CasdPz5X.js} +1 -1
  65. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DMndGvCT.js → chunk-KBJHAD2P-Cj8lRrla.js} +1 -1
  66. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-ByL4BgOJ.js → chunk-RYQCIY6F-CUvD1FSd.js} +1 -1
  67. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-BVe1b1gL.js → chunk-XXDRQBXY-CqLw_eqB.js} +1 -1
  68. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +1 -0
  69. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +1 -0
  70. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-BAyGzN8j.js → cose-bilkent-JH36ORCC-ku-WqJPx.js} +1 -1
  71. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-C5yVYU7E.js → cynefin-VYW2F7L2-Bq_PYDhl.js} +1 -1
  72. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-x-MwsEm9.js → cynefinDiagram-MW4NZA55-BiGZV641.js} +1 -1
  73. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CT5Tle5F.js → dagre-VZM6K2ZE-C4JRlglF.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-BARERhlS.js → diagram-7IWD3JNH-B0dNeWYG.js} +1 -1
  75. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-BwfUz0VM.js → diagram-B4RE2ZJO-Cj35YvYM.js} +1 -1
  76. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-Mk4r2WvJ.js → diagram-LBJQPF4R-uzQoJ2-8.js} +1 -1
  77. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-KUrxuBs0.js → diagram-Q27KOJAE-D1a-Buoz.js} +1 -1
  78. package/dist/worker/console/static/assets/{diagram-UB23O5K3-CjZOceZn.js → diagram-UB23O5K3-XjRrLRSs.js} +1 -1
  79. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-rnwUCiEI.js → ebnfDiagram-BXEA7PRR-Da_O24cW.js} +1 -1
  80. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DM4j23FM.js → erDiagram-JOGREHBK-BLJ8jrYU.js} +1 -1
  81. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-DD5HdDEs.js → flowDiagram-UKHOOZJN-B9GrLjM0.js} +1 -1
  82. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CIAGSF47.js → ganttDiagram-PKOTCBZU-CeJ0TqiK.js} +1 -1
  83. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-DbwFbVEL.js → gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js} +1 -1
  84. package/dist/worker/console/static/assets/index-H9rFJiGL.css +1 -0
  85. package/dist/worker/console/static/assets/{index-Bih9w9h9.js → index-xwu9GxEc.js} +80 -80
  86. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-B-Pb0T66.js → infoDiagram-6WML65LV-DB26i3d8.js} +1 -1
  87. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-B9Unk-YX.js → ishikawaDiagram-WSZJBQD7-BOGRyeZT.js} +1 -1
  88. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-C2F3JkPt.js → journeyDiagram-NVQOT4AX-CZRoBO_6.js} +1 -1
  89. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-Bl8-Xcoz.js → kanban-definition-27J2QSJJ-qlWhJyoB.js} +1 -1
  90. package/dist/worker/console/static/assets/{linear-yK4uZmC5.js → linear-CfUiDDB3.js} +1 -1
  91. package/dist/worker/console/static/assets/{mermaid.core-Bpa6PmuE.js → mermaid.core-BQe6fpqj.js} +5 -5
  92. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-BfGRoCSe.js → mindmap-definition-FAOFIHXS-zFzWHw64.js} +1 -1
  93. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bz6_HtST.js → pegDiagram-VL7TDLO6-DFGUqjZO.js} +1 -1
  94. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-i0Umbsby.js → pieDiagram-7S7Q4E2Y-BB5i0l8Z.js} +1 -1
  95. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DaVQBobe.js → quadrantDiagram-CIZ2JOQS-obbVZ_X5.js} +1 -1
  96. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-DMTGnSq7.js → railroadDiagram-AXF67PYL-BwdaNzc1.js} +1 -1
  97. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-2ao_DaSq.js → requirementDiagram-LRYGKXZP-CkTJaugh.js} +1 -1
  98. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-2SG1lTB9.js → sankeyDiagram-W5VNT64P-BwJ-hIgq.js} +1 -1
  99. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BQYOLybR.js → sequenceDiagram-SI44F4Z6-BzSgmm7w.js} +1 -1
  100. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-C8g3yjuI.js → sizeCapture-X5ZJPWSS-Bn3oj5Q1.js} +1 -1
  101. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-q_F8nAQG.js → stateDiagram-OKZ733FA-dxhGtL8K.js} +1 -1
  102. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +1 -0
  103. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-8LrWpH2s.js → swimlanes-SLNWSIFB-BLpEInya.js} +2 -2
  104. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +8 -0
  105. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-BQoY3qzl.js → timeline-definition-Z64GVDOM-ChJGYcXy.js} +1 -1
  106. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BDu1UjSp.js → vennDiagram-T6HMQDX7-DK-qjRer.js} +1 -1
  107. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-C48M7Y0a.js → wardleyDiagram-T6FBY63Y-3qeaWAg-.js} +1 -1
  108. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-YU6FTl_F.js → xychartDiagram-ELKLHX3M-CootlyP9.js} +1 -1
  109. package/dist/worker/console/static/index.html +7 -2
  110. package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
  111. package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
  112. package/dist/worker/console/workspace-context.js +28 -2
  113. package/dist/worker/console/workspace-registry.js +2 -2
  114. package/dist/worker/continuation/worker-continuation.js +278 -0
  115. package/dist/worker/observe/health.js +1 -1
  116. package/dist/worker/observe/node-transparency.js +572 -0
  117. package/dist/worker/observe/routes.js +51 -1
  118. package/dist/worker/observe/static/api.js +69 -5
  119. package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
  120. package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
  121. package/dist/worker/observe/static/state.js +3 -1
  122. package/dist/worker/observe/static/styles.css +1037 -126
  123. package/dist/worker/observe/static/views/dag-inspector.js +1215 -58
  124. package/dist/worker/observe/static/views/session-timeline.js +75 -10
  125. package/dist/worker/outcomes/adapters.js +19 -0
  126. package/dist/worker/outcomes/types.js +1 -0
  127. package/dist/worker/pool/attempt-lease.js +97 -66
  128. package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
  129. package/dist/worker/pool/reconcile.js +46 -1
  130. package/dist/worker/pool/run-store.js +2 -0
  131. package/dist/worker/pool/state-projection.js +2 -0
  132. package/dist/worker/task-spec/workflow-routing.js +8 -3
  133. package/dist/workflows/dag/artifact-bindings.js +149 -0
  134. package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
  135. package/dist/workflows/dag/budget-enforcement.js +10 -0
  136. package/dist/workflows/dag/context-receipt.js +305 -0
  137. package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
  138. package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
  139. package/dist/workflows/dag/context-transfer/renderers.js +93 -0
  140. package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
  141. package/dist/workflows/dag/init-hybrid.js +146 -0
  142. package/dist/workflows/dag/node-execution.js +112 -27
  143. package/dist/workflows/dag/path-safety.js +1 -0
  144. package/dist/workflows/dag/prompt.js +29 -4
  145. package/dist/workflows/dag/rerun-plan.js +555 -1
  146. package/dist/workflows/dag/rerun-run.js +601 -15
  147. package/dist/workflows/dag/runner.js +20 -1
  148. package/dist/workflows/dag/skill-snapshot.js +17 -0
  149. package/dist/workflows/dag/types.js +183 -7
  150. package/docs/architecture/runtime-boundaries.md +6 -6
  151. package/docs/architecture/worker-and-feature.md +1 -1
  152. package/docs/templates/agent-dag.schema.json +32 -0
  153. package/docs/templates/product-line/task.yaml +1 -1
  154. package/package.json +3 -1
  155. package/skills/loop-agent/references/command-reference.md +6 -0
  156. package/dist/worker/console/static/assets/channel-CL1NRAmS.js +0 -1
  157. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-PBGzzjZA.js +0 -1
  158. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-PBGzzjZA.js +0 -1
  159. package/dist/worker/console/static/assets/index-BfleX-qj.css +0 -1
  160. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DrxGBDJu.js +0 -1
  161. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-BhpDxzAf.js +0 -8
  162. /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
@@ -1,14 +1,22 @@
1
+ import { createHash, randomBytes } from "node:crypto";
1
2
  import { existsSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
3
+ import { readFile, rm } from "node:fs/promises";
3
4
  import path from "node:path";
4
5
  import { dispatchOperatorAction } from "./operator-actions.js";
5
6
  import { openSseResponse, parseLastEventId, writeSseEvent, } from "./operation-sse.js";
6
7
  import { probePiReadiness } from "./pi-readiness.js";
7
8
  import { buildOperatorSurfaceHealthV1 } from "./operator-surface-health.js";
8
9
  import { ROUTES } from "../observe/routes.js";
9
- import { evaluateMutationGate, injectConfirmationTokenScript, isMutationMethod, } from "./security.js";
10
+ import { evaluateProtectedDownloadGate, evaluateMutationGate, injectConfirmationTokenScript, isMutationMethod, } from "./security.js";
10
11
  import { listDagRunsForRecovery } from "./resolve-dag-run-for-task.js";
11
- import { installPiPlugin, listPiPluginVersions, listPiPlugins, uninstallPiPlugin, } from "./pi-plugins.js";
12
+ import { writeArtifactRevisionUpload } from "../../infrastructure/console/artifact-revision-store.js";
13
+ import { ensureSecureDir } from "../../infrastructure/console/app-data.js";
14
+ import { acquireContextExportLease, createContextExport, releaseContextExportLease, } from "../../infrastructure/console/context-export-store.js";
15
+ import { acceptOperation } from "./operator-actions.js";
16
+ import { recordContextTransferDiagnostic, recordContextTransferEvent, } from "./context-transfer-diagnostics.js";
17
+ import { isDagRerunPromptOverrideEnabled } from "../../shared/dag-prompt-override.js";
18
+ import { beginWorkerContinuation, resolveWorkerContinuationAuthority, settleWorkerContinuation, } from "../continuation/worker-continuation.js";
19
+ import { installPiPlugin, listPiPluginVersions, listPiPlugins, uninstallPiPlugin, checkPiPluginUpdates, } from "./pi-plugins.js";
12
20
  import { pickNativeWorkspaceDirectory } from "./native-directory-picker.js";
13
21
  export function sendJson(res, status, body, extraHeaders) {
14
22
  const payload = JSON.stringify(body);
@@ -149,7 +157,516 @@ async function handleSessionPing(req, res, ctx) {
149
157
  export const INSPECT_MUTATION_ALLOWED_ACTIONS = new Set([
150
158
  "dagRerunPlan",
151
159
  "dagRerun",
160
+ "contextExport",
161
+ "artifactRevisionStage",
162
+ "artifactRevisionPlan",
163
+ "artifactRevisionExecute",
152
164
  ]);
165
+ function evaluateConsoleCapability(req, ctx) {
166
+ const common = {
167
+ consoleOrigin: ctx.getConsoleOrigin(),
168
+ allowNonLoopbackAccess: ctx.allowNonLoopbackAccess === true,
169
+ };
170
+ const general = evaluateMutationGate(req, {
171
+ ...common,
172
+ cookieName: ctx.bootToken.cookieName,
173
+ bootToken: ctx.bootToken.value,
174
+ confirmationToken: ctx.bootToken.confirmationToken,
175
+ });
176
+ if (!general || !ctx.inspectBootToken)
177
+ return general;
178
+ return evaluateMutationGate(req, {
179
+ ...common,
180
+ cookieName: ctx.inspectBootToken.cookieName,
181
+ bootToken: ctx.inspectBootToken.value,
182
+ confirmationToken: ctx.inspectBootToken.confirmationToken,
183
+ });
184
+ }
185
+ function evaluateConsoleDownloadCapability(req, ctx) {
186
+ const common = {
187
+ consoleOrigin: ctx.getConsoleOrigin(),
188
+ allowNonLoopbackAccess: ctx.allowNonLoopbackAccess === true,
189
+ };
190
+ const general = evaluateProtectedDownloadGate(req, {
191
+ ...common,
192
+ cookieName: ctx.bootToken.cookieName,
193
+ bootToken: ctx.bootToken.value,
194
+ confirmationToken: ctx.bootToken.confirmationToken,
195
+ });
196
+ if (!general || !ctx.inspectBootToken)
197
+ return general;
198
+ return evaluateProtectedDownloadGate(req, {
199
+ ...common,
200
+ cookieName: ctx.inspectBootToken.cookieName,
201
+ bootToken: ctx.inspectBootToken.value,
202
+ confirmationToken: ctx.inspectBootToken.confirmationToken,
203
+ });
204
+ }
205
+ async function acceptContextExportOperation(input) {
206
+ const operator = input.ctx.operator;
207
+ const clientRequestId = input.clientRequestId?.trim();
208
+ if (!clientRequestId) {
209
+ return {
210
+ status: 400,
211
+ body: { ok: false, error: { code: "INVALID_INPUT", message: "clientRequestId is required" } },
212
+ };
213
+ }
214
+ const runId = typeof input.actionParams.runId === "string" ? input.actionParams.runId.trim() : "";
215
+ const nodeId = typeof input.actionParams.nodeId === "string" ? input.actionParams.nodeId.trim() : "";
216
+ const attempt = Number(input.actionParams.attempt ?? 1);
217
+ const profile = input.actionParams.profile === "raw" ? "raw" : "redacted";
218
+ const target = ["generic", "pi-session", "pi-handoff", "readable"].includes(String(input.actionParams.target))
219
+ ? input.actionParams.target
220
+ : "generic";
221
+ if (!runId || !nodeId || !Number.isInteger(attempt) || attempt < 1) {
222
+ return {
223
+ status: 400,
224
+ body: { ok: false, error: { code: "INVALID_INPUT", message: "runId, nodeId and positive attempt are required" } },
225
+ };
226
+ }
227
+ if (profile === "raw" && input.actionParams.rawAcknowledged !== true) {
228
+ return {
229
+ status: 400,
230
+ body: { ok: false, error: { code: "RAW_EXPORT_ACKNOWLEDGEMENT_REQUIRED", message: "raw export requires explicit acknowledgement" } },
231
+ };
232
+ }
233
+ const created = await operator.operations.create({
234
+ clientRequestId,
235
+ action: "contextExport",
236
+ actionParams: { runId, nodeId, attempt, profile, target, rawAcknowledged: profile === "raw" },
237
+ });
238
+ if (created.kind === "conflict") {
239
+ return {
240
+ status: 409,
241
+ body: { ok: false, error: { code: created.code, message: created.message } },
242
+ };
243
+ }
244
+ if (created.kind === "created") {
245
+ void recordContextTransferEvent({
246
+ appData: operator.actionContext.appData,
247
+ event: "context_export.planned",
248
+ fields: {
249
+ runId,
250
+ nodeId,
251
+ profile,
252
+ target,
253
+ operationId: created.operation.operationId,
254
+ },
255
+ }).catch(() => undefined);
256
+ queueMicrotask(() => {
257
+ void (async () => {
258
+ const startedAt = Date.now();
259
+ await operator.operations.update(created.operation.operationId, {
260
+ state: "running",
261
+ startedAt: new Date().toISOString(),
262
+ });
263
+ try {
264
+ const appData = operator.actionContext.appData;
265
+ if (!appData.contextExports)
266
+ throw new Error("console context export root unavailable");
267
+ ensureSecureDir(appData.contextExports);
268
+ const incomingPath = path.join(appData.contextExports, `${randomBytes(16).toString("hex")}.incoming.zip`);
269
+ await rm(incomingPath, { force: true });
270
+ try {
271
+ const kernel = await runKernelCommand({
272
+ ctx: input.ctx,
273
+ args: [
274
+ "dag", "context", "export",
275
+ "--run-id", runId,
276
+ "--node-id", nodeId,
277
+ "--attempt", String(attempt),
278
+ "--target", target,
279
+ "--redaction", profile,
280
+ ...(profile === "raw" ? ["--acknowledge-sensitive-data"] : []),
281
+ "--output", incomingPath,
282
+ "--json",
283
+ ],
284
+ });
285
+ const payload = (kernel.json ?? {});
286
+ if (!kernel.ok || !payload.bundleId || !payload.bundleSha256) {
287
+ throw new Error(kernelFailureMessage(kernel));
288
+ }
289
+ const zipBytes = await readFile(incomingPath);
290
+ const exported = await createContextExport({
291
+ appData,
292
+ bundle: {
293
+ zipBytes,
294
+ manifest: {
295
+ bundleId: payload.bundleId,
296
+ bundleSha256: payload.bundleSha256,
297
+ profile,
298
+ target,
299
+ },
300
+ },
301
+ });
302
+ await recordContextTransferDiagnostic({
303
+ appData: operator.actionContext.appData,
304
+ event: "context-bundle.generate",
305
+ dimensions: {
306
+ outcome: "succeeded",
307
+ target,
308
+ profile,
309
+ bytes: exported.bytes,
310
+ durationMs: Date.now() - startedAt,
311
+ },
312
+ }).catch(() => undefined);
313
+ await recordContextTransferEvent({
314
+ appData: operator.actionContext.appData,
315
+ event: "context_export.ready",
316
+ fields: {
317
+ runId,
318
+ nodeId,
319
+ exportId: exported.exportId,
320
+ profile,
321
+ target,
322
+ bytes: exported.bytes,
323
+ durationMs: Date.now() - startedAt,
324
+ operationId: created.operation.operationId,
325
+ },
326
+ }).catch(() => undefined);
327
+ await operator.operations.update(created.operation.operationId, {
328
+ state: "succeeded",
329
+ finishedAt: new Date().toISOString(),
330
+ result: {
331
+ ok: true,
332
+ exportId: exported.exportId,
333
+ bundleId: exported.bundleId,
334
+ bundleSha256: exported.bundleSha256,
335
+ bytes: exported.bytes,
336
+ expiresAt: exported.expiresAt,
337
+ downloadUrl: `/api/operator/v1/context-exports/${exported.exportId}/download`,
338
+ },
339
+ });
340
+ }
341
+ finally {
342
+ await rm(incomingPath, { force: true }).catch(() => undefined);
343
+ }
344
+ }
345
+ catch (error) {
346
+ await recordContextTransferDiagnostic({
347
+ appData: operator.actionContext.appData,
348
+ event: "context-bundle.generate",
349
+ dimensions: {
350
+ outcome: "failed",
351
+ target,
352
+ profile,
353
+ reasonCode: "CONTEXT_EXPORT_FAILED",
354
+ durationMs: Date.now() - startedAt,
355
+ },
356
+ }).catch(() => undefined);
357
+ await recordContextTransferEvent({
358
+ appData: operator.actionContext.appData,
359
+ event: "context_export.failed",
360
+ fields: {
361
+ runId,
362
+ nodeId,
363
+ profile,
364
+ target,
365
+ reasonCode: "CONTEXT_EXPORT_FAILED",
366
+ durationMs: Date.now() - startedAt,
367
+ operationId: created.operation.operationId,
368
+ },
369
+ }).catch(() => undefined);
370
+ await operator.operations.update(created.operation.operationId, {
371
+ state: "failed",
372
+ finishedAt: new Date().toISOString(),
373
+ errorCode: "CONTEXT_EXPORT_FAILED",
374
+ errorMessage: error instanceof Error ? error.message : String(error),
375
+ });
376
+ }
377
+ })();
378
+ });
379
+ }
380
+ return {
381
+ status: 202,
382
+ body: {
383
+ operationId: created.operation.operationId,
384
+ state: created.operation.state,
385
+ clientRequestId: created.operation.clientRequestId,
386
+ action: created.operation.action,
387
+ },
388
+ };
389
+ }
390
+ async function runKernelCommand(input) {
391
+ const actionContext = input.ctx.operator.actionContext;
392
+ const run = actionContext.runCommand ??
393
+ ((args, options) => actionContext.client.run(args, options));
394
+ return (await run(input.args, {
395
+ cwd: actionContext.repoRoot,
396
+ artifactName: `kernel-${input.args.filter((arg) => !arg.startsWith("--")).slice(0, 3).join("-").replace(/\s+/g, "-")}`,
397
+ expectJson: true,
398
+ env: { ...process.env, LOOP_CONSOLE_APP_DATA: actionContext.appData.root },
399
+ }));
400
+ }
401
+ function kernelFailureMessage(result) {
402
+ const stderr = result.stderr.trim();
403
+ if (stderr) {
404
+ const lastLine = stderr.split("\n").filter(Boolean).pop() ?? stderr;
405
+ return lastLine.slice(0, 300);
406
+ }
407
+ const stdout = result.stdout.trim();
408
+ if (stdout)
409
+ return stdout.slice(0, 300);
410
+ return `kernel command failed with exit code ${result.exitCode}`;
411
+ }
412
+ function decodeBoundedBase64(value) {
413
+ if (typeof value !== "string" || value.length === 0 || value.length > 14_000_000) {
414
+ throw new Error("candidateBase64 is required and exceeds no more than 10 MiB decoded");
415
+ }
416
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value) || value.length % 4 !== 0) {
417
+ throw new Error("candidateBase64 is invalid");
418
+ }
419
+ const bytes = Buffer.from(value, "base64");
420
+ if (bytes.byteLength > 10 * 1024 * 1024)
421
+ throw new Error("candidate exceeds 10 MiB");
422
+ return bytes;
423
+ }
424
+ function revisionProjection(revision) {
425
+ if (!revision)
426
+ return undefined;
427
+ return {
428
+ revisionId: revision.revisionId,
429
+ state: revision.state,
430
+ parent: revision.parent,
431
+ candidate: {
432
+ sha256: revision.candidate.sha256,
433
+ bytes: revision.candidate.bytes,
434
+ mediaType: revision.candidate.mediaType,
435
+ uploadedAt: revision.candidate.uploadedAt,
436
+ },
437
+ validation: revision.validation,
438
+ diff: {
439
+ kind: revision.diff.kind,
440
+ summary: revision.diff.summary,
441
+ truncated: revision.diff.truncated,
442
+ },
443
+ expiresAt: revision.expiresAt,
444
+ };
445
+ }
446
+ async function acceptArtifactRevisionOperation(input) {
447
+ const operator = input.ctx.operator;
448
+ const clientRequestId = input.clientRequestId?.trim();
449
+ if (!clientRequestId) {
450
+ return { status: 400, body: { ok: false, error: { code: "INVALID_INPUT", message: "clientRequestId is required" } } };
451
+ }
452
+ let candidateBytes;
453
+ try {
454
+ if (input.action === "artifactRevisionStage") {
455
+ candidateBytes = decodeBoundedBase64(input.actionParams.candidateBase64);
456
+ }
457
+ }
458
+ catch (error) {
459
+ return { status: 400, body: { ok: false, error: { code: "INVALID_INPUT", message: error instanceof Error ? error.message : String(error) } } };
460
+ }
461
+ const runId = typeof input.actionParams.runId === "string" ? input.actionParams.runId.trim() : "";
462
+ const nodeId = typeof input.actionParams.nodeId === "string" ? input.actionParams.nodeId.trim() : "";
463
+ const artifactId = typeof input.actionParams.artifactId === "string" ? input.actionParams.artifactId.trim() : "";
464
+ const revisionId = typeof input.actionParams.revisionId === "string" ? input.actionParams.revisionId.trim() : "";
465
+ const planHash = typeof input.actionParams.planHash === "string" ? input.actionParams.planHash.trim() : "";
466
+ const reason = typeof input.actionParams.reason === "string" ? input.actionParams.reason.trim() : "";
467
+ const mediaType = typeof input.actionParams.mediaType === "string" ? input.actionParams.mediaType.trim() : "";
468
+ if (!runId || !artifactId || (input.action === "artifactRevisionStage" && (!nodeId || !mediaType))) {
469
+ return { status: 400, body: { ok: false, error: { code: "INVALID_INPUT", message: "runId, artifactId, nodeId and mediaType are required" } } };
470
+ }
471
+ if (input.action !== "artifactRevisionStage" && !revisionId) {
472
+ return { status: 400, body: { ok: false, error: { code: "INVALID_INPUT", message: "revisionId is required" } } };
473
+ }
474
+ if (input.action === "artifactRevisionExecute" && (!planHash || !reason)) {
475
+ return { status: 400, body: { ok: false, error: { code: "INVALID_INPUT", message: "planHash and reason are required" } } };
476
+ }
477
+ let workerContinuation;
478
+ if (input.action !== "artifactRevisionStage") {
479
+ try {
480
+ workerContinuation = await resolveWorkerContinuationAuthority({
481
+ repoRoot: input.ctx.repoRoot,
482
+ parentDagRunId: runId,
483
+ revisionId,
484
+ allowActiveRevision: input.action === "artifactRevisionExecute",
485
+ });
486
+ }
487
+ catch (error) {
488
+ const message = error instanceof Error ? error.message : String(error);
489
+ return {
490
+ status: 422,
491
+ body: {
492
+ ok: false,
493
+ error: {
494
+ code: "WORKER_CONTINUATION_AUTHORITY_MISMATCH",
495
+ message,
496
+ },
497
+ },
498
+ };
499
+ }
500
+ }
501
+ const safeActionParams = {
502
+ runId,
503
+ artifactId,
504
+ ...(nodeId ? { nodeId } : {}),
505
+ ...(revisionId ? { revisionId } : {}),
506
+ ...(planHash ? { planHash } : {}),
507
+ ...(reason ? { reason } : {}),
508
+ ...(mediaType ? { mediaType } : {}),
509
+ ...(candidateBytes
510
+ ? { candidateSha256: createHash("sha256").update(candidateBytes).digest("hex"), candidateBytes: candidateBytes.byteLength }
511
+ : {}),
512
+ ...(workerContinuation
513
+ ? { workerContinuation: workerContinuation.authority }
514
+ : {}),
515
+ };
516
+ const appData = operator.actionContext.appData;
517
+ if (!appData.artifactRevisions) {
518
+ return { status: 503, body: { ok: false, error: { code: "INTERNAL_ERROR", message: "console artifact revision root unavailable" } } };
519
+ }
520
+ let cliArgs;
521
+ if (input.action === "artifactRevisionStage") {
522
+ const candidateSha256 = safeActionParams.candidateSha256;
523
+ cliArgs = [
524
+ "dag", "artifact", "revision", "stage",
525
+ "--run-id", runId,
526
+ "--node-id", nodeId,
527
+ "--artifact-id", artifactId,
528
+ "--candidate-upload", candidateSha256,
529
+ "--request-id", `console-${clientRequestId}`,
530
+ "--json",
531
+ ];
532
+ }
533
+ else if (input.action === "artifactRevisionPlan") {
534
+ cliArgs = [
535
+ "dag", "rerun",
536
+ "--run-id", runId,
537
+ "--artifact-revision", revisionId,
538
+ "--plan", "--json",
539
+ ];
540
+ }
541
+ else {
542
+ cliArgs = [
543
+ "dag", "rerun",
544
+ "--run-id", runId,
545
+ "--artifact-revision", revisionId,
546
+ "--plan-hash", planHash,
547
+ "--request-id", `console-${clientRequestId}`,
548
+ "--reason", reason,
549
+ "--json",
550
+ ];
551
+ }
552
+ if (workerContinuation) {
553
+ cliArgs.push("--worker-continuation", JSON.stringify(workerContinuation.authority));
554
+ }
555
+ let admittedOperationId;
556
+ const accepted = await acceptOperation(operator.actionContext, {
557
+ action: input.action,
558
+ actionParams: safeActionParams,
559
+ clientRequestId,
560
+ cliArgs,
561
+ ...(input.action === "artifactRevisionStage" ||
562
+ (input.action === "artifactRevisionExecute" && workerContinuation)
563
+ ? {
564
+ prepare: async () => {
565
+ if (input.action === "artifactRevisionStage") {
566
+ await writeArtifactRevisionUpload({
567
+ appData,
568
+ candidateSha256: safeActionParams.candidateSha256,
569
+ candidateBytes: candidateBytes,
570
+ });
571
+ }
572
+ else if (workerContinuation && admittedOperationId) {
573
+ await operator.actionContext.operations.update(admittedOperationId, { dagRunId: workerContinuation.authority.childWorkerRunId });
574
+ await beginWorkerContinuation({
575
+ repoRoot: input.ctx.repoRoot,
576
+ resolved: workerContinuation,
577
+ operationId: admittedOperationId,
578
+ client: operator.actionContext.client,
579
+ });
580
+ }
581
+ else {
582
+ throw new Error("Worker continuation operation identity is unavailable");
583
+ }
584
+ },
585
+ prepareFailure: (error) => {
586
+ const message = error instanceof Error ? error.message : String(error);
587
+ if (input.action === "artifactRevisionExecute") {
588
+ const conflict = message.includes("attempt-lease-conflict") || message.includes("state-conflict");
589
+ return {
590
+ status: conflict ? 409 : 422,
591
+ errorCode: conflict
592
+ ? "WORKER_CONTINUATION_LEASE_CONFLICT"
593
+ : "WORKER_CONTINUATION_AUTHORITY_MISMATCH",
594
+ body: {
595
+ ok: false,
596
+ error: {
597
+ code: conflict
598
+ ? "WORKER_CONTINUATION_LEASE_CONFLICT"
599
+ : "WORKER_CONTINUATION_AUTHORITY_MISMATCH",
600
+ message,
601
+ },
602
+ },
603
+ };
604
+ }
605
+ const stableCode = [
606
+ "ARTIFACT_REVISION_QUOTA_EXCEEDED",
607
+ "ARTIFACT_REVISION_DRIFT",
608
+ "ARTIFACT_REVISION_PATH_INVALID",
609
+ ].find((code) => message.startsWith(code));
610
+ void recordContextTransferEvent({
611
+ appData,
612
+ event: "artifact_revision.rejected",
613
+ fields: {
614
+ runId,
615
+ nodeId,
616
+ reasonCode: stableCode ?? "INVALID_INPUT",
617
+ },
618
+ }).catch(() => undefined);
619
+ return {
620
+ status: stableCode ? 422 : 400,
621
+ errorCode: stableCode ?? "INVALID_INPUT",
622
+ body: { ok: false, error: { code: stableCode ?? "INVALID_INPUT", message } },
623
+ };
624
+ },
625
+ }
626
+ : {}),
627
+ onAdmitted: (operation) => {
628
+ admittedOperationId = operation.operationId;
629
+ if (input.action === "artifactRevisionStage") {
630
+ return recordContextTransferEvent({
631
+ appData,
632
+ event: "artifact_revision.upload_started",
633
+ fields: {
634
+ runId,
635
+ nodeId,
636
+ operationId: operation.operationId,
637
+ },
638
+ }).catch(() => undefined);
639
+ }
640
+ if (input.action === "artifactRevisionExecute") {
641
+ return recordContextTransferEvent({
642
+ appData,
643
+ event: "artifact_rerun.execute_requested",
644
+ fields: {
645
+ runId,
646
+ revisionId,
647
+ operationId: operation.operationId,
648
+ },
649
+ }).catch(() => undefined);
650
+ }
651
+ return undefined;
652
+ },
653
+ ...(input.action === "artifactRevisionExecute" && workerContinuation
654
+ ? {
655
+ beforeTerminal: (operation, state, info) => settleWorkerContinuation({
656
+ repoRoot: input.ctx.repoRoot,
657
+ resolved: workerContinuation,
658
+ operation,
659
+ state,
660
+ info,
661
+ }),
662
+ }
663
+ : {}),
664
+ });
665
+ if (accepted.kind === "error") {
666
+ return { status: accepted.status, body: accepted.body };
667
+ }
668
+ return { status: accepted.status, body: accepted.body };
669
+ }
153
670
  async function handleCreateOperation(req, res, ctx) {
154
671
  if (!ctx.operator) {
155
672
  sendJson(res, 503, {
@@ -195,7 +712,7 @@ async function handleCreateOperation(req, res, ctx) {
195
712
  }
196
713
  let body;
197
714
  try {
198
- body = (await readJsonBody(req));
715
+ body = (await readJsonBody(req, 15 * 1024 * 1024));
199
716
  }
200
717
  catch (error) {
201
718
  sendJson(res, 400, {
@@ -222,6 +739,27 @@ async function handleCreateOperation(req, res, ctx) {
222
739
  });
223
740
  return;
224
741
  }
742
+ if (action === "contextExport") {
743
+ const accepted = await acceptContextExportOperation({
744
+ ctx,
745
+ actionParams,
746
+ clientRequestId,
747
+ });
748
+ sendJson(res, accepted.status, accepted.body);
749
+ return;
750
+ }
751
+ if (action === "artifactRevisionStage" ||
752
+ action === "artifactRevisionPlan" ||
753
+ action === "artifactRevisionExecute") {
754
+ const accepted = await acceptArtifactRevisionOperation({
755
+ ctx,
756
+ action,
757
+ actionParams,
758
+ clientRequestId,
759
+ });
760
+ sendJson(res, accepted.status, accepted.body);
761
+ return;
762
+ }
225
763
  const result = await dispatchOperatorAction(ctx.operator.actionContext, {
226
764
  action,
227
765
  actionParams,
@@ -365,6 +903,34 @@ async function handleOperationEvents(req, res, ctx, operationId) {
365
903
  * Path-escape-safe static file serve for Console SPA.
366
904
  * Sets boot capability cookie on HTML responses.
367
905
  */
906
+ /**
907
+ * Inject the general Console confirmation token into HTML, and additionally
908
+ * the scoped inspect mutation token when the experimental prompt-override
909
+ * switch is enabled and inspect credentials are available. The standalone
910
+ * /inspect/ document canonicalizes into the Console SPA (/#/inspect...), so
911
+ * the SPA must carry the scoped inspect token for the export/rerun tab to be
912
+ * rendered by the mounted inspect workspace.
913
+ */
914
+ function injectScopedInspectTokens(raw, ctx) {
915
+ let content = injectConfirmationTokenScript(raw, ctx.bootToken.confirmationToken);
916
+ if (isDagRerunPromptOverrideEnabled() && ctx.inspectBootToken) {
917
+ content = injectConfirmationTokenScript(content, ctx.inspectBootToken.confirmationToken, "__INSPECT_MUTATION_TOKEN__");
918
+ }
919
+ return content;
920
+ }
921
+ /**
922
+ * HTML response Set-Cookie headers for the Console SPA. The general boot
923
+ * cookie is always set; when the experimental prompt-override switch is
924
+ * enabled the scoped inspect cookie is set alongside so the hosted inspect
925
+ * workspace (mounted inside the SPA) can authenticate its allowlisted
926
+ * mutation requests.
927
+ */
928
+ function scopedSetCookieHeaders(ctx) {
929
+ if (isDagRerunPromptOverrideEnabled() && ctx.inspectBootToken) {
930
+ return [ctx.bootToken.setCookieHeader, ctx.inspectBootToken.setCookieHeader];
931
+ }
932
+ return ctx.bootToken.setCookieHeader;
933
+ }
368
934
  export async function serveConsoleStatic(req, res, ctx) {
369
935
  const url = new URL(req.url ?? "/", "http://localhost");
370
936
  let pathname = decodeURIComponent(url.pathname);
@@ -383,11 +949,14 @@ export async function serveConsoleStatic(req, res, ctx) {
383
949
  // SPA fallback for non-file routes
384
950
  const indexPath = path.join(resolvedStatic, "index.html");
385
951
  if (existsSync(indexPath) && !pathname.includes(".")) {
386
- const html = injectConfirmationTokenScript(await readFile(indexPath), ctx.bootToken.confirmationToken);
952
+ const html = injectScopedInspectTokens(await readFile(indexPath), ctx);
387
953
  res.writeHead(200, {
388
954
  "Content-Type": "text/html; charset=utf-8",
389
955
  "Content-Length": html.byteLength,
390
- "Set-Cookie": ctx.bootToken.setCookieHeader,
956
+ // Boot capability tokens are baked into the document; a cached
957
+ // page from a previous Console process makes every mutation 403.
958
+ "Cache-Control": "no-store",
959
+ "Set-Cookie": scopedSetCookieHeaders(ctx),
391
960
  });
392
961
  res.end(html);
393
962
  return;
@@ -398,14 +967,15 @@ export async function serveConsoleStatic(req, res, ctx) {
398
967
  const raw = await readFile(resolvedFile);
399
968
  const contentType = contentTypeFor(pathname);
400
969
  const content = contentType.startsWith("text/html")
401
- ? injectConfirmationTokenScript(raw, ctx.bootToken.confirmationToken)
970
+ ? injectScopedInspectTokens(raw, ctx)
402
971
  : raw;
403
972
  const headers = {
404
973
  "Content-Type": contentType,
405
974
  "Content-Length": content.byteLength,
406
975
  };
407
976
  if (contentType.startsWith("text/html")) {
408
- headers["Set-Cookie"] = ctx.bootToken.setCookieHeader;
977
+ headers["Cache-Control"] = "no-store";
978
+ headers["Set-Cookie"] = scopedSetCookieHeaders(ctx);
409
979
  }
410
980
  res.writeHead(200, headers);
411
981
  res.end(content);
@@ -581,6 +1151,47 @@ export async function handleConsoleOperatorRequest(req, res, ctx) {
581
1151
  await handleListOperations(req, res, ctx);
582
1152
  return true;
583
1153
  }
1154
+ const contextDownloadMatch = pathname.match(/^\/api\/operator\/v1\/context-exports\/([a-f0-9]{32})\/download$/);
1155
+ if (method === "GET" && contextDownloadMatch) {
1156
+ if (!ctx.operator) {
1157
+ sendJson(res, 503, { ok: false, error: { code: "INTERNAL_ERROR", message: "operator runtime unavailable" } });
1158
+ return true;
1159
+ }
1160
+ const failure = evaluateConsoleDownloadCapability(req, ctx);
1161
+ if (failure) {
1162
+ sendJson(res, failure.status, { ok: false, error: { code: failure.code, message: failure.message } });
1163
+ return true;
1164
+ }
1165
+ try {
1166
+ const exportId = contextDownloadMatch[1];
1167
+ const leased = await acquireContextExportLease({
1168
+ appData: ctx.operator.actionContext.appData,
1169
+ exportId,
1170
+ });
1171
+ res.writeHead(200, {
1172
+ "Content-Type": "application/zip",
1173
+ "Content-Length": leased.bytes.byteLength,
1174
+ "Content-Disposition": `attachment; filename="context-${leased.record.bundleId}.zip"`,
1175
+ "Cache-Control": "no-store",
1176
+ "X-Content-Type-Options": "nosniff",
1177
+ });
1178
+ res.end(leased.bytes);
1179
+ await releaseContextExportLease({
1180
+ appData: ctx.operator.actionContext.appData,
1181
+ exportId,
1182
+ leaseId: leased.leaseId,
1183
+ }).catch(() => undefined);
1184
+ }
1185
+ catch (error) {
1186
+ if (res.headersSent)
1187
+ return true;
1188
+ sendJson(res, 404, {
1189
+ ok: false,
1190
+ error: { code: "CONTEXT_EXPORT_UNAVAILABLE", message: error instanceof Error ? error.message : String(error) },
1191
+ });
1192
+ }
1193
+ return true;
1194
+ }
584
1195
  if (method === "GET" && pathname === "/api/operator/v1/night-aux-ticker") {
585
1196
  await handleNightAuxTickerGet(req, res, ctx);
586
1197
  return true;
@@ -613,6 +1224,24 @@ export async function handleConsoleOperatorRequest(req, res, ctx) {
613
1224
  });
614
1225
  return true;
615
1226
  }
1227
+ if (method === "GET" && pathname === "/api/operator/v1/pi-plugins/updates") {
1228
+ try {
1229
+ const view = await checkPiPluginUpdates({
1230
+ force: url.searchParams.get("force") === "1",
1231
+ });
1232
+ sendJson(res, 200, { ok: true, data: view });
1233
+ }
1234
+ catch (error) {
1235
+ sendJson(res, 500, {
1236
+ ok: false,
1237
+ error: {
1238
+ code: "INTERNAL_ERROR",
1239
+ message: error instanceof Error ? error.message : String(error),
1240
+ },
1241
+ });
1242
+ }
1243
+ return true;
1244
+ }
616
1245
  const opMatch = pathname.match(/^\/api\/operator\/v1\/operations\/([^/]+)(?:\/(events))?$/);
617
1246
  if (opMatch) {
618
1247
  const operationId = decodeURIComponent(opMatch[1]);