@tea-agent/loop-agent 0.40.0-next.12 → 0.40.0-next.13

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 (114) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +1 -1
  3. package/dist/build-stamp.json +3 -3
  4. package/dist/cli/command-definitions.js +9 -5
  5. package/dist/cli/program.js +38 -3
  6. package/dist/commands/task-advance.js +23 -1
  7. package/dist/commands/task-source-prepare.js +5 -0
  8. package/dist/commands/task-status.js +1 -0
  9. package/dist/shared/operator/capabilities.js +9 -1
  10. package/dist/worker/console/chat/assistant-content.js +7 -0
  11. package/dist/worker/console/chat/chat-event-store.js +82 -11
  12. package/dist/worker/console/chat/goal-round-driver.js +72 -0
  13. package/dist/worker/console/chat/goals.js +300 -0
  14. package/dist/worker/console/chat/pi-runtime.js +140 -4
  15. package/dist/worker/console/chat/repo-browser.js +94 -12
  16. package/dist/worker/console/chat/routes.js +482 -29
  17. package/dist/worker/console/chat/session-catalog.js +48 -0
  18. package/dist/worker/console/chat/session-store.js +27 -18
  19. package/dist/worker/console/chat/shortcuts.js +7 -0
  20. package/dist/worker/console/chat/turn-process.js +32 -3
  21. package/dist/worker/console/operator-actions.js +16 -2
  22. package/dist/worker/console/operator-selection.js +6 -2
  23. package/dist/worker/console/pi-plugins.catalog.json +73658 -0
  24. package/dist/worker/console/pi-plugins.js +100 -0
  25. package/dist/worker/console/routes.js +221 -6
  26. package/dist/worker/console/server.js +172 -42
  27. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C_hkwt2J.js → abnfDiagram-N423BO3Z-Wj-fsQlk.js} +1 -1
  28. package/dist/worker/console/static/assets/{arc-s2Q6r96B.js → arc-CkXT-vzQ.js} +1 -1
  29. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CtgsrwU1.js → architectureDiagram-T3A2C74G-xlfnojWU.js} +1 -1
  30. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DZ-A7EiJ.js → blockDiagram-VBNYF7ZC-BilahvuB.js} +1 -1
  31. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-D2qv0Nyo.js → c4Diagram-5PPSVZJV-DqtAVhjz.js} +1 -1
  32. package/dist/worker/console/static/assets/channel-SnQ_u-Td.js +1 -0
  33. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-D-UYpSaW.js → chunk-2GRJ4B5K-pAqQXZ5y.js} +1 -1
  34. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-DAS3jrrt.js → chunk-2Q5K7J3B-BEWxeq2r.js} +1 -1
  35. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CSbuRcGB.js → chunk-5RXB4S5H-CvlbGDTp.js} +1 -1
  36. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BDH1ZO40.js → chunk-5VM5RSS4-D0uGd9-f.js} +1 -1
  37. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Dc2cqQIb.js → chunk-6Q2QTUOP-DSdWIteg.js} +1 -1
  38. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CkZuemIW.js → chunk-GF5L2VYU-Dweg7f0v.js} +1 -1
  39. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-URwjQnte.js → chunk-JWPE2WC7-BpZKI_k7.js} +1 -1
  40. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-04RHylJK.js → chunk-KBJHAD2P-IPLknC4m.js} +1 -1
  41. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CvQX56Hh.js → chunk-RYQCIY6F-E93k9lVI.js} +1 -1
  42. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-dUF7qura.js → chunk-XXDRQBXY-BrESwkPq.js} +1 -1
  43. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-SGM51NVY.js +1 -0
  44. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-SGM51NVY.js +1 -0
  45. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-COwbfxJE.js → cose-bilkent-JH36ORCC-DmFHvJkB.js} +1 -1
  46. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-CaBEJekQ.js → cynefin-VYW2F7L2-CDigXiSL.js} +1 -1
  47. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-COWUBdet.js → cynefinDiagram-MW4NZA55-B0zhrbaA.js} +1 -1
  48. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BLxVqTn4.js → dagre-VZM6K2ZE-DrKybVxm.js} +1 -1
  49. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-WL0yKfEQ.js → diagram-7IWD3JNH-CeUrnbFk.js} +1 -1
  50. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DaVkKCle.js → diagram-B4RE2ZJO-BJyunUMi.js} +1 -1
  51. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DdTgnRGf.js → diagram-LBJQPF4R-a5TBP73u.js} +1 -1
  52. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-PG2Axo6A.js → diagram-Q27KOJAE-ChRgBURJ.js} +1 -1
  53. package/dist/worker/console/static/assets/{diagram-UB23O5K3-6vuQzlzI.js → diagram-UB23O5K3-CqCnPRSk.js} +1 -1
  54. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BpXeBz-g.js → ebnfDiagram-BXEA7PRR-BI-UD9_S.js} +1 -1
  55. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-PxtUnaa1.js → erDiagram-JOGREHBK-CDLvvkYZ.js} +1 -1
  56. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B--Qgvs1.js → flowDiagram-UKHOOZJN-Heoo4afj.js} +1 -1
  57. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BIfQCgtx.js → ganttDiagram-PKOTCBZU-kFpOitHd.js} +1 -1
  58. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-BRaX_E4j.js → gitGraphDiagram-DS77QQ5N-FvyZ9WAY.js} +1 -1
  59. package/dist/worker/console/static/assets/index-C80SO8y0.js +437 -0
  60. package/dist/worker/console/static/assets/index-CmJCHmJP.css +1 -0
  61. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-Cd93r4Ab.js → infoDiagram-6WML65LV-HBzlwtgI.js} +1 -1
  62. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-C4c80iHt.js → ishikawaDiagram-WSZJBQD7-C9Iim5sm.js} +1 -1
  63. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-Ch5ajf3s.js → journeyDiagram-NVQOT4AX-CJwJTg0S.js} +1 -1
  64. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-DbpyW1Ut.js → kanban-definition-27J2QSJJ-CZNJ7S4q.js} +1 -1
  65. package/dist/worker/console/static/assets/{linear-BHVjBnTW.js → linear-RZ0AnGi8.js} +1 -1
  66. package/dist/worker/console/static/assets/{mermaid.core-DO70kJ34.js → mermaid.core-EYSdm-nd.js} +5 -5
  67. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-_P44Zpvs.js → mindmap-definition-FAOFIHXS-D0OLECFN.js} +1 -1
  68. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Bgi9FmEd.js → pegDiagram-VL7TDLO6-CPTvGbh7.js} +1 -1
  69. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-C9InDfJn.js → pieDiagram-7S7Q4E2Y-Ddc1aDea.js} +1 -1
  70. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-DtKrUouv.js → quadrantDiagram-CIZ2JOQS-CWCTCdAt.js} +1 -1
  71. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-zgAD_6Yo.js → railroadDiagram-AXF67PYL-CfsaBL8E.js} +1 -1
  72. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CQNO6kj4.js → requirementDiagram-LRYGKXZP-BH2rqGf6.js} +1 -1
  73. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-knKu111P.js → sankeyDiagram-W5VNT64P-BZgJ1Til.js} +1 -1
  74. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-D6tshiQ6.js → sequenceDiagram-SI44F4Z6-DmPr-o-y.js} +1 -1
  75. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-BMIWJcir.js → sizeCapture-X5ZJPWSS-CF6HzDh_.js} +1 -1
  76. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-BP9wRDqc.js → stateDiagram-OKZ733FA-DhS8N_B9.js} +1 -1
  77. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-N56zxwsk.js +1 -0
  78. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DEkM5Dhe.js → swimlanes-SLNWSIFB-CYNzoiaD.js} +2 -2
  79. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-o47kGgVt.js +8 -0
  80. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ByBKsL35.js → timeline-definition-Z64GVDOM-QBICF5qH.js} +1 -1
  81. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-waFbNCUy.js → vennDiagram-T6HMQDX7-fquBvWcf.js} +1 -1
  82. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-1ljiY0eZ.js → wardleyDiagram-T6FBY63Y-BYaJLlSd.js} +1 -1
  83. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-B4ZLHr_L.js → xychartDiagram-ELKLHX3M--ePe_JCJ.js} +1 -1
  84. package/dist/worker/console/static/index.html +2 -2
  85. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
  86. package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
  87. package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
  88. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +24 -19
  89. package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
  90. package/dist/worker/console/static-src/operator-chat/useChatThread.js +8 -1
  91. package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
  92. package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
  93. package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
  94. package/dist/worker/console/workspace-context.js +28 -3
  95. package/dist/worker/console/workspace-registry.js +333 -60
  96. package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
  97. package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
  98. package/dist/worker/observe/static/inspect-workspace.js +54 -10
  99. package/dist/worker/observe/static/operator-chrome.css +23 -4
  100. package/dist/worker/observe/static/operator-chrome.js +6 -2
  101. package/dist/worker/observe/static/styles.css +78 -25
  102. package/dist/worker/observe/static/views/dag-inspector.js +2 -1
  103. package/dist/worker/observe/static/views/dags.js +7 -5
  104. package/dist/workflows/dag/init-hybrid.js +3 -3
  105. package/docs/templates/backend-test-dag.json +3 -3
  106. package/package.json +1 -1
  107. package/skills/loop-agent/references/command-reference.md +14 -0
  108. package/dist/worker/console/static/assets/channel-BluaMT3-.js +0 -1
  109. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-H-ZpSafr.js +0 -1
  110. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-H-ZpSafr.js +0 -1
  111. package/dist/worker/console/static/assets/index-Crrgl8Mx.css +0 -1
  112. package/dist/worker/console/static/assets/index-vPsp-idE.js +0 -437
  113. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-ClEhytQG.js +0 -1
  114. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DPg5IMuu.js +0 -8
@@ -24,7 +24,7 @@ import { ResourcePreferencesError, setSkillAutoInvocationPreference, } from "./r
24
24
  import { ComposerDraftStore } from "./composer-draft-store.js";
25
25
  import { walkRepoFiles } from "./repo-walk.js";
26
26
  import { isSensitivePath, scrubSecrets } from "./explore-tools.js";
27
- import { listRepoDirectory, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
27
+ import { listRepoDirectory, matchRepoHtmlPreviewRoute, readRepoHtmlAsset, readRepoPreview, RepoBrowserError, } from "./repo-browser.js";
28
28
  import { projectRuntimeContext } from "./runtime-context.js";
29
29
  import { patchModelsConfig, readModelsConfig, readPackageInventory, } from "./pi-console-config.js";
30
30
  import { ChatInterviewAdapter, projectChatInterviewState, } from "./interview-adapter.js";
@@ -39,6 +39,71 @@ import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, }
39
39
  import { sendJson } from "../routes.js";
40
40
  import { openSseResponse, writeSseEvent } from "../operation-sse.js";
41
41
  import { buildTodoCompactionInstructions, projectCurrentTodos } from "./todos.js";
42
+ import { buildGoalCompactionInstructions, buildGoalContinuationContext, } from "./goals.js";
43
+ import { classifyGoalRoundAdmission, } from "./goal-round-driver.js";
44
+ import { isTerminalOperationState } from "../operation-store.js";
45
+ /**
46
+ * Reattach the durable event-ring order to session-store rows. The session
47
+ * store remains the content authority; the ring only supplies chronology.
48
+ * This makes refresh hydration use the same turnId/seq ordering as live SSE.
49
+ */
50
+ export function projectStoredMessagesWithEventOrder(messages, events) {
51
+ let assistantCursor = 0;
52
+ const nextAssistantEvent = (message) => {
53
+ const expectedKind = message.kind === "provider-error" ? "error" : "message_end";
54
+ const messageAt = Date.parse(message.at);
55
+ for (let index = assistantCursor; index < events.length; index += 1) {
56
+ const event = events[index];
57
+ if (event.kind !== expectedKind)
58
+ continue;
59
+ const data = event.data;
60
+ const eventText = typeof data.text === "string"
61
+ ? data.text
62
+ : typeof data.message === "string"
63
+ ? data.message
64
+ : undefined;
65
+ const closeInTime = Number.isFinite(messageAt) && Number.isFinite(Date.parse(event.at))
66
+ ? Math.abs(Date.parse(event.at) - messageAt) <= 30_000
67
+ : true;
68
+ if (eventText === message.text && closeInTime) {
69
+ assistantCursor = index + 1;
70
+ return event;
71
+ }
72
+ }
73
+ // The bounded ring may no longer contain this old message. Leave it on the
74
+ // legacy timestamp fallback; never steal a newer event just to fill a seq.
75
+ return undefined;
76
+ };
77
+ return messages.map((message) => {
78
+ let start;
79
+ let finish;
80
+ if (message.role === "user") {
81
+ start = events.find((event) => {
82
+ if (event.kind !== "user_message")
83
+ return false;
84
+ const payload = event.data;
85
+ return payload.message?.id === message.id;
86
+ });
87
+ }
88
+ else if (message.role === "assistant") {
89
+ start = nextAssistantEvent(message);
90
+ }
91
+ else if (message.role === "tool" && message.toolCallId) {
92
+ start = events.find((event) => event.kind === "tool_call" &&
93
+ event.data.toolCallId ===
94
+ message.toolCallId);
95
+ finish = events.find((event) => event.kind === "tool_result" &&
96
+ event.data.toolCallId ===
97
+ message.toolCallId);
98
+ start ??= finish;
99
+ }
100
+ return {
101
+ ...message,
102
+ ...(start ? { eventSeq: start.seq, turnId: start.turnId } : {}),
103
+ ...(finish ? { finishedSeq: finish.seq } : {}),
104
+ };
105
+ });
106
+ }
42
107
  function turnSummary(turn) {
43
108
  return {
44
109
  turnId: turn.turnId,
@@ -514,6 +579,7 @@ workspaceId) {
514
579
  // a fallback for direct (non-dispatcher) handler tests.
515
580
  workspaceId: workspaceId ?? (typeof body.workspaceId === "string" && body.workspaceId ? body.workspaceId : undefined),
516
581
  });
582
+ await deps.attachSession?.(handle.sessionId).catch(() => undefined);
517
583
  if (snapshot)
518
584
  deps.events.append(handle.sessionId, `${handle.sessionId}:snapshot`, {
519
585
  kind: "compact",
@@ -595,6 +661,7 @@ export async function handleForkChatSession(req, res, deps, sourceSessionId) {
595
661
  targetSessionId: handle.sessionId,
596
662
  targetSessionFile: handle.sessionFile,
597
663
  });
664
+ await deps.attachSession?.(handle.sessionId).catch(() => undefined);
598
665
  const copied = deps.events.forkContextRefs({
599
666
  sourceSessionId,
600
667
  targetSessionId: handle.sessionId,
@@ -746,6 +813,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
746
813
  eventEpoch: deps.events.epoch(sessionId),
747
814
  session: {
748
815
  ...record,
816
+ messages: projectStoredMessagesWithEventOrder(record.messages, snapshot),
749
817
  composerDraft,
750
818
  activeTurnId: activeTurn?.turnId,
751
819
  turnPhase: activeTurn
@@ -755,6 +823,7 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
755
823
  : "idle",
756
824
  pendingQuestion: pendingQuestion ?? null,
757
825
  todos: projectCurrentTodos(snapshot) ?? [],
826
+ goal: deps.goalRegistry?.get(sessionId) ?? null,
758
827
  },
759
828
  hasBash: true,
760
829
  sessionStats: telemetry.stats,
@@ -770,6 +839,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
770
839
  eventId: event.eventId,
771
840
  kind: event.kind,
772
841
  at: event.at,
842
+ seq: event.seq,
843
+ turnId: event.turnId,
773
844
  data: event.data,
774
845
  })),
775
846
  lastEventId: lastEvent?.eventId ?? null,
@@ -1073,9 +1144,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
1073
1144
  deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
1074
1145
  pendingQuestion: pendingQuestion ?? null,
1075
1146
  todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
1147
+ goal: deps.goalRegistry?.get(sessionId) ?? null,
1076
1148
  },
1077
1149
  });
1078
1150
  }
1151
+ function goalHttpStatus(code) {
1152
+ if (code === "GOAL_NOT_FOUND")
1153
+ return 404;
1154
+ if (code === "GOAL_INVALID_INPUT")
1155
+ return 400;
1156
+ return 409;
1157
+ }
1158
+ export async function handleChatGoal(req, res, deps, sessionId) {
1159
+ const registry = deps.goalRegistry;
1160
+ if (!registry) {
1161
+ sendJson(res, 503, {
1162
+ ok: false,
1163
+ error: { code: "GOAL_UNAVAILABLE", message: "Goal runtime is unavailable" },
1164
+ });
1165
+ return;
1166
+ }
1167
+ if (!(await deps.store.get(sessionId))) {
1168
+ sendJson(res, 404, {
1169
+ ok: false,
1170
+ error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
1171
+ });
1172
+ return;
1173
+ }
1174
+ if (req.method === "GET") {
1175
+ sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
1176
+ return;
1177
+ }
1178
+ const gate = gateMutation(req, deps);
1179
+ if (!gate.ok) {
1180
+ sendJson(res, gate.status, {
1181
+ ok: false,
1182
+ error: { code: gate.code, message: gate.message },
1183
+ });
1184
+ return;
1185
+ }
1186
+ let body = {};
1187
+ try {
1188
+ body = await readJsonBody(req);
1189
+ }
1190
+ catch (error) {
1191
+ sendJson(res, 400, {
1192
+ ok: false,
1193
+ error: {
1194
+ code: "GOAL_INVALID_INPUT",
1195
+ message: error instanceof Error ? error.message : String(error),
1196
+ },
1197
+ });
1198
+ return;
1199
+ }
1200
+ if (req.method === "POST") {
1201
+ const result = registry.create({
1202
+ sessionId,
1203
+ objective: body.objective,
1204
+ maxRounds: typeof body.maxRounds === "number" ? body.maxRounds : undefined,
1205
+ arm: true,
1206
+ });
1207
+ if (!result.ok) {
1208
+ sendJson(res, goalHttpStatus(result.code), {
1209
+ ok: false,
1210
+ error: { code: result.code, message: result.message },
1211
+ goal: result.goal,
1212
+ });
1213
+ return;
1214
+ }
1215
+ deps.goalRoundDriver?.wake(sessionId);
1216
+ sendJson(res, 201, { ok: true, goal: result.goal });
1217
+ return;
1218
+ }
1219
+ const current = registry.get(sessionId);
1220
+ const goalId = typeof body.goalId === "string" ? body.goalId : "";
1221
+ const revision = typeof body.revision === "number" ? body.revision : Number.NaN;
1222
+ if (!current || !goalId || !Number.isSafeInteger(revision)) {
1223
+ sendJson(res, current ? 400 : 404, {
1224
+ ok: false,
1225
+ error: {
1226
+ code: current ? "GOAL_INVALID_INPUT" : "GOAL_NOT_FOUND",
1227
+ message: current
1228
+ ? "goalId and integer revision are required"
1229
+ : "Goal not found",
1230
+ },
1231
+ });
1232
+ return;
1233
+ }
1234
+ if (req.method === "DELETE") {
1235
+ const result = registry.clear({
1236
+ sessionId,
1237
+ goalId,
1238
+ expectedRevision: revision,
1239
+ });
1240
+ if (!result.ok) {
1241
+ sendJson(res, goalHttpStatus(result.code), {
1242
+ ok: false,
1243
+ error: { code: result.code, message: result.message },
1244
+ goal: result.goal,
1245
+ });
1246
+ return;
1247
+ }
1248
+ deps.goalRoundDriver?.cancel(sessionId);
1249
+ sendJson(res, 200, { ok: true, goal: null });
1250
+ return;
1251
+ }
1252
+ if (req.method === "PATCH") {
1253
+ const action = typeof body.action === "string" ? body.action : "edit";
1254
+ let phase;
1255
+ let arm;
1256
+ if (action === "pause") {
1257
+ phase = "paused";
1258
+ arm = false;
1259
+ }
1260
+ else if (action === "resume") {
1261
+ phase = "active";
1262
+ arm = true;
1263
+ }
1264
+ else if (action === "edit") {
1265
+ phase = current.phase;
1266
+ arm = current.armed;
1267
+ }
1268
+ else {
1269
+ sendJson(res, 400, {
1270
+ ok: false,
1271
+ error: { code: "GOAL_INVALID_INPUT", message: "unsupported Goal action" },
1272
+ });
1273
+ return;
1274
+ }
1275
+ const result = registry.update({
1276
+ sessionId,
1277
+ goalId,
1278
+ expectedRevision: revision,
1279
+ phase,
1280
+ arm,
1281
+ ...(body.objective !== undefined ? { objective: body.objective } : {}),
1282
+ });
1283
+ if (!result.ok) {
1284
+ sendJson(res, goalHttpStatus(result.code), {
1285
+ ok: false,
1286
+ error: { code: result.code, message: result.message },
1287
+ goal: result.goal,
1288
+ });
1289
+ return;
1290
+ }
1291
+ if (action === "resume")
1292
+ deps.goalRoundDriver?.wake(sessionId);
1293
+ else if (action === "pause")
1294
+ deps.goalRoundDriver?.cancel(sessionId);
1295
+ sendJson(res, 200, { ok: true, goal: result.goal });
1296
+ return;
1297
+ }
1298
+ sendJson(res, 405, {
1299
+ ok: false,
1300
+ error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" },
1301
+ });
1302
+ }
1079
1303
  export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
1080
1304
  const gate = gateMutation(req, deps);
1081
1305
  if (!gate.ok) {
@@ -1124,27 +1348,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
1124
1348
  }
1125
1349
  }
1126
1350
  export async function handleListChatSessions(_req, res, deps) {
1127
- const sessions = await deps.store.list({ includeArchived: true });
1351
+ const sessions = await listChatSessionSummaries(deps);
1128
1352
  sendJson(res, 200, {
1129
1353
  ok: true,
1130
1354
  schemaVersion: 2,
1131
- sessions: sessions.map((session) => {
1132
- const active = deps.events.getActiveTurn(session.sessionId);
1133
- return {
1134
- ...session,
1135
- activeTurnId: active?.turnId,
1136
- turnPhase: active?.abortRequestedAt
1137
- ? "stopping"
1138
- : active
1139
- ? "running"
1140
- : "idle",
1141
- deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
1142
- deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
1143
- };
1144
- }),
1355
+ sessions,
1145
1356
  hasBash: true,
1146
1357
  });
1147
1358
  }
1359
+ /**
1360
+ * Build sidebar-only summaries from one Workspace store without opening a
1361
+ * runtime. Message bodies, Pi session paths and tool audit rows belong to the
1362
+ * detail endpoint; returning them here makes the global multi-Workspace index
1363
+ * grow with the entire chat history.
1364
+ */
1365
+ export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
1366
+ const sessions = await deps.store.list({ includeArchived: true });
1367
+ return sessions.map((session) => {
1368
+ const active = deps.events?.getActiveTurn(session.sessionId);
1369
+ const workspaceId = ownerWorkspaceId ?? session.workspaceId;
1370
+ return {
1371
+ sessionId: session.sessionId,
1372
+ ...(workspaceId ? { workspaceId } : {}),
1373
+ state: session.state,
1374
+ createdAt: session.createdAt,
1375
+ updatedAt: session.updatedAt,
1376
+ ...(session.title !== undefined ? { title: session.title } : {}),
1377
+ ...(session.displayTitle !== undefined
1378
+ ? { displayTitle: session.displayTitle }
1379
+ : {}),
1380
+ ...(session.modelProvider !== undefined
1381
+ ? { modelProvider: session.modelProvider }
1382
+ : {}),
1383
+ ...(session.modelId !== undefined ? { modelId: session.modelId } : {}),
1384
+ ...(session.thinkingLevel !== undefined
1385
+ ? { thinkingLevel: session.thinkingLevel }
1386
+ : {}),
1387
+ activeTurnId: active?.turnId,
1388
+ turnPhase: active?.abortRequestedAt
1389
+ ? "stopping"
1390
+ : active
1391
+ ? "running"
1392
+ : "idle",
1393
+ deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
1394
+ deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
1395
+ };
1396
+ });
1397
+ }
1148
1398
  export async function handlePatchChatSession(req, res, deps, sessionId) {
1149
1399
  const gate = gateMutation(req, deps);
1150
1400
  if (!gate.ok) {
@@ -1647,6 +1897,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1647
1897
  });
1648
1898
  return;
1649
1899
  }
1900
+ try {
1901
+ trustedPayload?.onTurnCreated?.(turn.turnId);
1902
+ }
1903
+ catch (error) {
1904
+ const message = error instanceof Error ? error.message : String(error);
1905
+ deps.events.setTurnState(sessionId, turn.turnId, "failed", {
1906
+ code: "GOAL_ADMISSION_STALE",
1907
+ message,
1908
+ });
1909
+ sendJson(res, 409, {
1910
+ ok: false,
1911
+ accepted: false,
1912
+ error: { code: "GOAL_ADMISSION_STALE", message },
1913
+ });
1914
+ return;
1915
+ }
1650
1916
  const controller = new AbortController();
1651
1917
  const execution = deps.turnExecutions?.register({
1652
1918
  sessionId,
@@ -1696,6 +1962,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1696
1962
  if (placeholder)
1697
1963
  emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
1698
1964
  },
1965
+ persistUserMessage: trustedPayload?.persistUserMessage,
1699
1966
  onOperationAccepted: (accepted) => deps.operationLinker.link({
1700
1967
  sessionId,
1701
1968
  turnId: turn.turnId,
@@ -1758,16 +2025,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
1758
2025
  }
1759
2026
  }
1760
2027
  finally {
2028
+ if (terminalState !== "settled") {
2029
+ pauseGoalForSafety(deps, sessionId, turn.turnId);
2030
+ deps.goalRoundDriver?.cancel(sessionId);
2031
+ }
1761
2032
  execution?.resolveCompletion({
1762
2033
  ok: terminalState === "settled",
1763
2034
  state: terminalState,
1764
2035
  error: terminalError,
1765
2036
  });
1766
2037
  deps.turnExecutions?.clear(sessionId, turn.turnId);
1767
- void dispatchDeferredTurn(deps, sessionId);
2038
+ void dispatchDeferredTurn(deps, sessionId).finally(() => {
2039
+ deps.goalRoundDriver?.wake(sessionId);
2040
+ });
1768
2041
  }
1769
2042
  })();
1770
2043
  }
2044
+ function buildGoalRoundPrompt(goal) {
2045
+ const remaining = Math.max(0, goal.maxRounds - goal.round);
2046
+ return [
2047
+ buildGoalContinuationContext(goal),
2048
+ `This is autonomous Goal round ${goal.round + 1} of at most ${goal.maxRounds} (${remaining} rounds remain before this one).`,
2049
+ "Continue from the current session state. Prefer the user's queued or newly supplied instructions when present. Use todo_write for multi-step work.",
2050
+ "Before ending this round, call get_goal. If the objective is fully achieved, call update_goal(status=complete). If no safe progress remains, call update_goal(status=blocked) with the concrete blocker. Otherwise leave it active for the next bounded round.",
2051
+ ].filter(Boolean).join("\n\n");
2052
+ }
2053
+ function pauseGoalForSafety(deps, sessionId, turnId) {
2054
+ const current = deps.goalRegistry?.get(sessionId);
2055
+ if (!current || current.phase !== "active") {
2056
+ deps.goalRegistry?.disarm(sessionId);
2057
+ return;
2058
+ }
2059
+ deps.goalRegistry?.update({
2060
+ sessionId,
2061
+ goalId: current.goalId,
2062
+ expectedRevision: current.revision,
2063
+ phase: "paused",
2064
+ arm: false,
2065
+ turnId,
2066
+ });
2067
+ }
2068
+ export function linkedOperationIdsForGoal(events) {
2069
+ const linked = new Set();
2070
+ for (const event of events) {
2071
+ const operationId = event.data && typeof event.data === "object"
2072
+ ? event.data.operationId
2073
+ : undefined;
2074
+ if (typeof operationId !== "string" || !operationId)
2075
+ continue;
2076
+ if (event.kind === "operation-ref" || event.kind === "operation-status")
2077
+ linked.add(operationId);
2078
+ if (event.kind === "operation-unlinked")
2079
+ linked.delete(operationId);
2080
+ }
2081
+ return [...linked];
2082
+ }
2083
+ /** One admission attempt used by the process-local GoalRoundDriver. */
2084
+ export async function driveGoalRound(deps, sessionId) {
2085
+ const registry = deps.goalRegistry;
2086
+ if (!registry || !registry.isArmed(sessionId))
2087
+ return "idle";
2088
+ const goal = registry.get(sessionId);
2089
+ if (!goal || goal.phase !== "active" || !goal.armed)
2090
+ return "idle";
2091
+ if (goal.round >= goal.maxRounds) {
2092
+ registry.update({
2093
+ sessionId,
2094
+ goalId: goal.goalId,
2095
+ expectedRevision: goal.revision,
2096
+ phase: "paused",
2097
+ arm: false,
2098
+ });
2099
+ return "idle";
2100
+ }
2101
+ const record = await deps.store.get(sessionId);
2102
+ if (!record) {
2103
+ registry.disarm(sessionId);
2104
+ return "idle";
2105
+ }
2106
+ const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
2107
+ const operationStore = deps.runtime.actionContext?.operations;
2108
+ const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
2109
+ const operations = operationStore
2110
+ ? await Promise.all(linkedOperationIds.map((operationId) => operationStore.get(operationId)))
2111
+ : [];
2112
+ const admission = classifyGoalRoundAdmission({
2113
+ phase: goal.phase,
2114
+ armed: goal.armed,
2115
+ round: goal.round,
2116
+ maxRounds: goal.maxRounds,
2117
+ hasQueuedUserTurn: (record.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"),
2118
+ hasActiveTurn: Boolean(deps.events.getActiveTurn(sessionId)),
2119
+ runtimeBusy: Object.values(runtimeFacts).some((value) => value === true),
2120
+ operationBusy: operations.some((operation) => Boolean(operation) && !isTerminalOperationState(operation.state)),
2121
+ });
2122
+ if (admission.action === "wait")
2123
+ return "wait";
2124
+ if (admission.action === "idle")
2125
+ return "idle";
2126
+ const expectedRound = goal.round + 1;
2127
+ const clientRequestId = `goal:${goal.goalId}:${expectedRound}`;
2128
+ const started = await startTrustedChatTurn(deps, sessionId, {
2129
+ clientRequestId,
2130
+ text: buildGoalRoundPrompt(goal),
2131
+ images: [],
2132
+ persistUserMessage: false,
2133
+ onTurnCreated: (turnId) => {
2134
+ const advanced = registry.beginRound({
2135
+ sessionId,
2136
+ goalId: goal.goalId,
2137
+ expectedRevision: goal.revision,
2138
+ turnId,
2139
+ });
2140
+ if (!advanced.ok) {
2141
+ registry.disarm(sessionId);
2142
+ throw new Error(advanced.message);
2143
+ }
2144
+ },
2145
+ });
2146
+ const accepted = started.body.accepted === true;
2147
+ const turnId = started.body.turn && typeof started.body.turn === "object"
2148
+ ? started.body.turn.turnId
2149
+ : undefined;
2150
+ if (!accepted || !turnId) {
2151
+ const code = started.body.error && typeof started.body.error === "object"
2152
+ ? String(started.body.error.code ?? "")
2153
+ : "";
2154
+ if (code === "TURN_ACTIVE")
2155
+ return "wait";
2156
+ registry.disarm(sessionId);
2157
+ return "idle";
2158
+ }
2159
+ return "started";
2160
+ }
1771
2161
  async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
1772
2162
  const record = await deps.store.get(sessionId);
1773
2163
  if (!record)
@@ -1988,6 +2378,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
1988
2378
  });
1989
2379
  return;
1990
2380
  }
2381
+ pauseGoalForSafety(deps, sessionId, turnId);
2382
+ deps.goalRoundDriver?.cancel(sessionId);
1991
2383
  if (isTerminalTurnState(turn.state)) {
1992
2384
  sendJson(res, 200, {
1993
2385
  ok: true,
@@ -2158,6 +2550,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
2158
2550
  }
2159
2551
  await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
2160
2552
  deps.todoRegistry?.clear(sessionId);
2553
+ deps.goalRegistry?.clearRuntime(sessionId);
2554
+ deps.goalRoundDriver?.cancel(sessionId);
2161
2555
  deps.runtime.dispose(sessionId);
2162
2556
  deps.operationLinker.clearSession(sessionId);
2163
2557
  deps.events.clear(sessionId);
@@ -2173,6 +2567,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
2173
2567
  });
2174
2568
  return;
2175
2569
  }
2570
+ await deps.detachSession?.(sessionId).catch(() => undefined);
2176
2571
  sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
2177
2572
  }
2178
2573
  export async function handleGetTaskContext(_res, res, deps, sessionId) {
@@ -2232,6 +2627,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
2232
2627
  });
2233
2628
  }
2234
2629
  }
2630
+ async function handleRepoHtmlPreview(res, deps, rawPath) {
2631
+ try {
2632
+ const asset = await readRepoHtmlAsset(deps.appData.repoRoot, rawPath);
2633
+ const origin = deps.getConsoleOrigin();
2634
+ res.writeHead(200, {
2635
+ "Content-Type": asset.contentType,
2636
+ "Content-Length": asset.body.byteLength,
2637
+ "Cache-Control": "no-store",
2638
+ "X-Content-Type-Options": "nosniff",
2639
+ "Referrer-Policy": "no-referrer",
2640
+ // The iframe also has an empty sandbox attribute. This response-level
2641
+ // sandbox keeps direct/popup loads isolated, while explicit local-origin
2642
+ // style/image/font/media sources let relative project assets render.
2643
+ "Content-Security-Policy": [
2644
+ "sandbox",
2645
+ "default-src 'none'",
2646
+ "script-src 'none'",
2647
+ "object-src 'none'",
2648
+ "frame-src 'none'",
2649
+ "form-action 'none'",
2650
+ "base-uri 'none'",
2651
+ "connect-src 'none'",
2652
+ `style-src 'unsafe-inline' ${origin}`,
2653
+ `img-src data: blob: ${origin}`,
2654
+ `font-src data: ${origin}`,
2655
+ `media-src blob: ${origin}`,
2656
+ ].join("; "),
2657
+ });
2658
+ res.end(asset.body);
2659
+ }
2660
+ catch (error) {
2661
+ const failure = error instanceof RepoBrowserError
2662
+ ? error
2663
+ : new RepoBrowserError(500, "READ_FAILED", error instanceof Error ? error.message : String(error));
2664
+ sendJson(res, failure.status, {
2665
+ ok: false,
2666
+ error: { code: failure.code, message: failure.message },
2667
+ });
2668
+ }
2669
+ }
2235
2670
  async function handlePiConfig(req, res, deps, resource) {
2236
2671
  try {
2237
2672
  if (resource === "models") {
@@ -2977,7 +3412,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
2977
3412
  taskId,
2978
3413
  confirmationId,
2979
3414
  expiresAt: confirmation.expiresAt,
2980
- inspectHref: `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3415
+ inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
2981
3416
  humanGateToken: confirmation.humanGateToken,
2982
3417
  displayOnlyChallenges: [
2983
3418
  "source",
@@ -3003,7 +3438,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
3003
3438
  receiptId: receipt.receiptId,
3004
3439
  expiresAt: receipt.expiresAt,
3005
3440
  humanGateToken: receipt.humanGateToken,
3006
- inspectHref: `/inspect/#/`,
3441
+ inspectHref: `/#/inspect`,
3007
3442
  }));
3008
3443
  }
3009
3444
  function actionResultBody(result) {
@@ -3092,7 +3527,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3092
3527
  humanGateToken,
3093
3528
  contractRevision: binding.revision,
3094
3529
  contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
3095
- inspectHref: `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3530
+ inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
3096
3531
  });
3097
3532
  appendHumanGateCard(deps, sessionId, card);
3098
3533
  sendJson(res, 201, { ok: true, card });
@@ -3146,8 +3581,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3146
3581
  contractRevision: receipt.revision,
3147
3582
  operationId,
3148
3583
  inspectHref: operationId
3149
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3150
- : `/inspect/?taskId=${encodeURIComponent(receipt.taskId)}#/`,
3584
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3585
+ : `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
3151
3586
  });
3152
3587
  appendHumanGateCard(deps, sessionId, card);
3153
3588
  sendJson(res, 202, {
@@ -3193,8 +3628,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
3193
3628
  contractRevision: receipt.revision,
3194
3629
  operationId,
3195
3630
  inspectHref: operationId
3196
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3197
- : `/inspect/?taskId=${encodeURIComponent(receipt.taskId)}#/`,
3631
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3632
+ : `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
3198
3633
  });
3199
3634
  appendHumanGateCard(deps, sessionId, card);
3200
3635
  if (operationId)
@@ -3295,8 +3730,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
3295
3730
  confirmationId,
3296
3731
  expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
3297
3732
  inspectHref: operationId
3298
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3299
- : `/inspect/?taskId=${encodeURIComponent(taskId)}#/`,
3733
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3734
+ : `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
3300
3735
  ...(confirmation?.humanGateToken
3301
3736
  ? { humanGateToken: confirmation.humanGateToken }
3302
3737
  : {}),
@@ -3390,8 +3825,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
3390
3825
  expiresAt: receipt.expiresAt,
3391
3826
  operationId,
3392
3827
  inspectHref: operationId
3393
- ? `/inspect/?operationId=${encodeURIComponent(operationId)}#/`
3394
- : `/inspect/#/`,
3828
+ ? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
3829
+ : `/#/inspect`,
3395
3830
  });
3396
3831
  appendHumanGateCard(deps, sessionId, card);
3397
3832
  if (operationId) {
@@ -3515,7 +3950,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
3515
3950
  }
3516
3951
  const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
3517
3952
  const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
3518
- const customInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
3953
+ const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
3954
+ const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
3519
3955
  const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
3520
3956
  const compactionId = `${sessionId}:compaction:${randomUUID()}`;
3521
3957
  deps.events.append(sessionId, turnId, {
@@ -3652,6 +4088,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
3652
4088
  await handleChatCapabilities(req, res, deps);
3653
4089
  return true;
3654
4090
  }
4091
+ const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
4092
+ if (method === "GET" && htmlPreviewRoute) {
4093
+ if (!htmlPreviewRoute.rawPath)
4094
+ sendJson(res, 400, {
4095
+ ok: false,
4096
+ error: { code: "INVALID_PATH", message: "path is required" },
4097
+ });
4098
+ else
4099
+ await handleRepoHtmlPreview(res, deps, htmlPreviewRoute.rawPath);
4100
+ return true;
4101
+ }
3655
4102
  if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
3656
4103
  await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
3657
4104
  return true;
@@ -3783,6 +4230,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
3783
4230
  await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
3784
4231
  return true;
3785
4232
  }
4233
+ const sessionGoal = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/goal$/);
4234
+ if (sessionGoal &&
4235
+ (method === "GET" || method === "POST" || method === "PATCH" || method === "DELETE")) {
4236
+ await handleChatGoal(req, res, deps, decodeURIComponent(sessionGoal[1]));
4237
+ return true;
4238
+ }
3786
4239
  const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
3787
4240
  if (method === "PUT" && deferredTurn) {
3788
4241
  await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));