@tea-agent/loop-agent 0.42.0-next.2 → 0.42.0-next.4
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/CHANGELOG.md +244 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +5 -0
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3789 -232
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +942 -187
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +34 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +15 -9
- package/dist/task/source-prepare/prepare.js +89 -1
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/pi-runtime.js +173 -7
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +30 -19
- package/dist/worker/console/chat/session-store.js +117 -70
- package/dist/worker/console/chat/shortcuts.js +1 -1
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/server.js +3 -0
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CeznJ4Iw.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-B4u9u7SX.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-oghpT81p.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-BPJ5Kvcj.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-CoIu69pd.js} +1 -1
- package/dist/worker/console/static/assets/channel-DmIXqJ6B.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-DtgiAsSc.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-YSu_EVSD.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-DaRO-NB6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-Dh0ZKiGA.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DOrvbR-H.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-C3b22LN2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-CXlqO8l6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-BblTBrDu.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-DswqPALe.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-CfTwVOlc.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-MFV5Qxb5.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-bLD8jVFo.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-BvoG1fh5.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-DEPAdJu3.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-DmrNvBLy.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-CdwzIwvs.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-CJ3hj-md.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-C6YJT2a4.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-C_LPfjhX.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-xvsSb5Ie.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-Rxp2tKcD.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-DRyLYmZh.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-TLSU_D2B.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DGqeTvHC.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-BZxyH2wh.js} +1 -1
- package/dist/worker/console/static/assets/index-C8V8K9b4.js +451 -0
- package/dist/worker/console/static/assets/index-Cnf_KxTQ.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-BsYtzx01.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-Qr-ezGv-.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-DYbB3ibo.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-BHJHewZO.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-CmPIPmCG.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-F--C1uV0.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-YTXfL1st.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-DdDMYXP9.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-C3nH4KVd.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-kXyxjG7Y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-KNzbl-7w.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-CSwI6dG0.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-Bl1AyuXy.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-2y4EmXQa.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-E8v8YrMg.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-eGGlR856.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8e4J3at.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-B7g7xc07.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Do88I1Nb.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-DR3VCHBG.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-LUvapZIy.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-C7GDcaXq.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CbNAqoGK.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/workspace-context.js +111 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +10 -0
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +907 -162
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +255 -349
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +202 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
- package/dist/workflows/dag/frontend-recovery-run.js +33 -20
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +914 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +249 -24
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +285 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +677 -492
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +732 -4
- package/dist/workflows/dag/prompt.js +34 -1
- package/dist/workflows/dag/recovery-lease.js +80 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +256 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +23 -0
- package/dist/workflows/dag/rerun-task.js +29 -0
- package/dist/workflows/dag/retry-policy.js +214 -104
- package/dist/workflows/dag/runner.js +433 -126
- package/dist/workflows/dag/scheduler.js +114 -16
- package/dist/workflows/dag/types.js +239 -12
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-dag.json +5 -2
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -384,6 +384,44 @@ export class ChatSessionStore {
|
|
|
384
384
|
return { deferredTurn: existing, clearedRevision: existing.revision + 1 };
|
|
385
385
|
});
|
|
386
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Commit Pi's steering acceptance as one durable record transition. Removing
|
|
389
|
+
* the dispatching queue item and appending its user message together prevents
|
|
390
|
+
* a crash between two writes from replaying or losing the accepted input.
|
|
391
|
+
*/
|
|
392
|
+
async acceptSteeringMessage(sessionId, clientRequestId, text) {
|
|
393
|
+
return this.serialize(sessionId, async () => {
|
|
394
|
+
const record = await this.get(sessionId);
|
|
395
|
+
if (!record)
|
|
396
|
+
throw new Error(`chat session not found: ${sessionId}`);
|
|
397
|
+
const queue = [...(record.deferredTurns ?? [])];
|
|
398
|
+
const index = queue.findIndex((item) => item.clientRequestId === clientRequestId);
|
|
399
|
+
const deferredTurn = index >= 0 ? queue[index] : undefined;
|
|
400
|
+
if (!deferredTurn || deferredTurn.state !== "dispatching")
|
|
401
|
+
return undefined;
|
|
402
|
+
const message = {
|
|
403
|
+
id: `m_${record.messages.length + 1}_${Math.random().toString(36).slice(2, 8)}`,
|
|
404
|
+
role: "user",
|
|
405
|
+
text,
|
|
406
|
+
at: new Date().toISOString(),
|
|
407
|
+
};
|
|
408
|
+
queue.splice(index, 1);
|
|
409
|
+
record.messages.push(message);
|
|
410
|
+
record.deferredTurns = queue;
|
|
411
|
+
record.deferredTurn = queue[0];
|
|
412
|
+
record.deferredQueueRevision = (record.deferredQueueRevision ?? 0) + 1;
|
|
413
|
+
if (!record.displayTitle)
|
|
414
|
+
record.displayTitle = cleanChatTitle(message.text);
|
|
415
|
+
record.updatedAt = message.at;
|
|
416
|
+
await this.persist(record);
|
|
417
|
+
return {
|
|
418
|
+
record,
|
|
419
|
+
message,
|
|
420
|
+
deferredTurn,
|
|
421
|
+
clearedRevision: deferredTurn.revision + 1,
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
}
|
|
387
425
|
async appendMessage(sessionId, message) {
|
|
388
426
|
return this.serialize(sessionId, async () => {
|
|
389
427
|
const record = await this.get(sessionId);
|
|
@@ -520,84 +558,91 @@ export async function runChatTurn(options) {
|
|
|
520
558
|
// observable to tests and to a reconnecting client reading the record).
|
|
521
559
|
const pending = [];
|
|
522
560
|
let settledEvent;
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
else {
|
|
532
|
-
options.onEvent?.(event);
|
|
561
|
+
let eventChain = Promise.resolve();
|
|
562
|
+
const processEvent = async (event) => {
|
|
563
|
+
if (event.type === "steering_accepted") {
|
|
564
|
+
const committed = await store.acceptSteeringMessage(sessionId, event.clientRequestId, event.text);
|
|
565
|
+
if (committed) {
|
|
566
|
+
await options.onSteeringMessagePersisted?.(committed.record, committed.message, {
|
|
567
|
+
clientRequestId: event.clientRequestId,
|
|
568
|
+
});
|
|
533
569
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
// execution happens in the SDK's customTools[].execute(), which
|
|
550
|
-
// routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
|
|
551
|
-
// that would double-execute every tool call (P0.2).
|
|
552
|
-
break;
|
|
553
|
-
case "tool_result": {
|
|
554
|
-
const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
|
|
555
|
-
if (acceptedOperationId && options.onOperationAccepted) {
|
|
556
|
-
pending.push(Promise.resolve(options.onOperationAccepted({
|
|
557
|
-
sessionId,
|
|
558
|
-
toolCallId: event.toolCallId,
|
|
559
|
-
toolName: event.toolName,
|
|
560
|
-
operationId: acceptedOperationId,
|
|
561
|
-
})));
|
|
562
|
-
}
|
|
563
|
-
// tool_execution_end carries the real result of the tool call.
|
|
564
|
-
// Persist exactly once (the tool message + audit trail). This is
|
|
565
|
-
// the single point of record for tool outcomes (P0.2).
|
|
566
|
-
const resultText = safeStringifyResult(event.result);
|
|
567
|
-
// ChatSessionStore serializes writes per session, so these two calls
|
|
568
|
-
// are safe to fire concurrently — neither clobbers the other.
|
|
569
|
-
pending.push(store.appendMessage(sessionId, {
|
|
570
|
-
role: "tool",
|
|
571
|
-
text: resultText,
|
|
572
|
-
toolCallId: event.toolCallId,
|
|
573
|
-
toolName: event.toolName,
|
|
574
|
-
isError: event.isError,
|
|
575
|
-
// Durable tool→operation link (plan A): the restored timeline can
|
|
576
|
-
// resolve operation:<id> refs without the live operation-ref event.
|
|
577
|
-
...(acceptedOperationId
|
|
578
|
-
? { operationId: acceptedOperationId }
|
|
579
|
-
: {}),
|
|
580
|
-
}));
|
|
581
|
-
pending.push(store.recordToolInvocation(sessionId, {
|
|
582
|
-
toolCallId: event.toolCallId,
|
|
583
|
-
toolName: event.toolName,
|
|
584
|
-
ok: !event.isError,
|
|
585
|
-
}));
|
|
586
|
-
break;
|
|
587
|
-
}
|
|
588
|
-
case "error":
|
|
589
|
-
turnError = { code: "CHAT_TURN_ERROR", message: event.message };
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (event.type === "agent_settled") {
|
|
573
|
+
// The durable operation-ref linker may perform an async canonical
|
|
574
|
+
// operation read. Hold settled until those writes finish so replay
|
|
575
|
+
// never observes agent_settled before its accepted operation ref.
|
|
576
|
+
settledEvent = event;
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
options.onEvent?.(event);
|
|
580
|
+
}
|
|
581
|
+
switch (event.type) {
|
|
582
|
+
case "message_update":
|
|
583
|
+
case "message_end":
|
|
584
|
+
if (event.type === "message_end") {
|
|
590
585
|
pending.push(store.appendMessage(sessionId, {
|
|
591
586
|
role: "assistant",
|
|
592
|
-
|
|
593
|
-
text: event.providerError.message,
|
|
594
|
-
providerError: event.providerError,
|
|
587
|
+
text: event.text,
|
|
595
588
|
...(event.content && event.content.length > 0
|
|
596
589
|
? { content: event.content }
|
|
597
590
|
: {}),
|
|
598
591
|
}));
|
|
599
|
-
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
case "tool_call":
|
|
595
|
+
// Forwarded to the caller (SSE) for UI feedback. The actual tool
|
|
596
|
+
// execution happens in the SDK's customTools[].execute(), which
|
|
597
|
+
// routes to dispatchChatToolCall (Gate 3). Do NOT dispatch here —
|
|
598
|
+
// that would double-execute every tool call (P0.2).
|
|
599
|
+
break;
|
|
600
|
+
case "tool_result": {
|
|
601
|
+
const acceptedOperationId = acceptedOperationIdFromResult(event.toolName, event.result);
|
|
602
|
+
if (acceptedOperationId && options.onOperationAccepted) {
|
|
603
|
+
pending.push(Promise.resolve(options.onOperationAccepted({
|
|
604
|
+
sessionId,
|
|
605
|
+
toolCallId: event.toolCallId,
|
|
606
|
+
toolName: event.toolName,
|
|
607
|
+
operationId: acceptedOperationId,
|
|
608
|
+
})));
|
|
609
|
+
}
|
|
610
|
+
const resultText = safeStringifyResult(event.result);
|
|
611
|
+
pending.push(store.appendMessage(sessionId, {
|
|
612
|
+
role: "tool",
|
|
613
|
+
text: resultText,
|
|
614
|
+
toolCallId: event.toolCallId,
|
|
615
|
+
toolName: event.toolName,
|
|
616
|
+
isError: event.isError,
|
|
617
|
+
...(acceptedOperationId ? { operationId: acceptedOperationId } : {}),
|
|
618
|
+
}));
|
|
619
|
+
pending.push(store.recordToolInvocation(sessionId, {
|
|
620
|
+
toolCallId: event.toolCallId,
|
|
621
|
+
toolName: event.toolName,
|
|
622
|
+
ok: !event.isError,
|
|
623
|
+
}));
|
|
624
|
+
break;
|
|
600
625
|
}
|
|
626
|
+
case "error":
|
|
627
|
+
turnError = { code: "CHAT_TURN_ERROR", message: event.message };
|
|
628
|
+
pending.push(store.appendMessage(sessionId, {
|
|
629
|
+
role: "assistant",
|
|
630
|
+
kind: "provider-error",
|
|
631
|
+
text: event.providerError.message,
|
|
632
|
+
providerError: event.providerError,
|
|
633
|
+
...(event.content && event.content.length > 0
|
|
634
|
+
? { content: event.content }
|
|
635
|
+
: {}),
|
|
636
|
+
}));
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
try {
|
|
641
|
+
await runtime.prompt(sessionId, text, (event) => {
|
|
642
|
+
// Pi's event callback is synchronous. Serialize projection work so the
|
|
643
|
+
// accepted steering user_message is durable and sequenced before any
|
|
644
|
+
// assistant/tool event that follows it.
|
|
645
|
+
eventChain = eventChain.then(() => processEvent(event)).catch(() => undefined);
|
|
601
646
|
}, {
|
|
602
647
|
signal: options.signal,
|
|
603
648
|
images: options.images,
|
|
@@ -605,10 +650,12 @@ export async function runChatTurn(options) {
|
|
|
605
650
|
});
|
|
606
651
|
}
|
|
607
652
|
catch (error) {
|
|
653
|
+
await eventChain;
|
|
608
654
|
await Promise.allSettled(pending);
|
|
609
655
|
const message = error instanceof Error ? error.message : String(error);
|
|
610
656
|
return { ok: false, error: { code: "CHAT_TURN_FAILED", message } };
|
|
611
657
|
}
|
|
658
|
+
await eventChain;
|
|
612
659
|
await Promise.allSettled(pending);
|
|
613
660
|
if (settledEvent)
|
|
614
661
|
options.onEvent?.(settledEvent);
|
|
@@ -37,7 +37,7 @@ export const SHORTCUTS = [
|
|
|
37
37
|
{
|
|
38
38
|
command: "/reload",
|
|
39
39
|
label: "Reload session",
|
|
40
|
-
description: "重新加载当前 Pi
|
|
40
|
+
description: "重新加载当前 Pi 会话的设置、扩展、技能、提示词、主题、上下文文件及模型与认证配置(不产生消息)",
|
|
41
41
|
action: "reload",
|
|
42
42
|
source: "operator",
|
|
43
43
|
},
|
|
@@ -96,7 +96,10 @@ export function hydrateSessionThread(raw) {
|
|
|
96
96
|
...(item.operationId ? { operationId: item.operationId } : {}),
|
|
97
97
|
};
|
|
98
98
|
toolCalls.push(entry);
|
|
99
|
-
|
|
99
|
+
// Durable rows with an event sequence are already self-ordering. Only
|
|
100
|
+
// legacy rows need the historical "next assistant" attribution fallback.
|
|
101
|
+
if (item.eventSeq === undefined)
|
|
102
|
+
pendingTools.push(entry);
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
return { messages, toolCalls };
|
|
@@ -151,48 +154,81 @@ function collapseDagProgressItems(items) {
|
|
|
151
154
|
return true;
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
157
|
+
function fillProcessDetails(group) {
|
|
158
|
+
const usedToolIds = new Set();
|
|
159
|
+
const nodes = [];
|
|
160
|
+
let insertionOrder = 0;
|
|
161
|
+
const addNode = (input) => {
|
|
162
|
+
if (input.items.length === 0)
|
|
163
|
+
return;
|
|
164
|
+
nodes.push({
|
|
165
|
+
...(input.eventSeq === undefined ? {} : { eventSeq: input.eventSeq }),
|
|
166
|
+
createdAt: input.createdAt,
|
|
167
|
+
insertionOrder: insertionOrder++,
|
|
168
|
+
items: input.items,
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const addAssistantNode = (assistant, blocks, includeProviderError = false) => {
|
|
172
|
+
let items = [];
|
|
173
|
+
let anchorSeq = assistant.eventSeq;
|
|
174
|
+
let anchorAt = assistant.createdAt;
|
|
175
|
+
const flushItems = () => {
|
|
176
|
+
addNode({
|
|
177
|
+
items,
|
|
178
|
+
...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
|
|
179
|
+
createdAt: anchorAt,
|
|
180
|
+
});
|
|
181
|
+
items = [];
|
|
182
|
+
};
|
|
183
|
+
for (const block of blocks) {
|
|
184
|
+
if (block.type === "thinking") {
|
|
185
|
+
items.push({ kind: "thinking", thinking: block.thinking });
|
|
186
|
+
continue;
|
|
168
187
|
}
|
|
169
|
-
|
|
188
|
+
if (block.type === "text") {
|
|
189
|
+
if (block.text.trim())
|
|
190
|
+
items.push({ kind: "text", text: block.text });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const tool = group.tools.find((entry) => entry.id === block.toolCallId);
|
|
194
|
+
if (!tool || usedToolIds.has(tool.id))
|
|
195
|
+
continue;
|
|
196
|
+
flushItems();
|
|
197
|
+
usedToolIds.add(tool.id);
|
|
198
|
+
const toolItems = [];
|
|
199
|
+
flushToolBuffer(toolItems, [tool]);
|
|
200
|
+
addNode({
|
|
201
|
+
items: toolItems,
|
|
202
|
+
...(tool.eventSeq === undefined
|
|
203
|
+
? anchorSeq === undefined
|
|
204
|
+
? {}
|
|
205
|
+
: { eventSeq: anchorSeq }
|
|
206
|
+
: { eventSeq: tool.eventSeq }),
|
|
207
|
+
createdAt: tool.createdAt,
|
|
208
|
+
});
|
|
209
|
+
anchorSeq = tool.eventSeq ?? anchorSeq;
|
|
210
|
+
anchorAt = tool.createdAt;
|
|
170
211
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
if (includeProviderError && assistant.providerError) {
|
|
213
|
+
items.push({ kind: "provider-error", error: assistant.providerError });
|
|
214
|
+
}
|
|
215
|
+
flushItems();
|
|
216
|
+
// Preserve the old-data fallback after this stage. Sequenced unmatched
|
|
217
|
+
// tools stay independent and are merged by their own eventSeq below.
|
|
218
|
+
const legacyUnmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) &&
|
|
219
|
+
tool.eventSeq === undefined &&
|
|
220
|
+
tool.assistantMessageId === assistant.id);
|
|
221
|
+
for (const tool of sortToolsStable(legacyUnmatched)) {
|
|
174
222
|
usedToolIds.add(tool.id);
|
|
223
|
+
const toolItems = [];
|
|
224
|
+
flushToolBuffer(toolItems, [tool]);
|
|
225
|
+
addNode({
|
|
226
|
+
items: toolItems,
|
|
227
|
+
...(anchorSeq === undefined ? {} : { eventSeq: anchorSeq }),
|
|
228
|
+
createdAt: Math.max(anchorAt, tool.createdAt),
|
|
229
|
+
});
|
|
175
230
|
}
|
|
176
|
-
}
|
|
177
|
-
flushToolBuffer(processItems, buffer);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Insert tools attributed to this assistant stage but without a matching
|
|
181
|
-
* content toolCall block (live SSE tool_call frames often arrive before the
|
|
182
|
-
* assistant content does). They follow the stage's thinking/text instead of
|
|
183
|
-
* sinking to the end of the whole turn.
|
|
184
|
-
*/
|
|
185
|
-
function appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds) {
|
|
186
|
-
const unmatched = group.tools.filter((tool) => !usedToolIds.has(tool.id) && tool.assistantMessageId === assistant.id);
|
|
187
|
-
if (unmatched.length === 0)
|
|
188
|
-
return;
|
|
189
|
-
for (const tool of unmatched)
|
|
190
|
-
usedToolIds.add(tool.id);
|
|
191
|
-
flushToolBuffer(processItems, unmatched);
|
|
192
|
-
}
|
|
193
|
-
function fillProcessDetails(group) {
|
|
194
|
-
const usedToolIds = new Set();
|
|
195
|
-
const processItems = [];
|
|
231
|
+
};
|
|
196
232
|
const lastAssistant = group.assistants.at(-1);
|
|
197
233
|
const finalAssistant = lastAssistant?.kind === "provider-error" ? undefined : lastAssistant;
|
|
198
234
|
const finalIndex = finalAssistant
|
|
@@ -202,29 +238,46 @@ function fillProcessDetails(group) {
|
|
|
202
238
|
if (index === finalIndex)
|
|
203
239
|
continue;
|
|
204
240
|
if (assistant.kind === "provider-error" && assistant.providerError) {
|
|
205
|
-
|
|
206
|
-
appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
|
|
207
|
-
processItems.push({
|
|
208
|
-
kind: "provider-error",
|
|
209
|
-
error: assistant.providerError,
|
|
210
|
-
});
|
|
241
|
+
addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []), true);
|
|
211
242
|
continue;
|
|
212
243
|
}
|
|
213
|
-
|
|
214
|
-
appendAssistantUnmatchedTools(group, assistant, processItems, usedToolIds);
|
|
244
|
+
addAssistantNode(assistant, getDisplayableAssistantBlocks(assistant.content ?? []));
|
|
215
245
|
}
|
|
216
246
|
const blocks = finalAssistant?.content ?? [];
|
|
217
247
|
const split = splitFinalAssistantBlocks(blocks, { isStreaming: group.live });
|
|
218
|
-
emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
|
|
219
248
|
if (finalAssistant) {
|
|
220
|
-
|
|
249
|
+
addAssistantNode(finalAssistant, split.processBlocks);
|
|
221
250
|
}
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
// DSH-style peer nodes: a sequenced tool keeps its own anchor instead of
|
|
252
|
+
// being appended to whichever assistant id happens to own it at render time.
|
|
253
|
+
for (const tool of group.tools) {
|
|
254
|
+
if (usedToolIds.has(tool.id) || tool.eventSeq === undefined)
|
|
255
|
+
continue;
|
|
256
|
+
usedToolIds.add(tool.id);
|
|
257
|
+
const items = [];
|
|
258
|
+
flushToolBuffer(items, [tool]);
|
|
259
|
+
nodes.push({
|
|
260
|
+
eventSeq: tool.eventSeq,
|
|
261
|
+
createdAt: tool.createdAt,
|
|
262
|
+
insertionOrder: insertionOrder++,
|
|
263
|
+
items,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
const fullySequenced = nodes.every((node) => node.eventSeq !== undefined);
|
|
267
|
+
nodes.sort((a, b) => {
|
|
268
|
+
if (fullySequenced && a.eventSeq !== b.eventSeq) {
|
|
269
|
+
return a.eventSeq - b.eventSeq;
|
|
270
|
+
}
|
|
271
|
+
if (a.createdAt !== b.createdAt)
|
|
272
|
+
return a.createdAt - b.createdAt;
|
|
273
|
+
return a.insertionOrder - b.insertionOrder;
|
|
274
|
+
});
|
|
275
|
+
const processItems = nodes.flatMap((node) => node.items);
|
|
276
|
+
// Bounded fallback for legacy restored rows with neither event sequence nor
|
|
277
|
+
// assistant attribution: keep them at the turn end in stable local order.
|
|
278
|
+
const legacyOrphans = group.tools.filter((tool) => !usedToolIds.has(tool.id));
|
|
279
|
+
if (legacyOrphans.length > 0)
|
|
280
|
+
flushToolBuffer(processItems, legacyOrphans);
|
|
228
281
|
group.processItems = collapseDagProgressItems(processItems);
|
|
229
282
|
if (!finalAssistant)
|
|
230
283
|
return;
|
|
@@ -306,7 +359,9 @@ export function buildTurnProcessGroups(input) {
|
|
|
306
359
|
if (assigned.has(tool.id))
|
|
307
360
|
continue;
|
|
308
361
|
if (tool.turnId) {
|
|
309
|
-
const byTurn = groups.find((group) => group.
|
|
362
|
+
const byTurn = groups.find((group) => group.user?.turnId === tool.turnId ||
|
|
363
|
+
group.assistants.some((assistant) => assistant.turnId === tool.turnId) ||
|
|
364
|
+
group.tools.some((t) => t.turnId === tool.turnId) ||
|
|
310
365
|
group.key === `turn-${tool.turnId}` ||
|
|
311
366
|
(group.user && `restored-${group.user.id}` === tool.turnId));
|
|
312
367
|
if (byTurn) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parseFrontendHumanDecisionProjection } from "../../workflows/dag/frontend-human-decision.js";
|
|
2
|
+
/** Worker/Console persistence seam. Canonical validation and freshness remain in the workflow service. */
|
|
3
|
+
export function createFrontendHumanDecisionAdapter(store) {
|
|
4
|
+
return {
|
|
5
|
+
async load(operationId) {
|
|
6
|
+
const record = await store.get(operationId);
|
|
7
|
+
if (!record || record.revision === undefined || !record.frontendHumanDecision)
|
|
8
|
+
return undefined;
|
|
9
|
+
return { revision: record.revision, projection: parseFrontendHumanDecisionProjection(record.frontendHumanDecision) };
|
|
10
|
+
},
|
|
11
|
+
materialize(input) {
|
|
12
|
+
return store.materializeFrontendHumanDecision(input);
|
|
13
|
+
},
|
|
14
|
+
consume(input) {
|
|
15
|
+
return store.consumeFrontendHumanDecision(input);
|
|
16
|
+
},
|
|
17
|
+
initializeSplit: store.initializeFrontendHumanApprovedSplit.bind(store),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FrontendSplitOrchestrator, frontendSplitProjectionSchema, } from "../../workflows/dag/frontend-split-application-service.js";
|
|
2
|
+
/** Worker/Console boundary adapter. All orchestration policy remains canonical. */
|
|
3
|
+
export function createFrontendSplitOperationAdapter(dependencies) {
|
|
4
|
+
const ports = {
|
|
5
|
+
load: async (operationId) => {
|
|
6
|
+
const record = await dependencies.store.get(operationId);
|
|
7
|
+
return record?.frontendSplit === undefined ? undefined : frontendSplitProjectionSchema.parse(record.frontendSplit);
|
|
8
|
+
},
|
|
9
|
+
compareAndSwap: (input) => dependencies.store.compareAndSwapFrontendSplit(input),
|
|
10
|
+
materialize: async (request) => {
|
|
11
|
+
const result = await dependencies.materializer.materialize(request);
|
|
12
|
+
return { taskId: result.taskId, operationId: result.operationId };
|
|
13
|
+
},
|
|
14
|
+
schedule: dependencies.schedule,
|
|
15
|
+
auditStale: dependencies.auditStale,
|
|
16
|
+
loadChildCloseoutFacts: (input) => dependencies.store.loadChildCloseoutFacts(input),
|
|
17
|
+
settleAggregate: (input) => dependencies.store.settleFrontendSplitAggregate({ ...input, prepareAggregate: dependencies.prepareAggregate }),
|
|
18
|
+
};
|
|
19
|
+
return new FrontendSplitOrchestrator(ports);
|
|
20
|
+
}
|
|
@@ -11,6 +11,9 @@ export { createClosedResourceLoader, createInterviewResourceLoader, tryActivateT
|
|
|
11
11
|
export { resolveSiblingLoopAgentBin } from "./sibling-controller.js";
|
|
12
12
|
export { openConsoleAppData, defaultConsoleAppDataRoot } from "../../infrastructure/console/app-data.js";
|
|
13
13
|
export { OperationStore } from "../../infrastructure/console/operation-store.js";
|
|
14
|
+
export { createFrontendSplitOperationAdapter } from "./frontend-split-operation-adapter.js";
|
|
15
|
+
export { createFrontendHumanDecisionAdapter } from "./frontend-human-decision-adapter.js";
|
|
16
|
+
export { FrontendSplitTaskMaterializer } from "../materialize/frontend-split-task-materializer.js";
|
|
14
17
|
export { createOperationEventStore } from "./operation-sse.js";
|
|
15
18
|
export { DagConfirmationStore } from "./dag-confirmation.js";
|
|
16
19
|
export { DraftStore } from "./draft-store.js";
|