otacon 0.1.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/LICENSE +21 -0
- package/README.md +88 -0
- package/dist/cli/client.js +188 -0
- package/dist/cli/client.js.map +1 -0
- package/dist/cli/commands/answer.js +63 -0
- package/dist/cli/commands/answer.js.map +1 -0
- package/dist/cli/commands/ask.js +117 -0
- package/dist/cli/commands/ask.js.map +1 -0
- package/dist/cli/commands/clean.js +48 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/doctor.js +86 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/expose.js +104 -0
- package/dist/cli/commands/expose.js.map +1 -0
- package/dist/cli/commands/implement-done.js +53 -0
- package/dist/cli/commands/implement-done.js.map +1 -0
- package/dist/cli/commands/install.js +113 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/open.js +37 -0
- package/dist/cli/commands/open.js.map +1 -0
- package/dist/cli/commands/progress.js +45 -0
- package/dist/cli/commands/progress.js.map +1 -0
- package/dist/cli/commands/start.js +66 -0
- package/dist/cli/commands/start.js.map +1 -0
- package/dist/cli/commands/status.js +44 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/submit.js +64 -0
- package/dist/cli/commands/submit.js.map +1 -0
- package/dist/cli/commands/wait.js +66 -0
- package/dist/cli/commands/wait.js.map +1 -0
- package/dist/cli/install/assets.js +285 -0
- package/dist/cli/install/assets.js.map +1 -0
- package/dist/cli/install/locations.js +92 -0
- package/dist/cli/install/locations.js.map +1 -0
- package/dist/cli/install/tailscale.js +39 -0
- package/dist/cli/install/tailscale.js.map +1 -0
- package/dist/cli/main.js +73 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.js +39 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/session.js +77 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/daemon/activity.js +56 -0
- package/dist/daemon/activity.js.map +1 -0
- package/dist/daemon/anchor.js +143 -0
- package/dist/daemon/anchor.js.map +1 -0
- package/dist/daemon/app.js +1081 -0
- package/dist/daemon/app.js.map +1 -0
- package/dist/daemon/approve.js +71 -0
- package/dist/daemon/approve.js.map +1 -0
- package/dist/daemon/desktop-notify.js +69 -0
- package/dist/daemon/desktop-notify.js.map +1 -0
- package/dist/daemon/diff.js +187 -0
- package/dist/daemon/diff.js.map +1 -0
- package/dist/daemon/linter/index.js +19 -0
- package/dist/daemon/linter/index.js.map +1 -0
- package/dist/daemon/linter/parse.js +350 -0
- package/dist/daemon/linter/parse.js.map +1 -0
- package/dist/daemon/linter/rules.js +359 -0
- package/dist/daemon/linter/rules.js.map +1 -0
- package/dist/daemon/main.js +48 -0
- package/dist/daemon/main.js.map +1 -0
- package/dist/daemon/notify.js +23 -0
- package/dist/daemon/notify.js.map +1 -0
- package/dist/daemon/presence.js +37 -0
- package/dist/daemon/presence.js.map +1 -0
- package/dist/daemon/queue.js +160 -0
- package/dist/daemon/queue.js.map +1 -0
- package/dist/daemon/store.js +393 -0
- package/dist/daemon/store.js.map +1 -0
- package/dist/daemon/threads.js +153 -0
- package/dist/daemon/threads.js.map +1 -0
- package/dist/daemon/transcript.js +89 -0
- package/dist/daemon/transcript.js.map +1 -0
- package/dist/daemon/ui.js +175 -0
- package/dist/daemon/ui.js.map +1 -0
- package/dist/shared/config.js +93 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/gwt.js +69 -0
- package/dist/shared/gwt.js.map +1 -0
- package/dist/shared/paths.js +67 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/question-spec.js +44 -0
- package/dist/shared/question-spec.js.map +1 -0
- package/dist/shared/types.js +35 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/shared/version.js +5 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/ui/assets/arc-HhPfdCPZ.js +1 -0
- package/dist/ui/assets/architecture-7EHR7CIX-BPLblcyi.js +1 -0
- package/dist/ui/assets/architectureDiagram-3BPJPVTR-D2PIxGOb.js +36 -0
- package/dist/ui/assets/array-BifhSqXX.js +1 -0
- package/dist/ui/assets/blockDiagram-GPEHLZMM-DQ3Dn17h.js +132 -0
- package/dist/ui/assets/c4Diagram-AAUBKEIU-DxITrQgS.js +10 -0
- package/dist/ui/assets/channel-ipcU8ZNI.js +1 -0
- package/dist/ui/assets/chunk-2J33WTMH-Du1JoPx5.js +1 -0
- package/dist/ui/assets/chunk-3OPIFGDE-Dn7x2Yqf.js +62 -0
- package/dist/ui/assets/chunk-4BX2VUAB-DVnrE-4n.js +1 -0
- package/dist/ui/assets/chunk-55IACEB6-BAhFAimA.js +1 -0
- package/dist/ui/assets/chunk-5ZQYHXKU-0hEZptem.js +2 -0
- package/dist/ui/assets/chunk-727SXJPM-C1FN_cI3.js +206 -0
- package/dist/ui/assets/chunk-AQP2D5EJ-A656OBd4.js +231 -0
- package/dist/ui/assets/chunk-BSJP7CBP-D8oMbjm8.js +1 -0
- package/dist/ui/assets/chunk-CSCIHK7Q-DjIL8GLi.js +122 -0
- package/dist/ui/assets/chunk-FMBD7UC4-Otblfqvz.js +15 -0
- package/dist/ui/assets/chunk-KSCS5N6A-BOjTvm3H.js +10 -0
- package/dist/ui/assets/chunk-L5ZTLDWV-CaTLaw6L.js +1 -0
- package/dist/ui/assets/chunk-LZXEDZCA-Dq5p7qrD.js +2 -0
- package/dist/ui/assets/chunk-ND2GUHAM-jZ_NNnWi.js +1 -0
- package/dist/ui/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/ui/assets/chunk-NZK2D7GU-U_7l_sCh.js +1 -0
- package/dist/ui/assets/chunk-O5CBEL6O-MewqqNB7.js +70 -0
- package/dist/ui/assets/chunk-QZHKN3VN-DzGPH44B.js +1 -0
- package/dist/ui/assets/chunk-WU5MYG2G-DyEIVjoo.js +1 -0
- package/dist/ui/assets/chunk-XPW4576I-D5ArxNEF.js +32 -0
- package/dist/ui/assets/classDiagram-4FO5ZUOK-Byg2Hl9D.js +1 -0
- package/dist/ui/assets/classDiagram-v2-Q7XG4LA2-Byg2Hl9D.js +1 -0
- package/dist/ui/assets/cose-bilkent-S5V4N54A-PFXzf7WV.js +1 -0
- package/dist/ui/assets/cytoscape.esm-h6BdjjI9.js +321 -0
- package/dist/ui/assets/dagre-BM42HDAG-xrCfjZuZ.js +4 -0
- package/dist/ui/assets/dagre-Bx709z4p.js +1 -0
- package/dist/ui/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/ui/assets/diagram-2AECGRRQ-BFf-cyKY.js +43 -0
- package/dist/ui/assets/diagram-5GNKFQAL-kNPV4NfV.js +10 -0
- package/dist/ui/assets/diagram-KO2AKTUF-ByC1IUwG.js +3 -0
- package/dist/ui/assets/diagram-LMA3HP47-DZIJMPK0.js +24 -0
- package/dist/ui/assets/diagram-OG6HWLK6-CSDED9A-.js +24 -0
- package/dist/ui/assets/dist-YwjsDswi.js +1 -0
- package/dist/ui/assets/erDiagram-TEJ5UH35-yuzvjE6J.js +85 -0
- package/dist/ui/assets/eventmodeling-FCH6USID-CZR4eNG-.js +1 -0
- package/dist/ui/assets/flowDiagram-I6XJVG4X-ApPtVyYM.js +162 -0
- package/dist/ui/assets/ganttDiagram-6RSMTGT7-BeMLXtAr.js +292 -0
- package/dist/ui/assets/gitGraph-WXDBUCRP-JmTTBa7j.js +1 -0
- package/dist/ui/assets/gitGraphDiagram-PVQCEYII-Cjjnjs71.js +106 -0
- package/dist/ui/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/ui/assets/index-BFQVRcSI.js +11 -0
- package/dist/ui/assets/index-Bj_kTrwP.css +1 -0
- package/dist/ui/assets/info-J43DQDTF-8vZ3gome.js +1 -0
- package/dist/ui/assets/infoDiagram-5YYISTIA-CnMk1cA-.js +2 -0
- package/dist/ui/assets/init-D6jRqBbL.js +1 -0
- package/dist/ui/assets/ishikawaDiagram-YF4QCWOH-Bl8z6huD.js +70 -0
- package/dist/ui/assets/journeyDiagram-JHISSGLW-DYIVfMpS.js +139 -0
- package/dist/ui/assets/kanban-definition-UN3LZRKU-BnR0ZzOz.js +89 -0
- package/dist/ui/assets/katex-Vhh-h91d.js +257 -0
- package/dist/ui/assets/line-DcBdQit6.js +1 -0
- package/dist/ui/assets/linear-HKjRHFAO.js +1 -0
- package/dist/ui/assets/mermaid-parser.core-DkYXrPlA.js +4 -0
- package/dist/ui/assets/mermaid.core-BmkfCI3b.js +9 -0
- package/dist/ui/assets/mindmap-definition-RKZ34NQL-sIAd4nDi.js +96 -0
- package/dist/ui/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/ui/assets/otacon-DPXGiaVj.svg +11 -0
- package/dist/ui/assets/packet-YPE3B663-BxbxcfXN.js +1 -0
- package/dist/ui/assets/path-BWPyau1x.js +1 -0
- package/dist/ui/assets/pie-LRSECV5Y-BJxazjNs.js +1 -0
- package/dist/ui/assets/pieDiagram-4H26LBE5-BiOhc9GR.js +30 -0
- package/dist/ui/assets/plan-view-CH6NzUDb.js +74 -0
- package/dist/ui/assets/purify.es-CDvCXckx.js +3 -0
- package/dist/ui/assets/quadrantDiagram-W4KKPZXB-CVyHbWgo.js +7 -0
- package/dist/ui/assets/radar-GUYGQ44K-D9ohbnbV.js +1 -0
- package/dist/ui/assets/requirementDiagram-4Y6WPE33-Ba24_hqc.js +84 -0
- package/dist/ui/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/ui/assets/sankeyDiagram-5OEKKPKP-CxD4wiPL.js +40 -0
- package/dist/ui/assets/sequenceDiagram-3UESZ5HK-7qA7lD61.js +162 -0
- package/dist/ui/assets/src-IM8AE8MK.js +1 -0
- package/dist/ui/assets/stateDiagram-AJRCARHV-DNElRCuH.js +1 -0
- package/dist/ui/assets/stateDiagram-v2-BHNVJYJU-D6qTYpY3.js +1 -0
- package/dist/ui/assets/timeline-definition-PNZ67QCA-ChYC4Grd.js +120 -0
- package/dist/ui/assets/treeView-BLDUP644-Il0KnMi_.js +1 -0
- package/dist/ui/assets/treemap-LRROVOQU-CIiKcdRo.js +1 -0
- package/dist/ui/assets/vennDiagram-CIIHVFJN-Ulhkum9i.js +34 -0
- package/dist/ui/assets/wardley-L42UT6IY-BNd4ljz7.js +1 -0
- package/dist/ui/assets/wardleyDiagram-YWT4CUSO-BicXxh84.js +78 -0
- package/dist/ui/assets/xychartDiagram-2RQKCTM6-Duf-m_th.js +7 -0
- package/dist/ui/index.html +20 -0
- package/package.json +66 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Per-session thread persistence: .otacon/<id>/threads.json holds every
|
|
2
|
+
// comment and question thread so the review UI's rail can render the whole
|
|
3
|
+
// conversation on any load (DESIGN.md §9, §12) — the event queue drains, so
|
|
4
|
+
// it cannot be the rail's source. Same storage posture as the rest of the
|
|
5
|
+
// daemon: atomic writes, corrupt files quarantined and rebuilt empty, never
|
|
6
|
+
// fatal (DECISIONS.md "Threads: one threads.json per session").
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { relocateAnchor } from "./anchor.js";
|
|
9
|
+
import { segmentPlan } from "./diff.js";
|
|
10
|
+
import { quarantineCorruptFile, readJsonOr, stringify, writeFileAtomic } from "./store.js";
|
|
11
|
+
function isAnchor(raw) {
|
|
12
|
+
if (raw === null)
|
|
13
|
+
return true;
|
|
14
|
+
const anchor = raw;
|
|
15
|
+
return typeof anchor === "object" && typeof anchor.section === "string";
|
|
16
|
+
}
|
|
17
|
+
function isThread(raw) {
|
|
18
|
+
const thread = raw;
|
|
19
|
+
if (typeof thread !== "object" || thread === null)
|
|
20
|
+
return false;
|
|
21
|
+
if (typeof thread.id !== "string" || typeof thread.body !== "string")
|
|
22
|
+
return false;
|
|
23
|
+
if (typeof thread.createdAt !== "string" || !isAnchor(thread.anchor))
|
|
24
|
+
return false;
|
|
25
|
+
if (thread.anchorState !== undefined && thread.anchorState !== "orphaned")
|
|
26
|
+
return false;
|
|
27
|
+
if (thread.kind === "comment") {
|
|
28
|
+
if (typeof thread.batch !== "string")
|
|
29
|
+
return false;
|
|
30
|
+
const { resolution } = thread;
|
|
31
|
+
if (resolution === undefined)
|
|
32
|
+
return true;
|
|
33
|
+
return (typeof resolution === "object" &&
|
|
34
|
+
resolution !== null &&
|
|
35
|
+
typeof resolution.body === "string" &&
|
|
36
|
+
typeof resolution.revision === "number" &&
|
|
37
|
+
typeof resolution.resolvedAt === "string");
|
|
38
|
+
}
|
|
39
|
+
if (thread.kind === "question") {
|
|
40
|
+
if (thread.replyTo !== undefined && typeof thread.replyTo !== "string")
|
|
41
|
+
return false;
|
|
42
|
+
const { answer } = thread;
|
|
43
|
+
if (answer === undefined)
|
|
44
|
+
return true;
|
|
45
|
+
return (typeof answer === "object" &&
|
|
46
|
+
answer !== null &&
|
|
47
|
+
typeof answer.body === "string" &&
|
|
48
|
+
typeof answer.answeredAt === "string");
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Every element is validated, not just the envelope: a JSON-valid file with a
|
|
53
|
+
// corrupt element would otherwise flow a non-Thread into answerQuestion (500)
|
|
54
|
+
// and the rail (render crash) — exactly the "never fatal" failures quarantine
|
|
55
|
+
// exists to absorb.
|
|
56
|
+
function parseThreads(raw) {
|
|
57
|
+
const file = raw;
|
|
58
|
+
const valid = typeof file === "object" &&
|
|
59
|
+
file !== null &&
|
|
60
|
+
file.version === 1 &&
|
|
61
|
+
Array.isArray(file.threads) &&
|
|
62
|
+
file.threads.every(isThread);
|
|
63
|
+
return valid ? file : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** All threads, oldest first. Missing file = no threads yet; corrupt = quarantined, []. */
|
|
66
|
+
export function readThreads(path) {
|
|
67
|
+
if (!existsSync(path))
|
|
68
|
+
return [];
|
|
69
|
+
const file = parseThreads(readJsonOr(path));
|
|
70
|
+
if (!file) {
|
|
71
|
+
quarantineCorruptFile(path, "threads file");
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
return file.threads;
|
|
75
|
+
}
|
|
76
|
+
/** Durably append new threads (a comment batch's items, or one question). */
|
|
77
|
+
export function appendThreads(path, threads) {
|
|
78
|
+
const file = { version: 1, threads: [...readThreads(path), ...threads] };
|
|
79
|
+
writeFileAtomic(path, stringify(file));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Record the agent's answer on a question thread; returns the updated thread,
|
|
83
|
+
* or undefined when no question with that id exists (comment ids included —
|
|
84
|
+
* comments are resolved via resubmit, never answered). Re-answering
|
|
85
|
+
* overwrites: at-least-once delivery means the agent may legitimately answer
|
|
86
|
+
* the same question twice, and the newer text is the better one.
|
|
87
|
+
*/
|
|
88
|
+
export function answerQuestion(path, id, body) {
|
|
89
|
+
const threads = readThreads(path);
|
|
90
|
+
const thread = threads.find((t) => t.id === id && t.kind === "question");
|
|
91
|
+
if (!thread)
|
|
92
|
+
return undefined;
|
|
93
|
+
const answer = { body, answeredAt: new Date().toISOString() };
|
|
94
|
+
thread.answer = answer;
|
|
95
|
+
writeFileAtomic(path, stringify({ version: 1, threads }));
|
|
96
|
+
return { ...thread, answer };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Apply an accepted revision to the threads file, in one read + one atomic
|
|
100
|
+
* write: record the resolution replies on their comment threads (lint L5 has
|
|
101
|
+
* already vouched for them; re-resolving overwrites — at-least-once), then
|
|
102
|
+
* re-locate every thread's anchor in the new plan (DESIGN.md §4) — lost or
|
|
103
|
+
* ambiguous quotes turn anchorState "orphaned", re-found ones recover.
|
|
104
|
+
* Returns the threads that changed, for the daemon's SSE `thread` upserts.
|
|
105
|
+
*/
|
|
106
|
+
export function applyRevisionToThreads(path, opts) {
|
|
107
|
+
const threads = readThreads(path);
|
|
108
|
+
if (threads.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const changed = new Map();
|
|
111
|
+
const resolvedAt = new Date().toISOString();
|
|
112
|
+
// Segment the new plan once for the whole pass (lazily — a rail of
|
|
113
|
+
// whole-plan threads never needs it), not per anchored thread.
|
|
114
|
+
let units;
|
|
115
|
+
for (const thread of threads) {
|
|
116
|
+
if (thread.kind === "comment" && opts.replies[thread.id] !== undefined) {
|
|
117
|
+
thread.resolution = {
|
|
118
|
+
body: opts.replies[thread.id],
|
|
119
|
+
revision: opts.revision,
|
|
120
|
+
resolvedAt,
|
|
121
|
+
};
|
|
122
|
+
changed.set(thread.id, thread);
|
|
123
|
+
}
|
|
124
|
+
if (thread.anchor === null)
|
|
125
|
+
continue; // whole-plan threads have no place to lose
|
|
126
|
+
const result = relocateAnchor(thread.anchor, opts.plan, (units ??= segmentPlan(opts.plan)));
|
|
127
|
+
if (result.state === "orphaned") {
|
|
128
|
+
if (thread.anchorState !== "orphaned") {
|
|
129
|
+
thread.anchorState = "orphaned";
|
|
130
|
+
changed.set(thread.id, thread);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const moved = JSON.stringify(result.anchor) !== JSON.stringify(thread.anchor);
|
|
135
|
+
if (thread.anchorState === "orphaned" || moved) {
|
|
136
|
+
delete thread.anchorState;
|
|
137
|
+
thread.anchor = result.anchor;
|
|
138
|
+
changed.set(thread.id, thread);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (changed.size > 0) {
|
|
143
|
+
writeFileAtomic(path, stringify({ version: 1, threads }));
|
|
144
|
+
}
|
|
145
|
+
return [...changed.values()];
|
|
146
|
+
}
|
|
147
|
+
/** Comment threads with their resolved state — the daemon's L5 context input. */
|
|
148
|
+
export function commentThreadStates(path) {
|
|
149
|
+
return readThreads(path)
|
|
150
|
+
.filter((t) => t.kind === "comment")
|
|
151
|
+
.map((t) => ({ id: t.id, resolved: t.resolution !== undefined }));
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=threads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threads.js","sourceRoot":"","sources":["../../src/daemon/threads.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Per-session grill transcript: .otacon/<id>/transcript.json holds every
|
|
2
|
+
// agent question and the user's answer (DESIGN.md §8) — distinct from
|
|
3
|
+
// user-question threads (threads.json), because the Interview panel and the
|
|
4
|
+
// threads rail are different surfaces with different lifecycles, and the
|
|
5
|
+
// transcript ships with the approved artifact while threads stay review
|
|
6
|
+
// exhaust. Same storage posture as the rest of the daemon: atomic writes,
|
|
7
|
+
// corrupt files quarantined and rebuilt empty, never fatal.
|
|
8
|
+
import { existsSync } from "node:fs";
|
|
9
|
+
import { quarantineCorruptFile, readJsonOr, stringify, writeFileAtomic } from "./store.js";
|
|
10
|
+
function isAnswer(raw) {
|
|
11
|
+
const answer = raw;
|
|
12
|
+
if (typeof answer !== "object" || answer === null)
|
|
13
|
+
return false;
|
|
14
|
+
if (typeof answer.answeredAt !== "string")
|
|
15
|
+
return false;
|
|
16
|
+
if (answer.choice !== undefined && typeof answer.choice !== "string")
|
|
17
|
+
return false;
|
|
18
|
+
if (answer.choices !== undefined &&
|
|
19
|
+
!(Array.isArray(answer.choices) && answer.choices.every((c) => typeof c === "string"))) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return answer.text === undefined || typeof answer.text === "string";
|
|
23
|
+
}
|
|
24
|
+
// Every entry is validated, not just the envelope (same argument as
|
|
25
|
+
// threads.ts): a JSON-valid file with a corrupt entry would otherwise flow a
|
|
26
|
+
// non-entry into the answers endpoint (500) and the Interview panel.
|
|
27
|
+
function isEntry(raw) {
|
|
28
|
+
const entry = raw;
|
|
29
|
+
if (typeof entry !== "object" || entry === null)
|
|
30
|
+
return false;
|
|
31
|
+
if (typeof entry.id !== "string" || typeof entry.question !== "string")
|
|
32
|
+
return false;
|
|
33
|
+
if (typeof entry.askedAt !== "string")
|
|
34
|
+
return false;
|
|
35
|
+
if (entry.options !== undefined &&
|
|
36
|
+
!(Array.isArray(entry.options) && entry.options.every((o) => typeof o === "string"))) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (entry.recommend !== undefined && typeof entry.recommend !== "string")
|
|
40
|
+
return false;
|
|
41
|
+
if (entry.multi !== undefined && typeof entry.multi !== "boolean")
|
|
42
|
+
return false;
|
|
43
|
+
return entry.answer === undefined || isAnswer(entry.answer);
|
|
44
|
+
}
|
|
45
|
+
function parseTranscript(raw) {
|
|
46
|
+
const file = raw;
|
|
47
|
+
const valid = typeof file === "object" &&
|
|
48
|
+
file !== null &&
|
|
49
|
+
file.version === 1 &&
|
|
50
|
+
Array.isArray(file.entries) &&
|
|
51
|
+
file.entries.every(isEntry);
|
|
52
|
+
return valid ? file : undefined;
|
|
53
|
+
}
|
|
54
|
+
/** All entries, oldest first. Missing file = no transcript yet; corrupt = quarantined, []. */
|
|
55
|
+
export function readTranscript(path) {
|
|
56
|
+
if (!existsSync(path))
|
|
57
|
+
return [];
|
|
58
|
+
const file = parseTranscript(readJsonOr(path));
|
|
59
|
+
if (!file) {
|
|
60
|
+
quarantineCorruptFile(path, "grill transcript");
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return file.entries;
|
|
64
|
+
}
|
|
65
|
+
/** Durably append one asked question (otacon ask). */
|
|
66
|
+
export function appendEntry(path, entry) {
|
|
67
|
+
appendEntries(path, [entry]);
|
|
68
|
+
}
|
|
69
|
+
/** Durably append a batch of asked questions in one write (otacon ask --batch). */
|
|
70
|
+
export function appendEntries(path, entries) {
|
|
71
|
+
const file = { version: 1, entries: [...readTranscript(path), ...entries] };
|
|
72
|
+
writeFileAtomic(path, stringify(file));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Record the user's answer on a transcript entry; returns the updated entry,
|
|
76
|
+
* or undefined when no question with that id exists. Re-answering overwrites
|
|
77
|
+
* — at-least-once delivery makes a duplicate answer legitimate, and the newer
|
|
78
|
+
* one wins (same posture as threads.ts answerQuestion).
|
|
79
|
+
*/
|
|
80
|
+
export function answerEntry(path, id, answer) {
|
|
81
|
+
const entries = readTranscript(path);
|
|
82
|
+
const entry = entries.find((e) => e.id === id);
|
|
83
|
+
if (!entry)
|
|
84
|
+
return undefined;
|
|
85
|
+
entry.answer = answer;
|
|
86
|
+
writeFileAtomic(path, stringify({ version: 1, entries }));
|
|
87
|
+
return { ...entry };
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=transcript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/daemon/transcript.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// The daemon's browser-facing surface: the built SPA (static files from
|
|
2
|
+
// dist/ui) and the SSE streams the UI watches (DESIGN.md §6, §10). Static
|
|
3
|
+
// serving is hand-rolled because @hono/node-server's serveStatic resolves
|
|
4
|
+
// roots against process.cwd(), which is meaningless for a daemon spawned from
|
|
5
|
+
// any repo (DECISIONS.md "Daemon serves the built SPA from dist/ui").
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const HEARTBEAT_MS = 25_000;
|
|
10
|
+
/** Vite's output is flat hashed names — no slashes means no traversal. */
|
|
11
|
+
const ASSET_NAME = /^[A-Za-z0-9_.-]+$/;
|
|
12
|
+
const CONTENT_TYPES = {
|
|
13
|
+
".html": "text/html; charset=utf-8",
|
|
14
|
+
".js": "text/javascript; charset=utf-8",
|
|
15
|
+
".css": "text/css; charset=utf-8",
|
|
16
|
+
".map": "application/json",
|
|
17
|
+
".json": "application/json",
|
|
18
|
+
".svg": "image/svg+xml",
|
|
19
|
+
".png": "image/png",
|
|
20
|
+
".ico": "image/x-icon",
|
|
21
|
+
".txt": "text/plain; charset=utf-8",
|
|
22
|
+
".woff2": "font/woff2",
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* dist/daemon/ui.js → ../ui = dist/ui; a source-tree run under bun falls back
|
|
26
|
+
* to <root>/dist/ui. The `assets/` guard is load-bearing: since ui/ moved under
|
|
27
|
+
* src/ui/ (commit fac1349), the `../ui/` candidate also matches src/ui/ in a
|
|
28
|
+
* source run — but that dir holds only the Vite *dev* template (a `/main.tsx`
|
|
29
|
+
* script tag the daemon can't serve), not a build. Requiring a sibling
|
|
30
|
+
* `assets/` dir disqualifies the source template across every layout (published
|
|
31
|
+
* package, repo dist/, source run) so only a real build is ever served.
|
|
32
|
+
*/
|
|
33
|
+
function resolveBuiltUiDir() {
|
|
34
|
+
for (const candidate of ["../ui/", "../../dist/ui/"]) {
|
|
35
|
+
const dir = fileURLToPath(new URL(candidate, import.meta.url));
|
|
36
|
+
if (isBuiltUiDir(dir))
|
|
37
|
+
return dir;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* True only for a real Vite build: both `index.html` and a sibling `assets/`
|
|
43
|
+
* dir. The source `src/ui/` holds the dev template (index.html, no assets/), so
|
|
44
|
+
* this predicate is what keeps a source run from serving it (see
|
|
45
|
+
* resolveBuiltUiDir). Exported for the regression test.
|
|
46
|
+
*/
|
|
47
|
+
export function isBuiltUiDir(dir) {
|
|
48
|
+
return existsSync(join(dir, "index.html")) && existsSync(join(dir, "assets"));
|
|
49
|
+
}
|
|
50
|
+
const encoder = new TextEncoder();
|
|
51
|
+
function frame(event, data) {
|
|
52
|
+
return encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One SSE response: a `snapshot` frame immediately (so subscribers never race
|
|
56
|
+
* a separate fetch), then every published UiEvent — optionally filtered to one
|
|
57
|
+
* session — plus a comment heartbeat that keeps idle proxies from severing the
|
|
58
|
+
* stream. No event ids: a reconnect re-syncs from the fresh snapshot.
|
|
59
|
+
*/
|
|
60
|
+
function sse(c, deps, snapshot, onlySession) {
|
|
61
|
+
// Materializes node-server's lazy AbortController so client disconnects
|
|
62
|
+
// abort the signal (same trick as the events long-poll in app.ts).
|
|
63
|
+
const signal = c.req.raw.signal;
|
|
64
|
+
let cleanup;
|
|
65
|
+
const dispose = () => {
|
|
66
|
+
cleanup?.();
|
|
67
|
+
cleanup = undefined;
|
|
68
|
+
};
|
|
69
|
+
const stream = new ReadableStream({
|
|
70
|
+
start(controller) {
|
|
71
|
+
// Built before any resource exists: if snapshot() throws (a session read
|
|
72
|
+
// gone wrong), the error propagates out of sse() into app.onError with
|
|
73
|
+
// no subscription or heartbeat left behind to leak.
|
|
74
|
+
const snapshotFrame = frame("snapshot", snapshot());
|
|
75
|
+
const close = () => {
|
|
76
|
+
dispose();
|
|
77
|
+
try {
|
|
78
|
+
controller.close();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// already closed or cancelled
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const send = (chunk) => {
|
|
85
|
+
try {
|
|
86
|
+
controller.enqueue(chunk);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
close(); // reader vanished mid-enqueue
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const unsubscribe = deps.notifier.subscribe((event) => {
|
|
93
|
+
if (onlySession !== undefined && event.session !== onlySession)
|
|
94
|
+
return;
|
|
95
|
+
send(frame(event.type, event.data));
|
|
96
|
+
// `removed` is terminal for a single-session stream: the session left
|
|
97
|
+
// the registry, so nothing can ever be published for it again. End the
|
|
98
|
+
// response server-side too — otherwise a client that ignores the frame
|
|
99
|
+
// pins this connection (subscription + heartbeat) forever. The index
|
|
100
|
+
// stream stays open: other sessions keep flowing.
|
|
101
|
+
if (onlySession !== undefined && event.type === "removed")
|
|
102
|
+
close();
|
|
103
|
+
});
|
|
104
|
+
const heartbeat = setInterval(() => send(encoder.encode(": hb\n\n")), deps.heartbeatMs ?? HEARTBEAT_MS);
|
|
105
|
+
heartbeat.unref?.(); // never holds the process open
|
|
106
|
+
const onAbort = () => close();
|
|
107
|
+
cleanup = () => {
|
|
108
|
+
unsubscribe();
|
|
109
|
+
clearInterval(heartbeat);
|
|
110
|
+
signal.removeEventListener("abort", onAbort);
|
|
111
|
+
};
|
|
112
|
+
send(snapshotFrame);
|
|
113
|
+
if (signal.aborted)
|
|
114
|
+
close();
|
|
115
|
+
else
|
|
116
|
+
signal.addEventListener("abort", onAbort);
|
|
117
|
+
},
|
|
118
|
+
cancel() {
|
|
119
|
+
dispose();
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return new Response(stream, {
|
|
123
|
+
headers: {
|
|
124
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
125
|
+
"cache-control": "no-cache, no-transform",
|
|
126
|
+
connection: "keep-alive",
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function registerUiRoutes(app, deps) {
|
|
131
|
+
const uiDir = deps.uiDir === undefined ? resolveBuiltUiDir() : deps.uiDir;
|
|
132
|
+
// The SPA shell. /s/:id always answers 200 — a static shell cannot know
|
|
133
|
+
// session ids, so unknown sessions render a client-side not-found state.
|
|
134
|
+
const shell = (c) => {
|
|
135
|
+
if (uiDir == null) {
|
|
136
|
+
return c.text("otacon: web UI is not built — run `bun run build` (output: dist/ui)\n", 503);
|
|
137
|
+
}
|
|
138
|
+
return c.html(readFileSync(join(uiDir, "index.html"), "utf8"), 200, {
|
|
139
|
+
"cache-control": "no-cache",
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
app.get("/", shell);
|
|
143
|
+
app.get("/s/:id", shell);
|
|
144
|
+
app.get("/assets/*", (c) => {
|
|
145
|
+
const name = c.req.path.slice("/assets/".length);
|
|
146
|
+
const type = CONTENT_TYPES[name.slice(name.lastIndexOf("."))];
|
|
147
|
+
const path = uiDir != null && ASSET_NAME.test(name) && type !== undefined
|
|
148
|
+
? join(uiDir, "assets", name)
|
|
149
|
+
: undefined;
|
|
150
|
+
if (path === undefined || !existsSync(path))
|
|
151
|
+
return c.text("not found\n", 404);
|
|
152
|
+
// Vite asset names are content-hashed: immutable forever.
|
|
153
|
+
return new Response(new Uint8Array(readFileSync(path)), {
|
|
154
|
+
headers: { "content-type": type, "cache-control": "public, max-age=31536000, immutable" },
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
app.get("/api/stream", (c) => sse(c, deps, () => ({ sessions: deps.listSummaries() })));
|
|
158
|
+
app.get("/api/sessions/:id/stream", (c) => {
|
|
159
|
+
const id = c.req.param("id");
|
|
160
|
+
if (!deps.getSummary(id)) {
|
|
161
|
+
return c.json({ error: { code: "E_NOT_FOUND", message: `unknown session: ${id}` } }, 404);
|
|
162
|
+
}
|
|
163
|
+
// Threads, transcript, and the activity feed ride the snapshot so the rail,
|
|
164
|
+
// the Interview panel, and the activity log never race a separate fetch
|
|
165
|
+
// against `thread`/`grill`/`activity` frames (same argument as
|
|
166
|
+
// snapshot-first itself).
|
|
167
|
+
return sse(c, deps, () => ({
|
|
168
|
+
session: deps.getSummary(id),
|
|
169
|
+
threads: deps.getThreads(id),
|
|
170
|
+
transcript: deps.getTranscript(id),
|
|
171
|
+
activity: deps.getActivity(id),
|
|
172
|
+
}), id);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/daemon/ui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { globalConfigPath, repoConfigPath } from "./paths.js";
|
|
3
|
+
export const DEFAULT_CONFIG = {
|
|
4
|
+
budgets: {
|
|
5
|
+
summaryLines: 5,
|
|
6
|
+
contractLines: 12,
|
|
7
|
+
impactLines: 10,
|
|
8
|
+
decisionEntryLines: 3,
|
|
9
|
+
phaseGoalLines: 3,
|
|
10
|
+
phaseVerificationLines: 3,
|
|
11
|
+
gwtMaxScenarios: 6,
|
|
12
|
+
risksMaxItems: 5,
|
|
13
|
+
riskEntryLines: 2,
|
|
14
|
+
maxFencesPerReadSection: 1,
|
|
15
|
+
maxVisualsPerReadSection: 2,
|
|
16
|
+
detailsSoftCapLines: 80,
|
|
17
|
+
},
|
|
18
|
+
activity: {
|
|
19
|
+
cap: 20,
|
|
20
|
+
noteMaxChars: 200,
|
|
21
|
+
},
|
|
22
|
+
notifications: { desktop: true },
|
|
23
|
+
};
|
|
24
|
+
function readJsonFile(path) {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Overlay one config file's `<section>` (all positive-number keys); invalid
|
|
34
|
+
* values are ignored with a notice. Both `budgets` and `activity` are flat
|
|
35
|
+
* maps of positive numbers, so they share this merge.
|
|
36
|
+
*/
|
|
37
|
+
function mergeSection(base, raw, section, source) {
|
|
38
|
+
if (typeof raw !== "object" || raw === null)
|
|
39
|
+
return base;
|
|
40
|
+
const obj = raw[section];
|
|
41
|
+
if (typeof obj !== "object" || obj === null)
|
|
42
|
+
return base;
|
|
43
|
+
const merged = { ...base };
|
|
44
|
+
for (const key of Object.keys(base)) {
|
|
45
|
+
const value = obj[key];
|
|
46
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
47
|
+
merged[key] = value;
|
|
48
|
+
}
|
|
49
|
+
else if (value !== undefined) {
|
|
50
|
+
process.stderr.write(`otacon: ignoring invalid ${section}.${String(key)} in ${source}\n`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return merged;
|
|
54
|
+
}
|
|
55
|
+
/** Overlay one config file's notifications; a non-boolean is ignored with a notice. */
|
|
56
|
+
function mergeNotifications(base, raw, source) {
|
|
57
|
+
if (typeof raw !== "object" || raw === null)
|
|
58
|
+
return base;
|
|
59
|
+
const notifications = raw.notifications;
|
|
60
|
+
if (typeof notifications !== "object" || notifications === null)
|
|
61
|
+
return base;
|
|
62
|
+
const merged = { ...base };
|
|
63
|
+
for (const key of Object.keys(base)) {
|
|
64
|
+
const value = notifications[key];
|
|
65
|
+
if (typeof value === "boolean") {
|
|
66
|
+
merged[key] = value;
|
|
67
|
+
}
|
|
68
|
+
else if (value !== undefined) {
|
|
69
|
+
process.stderr.write(`otacon: ignoring invalid notifications.${key} in ${source}\n`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return merged;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* defaults ← $OTACON_HOME/config.json ← <repo>/otacon.config.json.
|
|
76
|
+
* Loaded fresh on every use so tuning takes effect immediately. Each file is
|
|
77
|
+
* overlaid section by section (budgets, activity, notifications).
|
|
78
|
+
*/
|
|
79
|
+
export function loadConfig(repoRoot) {
|
|
80
|
+
const overlay = (source, into) => {
|
|
81
|
+
const raw = readJsonFile(source);
|
|
82
|
+
return {
|
|
83
|
+
budgets: mergeSection(into.budgets, raw, "budgets", source),
|
|
84
|
+
activity: mergeSection(into.activity, raw, "activity", source),
|
|
85
|
+
notifications: mergeNotifications(into.notifications, raw, source),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
let config = overlay(globalConfigPath(), DEFAULT_CONFIG);
|
|
89
|
+
if (repoRoot)
|
|
90
|
+
config = overlay(repoConfigPath(repoRoot), config);
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/shared/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// The behavioral-assertion grammar (DESIGN.md §4, §9): a ```gwt fence inside a
|
|
2
|
+
// phase's Verification holds one or more Given/When/Then scenarios that double
|
|
3
|
+
// as the human's Test-Driven Review approve checklist. This tokenizer is the
|
|
4
|
+
// single source of truth for what a scenario *is*, shared by the daemon linter
|
|
5
|
+
// (shape + budget validation, rules.ts) and the UI scenario cards
|
|
6
|
+
// (scenario-card.tsx) so the two can never disagree about the grammar — unlike
|
|
7
|
+
// the line grammar, which is deliberately ported (DECISIONS.md), this construct
|
|
8
|
+
// is new, so one shared parser is the simpler, drift-proof choice. Purely
|
|
9
|
+
// structural: it tokenizes and flags well-formedness; the *verdicts* (empty,
|
|
10
|
+
// malformed, over budget) are the linter's (rules.ts).
|
|
11
|
+
// A step line: a leading keyword (case-insensitive) plus the rest as text.
|
|
12
|
+
// `And`/`But` continue whichever clause is open.
|
|
13
|
+
const STEP_RE = /^(given|when|then|and|but)\b\s*(.*)$/i;
|
|
14
|
+
const RANK = { given: 0, when: 1, then: 2 };
|
|
15
|
+
/** Tokenize one scenario's non-blank lines into a `GwtScenario`. */
|
|
16
|
+
function parseScenario(lines) {
|
|
17
|
+
const scenario = { given: [], when: [], then: [], valid: true };
|
|
18
|
+
let clause = null;
|
|
19
|
+
let maxRank = -1;
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
const match = STEP_RE.exec(line.trim());
|
|
22
|
+
if (!match) {
|
|
23
|
+
scenario.valid = false; // a line that isn't a Given/When/Then/And/But step
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const keyword = match[1].toLowerCase();
|
|
27
|
+
const text = match[2].trim();
|
|
28
|
+
if (keyword === "given" || keyword === "when" || keyword === "then") {
|
|
29
|
+
// Scenarios read top-down: a keyword may not regress (When then Given).
|
|
30
|
+
if (RANK[keyword] < maxRank)
|
|
31
|
+
scenario.valid = false;
|
|
32
|
+
maxRank = Math.max(maxRank, RANK[keyword]);
|
|
33
|
+
clause = keyword;
|
|
34
|
+
}
|
|
35
|
+
else if (clause === null) {
|
|
36
|
+
scenario.valid = false; // a dangling And/But with no clause to continue
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (clause)
|
|
40
|
+
scenario[clause].push(text);
|
|
41
|
+
}
|
|
42
|
+
if (scenario.given.length === 0 || scenario.when.length === 0 || scenario.then.length === 0) {
|
|
43
|
+
scenario.valid = false;
|
|
44
|
+
}
|
|
45
|
+
return scenario;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Parse a ```gwt fence body into scenarios. Scenarios are separated by one or
|
|
49
|
+
* more blank lines; empty groups (leading/trailing blanks) are dropped.
|
|
50
|
+
*/
|
|
51
|
+
export function parseGwt(body) {
|
|
52
|
+
const groups = [];
|
|
53
|
+
let current = [];
|
|
54
|
+
for (const line of body.split("\n")) {
|
|
55
|
+
if (line.trim() === "") {
|
|
56
|
+
if (current.length > 0) {
|
|
57
|
+
groups.push(current);
|
|
58
|
+
current = [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
current.push(line);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (current.length > 0)
|
|
66
|
+
groups.push(current);
|
|
67
|
+
return { scenarios: groups.map(parseScenario) };
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=gwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gwt.js","sourceRoot":"","sources":["../../src/shared/gwt.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,+EAA+E;AAC/E,kEAAkE;AAClE,+EAA+E;AAC/E,gFAAgF;AAChF,0EAA0E;AAC1E,6EAA6E;AAC7E,uDAAuD;AAoBvD,2EAA2E;AAC3E,iDAAiD;AACjD,MAAM,OAAO,GAAG,uCAAuC,CAAC;AAGxD,MAAM,IAAI,GAA2B,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAEpE,oEAAoE;AACpE,SAAS,aAAa,CAAC,KAAe;IACpC,MAAM,QAAQ,GAAgB,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC7E,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC;IACjB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,mDAAmD;YAC3E,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACpE,wEAAwE;YACxE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,OAAO;gBAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;YACpD,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3C,MAAM,GAAG,OAAO,CAAC;QACnB,CAAC;aAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,gDAAgD;YACxE,SAAS;QACX,CAAC;QACD,IAAI,MAAM;YAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5F,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;IACzB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,MAAM,GAAe,EAAE,CAAC;IAC9B,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACvB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrB,OAAO,GAAG,EAAE,CAAC;YACf,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;AAClD,CAAC"}
|