@diffohq/diffo 0.4.1 → 0.6.0
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/cli.mjs +244 -36
- package/dist/client/assets/{abnfDiagram-O67JEVCF-b6l_FGHi.js → abnfDiagram-O67JEVCF-vILxBZcB.js} +1 -1
- package/dist/client/assets/{arc-Cz0jZwLH.js → arc-KM37zfEu.js} +1 -1
- package/dist/client/assets/architecture-7GRP2DOG-DzlmD0Wj.js +1 -0
- package/dist/client/assets/{architectureDiagram-NJMV4G6O-CJdtqKce.js → architectureDiagram-NJMV4G6O-CCe24XcA.js} +1 -1
- package/dist/client/assets/{blockDiagram-BEXU5L5S-Ci-lnLGN.js → blockDiagram-BEXU5L5S-GL2esJ0w.js} +1 -1
- package/dist/client/assets/{c4Diagram-YGBWAQC7-DDO2mTMB.js → c4Diagram-YGBWAQC7-BcRujrvs.js} +1 -1
- package/dist/client/assets/channel-BffjfVqs.js +1 -0
- package/dist/client/assets/{chunk-3FUC2YCW-VyswqFt3.js → chunk-3FUC2YCW-Gj4X9KK9.js} +1 -1
- package/dist/client/assets/{chunk-5DYCD2WN-B4MBZW5P.js → chunk-5DYCD2WN-DZs-erj_.js} +1 -1
- package/dist/client/assets/{chunk-742MDFTN-Cxvmvrid.js → chunk-742MDFTN-Db1JJZ9G.js} +1 -1
- package/dist/client/assets/{chunk-7INBJB4K-DkBnRFbH.js → chunk-7INBJB4K-B_wqJ7Xn.js} +1 -1
- package/dist/client/assets/{chunk-7M6MHVWA-BvJgBwEi.js → chunk-7M6MHVWA-DuALBizw.js} +1 -1
- package/dist/client/assets/{chunk-7PRAP22T-OfiMNi_y.js → chunk-7PRAP22T-D9QvZUm3.js} +1 -1
- package/dist/client/assets/{chunk-GTNCS2PH-BodN5cRQ.js → chunk-GTNCS2PH-JFHsSpPU.js} +1 -1
- package/dist/client/assets/{chunk-GWA4HPMP-KifxxZcy.js → chunk-GWA4HPMP-CX_60U9Y.js} +1 -1
- package/dist/client/assets/{chunk-MBY4JIJT-rw02bHlj.js → chunk-MBY4JIJT-BVqUStmy.js} +1 -1
- package/dist/client/assets/{chunk-NETBCI7D-CWjhZoou.js → chunk-NETBCI7D-BoSm0O5f.js} +1 -1
- package/dist/client/assets/{chunk-O7XYJQB3-CyxnSfZ3.js → chunk-O7XYJQB3-DG64mbS_.js} +1 -1
- package/dist/client/assets/{chunk-UA2S7LBM-BReIFgsu.js → chunk-UA2S7LBM-BL039fcM.js} +1 -1
- package/dist/client/assets/{chunk-WEXAMYUT-DHwMy8v7.js → chunk-WEXAMYUT-BPFyuzbi.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-C398QZbS.js → chunk-XXDRQBXY-BzCs4oPB.js} +1 -1
- package/dist/client/assets/{chunk-Z7XXMR3K-hEV1GU64.js → chunk-Z7XXMR3K-D9UN9ldM.js} +1 -1
- package/dist/client/assets/{chunk-ZIGJFQKS-DJJL7llO.js → chunk-ZIGJFQKS-D47UlTiy.js} +1 -1
- package/dist/client/assets/{classDiagram-v2-NBCMYWYE-pSGZI880.js → classDiagram-v2-NBCMYWYE-CXeBds6Z.js} +1 -1
- package/dist/client/assets/{core-C05I4wHV.js → core-De6Bel6T.js} +1 -1
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DNV8Dqd8.js → cose-bilkent-JH36ORCC-C5C9BMn5.js} +1 -1
- package/dist/client/assets/{cynefin-OW5HDTMX-DF-6FOgS.js → cynefin-OW5HDTMX-TQm3krMs.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-VND7K2PF-D3-iJq2J.js → cynefinDiagram-VND7K2PF-D9L1g2S1.js} +1 -1
- package/dist/client/assets/{dagre-6A5THRUB-C9FDZkaa.js → dagre-6A5THRUB-D9iVd0tg.js} +1 -1
- package/dist/client/assets/{diagram-22UHCM2B-CfSJBDyN.js → diagram-22UHCM2B-CD_1yZgj.js} +1 -1
- package/dist/client/assets/{diagram-3UASUU5V-D946baYo.js → diagram-3UASUU5V-BLiXkub3.js} +1 -1
- package/dist/client/assets/{diagram-ATOU4E4O-CGrjukoA.js → diagram-ATOU4E4O-CnXY0tjf.js} +1 -1
- package/dist/client/assets/{diagram-CDSNMT55-B47UPtWq.js → diagram-CDSNMT55-BBnDsrf_.js} +1 -1
- package/dist/client/assets/{diagram-MLGK6HIB-D06yEd5R.js → diagram-MLGK6HIB-BUiu4OKD.js} +1 -1
- package/dist/client/assets/{diagram-MPIPVDR6-BRUBT2B9.js → diagram-MPIPVDR6-DiI9FWeO.js} +1 -1
- package/dist/client/assets/{dist-BfTRyLGm.js → dist-BWYGCgdo.js} +1 -1
- package/dist/client/assets/{dist-DVw88SBM.js → dist-Co85aeoI.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-ZINNZB2B-BnuExfrd.js → ebnfDiagram-ZINNZB2B-Bg_BLedv.js} +1 -1
- package/dist/client/assets/{elk-276RUBZZ-BmdcLjR9.js → elk-276RUBZZ-BO7Zoa7U.js} +1 -1
- package/dist/client/assets/{engine-oniguruma-Bw1dOuOO.js → engine-oniguruma-DCFwTJLE.js} +1 -1
- package/dist/client/assets/{erDiagram-OPXOYQCR-CKCR3jQT.js → erDiagram-OPXOYQCR-DjBak3Ld.js} +1 -1
- package/dist/client/assets/eventmodeling-NTZA5JFV-BA4dpxhq.js +1 -0
- package/dist/client/assets/flowDiagram-KWPJA3E3-BXSGu0Q5.js +1 -0
- package/dist/client/assets/{ganttDiagram-FUAMR5RP-BwkoweG7.js → ganttDiagram-FUAMR5RP-CEn0Np5L.js} +1 -1
- package/dist/client/assets/{gitGraph-4MIJSDKK-M7_hHVyH.js → gitGraph-4MIJSDKK-DVMdaaG2.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-X574FWY7-BVm_sLT_.js → gitGraphDiagram-X574FWY7-B6ZkiY7u.js} +1 -1
- package/dist/client/assets/index-6w3rS9cw.js +91 -0
- package/dist/client/assets/index-D934d4yh.css +1 -0
- package/dist/client/assets/{info-A6RAGUB7-DPBs3m_X.js → info-A6RAGUB7-Idt2RahI.js} +1 -1
- package/dist/client/assets/{infoDiagram-VRGFBTTK-DDb3KyOP.js → infoDiagram-VRGFBTTK-CA8T3csq.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-OU5B5YK6-CPt4RDlr.js → ishikawaDiagram-OU5B5YK6-DcgLwVJX.js} +1 -1
- package/dist/client/assets/{journeyDiagram-ZHPQQLJL-ECQSHJ0N.js → journeyDiagram-ZHPQQLJL-_AVEW5aE.js} +1 -1
- package/dist/client/assets/{kanban-definition-PNTS6WVX-tAmonxA8.js → kanban-definition-PNTS6WVX-CZ2gNo-B.js} +1 -1
- package/dist/client/assets/{line-x6vAh41a.js → line-CHew3ngr.js} +1 -1
- package/dist/client/assets/{linear-BqczUNss.js → linear-Baz1Hb3u.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-siQPZsry.js → mermaid-parser.core-D2BrkwRE.js} +3 -3
- package/dist/client/assets/{mermaid.core-CLHTQVF-.js → mermaid.core-CCFlYVgY.js} +4 -4
- package/dist/client/assets/{mindmap-definition-NLK3R4M7-C9fnEdyl.js → mindmap-definition-NLK3R4M7-BXG9w5sC.js} +1 -1
- package/dist/client/assets/{packet-AYTQ26CC-DAeK0Q4n.js → packet-AYTQ26CC-BOvg32Rh.js} +1 -1
- package/dist/client/assets/{pegDiagram-GJSIUBJH-BrWBFYPO.js → pegDiagram-GJSIUBJH-COukdq7o.js} +1 -1
- package/dist/client/assets/{pie-WAS4IAKB-CnieihkO.js → pie-WAS4IAKB-ChHsdIQV.js} +1 -1
- package/dist/client/assets/{pieDiagram-5QR66LMP-Bp5f6Eal.js → pieDiagram-5QR66LMP-Deng5oun.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-O4NWA36T-ECuLA3RS.js → quadrantDiagram-O4NWA36T-DmEJ83mR.js} +1 -1
- package/dist/client/assets/{radar-RG4KPBEZ-BJXkERy0.js → radar-RG4KPBEZ-YxcXBiXc.js} +1 -1
- package/dist/client/assets/{railroad-74A4TZTK-Di61fiV5.js → railroad-74A4TZTK-RkYX7A2D.js} +1 -1
- package/dist/client/assets/railroad-abnf-HS5TGJTU-CYF7Z2Wu.js +1 -0
- package/dist/client/assets/railroad-ebnf-LZEXJU2U-CqV11tet.js +1 -0
- package/dist/client/assets/railroad-peg-WCYAUIDC-CPusJj6I.js +1 -0
- package/dist/client/assets/{railroadDiagram-XR7U4H2S-Dn2WPOQe.js → railroadDiagram-XR7U4H2S-uj5HQbYy.js} +1 -1
- package/dist/client/assets/{requirementDiagram-PLB6GJNP-uuvtvHmq.js → requirementDiagram-PLB6GJNP-C_QtkIlI.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-IPEJSGJF-BXk6X0af.js → sankeyDiagram-IPEJSGJF-BfxM5TKv.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-PO4LG4MO-BsPoSXhg.js → sequenceDiagram-PO4LG4MO-DXflg1u_.js} +1 -1
- package/dist/client/assets/{src-B4djUhOi.js → src-YTrwJCk8.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-GCMORJYK-cCml5k6n.js → stateDiagram-v2-GCMORJYK-BjDmED1Q.js} +1 -1
- package/dist/client/assets/{swimlanes-2SLR337P-Dh7v55ZA.js → swimlanes-2SLR337P-BAvnQ6KT.js} +1 -1
- package/dist/client/assets/swimlanesDiagram-TC7HE7FX-CbFPwcec.js +8 -0
- package/dist/client/assets/{timeline-definition-EJHVYXUP-LFjkFV14.js → timeline-definition-EJHVYXUP-BYemcgr1.js} +1 -1
- package/dist/client/assets/{treeView-Q6P3EWNA-RYZHSXeB.js → treeView-Q6P3EWNA-CsuBtqLq.js} +1 -1
- package/dist/client/assets/{treemap-WGGIJYW6-UVw_-EPz.js → treemap-WGGIJYW6-DUk1Z6g4.js} +1 -1
- package/dist/client/assets/{usecaseDiagram-POWQR4AR-DZfPogyb.js → usecaseDiagram-POWQR4AR-j1nHXvyL.js} +1 -1
- package/dist/client/assets/{vennDiagram-UO4OBE2U-CN7Ldg-2.js → vennDiagram-UO4OBE2U-B5eUL-dD.js} +1 -1
- package/dist/client/assets/{wardley-WFR3VGLG-CXPbbIy_.js → wardley-WFR3VGLG-1YjpwPJM.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-VNRHLVJA-5p0A7_g6.js → wardleyDiagram-VNRHLVJA-C7laBq5A.js} +1 -1
- package/dist/client/assets/{xychartDiagram-PMCCYNJV-gJ7xwZXO.js → xychartDiagram-PMCCYNJV-BkYPx-ts.js} +1 -1
- package/dist/client/index.html +2 -2
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/diffo/SKILL.md +3 -3
- package/dist/client/assets/architecture-7GRP2DOG-BXYREMFQ.js +0 -1
- package/dist/client/assets/channel-CtvNwuQX.js +0 -1
- package/dist/client/assets/eventmodeling-NTZA5JFV-C7ouopD6.js +0 -1
- package/dist/client/assets/flowDiagram-KWPJA3E3-C2o4wCpi.js +0 -1
- package/dist/client/assets/index-BEG1ZxrX.css +0 -1
- package/dist/client/assets/index-BwDjlzOZ.js +0 -91
- package/dist/client/assets/railroad-abnf-HS5TGJTU-DCBYLLLl.js +0 -1
- package/dist/client/assets/railroad-ebnf-LZEXJU2U-VWOalqH1.js +0 -1
- package/dist/client/assets/railroad-peg-WCYAUIDC-BTTWaHra.js +0 -1
- package/dist/client/assets/swimlanesDiagram-TC7HE7FX-q8VF72XO.js +0 -8
package/dist/cli.mjs
CHANGED
|
@@ -73,11 +73,25 @@ function detectSessionPid() {
|
|
|
73
73
|
//#endregion
|
|
74
74
|
//#region src/shared/review.ts
|
|
75
75
|
const THREAD_INTENTS = ["fix", "question"];
|
|
76
|
+
function parseSuggestedReply(value) {
|
|
77
|
+
if (typeof value !== "string") return void 0;
|
|
78
|
+
const line = value.replace(/\s+/g, " ").trim();
|
|
79
|
+
return line ? line.slice(0, 120) : void 0;
|
|
80
|
+
}
|
|
76
81
|
/** The thread was started by the agent (its opening message is the agent's).
|
|
77
82
|
* Drives rendering — avatar, head label — and never changes. */
|
|
78
83
|
function startedByAgent(thread) {
|
|
79
84
|
return thread.messages[0]?.author === "agent";
|
|
80
85
|
}
|
|
86
|
+
/** The agent has this message: it replied after it, or a delivery carried it.
|
|
87
|
+
* Decides what an edit is — a draft fixed in place, or a change of mind the
|
|
88
|
+
* agent has to be told about. */
|
|
89
|
+
function seenByAgent(thread, index) {
|
|
90
|
+
const message = thread.messages[index];
|
|
91
|
+
if (!message) return false;
|
|
92
|
+
if (thread.messages.slice(index + 1).some((m) => m.author === "agent")) return true;
|
|
93
|
+
return thread.deliveredThrough !== void 0 && Date.parse(message.at) <= Date.parse(thread.deliveredThrough);
|
|
94
|
+
}
|
|
81
95
|
/** Agent-started with nothing of the reviewer's in it. Only these are excluded
|
|
82
96
|
* from the reviewer's counts, Send, and the finish flush — the moment the
|
|
83
97
|
* reviewer writes a reply into an agent thread, it carries their feedback and
|
|
@@ -274,20 +288,38 @@ const ACK_NEXT_STEP = {
|
|
|
274
288
|
/**
|
|
275
289
|
* The guide doctrine — the one agent comment that orients a cold reader.
|
|
276
290
|
* Stated once and interpolated into every surface that teaches it (the skill,
|
|
277
|
-
* `help agent`, the open-time nudge below), the same way
|
|
278
|
-
* poll rules aligned: the surfaces phrase it at different
|
|
279
|
-
* invariants cannot drift apart.
|
|
291
|
+
* `help agent`, `help guide`, the open-time nudge below), the same way
|
|
292
|
+
* POLL_STANCE keeps the poll rules aligned: the surfaces phrase it at different
|
|
293
|
+
* lengths, but these invariants cannot drift apart.
|
|
294
|
+
*
|
|
295
|
+
* It is a map, not a tour, written for a reader who never saw the session:
|
|
296
|
+
* the problem comes before the mechanism, and the diagram draws what happens,
|
|
297
|
+
* not which code names which — a change to text alone has no call graph worth
|
|
298
|
+
* drawing. Every other tool infers a summary by reading the diff back; the session that wrote the change still holds what no diff shows
|
|
299
|
+
* — the invariant it had to respect, the seam it chose, the alternative it
|
|
300
|
+
* rejected, what it left alone on purpose — and that is what the reviewer
|
|
301
|
+
* cannot get anywhere else. Reading order is not on the list: layers own it.
|
|
280
302
|
*/
|
|
281
303
|
const GUIDE = {
|
|
282
304
|
/** When one is warranted — and that silence is a valid outcome. */
|
|
283
305
|
when: "multi-file, structural, or subtle — skip when the diff explains itself",
|
|
284
|
-
/** What it contains. */
|
|
285
|
-
what: "
|
|
306
|
+
/** What it contains: ingredients, not sections — use the ones this change needs. */
|
|
307
|
+
what: "a map of the change, not a tour of it: the problem it solves and what it changes, in a sentence or two a reader who never saw your session follows; the flow it changes, from what sets it off to what someone sees, as a small ```mermaid diagram when a picture beats words — functions where code carries it, people and screens when they are the flow, never a file list or which code references which; what is worth checking as they read — an invariant the change must keep, a judgment call it made, a shortcoming it knows about — said in plain words, never behind a label of your own; and what is safe to skip",
|
|
286
308
|
/** The line it must not cross. */
|
|
287
309
|
stance: "Orient reading, never pre-review: no verdicts, nothing is \"fine\"",
|
|
310
|
+
/** What layers took over: the guide never orders the read. */
|
|
311
|
+
order: "no reading order and no file list — layers and the Files tab own those; a change that reads better in order gets a layers suggestion, not a numbered guide",
|
|
312
|
+
/** How much: one screen, and only the parts that apply. */
|
|
313
|
+
budget: "about a hundred words of prose plus the diagram — one screen; use only the parts this change needs, in whatever form fits",
|
|
314
|
+
/** The diagram convention: two tags, and a plain node means anything existing. */
|
|
315
|
+
legend: "in the diagram tag new code `:::new` and changed code `:::changed`, declared by the two classDef lines below; a plain node is anything existing the change now leans on",
|
|
288
316
|
/** Staleness: the guide is a thread, so updates land under it. */
|
|
289
317
|
update: "reply to your own guide thread with a short update"
|
|
290
318
|
};
|
|
319
|
+
/** The two classDef lines the legend refers to, verbatim — pasted at the foot
|
|
320
|
+
* of the diagram. Strokes only, in hexes both renderers honour and both themes
|
|
321
|
+
* read; fills stay the renderer's own, so the diagram still follows the theme. */
|
|
322
|
+
const GUIDE_CLASSDEFS = ["classDef new stroke:#2e9e4f,stroke-width:2px", "classDef changed stroke:#d99a00,stroke-width:2px"];
|
|
291
323
|
/**
|
|
292
324
|
* The layers doctrine — the agent's reading plan for a changeset with an order
|
|
293
325
|
* worth explaining. Same single-source rule as GUIDE: the skill, `help agent`,
|
|
@@ -302,7 +334,13 @@ const GUIDE = {
|
|
|
302
334
|
*/
|
|
303
335
|
const LAYERS = {
|
|
304
336
|
/** What one is. */
|
|
305
|
-
what: "one step of the change — a coherent unit you would explain in one breath — with the files that belong to it and a
|
|
337
|
+
what: "one step of the change — a coherent unit you would explain in one breath — with the files that belong to it and a short summary",
|
|
338
|
+
/** What the summary is for — a goal, not a template: the model writes it
|
|
339
|
+
* however this step reads best. The files sit right under it, so retelling
|
|
340
|
+
* them spends the reviewer's attention twice. */
|
|
341
|
+
summary: "say what this layer is about and give the reviewer enough context to review it — the part the files below cannot show on their own. Write it however fits the step, keep it short, and don't retell the diff",
|
|
342
|
+
/** Diagrams when they help: a picture of the step usually beats a paragraph. */
|
|
343
|
+
diagram: "when the step has a shape — a flow, a decision, a before/after — a small ```mermaid diagram usually says it better than prose; tag nodes as the guide does (`:::new`, `:::changed`, plain for anything existing) with the same two classDef lines",
|
|
306
344
|
/** How to order them. */
|
|
307
345
|
order: "the order you would explain it, not the order you wrote it — the file that explains the rest first, mechanical consequences last; for a feature, follow the request from entry point to effect; for a refactor, contract first, then consumers",
|
|
308
346
|
/** When to raise the flag at open — and that not raising it is the common case. */
|
|
@@ -329,7 +367,7 @@ const LAYERS = {
|
|
|
329
367
|
*/
|
|
330
368
|
function guideNudge(hasGuide) {
|
|
331
369
|
if (hasGuide) return null;
|
|
332
|
-
return `share the URL above with the user right now, as a message line, before anything else. Then, while they open it, orient them if this changeset needs it (${GUIDE.when}): post a guide — one comment on the whole changeset:
|
|
370
|
+
return `share the URL above with the user right now, as a message line, before anything else. Then, while they open it, orient them if this changeset needs it (${GUIDE.when}): post a guide — one comment on the whole changeset: \`${CLI_COMMANDS.guide}\` (no file, so it anchors to the changeset; it appears live at the top of their review). It is ${GUIDE.what}. Not in it: ${GUIDE.order}. How much: ${GUIDE.budget}. ${GUIDE.stance}. \`diffo help guide\` has the diagram legend and one example.`;
|
|
333
371
|
}
|
|
334
372
|
/**
|
|
335
373
|
* Printed by `diffo` (open) next to the guide nudge while the review has neither
|
|
@@ -340,7 +378,7 @@ function guideNudge(hasGuide) {
|
|
|
340
378
|
*/
|
|
341
379
|
function layersNudge(review) {
|
|
342
380
|
if (review.layers || review.layersSuggested) return null;
|
|
343
|
-
return `if this changeset reads better in order — ${LAYERS.suggest} — flag it: \`${CLI_COMMANDS.layersSuggest}\`, and offer it in your handoff ("say layers and I'll outline it"). Post the outline only when asked: \`${CLI_COMMANDS.layers}\` — each layer ${LAYERS.what}; ${LAYERS.order}. ${LAYERS.stance}. \`diffo help layers\` has the shape.`;
|
|
381
|
+
return `if this changeset reads better in order — ${LAYERS.suggest} — flag it: \`${CLI_COMMANDS.layersSuggest}\`, and offer it in your handoff ("say layers and I'll outline it"). Post the outline only when asked: \`${CLI_COMMANDS.layers}\` — each layer ${LAYERS.what}; ${LAYERS.summary}; ${LAYERS.diagram}; ${LAYERS.order}. ${LAYERS.stance}. \`diffo help layers\` has the shape.`;
|
|
344
382
|
}
|
|
345
383
|
/**
|
|
346
384
|
* Printed to stderr when a poll takes the review over and the review already
|
|
@@ -567,8 +605,9 @@ function threadBlock(thread, index) {
|
|
|
567
605
|
`id: ${thread.id}`,
|
|
568
606
|
...anchorNote(thread),
|
|
569
607
|
...snapshotBlock(thread),
|
|
608
|
+
...thread.rewound ? ["EDITED: the reviewer rewrote the message marked (edited) and withdrew everything after it, your earlier replies included. Answer the edited version. Code you changed for the withdrawn replies is still in the tree: keep it or revert it as the new message implies, and say which."] : [],
|
|
570
609
|
"Messages:",
|
|
571
|
-
...thread.messages.map((m) => `- ${m.author}: ${m.text.replace(/\n/g, "\n ")}`)
|
|
610
|
+
...thread.messages.map((m) => `- ${m.author}${m.editedAt ? " (edited)" : ""}: ${m.text.replace(/\n/g, "\n ")}`)
|
|
572
611
|
].join("\n");
|
|
573
612
|
}
|
|
574
613
|
function buildThreadPrompt(thread, ctx) {
|
|
@@ -611,11 +650,15 @@ function buildLayersRequestPrompt(ctx, existing) {
|
|
|
611
650
|
const refresh = existing !== null && existing.length > 0;
|
|
612
651
|
const titles = refresh ? existing.map((l) => `"${l.title}"`).join(", ") : "";
|
|
613
652
|
return [
|
|
614
|
-
refresh ? `The reviewer asked you to refresh the layers in \`${ctx.repo.name}\` (branch \`${ctx.repo.branch}\`): the code moved since you outlined it, and files outside the outline have been gathering under "Since your review". Re-post the whole list as the change stands now.` : `The reviewer asked for layers in \`${ctx.repo.name}\` (branch \`${ctx.repo.branch}\`): outline the changeset as steps to read in order. The Layers tab reads "
|
|
653
|
+
refresh ? `The reviewer asked you to refresh the layers in \`${ctx.repo.name}\` (branch \`${ctx.repo.branch}\`): the code moved since you outlined it, and files outside the outline have been gathering under "Since your review". Re-post the whole list as the change stands now.` : `The reviewer asked for layers in \`${ctx.repo.name}\` (branch \`${ctx.repo.branch}\`): outline the changeset as steps to read in order. The Layers tab reads "Outlining…" until you post.`,
|
|
615
654
|
"",
|
|
616
655
|
...ctx.changeset ? [specLine(ctx.changeset), ""] : [],
|
|
617
656
|
...refresh ? [`The outline they have: ${titles}. Keep a title that still fits its step, rename or drop the ones that don't — ${LAYERS.replace}.`, ""] : [],
|
|
618
|
-
`Each layer is ${LAYERS.what}.
|
|
657
|
+
`Each layer is ${LAYERS.what}. Summary: ${LAYERS.summary}. Diagram: ${LAYERS.diagram}:`,
|
|
658
|
+
"",
|
|
659
|
+
...GUIDE_CLASSDEFS.map((line) => ` ${line}`),
|
|
660
|
+
"",
|
|
661
|
+
`Order: ${LAYERS.order}. ${LAYERS.mechanical}. ${LAYERS.stance}. Files are whole files, by path relative to the repo root; list every file of the changeset somewhere, or the leftovers land in "Since your review".`,
|
|
619
662
|
"",
|
|
620
663
|
`Shape: ${LAYERS.shape}`,
|
|
621
664
|
"",
|
|
@@ -630,7 +673,7 @@ function buildClearedPrompt(ctx) {
|
|
|
630
673
|
`The reviewer cleared the review in \`${ctx.repo.name}\` (branch \`${ctx.repo.branch}\`): the previous round landed, and its threads and guide are gone. What the reviewer sees now is a fresh round.`,
|
|
631
674
|
"",
|
|
632
675
|
...ctx.changeset ? [specLine(ctx.changeset), ""] : [],
|
|
633
|
-
`There is no feedback to act on. But the fresh round has no guide — if this changeset needs one (${GUIDE.when}): post it — one comment on the whole changeset:
|
|
676
|
+
`There is no feedback to act on. But the fresh round has no guide — if this changeset needs one (${GUIDE.when}): post it — one comment on the whole changeset: \`${CLI_COMMANDS.guide}\` (no file, so it anchors to the changeset). It is ${GUIDE.what}. Not in it: ${GUIDE.order}. How much: ${GUIDE.budget}. ${GUIDE.stance}. \`diffo help guide\` has the diagram legend and one example.`,
|
|
634
677
|
"",
|
|
635
678
|
`Then run \`${CLI_COMMANDS.poll}\` again to keep listening (${POLL_STANCE}).`,
|
|
636
679
|
""
|
|
@@ -681,8 +724,8 @@ function buildFinishPrompt(threads, ctx, coverage) {
|
|
|
681
724
|
}
|
|
682
725
|
//#endregion
|
|
683
726
|
//#region src/shared/layers.ts
|
|
684
|
-
/** A summary is
|
|
685
|
-
*
|
|
727
|
+
/** A summary is short by doctrine; this is the backstop against an essay, the
|
|
728
|
+
* same cap a closing note gets. */
|
|
686
729
|
const SUMMARY_CAP = 4e3;
|
|
687
730
|
/** A note is one line by contract — anything past the first newline is dropped,
|
|
688
731
|
* and the line itself is capped so the file header stays a header. */
|
|
@@ -849,6 +892,7 @@ For the agent (the AI that wrote the change):
|
|
|
849
892
|
or --stdin posts the list, replacing the last one)
|
|
850
893
|
end Detach from the review politely
|
|
851
894
|
help agent The agent's whole protocol on one page
|
|
895
|
+
help guide The guide comment — the map of the change — with an example
|
|
852
896
|
|
|
853
897
|
Options (for opening the review):
|
|
854
898
|
--base <branch> Review everything since forking from <branch>
|
|
@@ -890,9 +934,12 @@ The loop:
|
|
|
890
934
|
2. Guide — post one only when the changeset needs orientation:
|
|
891
935
|
${GUIDE.when}.
|
|
892
936
|
Right after sharing the URL, while the reviewer opens the page, post ONE
|
|
893
|
-
comment on the whole changeset (\`diffo comment -m "…"\`, no file)
|
|
894
|
-
|
|
937
|
+
comment on the whole changeset (\`diffo comment -m "…"\`, no file). It is
|
|
938
|
+
${GUIDE.what}.
|
|
939
|
+
Not in it: ${GUIDE.order}.
|
|
940
|
+
How much: ${GUIDE.budget}.
|
|
895
941
|
${GUIDE.stance}.
|
|
942
|
+
Legend: ${GUIDE.legend} — \`diffo help guide\` has them, and one example.
|
|
896
943
|
It lands live at the top of their review — never hold the URL back for it.
|
|
897
944
|
If the changeset later shifts under the guide, ${GUIDE.update}.
|
|
898
945
|
3. Layers — offer them when the changeset reads better in order:
|
|
@@ -902,6 +949,7 @@ The loop:
|
|
|
902
949
|
outline only when the reviewer asks — in chat, or through the poll as a
|
|
903
950
|
\`"kind": "layers"\` payload: \`diffo layers --json '<Layer[]>'\`
|
|
904
951
|
(or pipe it to \`diffo layers --stdin\`). Each layer is ${LAYERS.what}.
|
|
952
|
+
Summary: ${LAYERS.summary}. Diagram: ${LAYERS.diagram}.
|
|
905
953
|
Order: ${LAYERS.order}. ${LAYERS.mechanical}. ${LAYERS.stance}.
|
|
906
954
|
${LAYERS.replace}. \`diffo help layers\` has the shape.
|
|
907
955
|
4. Listen: run \`${CLI_COMMANDS.firstPoll}\` — it blocks until the reviewer
|
|
@@ -922,6 +970,11 @@ The loop:
|
|
|
922
970
|
plain reply before the next poll.
|
|
923
971
|
7. Comment (sparingly): \`diffo comment [<file>] [--line <n>] -m "<text>"\`
|
|
924
972
|
starts a thread in your voice — a concern, or context that helps the read.
|
|
973
|
+
When a reply or comment of yours ends in a decision that is the
|
|
974
|
+
reviewer's ("want me to extract this?"), add \`--suggest-reply "<one
|
|
975
|
+
line>"\` — it shows as ghost text in their reply box, taken with Tab.
|
|
976
|
+
Offer the answer you expect, not a menu; skip it on messages that only
|
|
977
|
+
report.
|
|
925
978
|
8. Poll again only when the whole batch is handled — a new poll tells the
|
|
926
979
|
reviewer you are done with the previous one.
|
|
927
980
|
9. Detach: run \`diffo end\` when the review is over or the user moves on.
|
|
@@ -934,6 +987,54 @@ Rules:
|
|
|
934
987
|
a poll says it took the review over, or returns status "superseded", say so
|
|
935
988
|
to the user: their feedback moved with it.
|
|
936
989
|
- Resolving a thread is the reviewer's call, never yours.`,
|
|
990
|
+
guide: `diffo help guide — the map an agent posts on the whole changeset
|
|
991
|
+
|
|
992
|
+
Posted with \`diffo comment -m "…"\` (no file, so it anchors to the changeset)
|
|
993
|
+
right after sharing the URL, while the reviewer opens the page — and only
|
|
994
|
+
when the changeset needs orientation: ${GUIDE.when}.
|
|
995
|
+
|
|
996
|
+
It is ${GUIDE.what}.
|
|
997
|
+
Not in it: ${GUIDE.order}.
|
|
998
|
+
How much: ${GUIDE.budget}.
|
|
999
|
+
${GUIDE.stance}.
|
|
1000
|
+
Legend: ${GUIDE.legend}:
|
|
1001
|
+
|
|
1002
|
+
${GUIDE_CLASSDEFS[0]}
|
|
1003
|
+
${GUIDE_CLASSDEFS[1]}
|
|
1004
|
+
|
|
1005
|
+
A file named as \`path\` or \`path:line\` — in backticks, or in a diagram
|
|
1006
|
+
node — becomes a jump into the review, so the map is navigation too.
|
|
1007
|
+
If the changeset later shifts under the guide, ${GUIDE.update}.
|
|
1008
|
+
|
|
1009
|
+
Say each part in plain words a reader meets cold — "worth checking as you
|
|
1010
|
+
read", "safe to skip" — never a label of your own like "Hold:" that they
|
|
1011
|
+
would have to decode.
|
|
1012
|
+
|
|
1013
|
+
One shape that works — not THE shape: a rename needs no diagram, a one-file
|
|
1014
|
+
change needs no checks, and headings are optional.
|
|
1015
|
+
|
|
1016
|
+
A huge tool result could overflow the model's context. This caps it at the
|
|
1017
|
+
model boundary and adds \`read_tool_result\` so the model can page the
|
|
1018
|
+
rest — nothing is stored, the text is recomputed on read.
|
|
1019
|
+
|
|
1020
|
+
\`\`\`mermaid
|
|
1021
|
+
flowchart LR
|
|
1022
|
+
E[tool.execute] --> W[withToolResultCap]:::new
|
|
1023
|
+
W -->|over budget: slice + notice| P[provider]
|
|
1024
|
+
M[read_tool_result]:::new --> R[resolveFullToolResult]:::new
|
|
1025
|
+
R --> S[SDK steps · raw output]
|
|
1026
|
+
T[island-chat-transport.ts · prepareStep]:::changed --> W
|
|
1027
|
+
${GUIDE_CLASSDEFS[0]}
|
|
1028
|
+
${GUIDE_CLASSDEFS[1]}
|
|
1029
|
+
\`\`\`
|
|
1030
|
+
|
|
1031
|
+
Worth checking as you read: the capper and the reader must agree, byte for
|
|
1032
|
+
byte, on one string — \`canonicalToolText\` is the only definition both use.
|
|
1033
|
+
And the reader re-runs each tool's own \`toModelOutput\`, which assumes every
|
|
1034
|
+
converter is pure.
|
|
1035
|
+
|
|
1036
|
+
Safe to skip: four \`tool-*\` display components moved only because a prop
|
|
1037
|
+
went dead.`,
|
|
937
1038
|
poll: `diffo poll — wait for the reviewer's feedback
|
|
938
1039
|
|
|
939
1040
|
Usage: diffo poll [--title "<what the change is>"]
|
|
@@ -957,7 +1058,7 @@ Examples:
|
|
|
957
1058
|
diffo poll`,
|
|
958
1059
|
reply: `diffo reply — post a reply to a review thread
|
|
959
1060
|
|
|
960
|
-
Usage: diffo reply <threadId> --message "<text>"
|
|
1061
|
+
Usage: diffo reply <threadId> --message "<text>" [--suggest-reply "<one line>"]
|
|
961
1062
|
… | diffo reply <threadId> (long replies: pipe on stdin)
|
|
962
1063
|
|
|
963
1064
|
Thread ids arrive in poll payloads. Each run posts one message, so don't
|
|
@@ -966,6 +1067,11 @@ re-run a reply that succeeded.
|
|
|
966
1067
|
reply on this thread is promised. The reviewer keeps seeing the thread as
|
|
967
1068
|
"with the agent" until your next plain reply; going quiet instead marks it
|
|
968
1069
|
unanswered.
|
|
1070
|
+
--suggest-reply offers the reviewer their answer: one line that appears as
|
|
1071
|
+
ghost text in their reply box, taken with Tab, edited or ignored at will.
|
|
1072
|
+
Use it when your message ends in a decision that is theirs to make
|
|
1073
|
+
("want me to extract this?" → --suggest-reply "yes, extract it"); never
|
|
1074
|
+
on a message that only reports.
|
|
969
1075
|
Messages render GitHub-flavored markdown; a \`\`\`mermaid fence renders as a
|
|
970
1076
|
diagram in the review.
|
|
971
1077
|
|
|
@@ -975,7 +1081,7 @@ Example:
|
|
|
975
1081
|
diffo reply t-3 --message "fixed — the guard now covers the empty case"`,
|
|
976
1082
|
comment: `diffo comment — start a comment thread as the agent
|
|
977
1083
|
|
|
978
|
-
Usage: diffo comment [<file>] [--line <n>] --message "<text>"
|
|
1084
|
+
Usage: diffo comment [<file>] [--line <n>] --message "<text>" [--suggest-reply "<one line>"]
|
|
979
1085
|
… | diffo comment [<file>] [--line <n>] (long comments: pipe on stdin)
|
|
980
1086
|
|
|
981
1087
|
Anchors to a line (--line), a file, or — with no file — the whole changeset.
|
|
@@ -983,13 +1089,18 @@ A potential issue, or context that helps the reviewer read: either way it is
|
|
|
983
1089
|
one thread, labeled as yours, and it never counts as the reviewer's feedback
|
|
984
1090
|
until they reply into it — then it is theirs to send — or they resolve it.
|
|
985
1091
|
Spend these sparingly — an agent that annotates everything gets skimmed.
|
|
1092
|
+
--suggest-reply offers the reviewer their answer: one line that appears as
|
|
1093
|
+
ghost text in their reply box, taken with Tab, edited or ignored at will.
|
|
1094
|
+
Use it when the comment proposes something and the call is theirs; the
|
|
1095
|
+
reply they take hands the thread to you like any other.
|
|
986
1096
|
Messages render GitHub-flavored markdown; a \`\`\`mermaid fence renders as a
|
|
987
1097
|
diagram in the review.
|
|
988
1098
|
|
|
989
1099
|
Output: {"ok":true,"threadId":"t-1","next_step":"…"}
|
|
990
1100
|
|
|
991
|
-
|
|
992
|
-
diffo comment src/auth.ts --line 42 --message "this branch is unreachable"
|
|
1101
|
+
Examples:
|
|
1102
|
+
diffo comment src/auth.ts --line 42 --message "this branch is unreachable"
|
|
1103
|
+
diffo comment src/auth.ts --line 42 -m "I can fold these two guards into one. Want that?" --suggest-reply "yes, fold them"`,
|
|
993
1104
|
layers: `diffo layers — outline the changeset as steps to read in order
|
|
994
1105
|
|
|
995
1106
|
Usage: diffo layers --suggest ["<why, in one line>"] at open: this read benefits from layers
|
|
@@ -997,6 +1108,12 @@ Usage: diffo layers --suggest ["<why, in one line>"] at open: this read benefi
|
|
|
997
1108
|
… | diffo layers --stdin the same payload, piped
|
|
998
1109
|
|
|
999
1110
|
Each layer is ${LAYERS.what}.
|
|
1111
|
+
Summary: ${LAYERS.summary}.
|
|
1112
|
+
Diagram: ${LAYERS.diagram}:
|
|
1113
|
+
|
|
1114
|
+
${GUIDE_CLASSDEFS[0]}
|
|
1115
|
+
${GUIDE_CLASSDEFS[1]}
|
|
1116
|
+
|
|
1000
1117
|
Order: ${LAYERS.order}.
|
|
1001
1118
|
${LAYERS.mechanical}.
|
|
1002
1119
|
${LAYERS.stance}.
|
|
@@ -1012,7 +1129,7 @@ Output: {"ok":true,"layers":4,"next_step":"…"}
|
|
|
1012
1129
|
|
|
1013
1130
|
Examples:
|
|
1014
1131
|
diffo layers --suggest "the parser change explains the rest"
|
|
1015
|
-
diffo layers --json '[{"title":"Parser contract","summary":"
|
|
1132
|
+
diffo layers --json '[{"title":"Parser contract","summary":"The contract the rest of the change leans on: bad input now comes back as null, not an exception.","files":["src/parse.ts"]},{"title":"Callers adapted","kind":"mechanical","summary":"Call sites following the new return type.","files":["src/cli.ts","src/api.ts"]}]'`,
|
|
1016
1133
|
end: `diffo end — detach from the review politely
|
|
1017
1134
|
|
|
1018
1135
|
Usage: diffo end
|
|
@@ -1114,7 +1231,7 @@ function parseHelp(rest) {
|
|
|
1114
1231
|
message: "help takes at most one command"
|
|
1115
1232
|
};
|
|
1116
1233
|
if (topic === void 0 || topic === "help") return { kind: "help" };
|
|
1117
|
-
if (topic !== "agent" && !VERBS.has(topic)) return unknownCommand(topic);
|
|
1234
|
+
if (topic !== "agent" && topic !== "guide" && !VERBS.has(topic)) return unknownCommand(topic);
|
|
1118
1235
|
return {
|
|
1119
1236
|
kind: "help",
|
|
1120
1237
|
topic
|
|
@@ -1301,6 +1418,7 @@ function parseVerb(verb, rest) {
|
|
|
1301
1418
|
line: { type: "string" },
|
|
1302
1419
|
title: { type: "string" },
|
|
1303
1420
|
more: { type: "boolean" },
|
|
1421
|
+
"suggest-reply": { type: "string" },
|
|
1304
1422
|
json: { type: "boolean" },
|
|
1305
1423
|
help: {
|
|
1306
1424
|
type: "boolean",
|
|
@@ -1313,6 +1431,7 @@ function parseVerb(verb, rest) {
|
|
|
1313
1431
|
message: parsed.message
|
|
1314
1432
|
};
|
|
1315
1433
|
const { values, positionals } = parsed.value;
|
|
1434
|
+
const suggestReply = values["suggest-reply"];
|
|
1316
1435
|
if (values.help) return {
|
|
1317
1436
|
kind: "help",
|
|
1318
1437
|
topic: verb
|
|
@@ -1325,6 +1444,20 @@ function parseVerb(verb, rest) {
|
|
|
1325
1444
|
kind: "error",
|
|
1326
1445
|
message: `'${verb}' takes no --more`
|
|
1327
1446
|
};
|
|
1447
|
+
if (suggestReply !== void 0) {
|
|
1448
|
+
if (verb !== "reply" && verb !== "comment") return {
|
|
1449
|
+
kind: "error",
|
|
1450
|
+
message: `'${verb}' takes no --suggest-reply`
|
|
1451
|
+
};
|
|
1452
|
+
if (!suggestReply.trim()) return {
|
|
1453
|
+
kind: "error",
|
|
1454
|
+
message: "--suggest-reply needs the reply you are offering"
|
|
1455
|
+
};
|
|
1456
|
+
if (suggestReply.includes("\n")) return {
|
|
1457
|
+
kind: "error",
|
|
1458
|
+
message: "--suggest-reply is one line — the reviewer completes it"
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1328
1461
|
if (values.title !== void 0 && verb !== "poll") return {
|
|
1329
1462
|
kind: "error",
|
|
1330
1463
|
message: `'${verb}' takes no --title`
|
|
@@ -1373,7 +1506,8 @@ function parseVerb(verb, rest) {
|
|
|
1373
1506
|
kind: "reply",
|
|
1374
1507
|
threadId,
|
|
1375
1508
|
message: values.message ?? null,
|
|
1376
|
-
more: values.more === true
|
|
1509
|
+
more: values.more === true,
|
|
1510
|
+
suggestReply: suggestReply?.trim() ?? null
|
|
1377
1511
|
};
|
|
1378
1512
|
}
|
|
1379
1513
|
const file = positionals[0] ?? null;
|
|
@@ -1397,7 +1531,8 @@ function parseVerb(verb, rest) {
|
|
|
1397
1531
|
kind: "comment",
|
|
1398
1532
|
file,
|
|
1399
1533
|
line,
|
|
1400
|
-
message: values.message ?? null
|
|
1534
|
+
message: values.message ?? null,
|
|
1535
|
+
suggestReply: suggestReply?.trim() ?? null
|
|
1401
1536
|
};
|
|
1402
1537
|
}
|
|
1403
1538
|
//#endregion
|
|
@@ -2799,7 +2934,7 @@ var ReviewStore = class {
|
|
|
2799
2934
|
this.listeners.add(listener);
|
|
2800
2935
|
return () => this.listeners.delete(listener);
|
|
2801
2936
|
}
|
|
2802
|
-
createThread(anchor, text, capture, intent, author = "reviewer") {
|
|
2937
|
+
createThread(anchor, text, capture, intent, author = "reviewer", suggestedReply) {
|
|
2803
2938
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2804
2939
|
return this.insert({
|
|
2805
2940
|
id: randomUUID(),
|
|
@@ -2813,7 +2948,8 @@ var ReviewStore = class {
|
|
|
2813
2948
|
id: randomUUID(),
|
|
2814
2949
|
author,
|
|
2815
2950
|
text,
|
|
2816
|
-
at: now
|
|
2951
|
+
at: now,
|
|
2952
|
+
...author === "agent" && suggestedReply ? { suggestedReply } : {}
|
|
2817
2953
|
}],
|
|
2818
2954
|
createdAt: now,
|
|
2819
2955
|
updatedAt: now
|
|
@@ -2861,13 +2997,14 @@ var ReviewStore = class {
|
|
|
2861
2997
|
* the reviewer raced in since — appended at the end, it would render as an
|
|
2862
2998
|
* answer to a comment it never saw, and the raced comment would stop reading
|
|
2863
2999
|
* as "waiting on the agent" even though it still is. */
|
|
2864
|
-
addMessage(threadId, author, text, withheld = false, seenThroughMs, followUp = false) {
|
|
3000
|
+
addMessage(threadId, author, text, withheld = false, seenThroughMs, followUp = false, suggestedReply) {
|
|
2865
3001
|
return this.update(threadId, ({ unanswered: _answered, ...thread }) => {
|
|
2866
3002
|
const message = {
|
|
2867
3003
|
id: randomUUID(),
|
|
2868
3004
|
author,
|
|
2869
3005
|
text,
|
|
2870
|
-
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
3006
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3007
|
+
...author === "agent" && suggestedReply ? { suggestedReply } : {}
|
|
2871
3008
|
};
|
|
2872
3009
|
const raced = author === "agent" && seenThroughMs !== void 0 ? thread.messages.findIndex((m) => m.author === "reviewer" && Date.parse(m.at) > seenThroughMs) : -1;
|
|
2873
3010
|
const { awaitingFollowUp: _promised, ...settled } = thread;
|
|
@@ -2883,18 +3020,56 @@ var ReviewStore = class {
|
|
|
2883
3020
|
};
|
|
2884
3021
|
});
|
|
2885
3022
|
}
|
|
3023
|
+
/**
|
|
3024
|
+
* The reviewer rewrites one of their own messages. Words the agent never saw
|
|
3025
|
+
* are a draft fixed in place. Words it did see are a change of mind, like
|
|
3026
|
+
* editing a chat message: the message is stamped edited and everything after
|
|
3027
|
+
* it is cut — the agent's answers to the old words included — so the thread
|
|
3028
|
+
* is the agent's to answer again. Cut, not kept: the old branch is not worth
|
|
3029
|
+
* a second history on the card. `withheld` holds a seen rewrite back from the
|
|
3030
|
+
* agent, the same as a withheld reply.
|
|
3031
|
+
*/
|
|
3032
|
+
editMessage(threadId, messageId, text, withheld = false) {
|
|
3033
|
+
const current = this.state.threads.find((t) => t.id === threadId);
|
|
3034
|
+
const index = current?.messages.findIndex((m) => m.id === messageId) ?? -1;
|
|
3035
|
+
if (!current || index === -1) return null;
|
|
3036
|
+
const seen = seenByAgent(current, index);
|
|
3037
|
+
const cut = current.messages.slice(index + 1);
|
|
3038
|
+
const cutAnswer = cut.some((m) => m.author === "agent");
|
|
3039
|
+
return this.update(threadId, ({ unanswered, awaitingFollowUp, ...thread }) => {
|
|
3040
|
+
const before = thread.messages[index];
|
|
3041
|
+
const edited = {
|
|
3042
|
+
...before,
|
|
3043
|
+
text,
|
|
3044
|
+
...seen || before.editedAt ? { editedAt: (/* @__PURE__ */ new Date()).toISOString() } : {}
|
|
3045
|
+
};
|
|
3046
|
+
return {
|
|
3047
|
+
...thread,
|
|
3048
|
+
...unanswered && !seen ? { unanswered } : {},
|
|
3049
|
+
...awaitingFollowUp && !cutAnswer ? { awaitingFollowUp } : {},
|
|
3050
|
+
...cutAnswer && thread.state === "addressed" ? { state: "sent" } : {},
|
|
3051
|
+
...seen && cut.length > 0 ? { rewound: true } : {},
|
|
3052
|
+
...seen && withheld ? { withheld: true } : {},
|
|
3053
|
+
messages: [...thread.messages.slice(0, index), edited]
|
|
3054
|
+
};
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
2886
3057
|
/** A delivery just handed these threads to the agent — remember through when,
|
|
2887
|
-
* so replies and the typing indicator can tell delivered words from raced ones.
|
|
3058
|
+
* so replies and the typing indicator can tell delivered words from raced ones.
|
|
3059
|
+
* The delivery also carried the word that a rewind withdrew the old replies. */
|
|
2888
3060
|
markDelivered(threadIds) {
|
|
2889
3061
|
const ids = new Set(threadIds);
|
|
2890
3062
|
if (ids.size === 0) return;
|
|
2891
3063
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
2892
3064
|
this.state = {
|
|
2893
3065
|
...this.state,
|
|
2894
|
-
threads: this.state.threads.map((t) => ids.has(t.id) ? {
|
|
3066
|
+
threads: this.state.threads.map(({ rewound, ...t }) => ids.has(t.id) ? {
|
|
2895
3067
|
...t,
|
|
2896
3068
|
deliveredThrough: at
|
|
2897
|
-
} :
|
|
3069
|
+
} : {
|
|
3070
|
+
...t,
|
|
3071
|
+
...rewound ? { rewound } : {}
|
|
3072
|
+
})
|
|
2898
3073
|
};
|
|
2899
3074
|
this.commit();
|
|
2900
3075
|
}
|
|
@@ -3348,7 +3523,9 @@ function normalizeThread(value, now) {
|
|
|
3348
3523
|
author: msg.author,
|
|
3349
3524
|
text: msg.text,
|
|
3350
3525
|
at: typeof msg.at === "string" ? msg.at : now,
|
|
3351
|
-
...typeof msg.durationMs === "number" ? { durationMs: msg.durationMs } : {}
|
|
3526
|
+
...typeof msg.durationMs === "number" ? { durationMs: msg.durationMs } : {},
|
|
3527
|
+
...msg.author === "agent" && typeof msg.suggestedReply === "string" && msg.suggestedReply ? { suggestedReply: msg.suggestedReply } : {},
|
|
3528
|
+
...typeof msg.editedAt === "string" ? { editedAt: msg.editedAt } : {}
|
|
3352
3529
|
});
|
|
3353
3530
|
}
|
|
3354
3531
|
return {
|
|
@@ -3363,6 +3540,7 @@ function normalizeThread(value, now) {
|
|
|
3363
3540
|
...t.awaitingFollowUp === true && t.unanswered !== true ? { awaitingFollowUp: true } : {},
|
|
3364
3541
|
...t.closingNote === true ? { closingNote: true } : {},
|
|
3365
3542
|
...typeof t.sentAt === "string" ? { sentAt: t.sentAt } : {},
|
|
3543
|
+
...t.rewound === true ? { rewound: true } : {},
|
|
3366
3544
|
messages,
|
|
3367
3545
|
createdAt: typeof t.createdAt === "string" ? t.createdAt : now,
|
|
3368
3546
|
updatedAt: typeof t.updatedAt === "string" ? t.updatedAt : now
|
|
@@ -3912,7 +4090,7 @@ function createApp(ctx, store, review, queue) {
|
|
|
3912
4090
|
const line = typeof body?.line === "number" && Number.isInteger(body.line) && body.line >= 1 ? body.line : null;
|
|
3913
4091
|
const anchor = agentAnchor(file, line);
|
|
3914
4092
|
const capture = store ? captureAnchor(store.get(), anchor) : null;
|
|
3915
|
-
return c.json(review.createThread(anchor, text, capture, void 0, "agent"));
|
|
4093
|
+
return c.json(review.createThread(anchor, text, capture, void 0, "agent", parseSuggestedReply(body?.suggestedReply)));
|
|
3916
4094
|
}
|
|
3917
4095
|
const anchor = parseAnchor(body?.anchor);
|
|
3918
4096
|
if (!anchor) return c.json({ error: "need {anchor, text}" }, 400);
|
|
@@ -3957,7 +4135,7 @@ function createApp(ctx, store, review, queue) {
|
|
|
3957
4135
|
const more = body?.more === true;
|
|
3958
4136
|
const waitedMs = queue?.agentReplied(id, more) ?? null;
|
|
3959
4137
|
const seenThroughMs = waitedMs === null ? void 0 : Date.now() - waitedMs;
|
|
3960
|
-
const thread = review.addMessage(id, "agent", text, false, seenThroughMs, more);
|
|
4138
|
+
const thread = review.addMessage(id, "agent", text, false, seenThroughMs, more, parseSuggestedReply(body?.suggestedReply));
|
|
3961
4139
|
if (!thread) return c.json({ error: "no such thread" }, 404);
|
|
3962
4140
|
if (waitedMs !== null) review.annotateAgentReplies([thread.id], waitedMs);
|
|
3963
4141
|
return c.json({
|
|
@@ -3981,6 +4159,34 @@ function createApp(ctx, store, review, queue) {
|
|
|
3981
4159
|
presence: queue?.presence() ?? "waiting"
|
|
3982
4160
|
});
|
|
3983
4161
|
});
|
|
4162
|
+
app.patch("/api/review/threads/:id/messages/:messageId", async (c) => {
|
|
4163
|
+
if (!review) return c.json({ error: "review unavailable" }, 503);
|
|
4164
|
+
const body = await c.req.json().catch(() => null);
|
|
4165
|
+
const text = typeof body?.text === "string" ? body.text.trim() : "";
|
|
4166
|
+
if (!text) return c.json({ error: "need {text}" }, 400);
|
|
4167
|
+
const id = c.req.param("id");
|
|
4168
|
+
const messageId = c.req.param("messageId");
|
|
4169
|
+
const current = review.get().threads.find((t) => t.id === id);
|
|
4170
|
+
const index = current?.messages.findIndex((m) => m.id === messageId) ?? -1;
|
|
4171
|
+
if (!current || index === -1) return c.json({ error: "no such message" }, 404);
|
|
4172
|
+
if (current.messages[index].author !== "reviewer") return c.json({ error: "only your own messages can be edited" }, 400);
|
|
4173
|
+
if (current.state === "resolved") return c.json({ error: "reopen the thread to edit it" }, 409);
|
|
4174
|
+
if (queue?.deliveredThreadIds().includes(id)) return c.json({ error: "the agent is answering this thread — edit after it replies" }, 409);
|
|
4175
|
+
const seen = seenByAgent(current, index);
|
|
4176
|
+
const deliver = body?.deliver !== false;
|
|
4177
|
+
let thread = review.editMessage(id, messageId, text, !deliver);
|
|
4178
|
+
let delivered = false;
|
|
4179
|
+
if (seen && deliver && (thread.state === "sent" || thread.state === "addressed")) {
|
|
4180
|
+
delivered = deliverThreads([id]);
|
|
4181
|
+
review.clearWithheld([id]);
|
|
4182
|
+
thread = review.get().threads.find((t) => t.id === id) ?? thread;
|
|
4183
|
+
}
|
|
4184
|
+
return c.json({
|
|
4185
|
+
thread,
|
|
4186
|
+
delivered,
|
|
4187
|
+
presence: queue?.presence() ?? "waiting"
|
|
4188
|
+
});
|
|
4189
|
+
});
|
|
3984
4190
|
app.post("/api/review/threads/:id/state", async (c) => {
|
|
3985
4191
|
if (!review) return c.json({ error: "review unavailable" }, 503);
|
|
3986
4192
|
const state = (await c.req.json().catch(() => null))?.state;
|
|
@@ -5344,7 +5550,8 @@ if (command.kind === "reply") {
|
|
|
5344
5550
|
const { status, body } = await postJson(await requireServer(), `/api/review/threads/${encodeURIComponent(command.threadId)}/messages`, {
|
|
5345
5551
|
author: "agent",
|
|
5346
5552
|
text: message,
|
|
5347
|
-
...command.more ? { more: true } : {}
|
|
5553
|
+
...command.more ? { more: true } : {},
|
|
5554
|
+
...command.suggestReply ? { suggestedReply: command.suggestReply } : {}
|
|
5348
5555
|
});
|
|
5349
5556
|
if (status === 404) fail(`no thread with id '${command.threadId}'`);
|
|
5350
5557
|
if (status !== 200) fail(`reply failed (${status})`);
|
|
@@ -5365,7 +5572,8 @@ if (command.kind === "comment") {
|
|
|
5365
5572
|
author: "agent",
|
|
5366
5573
|
file: command.file,
|
|
5367
5574
|
line: command.line,
|
|
5368
|
-
text: message
|
|
5575
|
+
text: message,
|
|
5576
|
+
...command.suggestReply ? { suggestedReply: command.suggestReply } : {}
|
|
5369
5577
|
});
|
|
5370
5578
|
if (status !== 200) fail(`comment failed (${status})`);
|
|
5371
5579
|
console.log(JSON.stringify({
|
package/dist/client/assets/{abnfDiagram-O67JEVCF-b6l_FGHi.js → abnfDiagram-O67JEVCF-vILxBZcB.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-D2BrkwRE.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-YTrwJCk8.js";import"./chunk-O7XYJQB3-DG64mbS_.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-JFHsSpPU.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-CCFlYVgY.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-BWYGCgdo.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-FOHPRMQF-DzxwRta7.js";import{M as e}from"./mermaid-parser.core-D2BrkwRE.js";export{e as createArchitectureServices};
|