@tea-agent/loop-agent 0.40.0-next.9 → 0.41.0-beta.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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +130 -3
- package/README.md +2 -2
- package/dist/adapters/context-transfer/optional-pi-handoff.js +31 -0
- package/dist/adapters/context-transfer/pi-session.js +61 -0
- package/dist/application/dag/generate-task-dag.js +6 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +21 -5
- package/dist/cli/program.js +61 -4
- package/dist/commands/client-recovery.js +8 -36
- package/dist/commands/dag-artifact.js +284 -0
- package/dist/commands/dag-context.js +184 -0
- package/dist/commands/dag-rerun.js +206 -1
- package/dist/commands/init-model-catalog.js +22 -0
- package/dist/commands/task-advance.js +23 -1
- package/dist/commands/task-source-prepare.js +5 -0
- package/dist/commands/task-status.js +1 -0
- package/dist/executors/dag-pi-executor.js +39 -12
- package/dist/executors/pi-sdk-executor.js +36 -5
- package/dist/executors/shell-executor.js +64 -17
- package/dist/governance/checks.js +1 -0
- package/dist/{worker → infrastructure}/console/app-data.js +4 -0
- package/dist/infrastructure/console/artifact-revision-store.js +430 -0
- package/dist/infrastructure/console/context-export-store.js +160 -0
- package/dist/infrastructure/console/dir-lock.js +132 -0
- package/dist/{worker → infrastructure}/console/operation-store.js +28 -0
- package/dist/infrastructure/harness/artifact-store.js +10 -1
- package/dist/infrastructure/harness/atomic-write.js +12 -2
- package/dist/shared/context-transfer/artifact-revision.js +172 -0
- package/dist/shared/context-transfer.js +418 -0
- package/dist/shared/operator/capabilities.js +23 -1
- package/dist/shared/operator/safe-run-summary.js +1 -0
- package/dist/shared/path-safety.js +93 -0
- package/dist/shared/pi-context-pressure/checkpoint.js +116 -0
- package/dist/shared/pi-context-pressure/compaction-policy.js +151 -0
- package/dist/shared/pi-context-pressure/env.js +58 -0
- package/dist/shared/pi-context-pressure/extension.js +100 -0
- package/dist/shared/pi-context-pressure/index.js +7 -0
- package/dist/shared/pi-context-pressure/overflow.js +252 -0
- package/dist/shared/pi-context-pressure/sift-bridge.js +386 -0
- package/dist/shared/pi-context-pressure/telemetry.js +51 -0
- package/dist/shared/preview.js +28 -4
- package/dist/task/config-types.js +2 -1
- package/dist/task/task-demand-routing.js +3 -0
- package/dist/worker/console/chat/assistant-content.js +7 -0
- package/dist/worker/console/chat/chat-event-store.js +84 -13
- package/dist/worker/console/chat/goal-round-driver.js +72 -0
- package/dist/worker/console/chat/goals.js +300 -0
- package/dist/worker/console/chat/pi-runtime.js +285 -8
- package/dist/worker/console/chat/provider-error.js +2 -1
- package/dist/worker/console/chat/repo-browser.js +94 -12
- package/dist/worker/console/chat/resource-preferences-store.js +1 -1
- package/dist/worker/console/chat/routes.js +487 -30
- package/dist/worker/console/chat/session-catalog.js +48 -0
- package/dist/worker/console/chat/session-stats.js +96 -0
- package/dist/worker/console/chat/session-store.js +28 -19
- package/dist/worker/console/chat/shortcuts.js +7 -0
- package/dist/worker/console/chat/sift-bridge.js +1 -0
- package/dist/worker/console/chat/turn-process.js +32 -3
- package/dist/worker/console/chat/user-questions.js +1 -1
- package/dist/worker/console/console-update-runtime.js +1 -1
- package/dist/worker/console/context-transfer-diagnostics.js +198 -0
- package/dist/worker/console/dag-confirmation.js +1 -1
- package/dist/worker/console/dag-execution-receipt.js +21 -3
- package/dist/worker/console/doctor.js +1 -1
- package/dist/worker/console/draft-store.js +1 -1
- package/dist/worker/console/human-gate-token.js +1 -1
- package/dist/worker/console/index.js +3 -3
- package/dist/worker/console/interview/assessment.js +1 -1
- package/dist/worker/console/interview/session.js +1 -1
- package/dist/worker/console/operation-runner.js +45 -4
- package/dist/worker/console/operation-sse.js +1 -1
- package/dist/worker/console/operation-wait.js +1 -1
- package/dist/worker/console/operator-actions.js +124 -8
- package/dist/worker/console/operator-selection.js +6 -2
- package/dist/worker/console/operator-surface-health.js +1 -1
- package/dist/worker/console/operator-user-error.js +169 -0
- package/dist/worker/console/pi-plugins.catalog.json +73658 -0
- package/dist/worker/console/pi-plugins.js +179 -0
- package/dist/worker/console/routes.js +864 -13
- package/dist/worker/console/security.js +35 -0
- package/dist/worker/console/server.js +177 -44
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Bs-CDAXM.js → abnfDiagram-N423BO3Z-DSVcg1YO.js} +1 -1
- package/dist/worker/console/static/assets/{arc-CAkA3We3.js → arc-BtXhIqui.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-DEmI_zqr.js → architectureDiagram-T3A2C74G-B-nh-ilJ.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-uDHANebE.js → blockDiagram-VBNYF7ZC-BIUSVdZI.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-Bv97b4_B.js → c4Diagram-5PPSVZJV-Cmw3L4qM.js} +1 -1
- package/dist/worker/console/static/assets/channel-BhZFNefx.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-shmQKsFz.js → chunk-2GRJ4B5K-CGpMMjGm.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-BAraNvcN.js → chunk-2Q5K7J3B-BNBkEpEJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BirhxUop.js → chunk-5RXB4S5H-ubln3eaU.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C9LgUJpR.js → chunk-5VM5RSS4-_XcBTt1i.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DrU-s-t7.js → chunk-6Q2QTUOP-Ln30Pgrh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-Tx-H_FA2.js → chunk-GF5L2VYU-ESH3lhSx.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BjLtNFrR.js → chunk-JWPE2WC7-DAJt-qXB.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-UNLVwSsy.js → chunk-KBJHAD2P-Cx10LIcH.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CBEUX8XD.js → chunk-RYQCIY6F-CENeOxoJ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-WTL7IZOq.js → chunk-XXDRQBXY-DXqK-D38.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-Sbv-NJni.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CkHG6W3s.js → cose-bilkent-JH36ORCC-CE4dmvGY.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BRzFqTaL.js → cynefin-VYW2F7L2-CRacN1zK.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-2_CFp_i7.js → cynefinDiagram-MW4NZA55-Cneukjrt.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BJtsmdkE.js → dagre-VZM6K2ZE-DPFboRoL.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DNEFFo6w.js → diagram-7IWD3JNH-DIAQgcOP.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B_IWV7eo.js → diagram-B4RE2ZJO-VrYMKxuy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DvvRx2dz.js → diagram-LBJQPF4R-CLHvBghn.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-CsibsWLg.js → diagram-Q27KOJAE-DVCWBif8.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-les_djBQ.js → diagram-UB23O5K3-BTMmbBzJ.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BEm2n7qJ.js → ebnfDiagram-BXEA7PRR-BUZsKJ56.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BI7vHHVm.js → erDiagram-JOGREHBK-BsBZ5NXC.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CUFCERrB.js → flowDiagram-UKHOOZJN-D7fH_kDa.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-wxyLiGWt.js → ganttDiagram-PKOTCBZU-Bza5OohY.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bt897K8Z.js → gitGraphDiagram-DS77QQ5N-DpkoGR_Y.js} +1 -1
- package/dist/worker/console/static/assets/index-Ius3vxhf.css +1 -0
- package/dist/worker/console/static/assets/index-yASzcbBr.js +451 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DYNx_IPv.js → infoDiagram-6WML65LV-BYm3g6LT.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CzpLNEu6.js → ishikawaDiagram-WSZJBQD7-C2RS_H4v.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-97f9owNU.js → journeyDiagram-NVQOT4AX-B4slKpCo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BxM9hyBn.js → kanban-definition-27J2QSJJ-B7orRNpI.js} +1 -1
- package/dist/worker/console/static/assets/{linear-ByuxcHvp.js → linear-D1BeBxLQ.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-DwPGvpCv.js → mermaid.core-0TtMP8k4.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-DCoqG9RT.js → mindmap-definition-FAOFIHXS-XL5EbCtT.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BLSBmBMx.js → pegDiagram-VL7TDLO6-CwF7uas5.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-B0t_0oJW.js → pieDiagram-7S7Q4E2Y-A_Jo-72f.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-sTzDk7Nt.js → quadrantDiagram-CIZ2JOQS-CxOa4gKL.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-B3jRdATC.js → railroadDiagram-AXF67PYL-DoAoswIH.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-cTtXEN.js → requirementDiagram-LRYGKXZP-C1Lc1MlD.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B25igJRE.js → sankeyDiagram-W5VNT64P-DFXOkq0U.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-jW_lM1_D.js → sequenceDiagram-SI44F4Z6-B0ufUCuW.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CrzTgsHE.js → sizeCapture-X5ZJPWSS-iPbW55g3.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-B01MvlqE.js → stateDiagram-OKZ733FA-CHiXzEKP.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BtMgzo_H.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-YcF12FiU.js → swimlanes-SLNWSIFB-LyFs7TpX.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cb-OVd9Y.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ClnRcg2s.js → timeline-definition-Z64GVDOM-tNkJnb6z.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-B6gr_z-q.js → vennDiagram-T6HMQDX7-DFm7Oo8o.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CvSgguGH.js → wardleyDiagram-T6FBY63Y-tpblTp9i.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-23OtSZCb.js → xychartDiagram-ELKLHX3M-BCDLWR_D.js} +1 -1
- package/dist/worker/console/static/index.html +7 -2
- package/dist/worker/console/static-src/app/useOperatorActions.js +3 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +95 -25
- package/dist/worker/console/static-src/operator-chat/compaction-message.js +35 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +3 -1
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +26 -19
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +0 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +14 -1
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +34 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +28 -3
- package/dist/worker/console/static-src/operator-chat/workspace-reorder.js +18 -0
- package/dist/worker/console/static-src/shell/console-update-reload.js +25 -0
- package/dist/worker/console/static-src/shell/useWorkspaces.js +150 -34
- package/dist/worker/console/static-src/shell/workspace-route.js +3 -1
- package/dist/worker/console/workspace-context.js +56 -5
- package/dist/worker/console/workspace-registry.js +335 -62
- package/dist/worker/continuation/worker-continuation.js +278 -0
- package/dist/worker/observe/health.js +1 -1
- package/dist/worker/observe/node-transparency.js +572 -0
- package/dist/worker/observe/routes.js +51 -1
- package/dist/worker/observe/static/api.js +69 -5
- package/dist/worker/observe/static/dag-context-reason-labels.d.ts +9 -0
- package/dist/worker/observe/static/dag-context-reason-labels.js +120 -0
- package/dist/worker/observe/static/inspect-bootstrap.d.ts +8 -0
- package/dist/worker/observe/static/inspect-bootstrap.js +57 -0
- package/dist/worker/observe/static/inspect-workspace.js +54 -10
- package/dist/worker/observe/static/operator-chrome.css +23 -4
- package/dist/worker/observe/static/operator-chrome.js +7 -2
- package/dist/worker/observe/static/state.js +3 -1
- package/dist/worker/observe/static/styles.css +1108 -135
- package/dist/worker/observe/static/views/dag-inspector.js +1257 -59
- package/dist/worker/observe/static/views/dags.js +7 -5
- package/dist/worker/observe/static/views/session-timeline.js +210 -10
- package/dist/worker/outcomes/adapters.js +19 -0
- package/dist/worker/outcomes/types.js +1 -0
- package/dist/worker/pool/attempt-lease.js +97 -66
- package/dist/worker/pool/begin-attempt-with-lease.js +1 -0
- package/dist/worker/pool/reconcile.js +46 -1
- package/dist/worker/pool/run-store.js +2 -0
- package/dist/worker/pool/state-projection.js +2 -0
- package/dist/worker/task-spec/workflow-routing.js +8 -3
- package/dist/workflows/dag/artifact-bindings.js +149 -0
- package/dist/workflows/dag/artifact-revision-schema-registry.js +31 -0
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +162 -8
- package/dist/workflows/dag/backend-test-pytest-collection.js +70 -2
- package/dist/workflows/dag/backend-test-result-contract.js +4 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +339 -53
- package/dist/workflows/dag/backend-test-writer-completeness.js +11 -0
- package/dist/workflows/dag/budget-enforcement.js +10 -0
- package/dist/workflows/dag/context-receipt.js +305 -0
- package/dist/workflows/dag/context-transfer/context-bundle.js +423 -0
- package/dist/workflows/dag/context-transfer/operator-actions.js +61 -0
- package/dist/workflows/dag/context-transfer/renderers.js +93 -0
- package/dist/workflows/dag/frontend-writer-rollback.js +32 -0
- package/dist/workflows/dag/init-hybrid.js +151 -4
- package/dist/workflows/dag/node-execution.js +112 -27
- package/dist/workflows/dag/path-safety.js +1 -0
- package/dist/workflows/dag/prompt.js +29 -4
- package/dist/workflows/dag/rerun-plan.js +555 -1
- package/dist/workflows/dag/rerun-run.js +601 -15
- package/dist/workflows/dag/runner.js +20 -1
- package/dist/workflows/dag/skill-snapshot.js +17 -0
- package/dist/workflows/dag/types.js +183 -7
- package/docs/architecture/runtime-boundaries.md +8 -7
- package/docs/architecture/worker-and-feature.md +1 -1
- package/docs/templates/agent-dag.schema.json +32 -0
- package/docs/templates/backend-test-dag.json +6 -5
- package/docs/templates/init-managed-agents.md +1 -1
- package/docs/templates/product-line/task.yaml +1 -1
- package/package.json +5 -2
- package/skills/loop-agent/references/command-reference.md +21 -1
- package/dist/worker/console/static/assets/channel-amxUpk7o.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-bejwBVIz.js +0 -1
- package/dist/worker/console/static/assets/index-BHhUOTri.css +0 -1
- package/dist/worker/console/static/assets/index-DqJbO3-p.js +0 -407
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BvFKEtm2.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DIB2eVg-.js +0 -8
- /package/dist/{worker → infrastructure}/console/repo-fingerprint.js +0 -0
|
@@ -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";
|
|
@@ -34,11 +34,76 @@ import { dispatchOperatorAction } from "../operator-actions.js";
|
|
|
34
34
|
import { makeHumanGateCard, } from "./human-gate-card.js";
|
|
35
35
|
import { ContractApplyReceiptStore } from "./contract-apply-receipt-store.js";
|
|
36
36
|
import { projectTaskContext } from "./context-panel.js";
|
|
37
|
-
import { deriveChatSessionTelemetry } from "./session-stats.js";
|
|
37
|
+
import { deriveChatSessionTelemetry, deriveChatTurnTelemetry } from "./session-stats.js";
|
|
38
38
|
import { contractApplyPayloadHash, issueHumanGateToken, verifyHumanGateToken, } from "../human-gate-token.js";
|
|
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 "../../../infrastructure/console/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,10 +823,15 @@ 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,
|
|
761
830
|
usage: telemetry.usage,
|
|
831
|
+
turnTelemetry: deriveChatTurnTelemetry(snapshot),
|
|
832
|
+
contextPressure: typeof deps.runtime.getContextPressureTelemetry === "function"
|
|
833
|
+
? deps.runtime.getContextPressureTelemetry(sessionId)
|
|
834
|
+
: undefined,
|
|
762
835
|
compactions: snapshot
|
|
763
836
|
.filter((event) => event.kind === "compact" ||
|
|
764
837
|
event.kind === "compaction_start" ||
|
|
@@ -767,6 +840,8 @@ export async function handleGetChatSession(_req, res, deps, sessionId) {
|
|
|
767
840
|
eventId: event.eventId,
|
|
768
841
|
kind: event.kind,
|
|
769
842
|
at: event.at,
|
|
843
|
+
seq: event.seq,
|
|
844
|
+
turnId: event.turnId,
|
|
770
845
|
data: event.data,
|
|
771
846
|
})),
|
|
772
847
|
lastEventId: lastEvent?.eventId ?? null,
|
|
@@ -1070,9 +1145,162 @@ export async function handleGetChatSessionState(_req, res, deps, sessionId) {
|
|
|
1070
1145
|
deferredTurns: projectDeferredTurnSummaries(record.deferredTurns ?? []),
|
|
1071
1146
|
pendingQuestion: pendingQuestion ?? null,
|
|
1072
1147
|
todos: projectCurrentTodos(deps.events.snapshot(sessionId)) ?? [],
|
|
1148
|
+
goal: deps.goalRegistry?.get(sessionId) ?? null,
|
|
1073
1149
|
},
|
|
1074
1150
|
});
|
|
1075
1151
|
}
|
|
1152
|
+
function goalHttpStatus(code) {
|
|
1153
|
+
if (code === "GOAL_NOT_FOUND")
|
|
1154
|
+
return 404;
|
|
1155
|
+
if (code === "GOAL_INVALID_INPUT")
|
|
1156
|
+
return 400;
|
|
1157
|
+
return 409;
|
|
1158
|
+
}
|
|
1159
|
+
export async function handleChatGoal(req, res, deps, sessionId) {
|
|
1160
|
+
const registry = deps.goalRegistry;
|
|
1161
|
+
if (!registry) {
|
|
1162
|
+
sendJson(res, 503, {
|
|
1163
|
+
ok: false,
|
|
1164
|
+
error: { code: "GOAL_UNAVAILABLE", message: "Goal runtime is unavailable" },
|
|
1165
|
+
});
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (!(await deps.store.get(sessionId))) {
|
|
1169
|
+
sendJson(res, 404, {
|
|
1170
|
+
ok: false,
|
|
1171
|
+
error: { code: "NOT_FOUND", message: `chat session not found: ${sessionId}` },
|
|
1172
|
+
});
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (req.method === "GET") {
|
|
1176
|
+
sendJson(res, 200, { ok: true, goal: registry.get(sessionId) });
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const gate = gateMutation(req, deps);
|
|
1180
|
+
if (!gate.ok) {
|
|
1181
|
+
sendJson(res, gate.status, {
|
|
1182
|
+
ok: false,
|
|
1183
|
+
error: { code: gate.code, message: gate.message },
|
|
1184
|
+
});
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
let body = {};
|
|
1188
|
+
try {
|
|
1189
|
+
body = await readJsonBody(req);
|
|
1190
|
+
}
|
|
1191
|
+
catch (error) {
|
|
1192
|
+
sendJson(res, 400, {
|
|
1193
|
+
ok: false,
|
|
1194
|
+
error: {
|
|
1195
|
+
code: "GOAL_INVALID_INPUT",
|
|
1196
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1197
|
+
},
|
|
1198
|
+
});
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
if (req.method === "POST") {
|
|
1202
|
+
const result = registry.create({
|
|
1203
|
+
sessionId,
|
|
1204
|
+
objective: body.objective,
|
|
1205
|
+
maxRounds: typeof body.maxRounds === "number" ? body.maxRounds : undefined,
|
|
1206
|
+
arm: true,
|
|
1207
|
+
});
|
|
1208
|
+
if (!result.ok) {
|
|
1209
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1210
|
+
ok: false,
|
|
1211
|
+
error: { code: result.code, message: result.message },
|
|
1212
|
+
goal: result.goal,
|
|
1213
|
+
});
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1217
|
+
sendJson(res, 201, { ok: true, goal: result.goal });
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const current = registry.get(sessionId);
|
|
1221
|
+
const goalId = typeof body.goalId === "string" ? body.goalId : "";
|
|
1222
|
+
const revision = typeof body.revision === "number" ? body.revision : Number.NaN;
|
|
1223
|
+
if (!current || !goalId || !Number.isSafeInteger(revision)) {
|
|
1224
|
+
sendJson(res, current ? 400 : 404, {
|
|
1225
|
+
ok: false,
|
|
1226
|
+
error: {
|
|
1227
|
+
code: current ? "GOAL_INVALID_INPUT" : "GOAL_NOT_FOUND",
|
|
1228
|
+
message: current
|
|
1229
|
+
? "goalId and integer revision are required"
|
|
1230
|
+
: "Goal not found",
|
|
1231
|
+
},
|
|
1232
|
+
});
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (req.method === "DELETE") {
|
|
1236
|
+
const result = registry.clear({
|
|
1237
|
+
sessionId,
|
|
1238
|
+
goalId,
|
|
1239
|
+
expectedRevision: revision,
|
|
1240
|
+
});
|
|
1241
|
+
if (!result.ok) {
|
|
1242
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1243
|
+
ok: false,
|
|
1244
|
+
error: { code: result.code, message: result.message },
|
|
1245
|
+
goal: result.goal,
|
|
1246
|
+
});
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1250
|
+
sendJson(res, 200, { ok: true, goal: null });
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (req.method === "PATCH") {
|
|
1254
|
+
const action = typeof body.action === "string" ? body.action : "edit";
|
|
1255
|
+
let phase;
|
|
1256
|
+
let arm;
|
|
1257
|
+
if (action === "pause") {
|
|
1258
|
+
phase = "paused";
|
|
1259
|
+
arm = false;
|
|
1260
|
+
}
|
|
1261
|
+
else if (action === "resume") {
|
|
1262
|
+
phase = "active";
|
|
1263
|
+
arm = true;
|
|
1264
|
+
}
|
|
1265
|
+
else if (action === "edit") {
|
|
1266
|
+
phase = current.phase;
|
|
1267
|
+
arm = current.armed;
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
sendJson(res, 400, {
|
|
1271
|
+
ok: false,
|
|
1272
|
+
error: { code: "GOAL_INVALID_INPUT", message: "unsupported Goal action" },
|
|
1273
|
+
});
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const result = registry.update({
|
|
1277
|
+
sessionId,
|
|
1278
|
+
goalId,
|
|
1279
|
+
expectedRevision: revision,
|
|
1280
|
+
phase,
|
|
1281
|
+
arm,
|
|
1282
|
+
...(body.objective !== undefined ? { objective: body.objective } : {}),
|
|
1283
|
+
});
|
|
1284
|
+
if (!result.ok) {
|
|
1285
|
+
sendJson(res, goalHttpStatus(result.code), {
|
|
1286
|
+
ok: false,
|
|
1287
|
+
error: { code: result.code, message: result.message },
|
|
1288
|
+
goal: result.goal,
|
|
1289
|
+
});
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (action === "resume")
|
|
1293
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
1294
|
+
else if (action === "pause")
|
|
1295
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1296
|
+
sendJson(res, 200, { ok: true, goal: result.goal });
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
sendJson(res, 405, {
|
|
1300
|
+
ok: false,
|
|
1301
|
+
error: { code: "METHOD_NOT_ALLOWED", message: "method not allowed" },
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1076
1304
|
export async function handleAnswerUserQuestion(req, res, deps, sessionId, requestId) {
|
|
1077
1305
|
const gate = gateMutation(req, deps);
|
|
1078
1306
|
if (!gate.ok) {
|
|
@@ -1121,27 +1349,53 @@ export async function handleCancelUserQuestion(req, res, deps, sessionId, reques
|
|
|
1121
1349
|
}
|
|
1122
1350
|
}
|
|
1123
1351
|
export async function handleListChatSessions(_req, res, deps) {
|
|
1124
|
-
const sessions = await deps
|
|
1352
|
+
const sessions = await listChatSessionSummaries(deps);
|
|
1125
1353
|
sendJson(res, 200, {
|
|
1126
1354
|
ok: true,
|
|
1127
1355
|
schemaVersion: 2,
|
|
1128
|
-
sessions
|
|
1129
|
-
const active = deps.events.getActiveTurn(session.sessionId);
|
|
1130
|
-
return {
|
|
1131
|
-
...session,
|
|
1132
|
-
activeTurnId: active?.turnId,
|
|
1133
|
-
turnPhase: active?.abortRequestedAt
|
|
1134
|
-
? "stopping"
|
|
1135
|
-
: active
|
|
1136
|
-
? "running"
|
|
1137
|
-
: "idle",
|
|
1138
|
-
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1139
|
-
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1140
|
-
};
|
|
1141
|
-
}),
|
|
1356
|
+
sessions,
|
|
1142
1357
|
hasBash: true,
|
|
1143
1358
|
});
|
|
1144
1359
|
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Build sidebar-only summaries from one Workspace store without opening a
|
|
1362
|
+
* runtime. Message bodies, Pi session paths and tool audit rows belong to the
|
|
1363
|
+
* detail endpoint; returning them here makes the global multi-Workspace index
|
|
1364
|
+
* grow with the entire chat history.
|
|
1365
|
+
*/
|
|
1366
|
+
export async function listChatSessionSummaries(deps, ownerWorkspaceId) {
|
|
1367
|
+
const sessions = await deps.store.list({ includeArchived: true });
|
|
1368
|
+
return sessions.map((session) => {
|
|
1369
|
+
const active = deps.events?.getActiveTurn(session.sessionId);
|
|
1370
|
+
const workspaceId = ownerWorkspaceId ?? session.workspaceId;
|
|
1371
|
+
return {
|
|
1372
|
+
sessionId: session.sessionId,
|
|
1373
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
1374
|
+
state: session.state,
|
|
1375
|
+
createdAt: session.createdAt,
|
|
1376
|
+
updatedAt: session.updatedAt,
|
|
1377
|
+
...(session.title !== undefined ? { title: session.title } : {}),
|
|
1378
|
+
...(session.displayTitle !== undefined
|
|
1379
|
+
? { displayTitle: session.displayTitle }
|
|
1380
|
+
: {}),
|
|
1381
|
+
...(session.modelProvider !== undefined
|
|
1382
|
+
? { modelProvider: session.modelProvider }
|
|
1383
|
+
: {}),
|
|
1384
|
+
...(session.modelId !== undefined ? { modelId: session.modelId } : {}),
|
|
1385
|
+
...(session.thinkingLevel !== undefined
|
|
1386
|
+
? { thinkingLevel: session.thinkingLevel }
|
|
1387
|
+
: {}),
|
|
1388
|
+
activeTurnId: active?.turnId,
|
|
1389
|
+
turnPhase: active?.abortRequestedAt
|
|
1390
|
+
? "stopping"
|
|
1391
|
+
: active
|
|
1392
|
+
? "running"
|
|
1393
|
+
: "idle",
|
|
1394
|
+
deferredTurn: projectDeferredTurnSummary(session.deferredTurns?.[0]),
|
|
1395
|
+
deferredTurns: projectDeferredTurnSummaries(session.deferredTurns ?? []),
|
|
1396
|
+
};
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1145
1399
|
export async function handlePatchChatSession(req, res, deps, sessionId) {
|
|
1146
1400
|
const gate = gateMutation(req, deps);
|
|
1147
1401
|
if (!gate.ok) {
|
|
@@ -1644,6 +1898,22 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1644
1898
|
});
|
|
1645
1899
|
return;
|
|
1646
1900
|
}
|
|
1901
|
+
try {
|
|
1902
|
+
trustedPayload?.onTurnCreated?.(turn.turnId);
|
|
1903
|
+
}
|
|
1904
|
+
catch (error) {
|
|
1905
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1906
|
+
deps.events.setTurnState(sessionId, turn.turnId, "failed", {
|
|
1907
|
+
code: "GOAL_ADMISSION_STALE",
|
|
1908
|
+
message,
|
|
1909
|
+
});
|
|
1910
|
+
sendJson(res, 409, {
|
|
1911
|
+
ok: false,
|
|
1912
|
+
accepted: false,
|
|
1913
|
+
error: { code: "GOAL_ADMISSION_STALE", message },
|
|
1914
|
+
});
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1647
1917
|
const controller = new AbortController();
|
|
1648
1918
|
const execution = deps.turnExecutions?.register({
|
|
1649
1919
|
sessionId,
|
|
@@ -1693,6 +1963,7 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1693
1963
|
if (placeholder)
|
|
1694
1964
|
emitSessionTitle(deps, sessionId, turn.turnId, placeholder);
|
|
1695
1965
|
},
|
|
1966
|
+
persistUserMessage: trustedPayload?.persistUserMessage,
|
|
1696
1967
|
onOperationAccepted: (accepted) => deps.operationLinker.link({
|
|
1697
1968
|
sessionId,
|
|
1698
1969
|
turnId: turn.turnId,
|
|
@@ -1755,16 +2026,139 @@ export async function handleCreateChatTurn(req, res, deps, sessionId, trustedPay
|
|
|
1755
2026
|
}
|
|
1756
2027
|
}
|
|
1757
2028
|
finally {
|
|
2029
|
+
if (terminalState !== "settled") {
|
|
2030
|
+
pauseGoalForSafety(deps, sessionId, turn.turnId);
|
|
2031
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2032
|
+
}
|
|
1758
2033
|
execution?.resolveCompletion({
|
|
1759
2034
|
ok: terminalState === "settled",
|
|
1760
2035
|
state: terminalState,
|
|
1761
2036
|
error: terminalError,
|
|
1762
2037
|
});
|
|
1763
2038
|
deps.turnExecutions?.clear(sessionId, turn.turnId);
|
|
1764
|
-
void dispatchDeferredTurn(deps, sessionId)
|
|
2039
|
+
void dispatchDeferredTurn(deps, sessionId).finally(() => {
|
|
2040
|
+
deps.goalRoundDriver?.wake(sessionId);
|
|
2041
|
+
});
|
|
1765
2042
|
}
|
|
1766
2043
|
})();
|
|
1767
2044
|
}
|
|
2045
|
+
function buildGoalRoundPrompt(goal) {
|
|
2046
|
+
const remaining = Math.max(0, goal.maxRounds - goal.round);
|
|
2047
|
+
return [
|
|
2048
|
+
buildGoalContinuationContext(goal),
|
|
2049
|
+
`This is autonomous Goal round ${goal.round + 1} of at most ${goal.maxRounds} (${remaining} rounds remain before this one).`,
|
|
2050
|
+
"Continue from the current session state. Prefer the user's queued or newly supplied instructions when present. Use todo_write for multi-step work.",
|
|
2051
|
+
"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.",
|
|
2052
|
+
].filter(Boolean).join("\n\n");
|
|
2053
|
+
}
|
|
2054
|
+
function pauseGoalForSafety(deps, sessionId, turnId) {
|
|
2055
|
+
const current = deps.goalRegistry?.get(sessionId);
|
|
2056
|
+
if (!current || current.phase !== "active") {
|
|
2057
|
+
deps.goalRegistry?.disarm(sessionId);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
deps.goalRegistry?.update({
|
|
2061
|
+
sessionId,
|
|
2062
|
+
goalId: current.goalId,
|
|
2063
|
+
expectedRevision: current.revision,
|
|
2064
|
+
phase: "paused",
|
|
2065
|
+
arm: false,
|
|
2066
|
+
turnId,
|
|
2067
|
+
});
|
|
2068
|
+
}
|
|
2069
|
+
export function linkedOperationIdsForGoal(events) {
|
|
2070
|
+
const linked = new Set();
|
|
2071
|
+
for (const event of events) {
|
|
2072
|
+
const operationId = event.data && typeof event.data === "object"
|
|
2073
|
+
? event.data.operationId
|
|
2074
|
+
: undefined;
|
|
2075
|
+
if (typeof operationId !== "string" || !operationId)
|
|
2076
|
+
continue;
|
|
2077
|
+
if (event.kind === "operation-ref" || event.kind === "operation-status")
|
|
2078
|
+
linked.add(operationId);
|
|
2079
|
+
if (event.kind === "operation-unlinked")
|
|
2080
|
+
linked.delete(operationId);
|
|
2081
|
+
}
|
|
2082
|
+
return [...linked];
|
|
2083
|
+
}
|
|
2084
|
+
/** One admission attempt used by the process-local GoalRoundDriver. */
|
|
2085
|
+
export async function driveGoalRound(deps, sessionId) {
|
|
2086
|
+
const registry = deps.goalRegistry;
|
|
2087
|
+
if (!registry || !registry.isArmed(sessionId))
|
|
2088
|
+
return "idle";
|
|
2089
|
+
const goal = registry.get(sessionId);
|
|
2090
|
+
if (!goal || goal.phase !== "active" || !goal.armed)
|
|
2091
|
+
return "idle";
|
|
2092
|
+
if (goal.round >= goal.maxRounds) {
|
|
2093
|
+
registry.update({
|
|
2094
|
+
sessionId,
|
|
2095
|
+
goalId: goal.goalId,
|
|
2096
|
+
expectedRevision: goal.revision,
|
|
2097
|
+
phase: "paused",
|
|
2098
|
+
arm: false,
|
|
2099
|
+
});
|
|
2100
|
+
return "idle";
|
|
2101
|
+
}
|
|
2102
|
+
const record = await deps.store.get(sessionId);
|
|
2103
|
+
if (!record) {
|
|
2104
|
+
registry.disarm(sessionId);
|
|
2105
|
+
return "idle";
|
|
2106
|
+
}
|
|
2107
|
+
const runtimeFacts = deps.runtime.getSessionBusyFacts(sessionId);
|
|
2108
|
+
const operationStore = deps.runtime.actionContext?.operations;
|
|
2109
|
+
const linkedOperationIds = linkedOperationIdsForGoal(deps.events.snapshot(sessionId));
|
|
2110
|
+
const operations = operationStore
|
|
2111
|
+
? await Promise.all(linkedOperationIds.map((operationId) => operationStore.get(operationId)))
|
|
2112
|
+
: [];
|
|
2113
|
+
const admission = classifyGoalRoundAdmission({
|
|
2114
|
+
phase: goal.phase,
|
|
2115
|
+
armed: goal.armed,
|
|
2116
|
+
round: goal.round,
|
|
2117
|
+
maxRounds: goal.maxRounds,
|
|
2118
|
+
hasQueuedUserTurn: (record.deferredTurns ?? []).some((item) => item.state === "queued" || item.state === "dispatching"),
|
|
2119
|
+
hasActiveTurn: Boolean(deps.events.getActiveTurn(sessionId)),
|
|
2120
|
+
runtimeBusy: Object.values(runtimeFacts).some((value) => value === true),
|
|
2121
|
+
operationBusy: operations.some((operation) => Boolean(operation) && !isTerminalOperationState(operation.state)),
|
|
2122
|
+
});
|
|
2123
|
+
if (admission.action === "wait")
|
|
2124
|
+
return "wait";
|
|
2125
|
+
if (admission.action === "idle")
|
|
2126
|
+
return "idle";
|
|
2127
|
+
const expectedRound = goal.round + 1;
|
|
2128
|
+
const clientRequestId = `goal:${goal.goalId}:${expectedRound}`;
|
|
2129
|
+
const started = await startTrustedChatTurn(deps, sessionId, {
|
|
2130
|
+
clientRequestId,
|
|
2131
|
+
text: buildGoalRoundPrompt(goal),
|
|
2132
|
+
images: [],
|
|
2133
|
+
persistUserMessage: false,
|
|
2134
|
+
onTurnCreated: (turnId) => {
|
|
2135
|
+
const advanced = registry.beginRound({
|
|
2136
|
+
sessionId,
|
|
2137
|
+
goalId: goal.goalId,
|
|
2138
|
+
expectedRevision: goal.revision,
|
|
2139
|
+
turnId,
|
|
2140
|
+
});
|
|
2141
|
+
if (!advanced.ok) {
|
|
2142
|
+
registry.disarm(sessionId);
|
|
2143
|
+
throw new Error(advanced.message);
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
});
|
|
2147
|
+
const accepted = started.body.accepted === true;
|
|
2148
|
+
const turnId = started.body.turn && typeof started.body.turn === "object"
|
|
2149
|
+
? started.body.turn.turnId
|
|
2150
|
+
: undefined;
|
|
2151
|
+
if (!accepted || !turnId) {
|
|
2152
|
+
const code = started.body.error && typeof started.body.error === "object"
|
|
2153
|
+
? String(started.body.error.code ?? "")
|
|
2154
|
+
: "";
|
|
2155
|
+
if (code === "TURN_ACTIVE")
|
|
2156
|
+
return "wait";
|
|
2157
|
+
registry.disarm(sessionId);
|
|
2158
|
+
return "idle";
|
|
2159
|
+
}
|
|
2160
|
+
return "started";
|
|
2161
|
+
}
|
|
1768
2162
|
async function emitDeferredTurnUpdate(deps, sessionId, turnId, _deferredTurn, _revision) {
|
|
1769
2163
|
const record = await deps.store.get(sessionId);
|
|
1770
2164
|
if (!record)
|
|
@@ -1985,6 +2379,8 @@ export async function handleAbortChatTurn(req, res, deps, sessionId, turnId) {
|
|
|
1985
2379
|
});
|
|
1986
2380
|
return;
|
|
1987
2381
|
}
|
|
2382
|
+
pauseGoalForSafety(deps, sessionId, turnId);
|
|
2383
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
1988
2384
|
if (isTerminalTurnState(turn.state)) {
|
|
1989
2385
|
sendJson(res, 200, {
|
|
1990
2386
|
ok: true,
|
|
@@ -2155,6 +2551,8 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2155
2551
|
}
|
|
2156
2552
|
await deps.questionRegistry?.cancelForSession(sessionId, "SESSION_DELETED").catch(() => undefined);
|
|
2157
2553
|
deps.todoRegistry?.clear(sessionId);
|
|
2554
|
+
deps.goalRegistry?.clearRuntime(sessionId);
|
|
2555
|
+
deps.goalRoundDriver?.cancel(sessionId);
|
|
2158
2556
|
deps.runtime.dispose(sessionId);
|
|
2159
2557
|
deps.operationLinker.clearSession(sessionId);
|
|
2160
2558
|
deps.events.clear(sessionId);
|
|
@@ -2170,6 +2568,7 @@ export async function handleDeleteChatSession(req, res, deps, sessionId) {
|
|
|
2170
2568
|
});
|
|
2171
2569
|
return;
|
|
2172
2570
|
}
|
|
2571
|
+
await deps.detachSession?.(sessionId).catch(() => undefined);
|
|
2173
2572
|
sendJson(res, 200, { ok: true, sessionId, disposed: true, removed: true });
|
|
2174
2573
|
}
|
|
2175
2574
|
export async function handleGetTaskContext(_res, res, deps, sessionId) {
|
|
@@ -2229,6 +2628,46 @@ async function handleRepoBrowser(res, deps, kind, rawPath) {
|
|
|
2229
2628
|
});
|
|
2230
2629
|
}
|
|
2231
2630
|
}
|
|
2631
|
+
async function handleRepoHtmlPreview(res, deps, rawPath) {
|
|
2632
|
+
try {
|
|
2633
|
+
const asset = await readRepoHtmlAsset(deps.appData.repoRoot, rawPath);
|
|
2634
|
+
const origin = deps.getConsoleOrigin();
|
|
2635
|
+
res.writeHead(200, {
|
|
2636
|
+
"Content-Type": asset.contentType,
|
|
2637
|
+
"Content-Length": asset.body.byteLength,
|
|
2638
|
+
"Cache-Control": "no-store",
|
|
2639
|
+
"X-Content-Type-Options": "nosniff",
|
|
2640
|
+
"Referrer-Policy": "no-referrer",
|
|
2641
|
+
// The iframe also has an empty sandbox attribute. This response-level
|
|
2642
|
+
// sandbox keeps direct/popup loads isolated, while explicit local-origin
|
|
2643
|
+
// style/image/font/media sources let relative project assets render.
|
|
2644
|
+
"Content-Security-Policy": [
|
|
2645
|
+
"sandbox",
|
|
2646
|
+
"default-src 'none'",
|
|
2647
|
+
"script-src 'none'",
|
|
2648
|
+
"object-src 'none'",
|
|
2649
|
+
"frame-src 'none'",
|
|
2650
|
+
"form-action 'none'",
|
|
2651
|
+
"base-uri 'none'",
|
|
2652
|
+
"connect-src 'none'",
|
|
2653
|
+
`style-src 'unsafe-inline' ${origin}`,
|
|
2654
|
+
`img-src data: blob: ${origin}`,
|
|
2655
|
+
`font-src data: ${origin}`,
|
|
2656
|
+
`media-src blob: ${origin}`,
|
|
2657
|
+
].join("; "),
|
|
2658
|
+
});
|
|
2659
|
+
res.end(asset.body);
|
|
2660
|
+
}
|
|
2661
|
+
catch (error) {
|
|
2662
|
+
const failure = error instanceof RepoBrowserError
|
|
2663
|
+
? error
|
|
2664
|
+
: new RepoBrowserError(500, "READ_FAILED", error instanceof Error ? error.message : String(error));
|
|
2665
|
+
sendJson(res, failure.status, {
|
|
2666
|
+
ok: false,
|
|
2667
|
+
error: { code: failure.code, message: failure.message },
|
|
2668
|
+
});
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2232
2671
|
async function handlePiConfig(req, res, deps, resource) {
|
|
2233
2672
|
try {
|
|
2234
2673
|
if (resource === "models") {
|
|
@@ -2974,7 +3413,7 @@ async function ctxConfirmationCard(deps, sessionId, turnId, confirmationId) {
|
|
|
2974
3413
|
taskId,
|
|
2975
3414
|
confirmationId,
|
|
2976
3415
|
expiresAt: confirmation.expiresAt,
|
|
2977
|
-
inspectHref:
|
|
3416
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
2978
3417
|
humanGateToken: confirmation.humanGateToken,
|
|
2979
3418
|
displayOnlyChallenges: [
|
|
2980
3419
|
"source",
|
|
@@ -3000,7 +3439,7 @@ async function ctxMutationGateCard(deps, sessionId, receiptId) {
|
|
|
3000
3439
|
receiptId: receipt.receiptId,
|
|
3001
3440
|
expiresAt: receipt.expiresAt,
|
|
3002
3441
|
humanGateToken: receipt.humanGateToken,
|
|
3003
|
-
inspectHref:
|
|
3442
|
+
inspectHref: `/#/inspect`,
|
|
3004
3443
|
}));
|
|
3005
3444
|
}
|
|
3006
3445
|
function actionResultBody(result) {
|
|
@@ -3089,7 +3528,7 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3089
3528
|
humanGateToken,
|
|
3090
3529
|
contractRevision: binding.revision,
|
|
3091
3530
|
contractDiffSummary: `revision ${binding.revision}; draft ${binding.draftSha256.slice(0, 12)}; observed ${binding.expectedObservedHash.slice(0, 12)}`,
|
|
3092
|
-
inspectHref:
|
|
3531
|
+
inspectHref: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3093
3532
|
});
|
|
3094
3533
|
appendHumanGateCard(deps, sessionId, card);
|
|
3095
3534
|
sendJson(res, 201, { ok: true, card });
|
|
@@ -3143,8 +3582,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3143
3582
|
contractRevision: receipt.revision,
|
|
3144
3583
|
operationId,
|
|
3145
3584
|
inspectHref: operationId
|
|
3146
|
-
?
|
|
3147
|
-
:
|
|
3585
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3586
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3148
3587
|
});
|
|
3149
3588
|
appendHumanGateCard(deps, sessionId, card);
|
|
3150
3589
|
sendJson(res, 202, {
|
|
@@ -3190,8 +3629,8 @@ async function handleContractApplyHumanGate(req, res, deps, sessionId, step) {
|
|
|
3190
3629
|
contractRevision: receipt.revision,
|
|
3191
3630
|
operationId,
|
|
3192
3631
|
inspectHref: operationId
|
|
3193
|
-
?
|
|
3194
|
-
:
|
|
3632
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3633
|
+
: `/#/inspect?taskId=${encodeURIComponent(receipt.taskId)}`,
|
|
3195
3634
|
});
|
|
3196
3635
|
appendHumanGateCard(deps, sessionId, card);
|
|
3197
3636
|
if (operationId)
|
|
@@ -3292,8 +3731,8 @@ async function handleDagHumanGate(req, res, deps, sessionId, step) {
|
|
|
3292
3731
|
confirmationId,
|
|
3293
3732
|
expiresAt: String(confirmation?.expiresAt ?? new Date().toISOString()),
|
|
3294
3733
|
inspectHref: operationId
|
|
3295
|
-
?
|
|
3296
|
-
:
|
|
3734
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3735
|
+
: `/#/inspect?taskId=${encodeURIComponent(taskId)}`,
|
|
3297
3736
|
...(confirmation?.humanGateToken
|
|
3298
3737
|
? { humanGateToken: confirmation.humanGateToken }
|
|
3299
3738
|
: {}),
|
|
@@ -3387,8 +3826,8 @@ async function handleMutationHumanGate(req, res, deps, sessionId) {
|
|
|
3387
3826
|
expiresAt: receipt.expiresAt,
|
|
3388
3827
|
operationId,
|
|
3389
3828
|
inspectHref: operationId
|
|
3390
|
-
?
|
|
3391
|
-
:
|
|
3829
|
+
? `/#/inspect?operationId=${encodeURIComponent(operationId)}`
|
|
3830
|
+
: `/#/inspect`,
|
|
3392
3831
|
});
|
|
3393
3832
|
appendHumanGateCard(deps, sessionId, card);
|
|
3394
3833
|
if (operationId) {
|
|
@@ -3512,7 +3951,8 @@ export async function handleChatCompact(req, res, deps, sessionId) {
|
|
|
3512
3951
|
}
|
|
3513
3952
|
const rawInstructions = typeof body.instructions === "string" ? body.instructions : undefined;
|
|
3514
3953
|
const currentTodos = projectCurrentTodos(deps.events.snapshot(sessionId));
|
|
3515
|
-
const
|
|
3954
|
+
const todoInstructions = buildTodoCompactionInstructions(rawInstructions, currentTodos);
|
|
3955
|
+
const customInstructions = buildGoalCompactionInstructions(todoInstructions, deps.goalRegistry?.get(sessionId) ?? null);
|
|
3516
3956
|
const turnId = deps.events.latestTurnId(sessionId) ?? `${sessionId}:compact`;
|
|
3517
3957
|
const compactionId = `${sessionId}:compaction:${randomUUID()}`;
|
|
3518
3958
|
deps.events.append(sessionId, turnId, {
|
|
@@ -3649,6 +4089,17 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3649
4089
|
await handleChatCapabilities(req, res, deps);
|
|
3650
4090
|
return true;
|
|
3651
4091
|
}
|
|
4092
|
+
const htmlPreviewRoute = matchRepoHtmlPreviewRoute(pathname);
|
|
4093
|
+
if (method === "GET" && htmlPreviewRoute) {
|
|
4094
|
+
if (!htmlPreviewRoute.rawPath)
|
|
4095
|
+
sendJson(res, 400, {
|
|
4096
|
+
ok: false,
|
|
4097
|
+
error: { code: "INVALID_PATH", message: "path is required" },
|
|
4098
|
+
});
|
|
4099
|
+
else
|
|
4100
|
+
await handleRepoHtmlPreview(res, deps, htmlPreviewRoute.rawPath);
|
|
4101
|
+
return true;
|
|
4102
|
+
}
|
|
3652
4103
|
if (method === "GET" && pathname === "/api/operator/v1/chat/repo/tree") {
|
|
3653
4104
|
await handleRepoBrowser(res, deps, "tree", url.searchParams.get("path") ?? "");
|
|
3654
4105
|
return true;
|
|
@@ -3780,6 +4231,12 @@ export async function handleChatRequest(req, res, deps, pathname, workspaceScope
|
|
|
3780
4231
|
await handleCreateChatTurn(req, res, deps, decodeURIComponent(sessionTurn[1]));
|
|
3781
4232
|
return true;
|
|
3782
4233
|
}
|
|
4234
|
+
const sessionGoal = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/goal$/);
|
|
4235
|
+
if (sessionGoal &&
|
|
4236
|
+
(method === "GET" || method === "POST" || method === "PATCH" || method === "DELETE")) {
|
|
4237
|
+
await handleChatGoal(req, res, deps, decodeURIComponent(sessionGoal[1]));
|
|
4238
|
+
return true;
|
|
4239
|
+
}
|
|
3783
4240
|
const deferredTurn = pathname.match(/^\/api\/operator\/v1\/chat\/sessions\/([^/]+)\/deferred-turn$/);
|
|
3784
4241
|
if (method === "PUT" && deferredTurn) {
|
|
3785
4242
|
await handlePutDeferredTurn(req, res, deps, decodeURIComponent(deferredTurn[1]));
|