dominds 1.25.3 → 1.25.5
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/dist/dialog-display-state.js +6 -7
- package/dist/docs/dialog-system.md +15 -29
- package/dist/docs/dialog-system.zh.md +15 -29
- package/dist/evt-registry.js +2 -0
- package/dist/llm/kernel-driver/drive.js +137 -7
- package/dist/llm/kernel-driver/flow.js +46 -20
- package/dist/llm/kernel-driver/reply-guidance.js +5 -5
- package/dist/minds/system-prompt-parts.js +11 -0
- package/dist/persistence-errors.d.ts +1 -1
- package/dist/persistence.d.ts +50 -1
- package/dist/persistence.js +549 -4
- package/dist/runtime/driver-messages.d.ts +7 -1
- package/dist/runtime/driver-messages.js +33 -11
- package/dist/runtime/interjection-pause-stop.js +8 -7
- package/dist/server/dominds-self-update.js +133 -38
- package/dist/server/websocket-handler.js +73 -6
- package/dist/tools/cmd-runner-protocol.d.ts +1 -0
- package/dist/tools/cmd-runner-protocol.js +6 -0
- package/dist/tools/cmd-runner.js +1 -0
- package/dist/tools/os.d.ts +2 -4
- package/dist/tools/os.js +79 -4
- package/package.json +3 -3
- package/webapp/dist/assets/{_basePickBy-CePmq6WU.js → _basePickBy-fZ31r-iF.js} +3 -3
- package/webapp/dist/assets/_basePickBy-fZ31r-iF.js.map +1 -0
- package/webapp/dist/assets/{_baseUniq-DAB2FR9P.js → _baseUniq-CI1keLoL.js} +2 -2
- package/webapp/dist/assets/_baseUniq-CI1keLoL.js.map +1 -0
- package/webapp/dist/assets/{arc-BmAGUHtw.js → arc-1NeUqzoV.js} +2 -2
- package/webapp/dist/assets/arc-1NeUqzoV.js.map +1 -0
- package/webapp/dist/assets/{architectureDiagram-VXUJARFQ-BBsOS4fu.js → architectureDiagram-2XIMDMQ5-C7SdzYIh.js} +26 -8
- package/webapp/dist/assets/architectureDiagram-2XIMDMQ5-C7SdzYIh.js.map +1 -0
- package/webapp/dist/assets/{blockDiagram-VD42YOAC-Cuxh8em4.js → blockDiagram-WCTKOSBZ-Cmpu3kIy.js} +187 -170
- package/webapp/dist/assets/blockDiagram-WCTKOSBZ-Cmpu3kIy.js.map +1 -0
- package/webapp/dist/assets/{c4Diagram-YG6GDRKO-L56ctb3A.js → c4Diagram-IC4MRINW-ChGTHIor.js} +4 -4
- package/webapp/dist/assets/c4Diagram-IC4MRINW-ChGTHIor.js.map +1 -0
- package/webapp/dist/assets/{channel-Ic6QamsR.js → channel-ndS-XTQQ.js} +2 -2
- package/webapp/dist/assets/channel-ndS-XTQQ.js.map +1 -0
- package/webapp/dist/assets/{chunk-4BX2VUAB-B8UiHrZa.js → chunk-4BX2VUAB-Cw5FtBd_.js} +2 -2
- package/webapp/dist/assets/chunk-4BX2VUAB-Cw5FtBd_.js.map +1 -0
- package/webapp/dist/assets/{chunk-55IACEB6-9QqsT66L.js → chunk-55IACEB6-D3G71JdA.js} +2 -2
- package/webapp/dist/assets/chunk-55IACEB6-D3G71JdA.js.map +1 -0
- package/webapp/dist/assets/{chunk-FMBD7UC4-DimBaUep.js → chunk-FMBD7UC4-BcugPyy7.js} +2 -2
- package/webapp/dist/assets/chunk-FMBD7UC4-BcugPyy7.js.map +1 -0
- package/webapp/dist/assets/{chunk-TZMSLE5B-DtHl61is.js → chunk-JSJVCQXG-DvuxYuNq.js} +14 -6
- package/webapp/dist/assets/chunk-JSJVCQXG-DvuxYuNq.js.map +1 -0
- package/webapp/dist/assets/{chunk-QN33PNHL-HUOaMARz.js → chunk-KX2RTZJC-BQ42xd4s.js} +2 -2
- package/webapp/dist/assets/chunk-KX2RTZJC-BQ42xd4s.js.map +1 -0
- package/webapp/dist/assets/{chunk-DI55MBZ5-uGc9dcaR.js → chunk-NQ4KR5QH-CGEMYTch.js} +9 -7
- package/webapp/dist/assets/chunk-NQ4KR5QH-CGEMYTch.js.map +1 -0
- package/webapp/dist/assets/{chunk-QZHKN3VN-pR62PJlL.js → chunk-QZHKN3VN-CxNTVm5w.js} +2 -2
- package/webapp/dist/assets/chunk-QZHKN3VN-CxNTVm5w.js.map +1 -0
- package/webapp/dist/assets/{chunk-B4BG7PRW-Cs-W2Q5y.js → chunk-WL4C6EOR-CIwdSg5q.js} +171 -121
- package/webapp/dist/assets/chunk-WL4C6EOR-CIwdSg5q.js.map +1 -0
- package/webapp/dist/assets/{classDiagram-2ON5EDUG-DoZGDGgs.js → classDiagram-VBA2DB6C-Dl05PJAP.js} +7 -6
- package/webapp/dist/assets/classDiagram-VBA2DB6C-Dl05PJAP.js.map +1 -0
- package/webapp/dist/assets/{classDiagram-v2-WZHVMYZB-DoZGDGgs.js → classDiagram-v2-RAHNMMFH-Dl05PJAP.js} +7 -6
- package/webapp/dist/assets/classDiagram-v2-RAHNMMFH-Dl05PJAP.js.map +1 -0
- package/webapp/dist/assets/{clone-WTiKKPxQ.js → clone-KCj1-QMr.js} +2 -2
- package/webapp/dist/assets/clone-KCj1-QMr.js.map +1 -0
- package/webapp/dist/assets/{cose-bilkent-S5V4N54A-CQdrsAwh.js → cose-bilkent-S5V4N54A-BttciCz5.js} +2 -2
- package/webapp/dist/assets/cose-bilkent-S5V4N54A-BttciCz5.js.map +1 -0
- package/webapp/dist/assets/cytoscape.esm-Bm8DJGmZ.js.map +1 -1
- package/webapp/dist/assets/{dagre-6UL2VRFP-CUMu8kzn.js → dagre-KLK3FWXG-Dm5yE9k2.js} +7 -7
- package/webapp/dist/assets/dagre-KLK3FWXG-Dm5yE9k2.js.map +1 -0
- package/webapp/dist/assets/defaultLocale-B2RvLBDe.js.map +1 -1
- package/webapp/dist/assets/{diagram-PSM6KHXK-DB6XicOz.js → diagram-E7M64L7V-C80ZsiBG.js} +10 -10
- package/webapp/dist/assets/diagram-E7M64L7V-C80ZsiBG.js.map +1 -0
- package/webapp/dist/assets/{diagram-QEK2KX5R-BaeG3mbJ.js → diagram-IFDJBPK2-CqKidH4n.js} +9 -8
- package/webapp/dist/assets/diagram-IFDJBPK2-CqKidH4n.js.map +1 -0
- package/webapp/dist/assets/{diagram-S2PKOQOG-DF7TLMvc.js → diagram-P4PSJMXO-EHVjfzUV.js} +8 -8
- package/webapp/dist/assets/diagram-P4PSJMXO-EHVjfzUV.js.map +1 -0
- package/webapp/dist/assets/{erDiagram-Q2GNP2WA-BQr1dJmi.js → erDiagram-INFDFZHY-7MrtsOIt.js} +96 -75
- package/webapp/dist/assets/erDiagram-INFDFZHY-7MrtsOIt.js.map +1 -0
- package/webapp/dist/assets/{flowDiagram-NV44I4VS-BPOTtmjM.js → flowDiagram-PKNHOUZH-D6hXqCy7.js} +98 -81
- package/webapp/dist/assets/flowDiagram-PKNHOUZH-D6hXqCy7.js.map +1 -0
- package/webapp/dist/assets/{ganttDiagram-JELNMOA3-DsUkIvyS.js → ganttDiagram-A5KZAMGK-DLO0XCDk.js} +28 -3
- package/webapp/dist/assets/ganttDiagram-A5KZAMGK-DLO0XCDk.js.map +1 -0
- package/webapp/dist/assets/{gitGraphDiagram-V2S2FVAM-CnXo7Mx_.js → gitGraphDiagram-K3NZZRJ6-BmVD_nQP.js} +38 -46
- package/webapp/dist/assets/gitGraphDiagram-K3NZZRJ6-BmVD_nQP.js.map +1 -0
- package/webapp/dist/assets/graph-DQTj8O0Q.js +782 -0
- package/webapp/dist/assets/graph-DQTj8O0Q.js.map +1 -0
- package/webapp/dist/assets/{index-yycTJNYb.css → index-BQoNJEGT.css} +1 -1
- package/webapp/dist/assets/{index-Dpmu_hYq.js → index-BXjq-k48.js} +1378 -1094
- package/webapp/dist/assets/{index-Dpmu_hYq.js.map → index-BXjq-k48.js.map} +1 -1
- package/webapp/dist/assets/{infoDiagram-HS3SLOUP-OgTfggdn.js → infoDiagram-LFFYTUFH-D2fqM9Fn.js} +7 -7
- package/webapp/dist/assets/infoDiagram-LFFYTUFH-D2fqM9Fn.js.map +1 -0
- package/webapp/dist/assets/init-ZxktEp_H.js.map +1 -1
- package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-kp7lB6-f.js +966 -0
- package/webapp/dist/assets/ishikawaDiagram-PHBUUO56-kp7lB6-f.js.map +1 -0
- package/webapp/dist/assets/{journeyDiagram-XKPGCS4Q-5a_zmNY7.js → journeyDiagram-4ABVD52K-DmbA52J-.js} +5 -5
- package/webapp/dist/assets/journeyDiagram-4ABVD52K-DmbA52J-.js.map +1 -0
- package/webapp/dist/assets/{kanban-definition-3W4ZIXB7-DJTdbtlF.js → kanban-definition-K7BYSVSG-DRUw-P-d.js} +5 -3
- package/webapp/dist/assets/kanban-definition-K7BYSVSG-DRUw-P-d.js.map +1 -0
- package/webapp/dist/assets/{layout-BrRMWeju.js → layout-BUoHHXzn.js} +5 -5
- package/webapp/dist/assets/layout-BUoHHXzn.js.map +1 -0
- package/webapp/dist/assets/{linear-Dc9z6NyJ.js → linear-xVSAq8rI.js} +2 -2
- package/webapp/dist/assets/linear-xVSAq8rI.js.map +1 -0
- package/webapp/dist/assets/{mindmap-definition-VGOIOE7T-Dhltq6n6.js → mindmap-definition-YRQLILUH-BcajbGmW.js} +7 -5
- package/webapp/dist/assets/mindmap-definition-YRQLILUH-BcajbGmW.js.map +1 -0
- package/webapp/dist/assets/ordinal-CxptdPJm.js.map +1 -1
- package/webapp/dist/assets/{pieDiagram-ADFJNKIX-C-YmzhGj.js → pieDiagram-SKSYHLDU-Bu8kmCW6.js} +8 -8
- package/webapp/dist/assets/pieDiagram-SKSYHLDU-Bu8kmCW6.js.map +1 -0
- package/webapp/dist/assets/{quadrantDiagram-AYHSOK5B-BxqMSMrz.js → quadrantDiagram-337W2JSQ-DIBSNUGa.js} +3 -3
- package/webapp/dist/assets/quadrantDiagram-337W2JSQ-DIBSNUGa.js.map +1 -0
- package/webapp/dist/assets/{requirementDiagram-UZGBJVZJ-DzAytLM4.js → requirementDiagram-Z7DCOOCP-CkrDkbBt.js} +16 -6
- package/webapp/dist/assets/requirementDiagram-Z7DCOOCP-CkrDkbBt.js.map +1 -0
- package/webapp/dist/assets/{sankeyDiagram-TZEHDZUN-C1YODWlP.js → sankeyDiagram-WA2Y5GQK-BZ-1R8pc.js} +2 -2
- package/webapp/dist/assets/sankeyDiagram-WA2Y5GQK-BZ-1R8pc.js.map +1 -0
- package/webapp/dist/assets/{sequenceDiagram-WL72ISMW-CuqvCm6T.js → sequenceDiagram-2WXFIKYE-DYoNARdO.js} +601 -201
- package/webapp/dist/assets/sequenceDiagram-2WXFIKYE-DYoNARdO.js.map +1 -0
- package/webapp/dist/assets/{stateDiagram-FKZM4ZOC-Bdl_IDuo.js → stateDiagram-RAJIS63D-D9v19nvh.js} +9 -9
- package/webapp/dist/assets/stateDiagram-RAJIS63D-D9v19nvh.js.map +1 -0
- package/webapp/dist/assets/{stateDiagram-v2-4FDKWEC3-CApGpGJP.js → stateDiagram-v2-FVOUBMTO-BYw5ZwtH.js} +5 -5
- package/webapp/dist/assets/stateDiagram-v2-FVOUBMTO-BYw5ZwtH.js.map +1 -0
- package/webapp/dist/assets/{timeline-definition-IT6M3QCI-D58v81nI.js → timeline-definition-YZTLITO2-BT10dRIJ.js} +3 -3
- package/webapp/dist/assets/timeline-definition-YZTLITO2-BT10dRIJ.js.map +1 -0
- package/webapp/dist/assets/{treemap-GDKQZRPO-DAFtS5Nq.js → treemap-KZPCXAKY-yPoPC5hc.js} +37 -24
- package/webapp/dist/assets/treemap-KZPCXAKY-yPoPC5hc.js.map +1 -0
- package/webapp/dist/assets/vennDiagram-LZ73GAT5-tryg3OaY.js +2487 -0
- package/webapp/dist/assets/vennDiagram-LZ73GAT5-tryg3OaY.js.map +1 -0
- package/webapp/dist/assets/{xychartDiagram-PRI3JC2R-Dfairu24.js → xychartDiagram-JWTSCODW-BWLUzo9S.js} +4 -4
- package/webapp/dist/assets/xychartDiagram-JWTSCODW-BWLUzo9S.js.map +1 -0
- package/webapp/dist/index.html +2 -2
- package/webapp/dist/assets/_basePickBy-CePmq6WU.js.map +0 -1
- package/webapp/dist/assets/_baseUniq-DAB2FR9P.js.map +0 -1
- package/webapp/dist/assets/arc-BmAGUHtw.js.map +0 -1
- package/webapp/dist/assets/architectureDiagram-VXUJARFQ-BBsOS4fu.js.map +0 -1
- package/webapp/dist/assets/blockDiagram-VD42YOAC-Cuxh8em4.js.map +0 -1
- package/webapp/dist/assets/c4Diagram-YG6GDRKO-L56ctb3A.js.map +0 -1
- package/webapp/dist/assets/channel-Ic6QamsR.js.map +0 -1
- package/webapp/dist/assets/chunk-4BX2VUAB-B8UiHrZa.js.map +0 -1
- package/webapp/dist/assets/chunk-55IACEB6-9QqsT66L.js.map +0 -1
- package/webapp/dist/assets/chunk-B4BG7PRW-Cs-W2Q5y.js.map +0 -1
- package/webapp/dist/assets/chunk-DI55MBZ5-uGc9dcaR.js.map +0 -1
- package/webapp/dist/assets/chunk-FMBD7UC4-DimBaUep.js.map +0 -1
- package/webapp/dist/assets/chunk-QN33PNHL-HUOaMARz.js.map +0 -1
- package/webapp/dist/assets/chunk-QZHKN3VN-pR62PJlL.js.map +0 -1
- package/webapp/dist/assets/chunk-TZMSLE5B-DtHl61is.js.map +0 -1
- package/webapp/dist/assets/classDiagram-2ON5EDUG-DoZGDGgs.js.map +0 -1
- package/webapp/dist/assets/classDiagram-v2-WZHVMYZB-DoZGDGgs.js.map +0 -1
- package/webapp/dist/assets/clone-WTiKKPxQ.js.map +0 -1
- package/webapp/dist/assets/cose-bilkent-S5V4N54A-CQdrsAwh.js.map +0 -1
- package/webapp/dist/assets/dagre-6UL2VRFP-CUMu8kzn.js.map +0 -1
- package/webapp/dist/assets/diagram-PSM6KHXK-DB6XicOz.js.map +0 -1
- package/webapp/dist/assets/diagram-QEK2KX5R-BaeG3mbJ.js.map +0 -1
- package/webapp/dist/assets/diagram-S2PKOQOG-DF7TLMvc.js.map +0 -1
- package/webapp/dist/assets/erDiagram-Q2GNP2WA-BQr1dJmi.js.map +0 -1
- package/webapp/dist/assets/flowDiagram-NV44I4VS-BPOTtmjM.js.map +0 -1
- package/webapp/dist/assets/ganttDiagram-JELNMOA3-DsUkIvyS.js.map +0 -1
- package/webapp/dist/assets/gitGraphDiagram-V2S2FVAM-CnXo7Mx_.js.map +0 -1
- package/webapp/dist/assets/graph-C2Cpmq_d.js +0 -425
- package/webapp/dist/assets/graph-C2Cpmq_d.js.map +0 -1
- package/webapp/dist/assets/infoDiagram-HS3SLOUP-OgTfggdn.js.map +0 -1
- package/webapp/dist/assets/journeyDiagram-XKPGCS4Q-5a_zmNY7.js.map +0 -1
- package/webapp/dist/assets/kanban-definition-3W4ZIXB7-DJTdbtlF.js.map +0 -1
- package/webapp/dist/assets/layout-BrRMWeju.js.map +0 -1
- package/webapp/dist/assets/linear-Dc9z6NyJ.js.map +0 -1
- package/webapp/dist/assets/mindmap-definition-VGOIOE7T-Dhltq6n6.js.map +0 -1
- package/webapp/dist/assets/pieDiagram-ADFJNKIX-C-YmzhGj.js.map +0 -1
- package/webapp/dist/assets/quadrantDiagram-AYHSOK5B-BxqMSMrz.js.map +0 -1
- package/webapp/dist/assets/requirementDiagram-UZGBJVZJ-DzAytLM4.js.map +0 -1
- package/webapp/dist/assets/sankeyDiagram-TZEHDZUN-C1YODWlP.js.map +0 -1
- package/webapp/dist/assets/sequenceDiagram-WL72ISMW-CuqvCm6T.js.map +0 -1
- package/webapp/dist/assets/stateDiagram-FKZM4ZOC-Bdl_IDuo.js.map +0 -1
- package/webapp/dist/assets/stateDiagram-v2-4FDKWEC3-CApGpGJP.js.map +0 -1
- package/webapp/dist/assets/timeline-definition-IT6M3QCI-D58v81nI.js.map +0 -1
- package/webapp/dist/assets/treemap-GDKQZRPO-DAFtS5Nq.js.map +0 -1
- package/webapp/dist/assets/xychartDiagram-PRI3JC2R-Dfairu24.js.map +0 -1
|
@@ -182,9 +182,8 @@ async function getRunControlCountsSnapshot() {
|
|
|
182
182
|
isStoppedReasonResumable(latest.executionMarker.reason)) {
|
|
183
183
|
// Keep run-control counts aligned with actual Continue affordance:
|
|
184
184
|
// - ordinary interrupted dialogs count as resumable only when no Q4H suspension remains
|
|
185
|
-
// - interjection-paused dialogs still count as resumable even if Q4H remains,
|
|
186
|
-
// because
|
|
187
|
-
// and re-evaluates the original task from fresh facts
|
|
185
|
+
// - legacy interjection-paused dialogs still count as resumable even if Q4H remains,
|
|
186
|
+
// because Continue only re-evaluates the original task from fresh facts
|
|
188
187
|
if ((0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
|
|
189
188
|
resumable++;
|
|
190
189
|
}
|
|
@@ -615,10 +614,10 @@ async function refreshRunControlProjectionFromPersistenceFacts(dialogId, trigger
|
|
|
615
614
|
if (latest.executionMarker?.kind === 'interrupted' &&
|
|
616
615
|
(0, interjection_pause_stop_1.isUserInterjectionPauseStopReason)(latest.executionMarker.reason)) {
|
|
617
616
|
// WARNING:
|
|
618
|
-
// This is the one place where the projection intentionally preserves
|
|
619
|
-
// stopped state ahead of the current suspension facts. That is not a bug:
|
|
620
|
-
//
|
|
621
|
-
//
|
|
617
|
+
// This is the one place where the projection intentionally preserves legacy
|
|
618
|
+
// paused-interjection stopped state ahead of the current suspension facts. That is not a bug:
|
|
619
|
+
// the UI may still need to show that the original task was paused even if the underlying
|
|
620
|
+
// dialog is now waiting on Q4H.
|
|
622
621
|
//
|
|
623
622
|
// The true source-of-truth decision about what Continue should do next lives in `flow.ts`'s
|
|
624
623
|
// resume path, which performs a fresh fact scan at resume time and then either restores the
|
|
@@ -155,52 +155,38 @@ Dialog state is persisted to storage at key points:
|
|
|
155
155
|
|
|
156
156
|
This ensures crash recovery and enables the backend to resume from any persisted state without depending on frontend state.
|
|
157
157
|
|
|
158
|
-
### User Interjection
|
|
158
|
+
### User Interjection And A2H Semantics
|
|
159
159
|
|
|
160
160
|
When a dialog still carries an inter-dialog reply obligation, but the user temporarily interjects and asks it to handle a local question first, the system must distinguish between the **UI projection** and the **true driving source state**.
|
|
161
161
|
|
|
162
|
+
Plainly: the system should answer the user's interjection first. Once the user receives a visible answer, the backend records that answer as A2H (Answer to Human) in Human Attention so the user can find and acknowledge it even if the dialog immediately continues automatically.
|
|
163
|
+
|
|
162
164
|
**Normative semantics**:
|
|
163
165
|
|
|
164
166
|
1. Every user interjection message is driven as a complete normal round.
|
|
165
|
-
2. If that round needs tools, the system MUST finish the full tool round and any post-tool follow-up before
|
|
166
|
-
3.
|
|
167
|
-
4.
|
|
168
|
-
5.
|
|
169
|
-
6.
|
|
167
|
+
2. If that round needs tools, the system MUST finish the full tool round and any post-tool follow-up before treating the interjection as answered.
|
|
168
|
+
3. A visible assistant `saying` settles the pending user-interjection reply only when no same-round function/tellask call remains after that `saying`.
|
|
169
|
+
4. Settling the interjection appends an A2H item to the dialog's `a2h.yaml`. A2H is an acknowledgement queue, not a problem report and not durable drive work.
|
|
170
|
+
5. If an inter-dialog reply obligation still exists after the interjection is answered, the backend automatically reasserts that obligation and continues. The user should not need to click `Continue` merely because the interjection answer completed.
|
|
171
|
+
6. A2H disappears when the user acknowledges it. This is intentionally "read then burn"; the canonical answer remains in the dialog transcript at `answerRef`.
|
|
172
|
+
7. The Human Attention panel shows Q4H and A2H together. Q4H waits for a human answer; A2H waits only for human acknowledgement.
|
|
170
173
|
|
|
171
174
|
**Strict boundary**: a formal `askHuman` answer is not part of this "user interjection" category. As soon as a prompt carries a real `q4hAnswerCallId`, it belongs to the askHuman reply channel and semantically continues an already-materialized question/answer chain; it must never be downgraded into temporary local side-chat.
|
|
172
175
|
|
|
173
|
-
**Key point**:
|
|
174
|
-
|
|
175
|
-
After the user clicks `Continue`, the backend MUST re-evaluate fresh persistence facts and decide which true-source case now applies. It must not infer the result purely from the visible `displayState`:
|
|
176
|
-
|
|
177
|
-
- **Case 1: the dialog no longer has a reply obligation**
|
|
178
|
-
If there is also no user-wait fact such as Q4H, the dialog should simply continue driving. If it has already become ordinary idle-waiting-user, then `resume_dialog` is no longer actually resumable.
|
|
179
|
-
- **Case 2: the dialog still has a reply obligation and is waiting for user input**
|
|
180
|
-
The canonical example is pending Q4H. In this case, `Continue` should exit the interjection-paused projection and restore the true `blocked` state.
|
|
181
|
-
- **Case 3: the dialog still has a reply obligation but is no longer suspended and is eligible to proceed**
|
|
182
|
-
For example, the Q4H/user-wait fact has disappeared, or a queued prompt provides a valid continuation path. In this case, `Continue` must not first fall back to an intermediate placeholder `blocked/idle` state; it should keep driving immediately.
|
|
183
|
-
|
|
184
|
-
**This leads to two implementation constraints**:
|
|
185
|
-
|
|
186
|
-
- `refreshRunControlProjectionFromPersistenceFacts()` MUST preserve the special "interjection handled; original task paused" `stopped` projection until the user explicitly clicks `Continue`; otherwise the UI collapses back to ordinary `blocked` too early and breaks multi-turn interjection UX. Conversely, when there is no parked original task, this paused projection should not be created at all.
|
|
187
|
-
- The actual outcome of `Continue` MUST be decided in the resume drive path by re-reading fresh persistence facts. "Continue is clickable" does not mean "the dialog will definitely enter proceeding immediately".
|
|
188
|
-
- If `Continue` reveals that the true state is still `blocked`, the reply-obligation reassertion copy should be materialized immediately as a runtime guide in both `dlg.msgs` and persisted course history, while also surfacing as a frontend bubble. That lets the later real resume path rely on ordinary context replay instead of synthesizing a second duplicate runtime prompt.
|
|
189
|
-
- The run-control toolbar's `resumable` count should align with "manual Continue attempt is meaningful". Therefore an interjection-paused `stopped` dialog still counts as resumable even when underlying user-wait facts remain, because the business meaning of `Continue` there is "exit the temporary paused projection and re-evaluate from source-of-truth facts".
|
|
176
|
+
**Key point**: pending user-interjection reply and inter-dialog reply obligation are independent business facts. Reminder/footer copy can use those two facts directly: if the interjection is still pending, prioritize answering the user; if it is settled and the reply obligation remains active, continue toward the required inter-dialog closure.
|
|
190
177
|
|
|
191
178
|
**Mental-model warning**:
|
|
192
179
|
|
|
193
|
-
- Do not reason about this flow from `displayState.kind === 'stopped'` alone.
|
|
194
|
-
- Do not reason about it from user-wait facts alone and then wonder why the UI still shows `stopped`.
|
|
195
|
-
- Do not reason about it from `resume_dialog` eligibility alone and assume resumption always means immediate running.
|
|
196
180
|
- Do not flatten every `origin === 'user'` prompt into "interjection"; a non-empty `q4hAnswerCallId` means askHuman answer continuation and follows a different semantic path.
|
|
181
|
+
- Do not treat A2H as Q4H. A2H does not block drive and does not route input to an agent.
|
|
182
|
+
- Do not store A2H in the Problems panel. It belongs to Human Attention and is removed by Ack.
|
|
197
183
|
|
|
198
184
|
You need all of the following together to understand the behavior correctly:
|
|
199
185
|
|
|
200
186
|
- reply-guidance suppression / deferred reassertion for interjection turns
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
187
|
+
- pending user-interjection reply settlement after visible final `saying`
|
|
188
|
+
- A2H persistence and Ack flow
|
|
189
|
+
- automatic reply-obligation reassertion after the user-visible answer
|
|
204
190
|
|
|
205
191
|
This is an intentionally cross-module semantic contract. Do not locally "simplify" one piece based only on its surface meaning.
|
|
206
192
|
|
|
@@ -154,52 +154,38 @@ askerDialog 可以在执行期间接收来自当前需向它回复的支线对
|
|
|
154
154
|
|
|
155
155
|
这确保了崩溃恢复,并使后端能够从任何持久化状态恢复,而不依赖于前端状态。
|
|
156
156
|
|
|
157
|
-
###
|
|
157
|
+
### 用户插话与 A2H 语义
|
|
158
158
|
|
|
159
159
|
当某个对话仍带有跨对话回复义务,但用户临时插话要求它先处理本地问题时,系统必须区分**UI 投影**与**真实驱动源状态**。
|
|
160
160
|
|
|
161
|
+
直白地说:先把用户这次插话接住并答完。用户看到可见答复后,后端把这条答复记录成 A2H(Answer to Human)放进“待人处理”,这样即使对话马上自动续推,用户也能找到并确认已阅。
|
|
162
|
+
|
|
161
163
|
**规范语义**:
|
|
162
164
|
|
|
163
165
|
1. 每条用户插话消息都按正常驱动轮完整执行。
|
|
164
|
-
2. 若该轮需要工具,则必须先完整跑完该工具轮及其 post-tool follow-up
|
|
165
|
-
3.
|
|
166
|
-
4.
|
|
167
|
-
5.
|
|
168
|
-
6.
|
|
166
|
+
2. 若该轮需要工具,则必须先完整跑完该工具轮及其 post-tool follow-up,之后才可认为插话已答完。
|
|
167
|
+
3. 只有当模型产生可见 `saying`,且该 `saying` 后同轮没有普通 function/tellask call 继续挂起时,才清除“待回复用户插话”。
|
|
168
|
+
4. 清除该状态时,把答复追加到该对话的 `a2h.yaml`。A2H 是待人确认队列,不是 Problems,也不是持久驱动工作。
|
|
169
|
+
5. 如果插话答完后仍有跨对话回复义务,后端自动重申并继续推进;用户不应仅因为插话答完而需要手工点 `Continue`。
|
|
170
|
+
6. 用户 Ack 以后 A2H 立即消失,语义是“阅后即焚”;答复正文的真源仍在对话 transcript 中,由 `answerRef` 回链定位。
|
|
171
|
+
7. “待人处理”面板同时显示 Q4H 与 A2H:Q4H 等人回答,A2H 只等人确认已阅。
|
|
169
172
|
|
|
170
173
|
**严格边界**:`askHuman` 的正式回答不属于这里的“用户插话”。只要一条 prompt 带着真实的 `q4hAnswerCallId`,它就属于 askHuman 回复通道,语义上是在继续已 materialize 的提问/应答链路,绝不能被压入“本地临时插话聊天”。
|
|
171
174
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
点击 `Continue` 后,后端必须重新从 persistence 真源判定当前对话属于哪一种情况,而不能只根据表面的 `displayState` 做静态推断:
|
|
175
|
-
|
|
176
|
-
- **情况 1:当前对话没有回复义务**
|
|
177
|
-
这时若也没有 Q4H 等用户等待事实,就应直接继续 drive;若已回到普通待用户输入态,则 `resume_dialog` 不应再被视为可继续。
|
|
178
|
-
- **情况 2:当前对话仍有回复义务,但正在等待用户输入**
|
|
179
|
-
典型例子是 pending Q4H。此时 `Continue` 应退出插话 paused projection,并恢复成真实的 `blocked`。
|
|
180
|
-
- **情况 3:当前对话仍有回复义务,但已不再 suspend,具备继续推进条件**
|
|
181
|
-
例如 Q4H/用户等待事实已消失,或存在允许继续的 queued prompt。此时 `Continue` 不应先落一个中间 `blocked/idle` 占位态,而应直接继续 drive。
|
|
182
|
-
|
|
183
|
-
**因此有两个实现约束**:
|
|
184
|
-
|
|
185
|
-
- `refreshRunControlProjectionFromPersistenceFacts()` 在用户尚未点击 `Continue` 前,必须保留这层“插话已处理;原任务已暂停”的 `stopped` 投影;否则 UI 会过早塌回普通 `blocked`,破坏多轮插话体验。反过来,如果当前其实没有待恢复原任务,则根本不应建立这层 paused projection。
|
|
186
|
-
- 真正决定 `Continue` 结果的逻辑,必须在恢复驱动路径中重新读取 fresh persistence facts;不能把“可点 Continue”误解为“必然立即 proceeding”。
|
|
187
|
-
- 若 `Continue` 后真源仍是 `blocked`,回复责任重申文案应当立即作为 runtime guide 同时进入 `dlg.msgs` 与持久化课程历史,并同步发前端气泡;这样后面真正恢复 drive 时只需正常读取上下文,不应再额外补发一条重复的 runtime prompt。
|
|
188
|
-
- run-control 工具栏中的 `resumable` 计数,应与“是否允许手动 Continue 尝试”保持一致。因此,处于 interjection-paused `stopped` 的对话即便底层仍有用户等待事实,也应计入 `resumable`;因为 `Continue` 的业务语义正是“退出这层临时 paused projection,并从真源重判下一步”。
|
|
175
|
+
**关键点**:“是否还有用户插话待可见回复”和“是否还有跨对话回复义务”是两个独立业务事实。提醒项 footer 可以直接用这两个事实定制:插话尚未答复时优先答人;插话已答复且回复义务仍 active 时,继续按跨对话回贴要求收口。
|
|
189
176
|
|
|
190
177
|
**心智模型提醒**:
|
|
191
178
|
|
|
192
|
-
- 不能只看 `displayState.kind === 'stopped'` 就理解这条链路。
|
|
193
|
-
- 不能只看用户等待事实就理解为什么 UI 仍显示 `stopped`。
|
|
194
|
-
- 也不能只看 `resume_dialog` eligibility 就推断恢复后一定马上运行。
|
|
195
179
|
- 更不能把所有 `origin === 'user'` 的输入都笼统视作“用户插话”;`q4hAnswerCallId` 非空的 prompt 是 askHuman answer continuation,必须按另一条语义链处理。
|
|
180
|
+
- 不能把 A2H 当成 Q4H;A2H 不阻塞 drive,也不把输入路由给智能体。
|
|
181
|
+
- 不能把 A2H 放进 Problems 面板;它属于“待人处理”,由 Ack 删除。
|
|
196
182
|
|
|
197
183
|
必须把以下几块一起看,才能形成完整且精确的理解:
|
|
198
184
|
|
|
199
185
|
- reply-guidance 中对插话轮的回复义务 suppression / deferred reassertion
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
186
|
+
- 可见最终 `saying` 后对 pending user-interjection reply 的结算
|
|
187
|
+
- A2H 持久化与 Ack 流程
|
|
188
|
+
- 用户可见答复后的跨对话回复义务自动重申
|
|
203
189
|
|
|
204
190
|
这是一条跨模块协同语义,不允许在单点上做“表面看起来更简单”的局部简化。
|
|
205
191
|
|
package/dist/evt-registry.js
CHANGED
|
@@ -50,6 +50,8 @@ class DialogEventRegistryImpl {
|
|
|
50
50
|
switch (evt.type) {
|
|
51
51
|
case 'new_q4h_asked':
|
|
52
52
|
case 'q4h_answered':
|
|
53
|
+
case 'new_a2h_answered':
|
|
54
|
+
case 'a2h_acknowledged':
|
|
53
55
|
case 'sideDialog_created_evt':
|
|
54
56
|
case 'dlg_background_callee_summary_evt':
|
|
55
57
|
case 'dlg_touched_evt':
|
|
@@ -254,6 +254,22 @@ function resolveReminderContextFollowingDialogState(prompt, currentTurnDialogMsg
|
|
|
254
254
|
return 'none';
|
|
255
255
|
return prompt.origin === 'user' ? 'user_message' : 'runtime_notice';
|
|
256
256
|
}
|
|
257
|
+
async function resolveReminderContextFooterState(args) {
|
|
258
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
|
|
259
|
+
const deferredReplyReassertion = latest?.deferredReplyReassertion;
|
|
260
|
+
const activeReplyObligation = await persistence_1.DialogPersistence.loadActiveTellaskReplyObligation(args.dlg.id, args.dlg.status);
|
|
261
|
+
const pendingUserInterjectionReply = latest?.pendingUserInterjectionReply !== undefined;
|
|
262
|
+
const interDialogReplyObligation = deferredReplyReassertion?.reason === 'user_interjection_with_parked_original_task'
|
|
263
|
+
? 'parked_by_user_interjection'
|
|
264
|
+
: activeReplyObligation !== undefined
|
|
265
|
+
? 'active'
|
|
266
|
+
: 'none';
|
|
267
|
+
return {
|
|
268
|
+
followingDialogState: resolveReminderContextFollowingDialogState(args.prompt, args.currentTurnDialogMsgsForContext),
|
|
269
|
+
pendingUserInterjectionReply,
|
|
270
|
+
interDialogReplyObligation,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
257
273
|
function splitDialogMsgsForReminderInsertion(args) {
|
|
258
274
|
const msgId = args.currentPrompt?.msgId;
|
|
259
275
|
if (typeof msgId !== 'string' || msgId.trim() === '') {
|
|
@@ -281,6 +297,82 @@ function getUserOriginPromptMsgId(prompt) {
|
|
|
281
297
|
? prompt.msgId
|
|
282
298
|
: undefined;
|
|
283
299
|
}
|
|
300
|
+
function samePendingUserInterjectionMsg(pending, msgId) {
|
|
301
|
+
return pending.msgId === msgId;
|
|
302
|
+
}
|
|
303
|
+
async function maybeResolveAnsweredUserInterjection(args) {
|
|
304
|
+
if (args.userPromptMsgId === undefined ||
|
|
305
|
+
args.assistantSayingContent === null ||
|
|
306
|
+
args.assistantSayingContent.trim() === '' ||
|
|
307
|
+
args.assistantSayingGenseq === null) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
for (const rawGenseq of args.functionCallGenseqs) {
|
|
311
|
+
if (!Number.isFinite(rawGenseq) || rawGenseq <= 0) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (args.assistantSayingGenseq <= Math.floor(rawGenseq)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const latest = await persistence_1.DialogPersistence.loadDialogLatest(args.dlg.id, args.dlg.status);
|
|
319
|
+
const pending = latest?.pendingUserInterjectionReply;
|
|
320
|
+
if (pending === undefined || !samePendingUserInterjectionMsg(pending, args.userPromptMsgId)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const course = args.dlg.activeGenCourseOrUndefined ?? args.dlg.currentCourse;
|
|
324
|
+
const answerIdSource = [
|
|
325
|
+
args.dlg.id.rootId,
|
|
326
|
+
args.dlg.id.selfId,
|
|
327
|
+
`c${String(course)}`,
|
|
328
|
+
`g${String(args.assistantSayingGenseq)}`,
|
|
329
|
+
pending.msgId,
|
|
330
|
+
].join('|');
|
|
331
|
+
const answer = {
|
|
332
|
+
id: `a2h-${Buffer.from(answerIdSource).toString('base64url')}`,
|
|
333
|
+
content: args.assistantSayingContent,
|
|
334
|
+
answeredAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
|
|
335
|
+
userInterjection: {
|
|
336
|
+
msgId: pending.msgId,
|
|
337
|
+
course: pending.course,
|
|
338
|
+
genseq: pending.genseq,
|
|
339
|
+
},
|
|
340
|
+
answerRef: {
|
|
341
|
+
course,
|
|
342
|
+
genseq: args.assistantSayingGenseq,
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
const existingAnswers = await persistence_1.DialogPersistence.loadAnswersToHumanState(args.dlg.id, args.dlg.status);
|
|
346
|
+
if (!existingAnswers.some((item) => item.id === answer.id)) {
|
|
347
|
+
await persistence_1.DialogPersistence.appendAnswerToHumanState(args.dlg.id, answer, args.dlg.status);
|
|
348
|
+
const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(args.dlg.id, args.dlg.status);
|
|
349
|
+
const taskDocPath = metadata?.taskDocPath ?? args.dlg.taskDocPath ?? '';
|
|
350
|
+
const event = {
|
|
351
|
+
type: 'new_a2h_answered',
|
|
352
|
+
answer: {
|
|
353
|
+
...answer,
|
|
354
|
+
selfId: args.dlg.id.selfId,
|
|
355
|
+
rootId: args.dlg.id.rootId,
|
|
356
|
+
agentId: metadata?.agentId ?? args.dlg.agentId,
|
|
357
|
+
taskDocPath,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
(0, evt_registry_1.postDialogEvent)(args.dlg, event);
|
|
361
|
+
}
|
|
362
|
+
await persistence_1.DialogPersistence.mutateDialogLatest(args.dlg.id, (previous) => {
|
|
363
|
+
const previousPending = previous.pendingUserInterjectionReply;
|
|
364
|
+
const userPromptMsgId = args.userPromptMsgId;
|
|
365
|
+
if (previousPending === undefined ||
|
|
366
|
+
userPromptMsgId === undefined ||
|
|
367
|
+
!samePendingUserInterjectionMsg(previousPending, userPromptMsgId)) {
|
|
368
|
+
return { kind: 'noop' };
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
kind: 'patch',
|
|
372
|
+
patch: { pendingUserInterjectionReply: undefined },
|
|
373
|
+
};
|
|
374
|
+
}, args.dlg.status);
|
|
375
|
+
}
|
|
284
376
|
async function persistAndEmitRuntimeGuide(dlg, content) {
|
|
285
377
|
await dlg.addChatMessages({
|
|
286
378
|
type: 'transient_guide_msg',
|
|
@@ -2009,16 +2101,16 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2009
2101
|
}
|
|
2010
2102
|
lastBusinessContinuation = currentBusinessContinuation;
|
|
2011
2103
|
let generationBodyError;
|
|
2104
|
+
const q4hAnswerCallId = normalizeQ4HAnswerCallId(currentPrompt?.q4hAnswerCallId);
|
|
2105
|
+
const isQ4HAnswerPrompt = q4hAnswerCallId !== undefined;
|
|
2012
2106
|
try {
|
|
2013
2107
|
if (currentPrompt) {
|
|
2014
2108
|
if (currentPrompt.skipTaskdoc === true) {
|
|
2015
2109
|
skipTaskdocForThisDrive = true;
|
|
2016
2110
|
}
|
|
2017
2111
|
const persistedUserLanguageCode = currentPrompt.userLanguageCode ?? dlg.getLastUserLanguageCode();
|
|
2018
|
-
const q4hAnswerCallId = normalizeQ4HAnswerCallId(currentPrompt.q4hAnswerCallId);
|
|
2019
2112
|
// `q4hAnswerCallId` marks a continuation input for an already-materialized askHuman
|
|
2020
2113
|
// answer. It is not a second business-level user prompt that should re-enter transcript.
|
|
2021
|
-
const isQ4HAnswerPrompt = q4hAnswerCallId !== undefined;
|
|
2022
2114
|
const promptLanguage = persistedUserLanguageCode === 'zh' || persistedUserLanguageCode === 'en'
|
|
2023
2115
|
? persistedUserLanguageCode
|
|
2024
2116
|
: (0, work_language_1.getWorkLanguage)();
|
|
@@ -2034,12 +2126,12 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2034
2126
|
// User interjection suppression is a reversible state transition, not a one-shot
|
|
2035
2127
|
// latch. The normal cycle is:
|
|
2036
2128
|
// - user interjects -> suppress reply obligation
|
|
2037
|
-
// -
|
|
2129
|
+
// - the visible local answer auto-reasserts the reply obligation
|
|
2038
2130
|
// - user interjects again -> suppress it again
|
|
2039
2131
|
//
|
|
2040
|
-
//
|
|
2041
|
-
// state and re-materialize the suppression guide, even when the
|
|
2042
|
-
// directive itself did not change.
|
|
2132
|
+
// Legacy blocked-Continue paths may also re-enter here. A repeated interjection MUST
|
|
2133
|
+
// re-arm the deferred state and re-materialize the suppression guide, even when the
|
|
2134
|
+
// underlying reply directive itself did not change.
|
|
2043
2135
|
const existingDeferredReplyReassertion = await persistence_1.DialogPersistence.getDeferredReplyReassertion(dlg.id, dlg.status);
|
|
2044
2136
|
const nextDeferredReplyReassertion = {
|
|
2045
2137
|
reason: 'user_interjection_with_parked_original_task',
|
|
@@ -2077,6 +2169,18 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2077
2169
|
if (replyGuidance.promptContent === undefined) {
|
|
2078
2170
|
throw new Error(`kernel-driver reply guidance invariant violation: missing prompt content for dialog=${dlg.id.valueOf()} msgId=${currentPrompt.msgId}`);
|
|
2079
2171
|
}
|
|
2172
|
+
if (currentPrompt.origin === 'user' && !replyGuidance.isQ4HAnswerPrompt) {
|
|
2173
|
+
await persistence_1.DialogPersistence.mutateDialogLatest(dlg.id, () => ({
|
|
2174
|
+
kind: 'patch',
|
|
2175
|
+
patch: {
|
|
2176
|
+
pendingUserInterjectionReply: {
|
|
2177
|
+
msgId: currentPrompt.msgId,
|
|
2178
|
+
course: (0, storage_1.toDialogCourseNumber)(dlg.activeGenCourseOrUndefined ?? dlg.currentCourse),
|
|
2179
|
+
genseq: (0, storage_1.toCallSiteGenseqNo)(dlg.activeGenSeq),
|
|
2180
|
+
},
|
|
2181
|
+
},
|
|
2182
|
+
}), dlg.status);
|
|
2183
|
+
}
|
|
2080
2184
|
const renderPromptAsRuntimeGuideBubble = currentPrompt.origin === 'runtime' &&
|
|
2081
2185
|
(0, reply_prompt_copy_1.isStandaloneRuntimeGuidePromptContent)(replyGuidance.promptContent);
|
|
2082
2186
|
if (currentRuntimeGuideMsg) {
|
|
@@ -2225,7 +2329,11 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2225
2329
|
{
|
|
2226
2330
|
type: 'environment_msg',
|
|
2227
2331
|
role: 'user',
|
|
2228
|
-
content: (0, driver_messages_1.formatReminderContextFooter)((0, work_language_1.getWorkLanguage)(),
|
|
2332
|
+
content: (0, driver_messages_1.formatReminderContextFooter)((0, work_language_1.getWorkLanguage)(), await resolveReminderContextFooterState({
|
|
2333
|
+
dlg,
|
|
2334
|
+
prompt: currentPrompt,
|
|
2335
|
+
currentTurnDialogMsgsForContext: splitDialogMsgs.currentTurnDialogMsgsForContext,
|
|
2336
|
+
})),
|
|
2229
2337
|
},
|
|
2230
2338
|
]
|
|
2231
2339
|
: renderedReminders;
|
|
@@ -2588,10 +2696,13 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2588
2696
|
}
|
|
2589
2697
|
return { usage: res.usage, llmGenModel: res.llmGenModel };
|
|
2590
2698
|
};
|
|
2699
|
+
const previousAssistantSayingGenseq = lastAssistantSayingGenseq;
|
|
2591
2700
|
const llmOutput = await streamOrBatch();
|
|
2592
2701
|
if (typeof llmOutput.llmGenModel === 'string' && llmOutput.llmGenModel.trim() !== '') {
|
|
2593
2702
|
llmGenModelForGen = llmOutput.llmGenModel.trim();
|
|
2594
2703
|
}
|
|
2704
|
+
let currentRoundAssistantSayingContent = null;
|
|
2705
|
+
let currentRoundAssistantSayingGenseq = null;
|
|
2595
2706
|
contextHealthForGen = computeContextHealthSnapshot({
|
|
2596
2707
|
providerCfg,
|
|
2597
2708
|
model,
|
|
@@ -2618,6 +2729,8 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2618
2729
|
else {
|
|
2619
2730
|
lastAssistantSayingContent = msg.content;
|
|
2620
2731
|
lastAssistantSayingGenseq = msg.genseq;
|
|
2732
|
+
currentRoundAssistantSayingContent = msg.content;
|
|
2733
|
+
currentRoundAssistantSayingGenseq = msg.genseq;
|
|
2621
2734
|
lastAssistantReplyTarget = currentReplyTarget;
|
|
2622
2735
|
await emitAssistantSaying(dlg, msg.content);
|
|
2623
2736
|
}
|
|
@@ -2735,11 +2848,13 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2735
2848
|
});
|
|
2736
2849
|
streamedFuncCalls.length = 0;
|
|
2737
2850
|
streamedFuncCalls.push(...normalizedStreamedFuncCalls);
|
|
2851
|
+
const currentRoundFunctionCallGenseqs = [];
|
|
2738
2852
|
for (const call of streamedFuncCalls) {
|
|
2739
2853
|
const rawCallGenseq = call.genseq;
|
|
2740
2854
|
if (!Number.isFinite(rawCallGenseq) || rawCallGenseq <= 0)
|
|
2741
2855
|
continue;
|
|
2742
2856
|
const callGenseq = Math.floor(rawCallGenseq);
|
|
2857
|
+
currentRoundFunctionCallGenseqs.push(callGenseq);
|
|
2743
2858
|
if (lastFunctionCallGenseq === null || callGenseq > lastFunctionCallGenseq) {
|
|
2744
2859
|
lastFunctionCallGenseq = callGenseq;
|
|
2745
2860
|
}
|
|
@@ -2761,6 +2876,21 @@ async function driveDialogStreamCore(dlg, callbacks, humanPrompt, driveOptions)
|
|
|
2761
2876
|
newMsgs.push(...routed.pairedMessages);
|
|
2762
2877
|
}
|
|
2763
2878
|
await dlg.addChatMessages(...newMsgs);
|
|
2879
|
+
const streamedCurrentRoundSayingContent = batchOutputs.length === 0 && lastAssistantSayingGenseq !== previousAssistantSayingGenseq
|
|
2880
|
+
? lastAssistantSayingContent
|
|
2881
|
+
: null;
|
|
2882
|
+
const streamedCurrentRoundSayingGenseq = batchOutputs.length === 0 && lastAssistantSayingGenseq !== previousAssistantSayingGenseq
|
|
2883
|
+
? lastAssistantSayingGenseq
|
|
2884
|
+
: null;
|
|
2885
|
+
if (currentPrompt?.origin === 'user' && !isQ4HAnswerPrompt) {
|
|
2886
|
+
await maybeResolveAnsweredUserInterjection({
|
|
2887
|
+
dlg,
|
|
2888
|
+
userPromptMsgId: currentPrompt.msgId,
|
|
2889
|
+
assistantSayingContent: currentRoundAssistantSayingContent ?? streamedCurrentRoundSayingContent,
|
|
2890
|
+
assistantSayingGenseq: currentRoundAssistantSayingGenseq ?? streamedCurrentRoundSayingGenseq,
|
|
2891
|
+
functionCallGenseqs: currentRoundFunctionCallGenseqs,
|
|
2892
|
+
});
|
|
2893
|
+
}
|
|
2764
2894
|
const persistedFbrState = await loadDialogFbrState(dlg);
|
|
2765
2895
|
if (persistedFbrState) {
|
|
2766
2896
|
if (persistedFbrState.phase === 'finalization') {
|
|
@@ -1002,8 +1002,8 @@ async function maybeSurfaceDeferredReplyReassertionGuideForBlockedContinue(dialo
|
|
|
1002
1002
|
// 3. emit runtime_guide_evt so the user immediately sees the reassertion bubble after Continue.
|
|
1003
1003
|
//
|
|
1004
1004
|
// Do not "optimize" this into only an event or only a deferred prompt. The whole point is that
|
|
1005
|
-
// once blocked Continue
|
|
1006
|
-
// later real driving should need no special duplicate reassertion path.
|
|
1005
|
+
// once a legacy blocked Continue path surfaces the guide, it becomes a first-class historical
|
|
1006
|
+
// context fact and later real driving should need no special duplicate reassertion path.
|
|
1007
1007
|
await surfaceRuntimeGuide(dialog, content);
|
|
1008
1008
|
await persistence_1.DialogPersistence.setDeferredReplyReassertion(dialog.id, {
|
|
1009
1009
|
reason: 'user_interjection_with_parked_original_task',
|
|
@@ -1399,9 +1399,9 @@ async function executeDriveRound(args) {
|
|
|
1399
1399
|
if (!humanPrompt) {
|
|
1400
1400
|
// WARNING:
|
|
1401
1401
|
// `allowResumeFromInterrupted` covers multiple stop reasons, but the interjection-pause case
|
|
1402
|
-
// is semantically special.
|
|
1403
|
-
//
|
|
1404
|
-
//
|
|
1402
|
+
// is semantically special. Continue here does NOT mean "blindly clear stopped and drive". We
|
|
1403
|
+
// must re-read the fresh persistence facts first because there are three distinct true-source
|
|
1404
|
+
// cases behind the same visible resumption panel:
|
|
1405
1405
|
// - no active reply obligation / not suspended anymore -> continue real driving now
|
|
1406
1406
|
// - active reply obligation + suspended -> restore the true suspension state
|
|
1407
1407
|
// - active reply obligation + still proceeding continuation (for example queued prompt) ->
|
|
@@ -1623,6 +1623,15 @@ async function executeDriveRound(args) {
|
|
|
1623
1623
|
!replyGuidance.isQ4HAnswerPrompt &&
|
|
1624
1624
|
replyGuidance.suppressInterDialogReplyGuidance &&
|
|
1625
1625
|
replyGuidance.deferredReplyReassertionDirective !== undefined;
|
|
1626
|
+
if (effectivePrompt?.origin === 'user' &&
|
|
1627
|
+
!replyGuidance.isQ4HAnswerPrompt &&
|
|
1628
|
+
!replyGuidance.suppressInterDialogReplyGuidance &&
|
|
1629
|
+
replyGuidance.deferredReplyReassertionDirective !== undefined) {
|
|
1630
|
+
await persistence_1.DialogPersistence.setDeferredReplyReassertion(dialog.id, {
|
|
1631
|
+
reason: 'user_interjection_with_parked_original_task',
|
|
1632
|
+
directive: replyGuidance.deferredReplyReassertionDirective,
|
|
1633
|
+
}, dialog.status);
|
|
1634
|
+
}
|
|
1626
1635
|
activeTellaskReplyDirective =
|
|
1627
1636
|
replyGuidance.activeReplyDirective ?? replyContinuationScope.directive();
|
|
1628
1637
|
activePromptWasReplyToolReminder = isReplyToolReminderPrompt(effectivePrompt);
|
|
@@ -1927,6 +1936,36 @@ async function executeDriveRound(args) {
|
|
|
1927
1936
|
}
|
|
1928
1937
|
}
|
|
1929
1938
|
}
|
|
1939
|
+
if (shouldPauseAfterLocalUserInterjection &&
|
|
1940
|
+
!interruptedBySignal &&
|
|
1941
|
+
followUp === undefined &&
|
|
1942
|
+
driveResult?.lastAssistantSayingContent !== null) {
|
|
1943
|
+
const deferredReplyReassertion = await persistence_1.DialogPersistence.getDeferredReplyReassertion(dialog.id, dialog.status);
|
|
1944
|
+
if (deferredReplyReassertion?.reason === 'user_interjection_with_parked_original_task') {
|
|
1945
|
+
const language = (0, work_language_1.getWorkLanguage)();
|
|
1946
|
+
const prompt = await (0, reply_guidance_1.buildReplyObligationReassertionPrompt)({
|
|
1947
|
+
dlg: dialog,
|
|
1948
|
+
directive: deferredReplyReassertion.directive,
|
|
1949
|
+
language,
|
|
1950
|
+
});
|
|
1951
|
+
followUp = {
|
|
1952
|
+
kind: 'runtime_reply_reminder',
|
|
1953
|
+
prompt,
|
|
1954
|
+
msgId: (0, id_1.generateShortId)(),
|
|
1955
|
+
grammar: 'markdown',
|
|
1956
|
+
origin: 'runtime',
|
|
1957
|
+
userLanguageCode: language,
|
|
1958
|
+
tellaskReplyDirective: deferredReplyReassertion.directive,
|
|
1959
|
+
};
|
|
1960
|
+
await persistence_1.DialogPersistence.setDeferredReplyReassertion(dialog.id, undefined, dialog.status);
|
|
1961
|
+
log_1.log.debug('kernel-driver queued automatic reply-obligation reassertion after user interjection answer', undefined, {
|
|
1962
|
+
dialogId: dialog.id.valueOf(),
|
|
1963
|
+
rootId: dialog.id.rootId,
|
|
1964
|
+
selfId: dialog.id.selfId,
|
|
1965
|
+
targetCallId: deferredReplyReassertion.directive.targetCallId,
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1930
1969
|
if (followUp) {
|
|
1931
1970
|
if (followUp.kind === 'runtime_reply_reminder' ||
|
|
1932
1971
|
followUp.kind === 'runtime_sideDialog_reply_reminder') {
|
|
@@ -2016,21 +2055,8 @@ async function executeDriveRound(args) {
|
|
|
2016
2055
|
shouldDriveQueuedPromptAfterCore = true;
|
|
2017
2056
|
return driveResult;
|
|
2018
2057
|
}
|
|
2019
|
-
if (shouldPauseAfterLocalUserInterjection &&
|
|
2020
|
-
|
|
2021
|
-
followUp === undefined &&
|
|
2022
|
-
driveResult?.lastAssistantSayingContent !== null) {
|
|
2023
|
-
const pauseReason = (0, interjection_pause_stop_1.buildUserInterjectionPauseStopReason)();
|
|
2024
|
-
await (0, dialog_display_state_1.setDialogDisplayState)(dialog.id, {
|
|
2025
|
-
kind: 'stopped',
|
|
2026
|
-
reason: pauseReason,
|
|
2027
|
-
continueEnabled: true,
|
|
2028
|
-
});
|
|
2029
|
-
(0, dialog_display_state_1.broadcastDisplayStateMarker)(dialog.id, {
|
|
2030
|
-
kind: 'interrupted',
|
|
2031
|
-
reason: pauseReason,
|
|
2032
|
-
});
|
|
2033
|
-
log_1.log.debug('kernel-driver paused original task after local user interjection reply', undefined, {
|
|
2058
|
+
if (shouldPauseAfterLocalUserInterjection && !interruptedBySignal && followUp === undefined) {
|
|
2059
|
+
log_1.log.debug('kernel-driver observed local user interjection pause condition, but continuation is now fully automatic', undefined, {
|
|
2034
2060
|
dialogId: dialog.id.valueOf(),
|
|
2035
2061
|
rootId: dialog.id.rootId,
|
|
2036
2062
|
selfId: dialog.id.selfId,
|
|
@@ -196,15 +196,15 @@ async function shouldSuppressInterDialogReplyGuidanceForUserInterjection(args) {
|
|
|
196
196
|
// This suppression decision is not a cosmetic prompt tweak. It is one leg of the full
|
|
197
197
|
// interjection-pause state machine:
|
|
198
198
|
// 1. user interjection suppresses the live reply obligation here;
|
|
199
|
-
// 2. `flow.ts` answers locally and
|
|
200
|
-
// 3.
|
|
201
|
-
// blocked or resume real driving.
|
|
199
|
+
// 2. `flow.ts` answers locally and stores a deferred reply reassertion;
|
|
200
|
+
// 3. after a visible local answer, the driver reasserts the reply obligation automatically.
|
|
202
201
|
//
|
|
203
202
|
// Do not "simplify" this into a pure display-state check or a pure active-callee check.
|
|
204
203
|
// Proceeding dialogs with a still-active reply obligation are part of the same rule: a fresh
|
|
205
204
|
// user interjection should still suppress the live reply obligation and answer locally first.
|
|
206
|
-
// The business anchor is the deferred reply reassertion
|
|
207
|
-
// repeated interjection
|
|
205
|
+
// The business anchor is the deferred reply reassertion. A stale paused execution marker still
|
|
206
|
+
// means a repeated interjection should behave as local side conversation, but it is not durable
|
|
207
|
+
// drive work and must not force a manual Continue after the answer is visible.
|
|
208
208
|
const prompt = args.prompt;
|
|
209
209
|
if (!prompt) {
|
|
210
210
|
return false;
|
|
@@ -204,6 +204,11 @@ function getMemoryPromptCopy(ctx) {
|
|
|
204
204
|
? `工作流:先做事 → 再提炼(\`update_reminder\`;必要时整理差遣牒追加条目/更新提案并诉请 \`@${ctx.taskdocMaintainerId}\` 合并写入)→ 然后 \`clear_mind\` 清空噪音。`
|
|
205
205
|
: '工作流:停止扩张上下文 → 维护足够详尽的接续包提醒项(`add_reminder` 或 `update_reminder`,长度没有技术限制)→ 然后 `clear_mind` 开启新一程。',
|
|
206
206
|
mainDialogWorkflowLine: '工作流:先做事 -> 再提炼(`update_reminder` + `mind_more(progress)`;需要压缩/删旧时先 `recall_taskdoc` 取得 `content_hash`,再用带 `previous_content_hash` 的 `change_mind`;要删除整章文件时用 `never_mind`)-> 然后 `clear_mind` 清空噪音。',
|
|
207
|
+
...(ctx.isSideDialog
|
|
208
|
+
? {}
|
|
209
|
+
: {
|
|
210
|
+
progressVcsOrderLine: '硬性顺序:先补 `progress`,再动 git。只要这次代码/文档改动准备进 git(add / commit / push),先确认 `progress` 已经写清当前状态、决策、阻塞和下一步;如果还没写清,就先补写。简单判断:改动会进仓库 + `progress` 还没跟上 = 先别动 git。`git status` 只用来确认,不是提交动作。不要把“提交了某个 commit”“push 了”写进 `progress`。',
|
|
211
|
+
}),
|
|
207
212
|
contextHealthLine: contextHealthLineZh,
|
|
208
213
|
taskdocLogLine: taskdocLogLineZh,
|
|
209
214
|
};
|
|
@@ -239,6 +244,11 @@ function getMemoryPromptCopy(ctx) {
|
|
|
239
244
|
? `Workflow: do work → distill (\`update_reminder\`; when Taskdoc needs updates, draft append entries, a merged replacement, or a section deletion and ask \`@${ctx.taskdocMaintainerId}\`) → then \`clear_mind\` to drop noise.`
|
|
240
245
|
: 'Workflow: stop expanding context → maintain sufficiently detailed continuation-package reminders (`add_reminder` or `update_reminder`, with no technical length limit) → then `clear_mind` to start a new course.',
|
|
241
246
|
mainDialogWorkflowLine: 'Workflow: do work -> distill (`update_reminder` + `mind_more(progress)`; when compression/deletion is needed, first use `recall_taskdoc` to get `content_hash`, then use `change_mind` with `previous_content_hash`; use `never_mind` when removing a whole section file) -> then `clear_mind` to drop noise.',
|
|
247
|
+
...(ctx.isSideDialog
|
|
248
|
+
? {}
|
|
249
|
+
: {
|
|
250
|
+
progressVcsOrderLine: 'Hard order: update `progress` first, then use git. If this code/docs change is going into git (add / commit / push), make sure `progress` already says the current state, decisions, blockers, and next step; if not, write it first. Simple check: change will enter the repo + `progress` is behind = stop and update `progress` first. `git status` is only for checking, not a commit step. Do not write git events like “committed X” or “ran git push” into progress.',
|
|
251
|
+
}),
|
|
242
252
|
contextHealthLine: contextHealthLineEn,
|
|
243
253
|
taskdocLogLine: taskdocLogLineEn,
|
|
244
254
|
};
|
|
@@ -264,6 +274,7 @@ function buildMemorySystemPrompt(ctx) {
|
|
|
264
274
|
...(ctx.agentHasTeamMemoryTools ? [copy.teamMemoryHintLine] : []),
|
|
265
275
|
...(ctx.agentHasPersonalMemoryTools ? [copy.personalMemoryHintLine] : []),
|
|
266
276
|
ctx.isSideDialog ? copy.sideDialogWorkflowLine : copy.mainDialogWorkflowLine,
|
|
277
|
+
...(copy.progressVcsOrderLine === undefined ? [] : [copy.progressVcsOrderLine]),
|
|
267
278
|
copy.contextHealthLine,
|
|
268
279
|
copy.taskdocLogLine,
|
|
269
280
|
].join('\n');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DomindsPersistenceFileSource = 'dialog_state' | 'dialog_latest' | 'dialog_metadata' | 'dialog_asker_stack' | 'dialog_course_events' | 'reminder_state' | 'questions4human_state' | 'active_callees' | 'wake_queue' | 'sideDialog_responses' | 'sideDialog_registry';
|
|
1
|
+
export type DomindsPersistenceFileSource = 'dialog_state' | 'dialog_latest' | 'dialog_metadata' | 'dialog_asker_stack' | 'dialog_course_events' | 'reminder_state' | 'questions4human_state' | 'answers2human_state' | 'active_callees' | 'wake_queue' | 'sideDialog_responses' | 'sideDialog_registry';
|
|
2
2
|
export type DomindsPersistenceFileOperation = 'read' | 'parse';
|
|
3
3
|
export type DomindsPersistenceFileFormat = 'yaml' | 'json' | 'jsonl';
|
|
4
4
|
type DomindsPersistenceFileErrorArgs = {
|