@markusylisiurunen/tau 0.3.60 → 0.3.62
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/core/config/schema.js.map +1 -1
- package/dist/core/diff_review/bridge.js +1 -0
- package/dist/core/diff_review/bridge.js.map +1 -1
- package/dist/core/diff_review/protocol.js +4 -2
- package/dist/core/diff_review/protocol.js.map +1 -1
- package/dist/core/diff_review/review_instructions.js +2 -1
- package/dist/core/diff_review/review_instructions.js.map +1 -1
- package/dist/core/diff_review/tool.js +1 -1
- package/dist/core/diff_review/tool.js.map +1 -1
- package/dist/core/session/compaction.js +8 -7
- package/dist/core/session/compaction.js.map +1 -1
- package/dist/core/static/tau_docs/configuration.md +1 -1
- package/dist/core/static/tau_docs/index.md +1 -0
- package/dist/core/static/tau_docs/manifest.json +1 -0
- package/dist/core/static/tau_docs/node-sdk.md +8 -0
- package/dist/core/static/tau_docs/sdk-diff-review.md +79 -0
- package/dist/core/static/tau_docs/session-protocol-methods.md +4 -2
- package/dist/core/static/tau_docs/telegram.md +21 -22
- package/dist/core/static/tau_docs/troubleshooting.md +1 -1
- package/dist/core/static/tau_docs/tui.md +2 -0
- package/dist/core/telegram/config.js +20 -2
- package/dist/core/telegram/config.js.map +1 -1
- package/dist/core/telegram/workspace.js +3 -39
- package/dist/core/telegram/workspace.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/diff_tool/app/dist/assets/{angular-html-DoxI_och.js → angular-html-CNdMT1sj.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{angular-ts-C7CUd-D2.js → angular-ts-ByZ1I9zp.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{apl-7dieYM-l.js → apl-DTw4IZcL.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{astro-Czj3O4jM.js → astro-Cuihy-h6.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{blade-ClS0E2cC.js → blade-BRcWKIxm.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{c-BKvTh-Jt.js → c-CZrnSPrO.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{chapel-qvFrfwEr.js → chapel-BJWGSECM.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{cobol-B0vsaB-J.js → cobol-BtqNzZtm.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{coffee-BUlcrU75.js → coffee-DV53y0PJ.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{cpp-CKqEgX1l.js → cpp-Bn3c6Y8W.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{crystal-FCqSmIJ_.js → crystal-4DKOWvUP.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{css-zZvYFJFc.js → css-DRAuUHHc.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{edge-BKlbCo1f.js → edge-CvBIkt32.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{elixir-B2gSxtgZ.js → elixir-WxLR57iz.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{elm-DFEdzPJz.js → elm-C8UfKkLs.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{erb-DSgn8fHS.js → erb-DDaRKv4Z.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{git-rebase-CcgDECQr.js → git-rebase-BfBjC640.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{glimmer-js-Dwxi4cX2.js → glimmer-js-5QdPyQJP.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{glimmer-ts-BnJVER8W.js → glimmer-ts-Te0TMSCC.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{glsl-z7_gU-HN.js → glsl-D4tyKssQ.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{graphql-Dt7V-d3L.js → graphql-J0IcMfkN.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{hack-jVkr6EC8.js → hack-BcEFuXu_.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{haml-BF7cRiOF.js → haml-SbspNdjx.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{handlebars-DAhNxR3E.js → handlebars-DH3a4U2h.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{html-B7QW5YvU.js → html-CBno8dbY.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{html-derivative-B-vyaE-N.js → html-derivative-COFLhWU9.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{http-Drkl04RP.js → http-2GnhhV3-.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{hurl-7gTFrvcN.js → hurl-BIPzVQmD.js} +1 -1
- package/dist/diff_tool/app/dist/assets/index-Cb0TCxCI.js +1550 -0
- package/dist/diff_tool/app/dist/assets/index-esno8y5b.css +1 -0
- package/dist/diff_tool/app/dist/assets/{java-ZmvXuvs3.js → java-bZo_LLEv.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{javascript-C-OECu_Z.js → javascript-7XNFOfdJ.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{jinja-hvkV87CG.js → jinja-Yuf1yufJ.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{jison-eJBDxKI8.js → jison-DD2vLLcq.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{json-BPQU2w0u.js → json-CVOsPviK.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{jsx-C19s5MOj.js → jsx-BYrWYIX0.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{julia-CYISvCwi.js → julia-BZ_A4I5C.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{just-5omBg5pB.js → just-D8W5QT1p.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{latex-B_L85F2z.js → latex-BSsqyYVM.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{liquid-BxbaQPPS.js → liquid-CD5oivgr.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{lua-DQA0z-44.js → lua-BnXOnOdP.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{marko-Dj5RV8IH.js → marko-CjeqsSvM.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{mdc-CoHI6VJN.js → mdc-Bk-o_FzD.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{nginx-COnrY-gb.js → nginx-CU9exOaI.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{nim-Ccfp0p1j.js → nim-C4eS-_Qo.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{org-RForNsWm.js → org-gZKEwUlv.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{perl-DhHRsiUu.js → perl-d_jthyH-.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{php-BlxKhJek.js → php-Cldhvias.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{pug-o9YxnGzH.js → pug-1Im3v9J7.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{qml-Dzf494vL.js → qml-Bugdf4FS.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{r-Dp0OUrF6.js → r-DNz8aXhA.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{razor-gylahu-m.js → razor-CyO91A7-.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{regexp-C0rsDjCm.js → regexp-7IVcp_0b.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{rst-D-DRAFtq.js → rst-CPC6Rcsf.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{ruby-BnY40vXq.js → ruby-CIw26EG-.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{sas-BaSo_s7i.js → sas-CXXipNue.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{scss-BX5R-Yyw.js → scss-Cb8mUz_m.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{shellscript-CpRFVBRj.js → shellscript-8Dfk6ni5.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{shellsession-BGRUDtms.js → shellsession-BBb6xY52.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{soy-C2WL9FSb.js → soy-_rneSfGo.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{sql-CGmNnOfz.js → sql-CmdLYztO.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{stata-CDamV3Uz.js → stata-DmJpPQHj.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{surrealql-BqI4o6tP.js → surrealql-D1R_-CBd.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{svelte-DPaOUioa.js → svelte-CKPrzNdA.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{templ-aqrZIEoc.js → templ-C0Eqag1M.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{tex-Bjjtfacp.js → tex-IrZiFLxJ.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{ts-tags-BGFnsvdQ.js → ts-tags-09-sH-my.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{tsx-C_rOxPqZ.js → tsx-CSPybM8Z.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{twig-DYqVwCOf.js → twig-C17vnjiX.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{typescript-CppQYc7K.js → typescript-CvHNDrui.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{typst-KIbA0FZG.js → typst-Ct_9rHAw.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{vue-iOYoJEJi.js → vue-A0j7pYML.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{vue-html-DcZpMui9.js → vue-html-DiVQf4vL.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{vue-vine-C6v6c0Lr.js → vue-vine-CBf6VIg0.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{xml-zR0RltJO.js → xml-BvQnnFTL.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{xsl-B9pBLsIV.js → xsl-Dj-x_YCW.js} +1 -1
- package/dist/diff_tool/app/dist/assets/{yaml-sicIihvu.js → yaml-D6eQJHya.js} +1 -1
- package/dist/diff_tool/app/dist/index.html +2 -2
- package/dist/diff_tool/http_server.js +335 -56
- package/dist/diff_tool/http_server.js.map +1 -1
- package/dist/diff_tool/index.js.map +1 -1
- package/dist/diff_tool/review_prompts.js +168 -28
- package/dist/diff_tool/review_prompts.js.map +1 -1
- package/dist/diff_tool/review_state.js +213 -69
- package/dist/diff_tool/review_state.js.map +1 -1
- package/dist/diff_tool/review_state_persistence.js +282 -0
- package/dist/diff_tool/review_state_persistence.js.map +1 -0
- package/dist/diff_tool/shared_types.js +12 -0
- package/dist/diff_tool/shared_types.js.map +1 -1
- package/dist/host/hosted_ephemeral_agent_session.js +14 -2
- package/dist/host/hosted_ephemeral_agent_session.js.map +1 -1
- package/dist/host/local_session_host.js +67 -59
- package/dist/host/local_session_host.js.map +1 -1
- package/dist/host/session_protocol_handler.js +2 -0
- package/dist/host/session_protocol_handler.js.map +1 -1
- package/dist/protocol/session_protocol.d.ts +2 -0
- package/dist/protocol/session_protocol.js +9 -2
- package/dist/protocol/session_protocol.js.map +1 -1
- package/dist/sdk/diff_review.d.ts +40 -0
- package/dist/sdk/diff_review.js +112 -0
- package/dist/sdk/diff_review.js.map +1 -0
- package/dist/sdk/index.d.ts +2 -1
- package/dist/sdk/index.js +1 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/session.js +2 -0
- package/dist/sdk/session.js.map +1 -1
- package/dist/sdk/session_tool_execution_backend.d.ts +16 -0
- package/dist/sdk/session_tool_execution_backend.js +157 -0
- package/dist/sdk/session_tool_execution_backend.js.map +1 -0
- package/dist/sdk/types.d.ts +5 -2
- package/dist/tui/session_chat_controller.js +1 -0
- package/dist/tui/session_chat_controller.js.map +1 -1
- package/dist/tui/session_tool_execution_backend.js +1 -156
- package/dist/tui/session_tool_execution_backend.js.map +1 -1
- package/package.json +6 -6
- package/dist/diff_tool/app/dist/assets/index-Az_6lupE.css +0 -1
- package/dist/diff_tool/app/dist/assets/index-C9CiIimI.js +0 -1550
|
@@ -2,10 +2,12 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { extname, join, normalize, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { buildDiffReviewBootstrapPrompt,
|
|
5
|
+
import { buildDiffReviewBootstrapPrompt, buildDiffReviewCommentThreadPrompt, buildDiffReviewGuideOperationsPrompt, buildDiffReviewGuidePrompt, parseDiffReviewGuideOperationsResponse, parseDiffReviewGuideResponse, } from "./review_prompts.js";
|
|
6
6
|
import { DiffToolReviewStateStore } from "./review_state.js";
|
|
7
|
-
import {
|
|
7
|
+
import { createDiffReviewScopeFingerprint, createDiffToolPersistedReviewStateDocument, parseDiffToolPersistedReviewStateDocument, } from "./review_state_persistence.js";
|
|
8
|
+
import { DIFF_TOOL_CODE_THEMES, DIFF_TOOL_GUIDE_QUESTION_LIMIT, DIFF_TOOL_GUIDE_TOPIC_LIMIT, } from "./shared_types.js";
|
|
8
9
|
const JSON_BODY_LIMIT_BYTES = 1024 * 1024;
|
|
10
|
+
const DIFF_REVIEW_FORK_REASONING = "medium";
|
|
9
11
|
const CONTENT_TYPE_BY_EXT = {
|
|
10
12
|
".html": "text/html; charset=utf-8",
|
|
11
13
|
".js": "text/javascript; charset=utf-8",
|
|
@@ -27,9 +29,17 @@ export class DiffToolHttpServer {
|
|
|
27
29
|
removeSessionCloseListener;
|
|
28
30
|
staticDir;
|
|
29
31
|
reviewState;
|
|
32
|
+
storage;
|
|
33
|
+
onSubmit;
|
|
30
34
|
context;
|
|
31
35
|
files = [];
|
|
36
|
+
persistence;
|
|
37
|
+
stateMutationQueue = Promise.resolve();
|
|
38
|
+
submissionState = "active";
|
|
32
39
|
bootstrapThreadPromise;
|
|
40
|
+
guideGenerationPromise;
|
|
41
|
+
guideOperationQueue = [];
|
|
42
|
+
guideOperationRunning = false;
|
|
33
43
|
httpServerClosePromise;
|
|
34
44
|
sessionClosing = false;
|
|
35
45
|
server = createServer((request, response) => {
|
|
@@ -51,6 +61,8 @@ export class DiffToolHttpServer {
|
|
|
51
61
|
});
|
|
52
62
|
this.host = options.host ?? "127.0.0.1";
|
|
53
63
|
this.port = options.port ?? 0;
|
|
64
|
+
this.storage = options.storage;
|
|
65
|
+
this.onSubmit = options.onSubmit;
|
|
54
66
|
this.staticDir = resolveStaticDir();
|
|
55
67
|
this.removeClientCloseListener = this.client.onClose(() => {
|
|
56
68
|
if (this.closed) {
|
|
@@ -68,14 +80,27 @@ export class DiffToolHttpServer {
|
|
|
68
80
|
await this.client.connect();
|
|
69
81
|
this.context = await this.client.getContext();
|
|
70
82
|
this.files = (await this.client.listFiles()).files;
|
|
83
|
+
if (this.storage) {
|
|
84
|
+
const diff = await this.client.getDiff();
|
|
85
|
+
const persistence = {
|
|
86
|
+
storage: this.storage,
|
|
87
|
+
scopeFingerprint: createDiffReviewScopeFingerprint(this.context, this.files, diff.patch),
|
|
88
|
+
};
|
|
89
|
+
await this.restoreReviewState(persistence);
|
|
90
|
+
this.persistence = persistence;
|
|
91
|
+
}
|
|
92
|
+
if (this.closed || this.sessionClosing) {
|
|
93
|
+
throw new Error("diff tool session closed during startup");
|
|
94
|
+
}
|
|
71
95
|
await this.listen();
|
|
72
96
|
const address = this.server.address();
|
|
73
97
|
if (!address || typeof address === "string") {
|
|
74
98
|
throw new Error("failed to start diff tool http server");
|
|
75
99
|
}
|
|
76
|
-
const url = `http://${this.host}:${String(address.port)}
|
|
100
|
+
const url = `http://${formatHttpUrlHost(this.host)}:${String(address.port)}/`;
|
|
77
101
|
await this.client.setUiText({ text: url });
|
|
78
102
|
void this.startBootstrapThread().catch(() => { });
|
|
103
|
+
void this.startGuideGeneration().catch(() => { });
|
|
79
104
|
return {
|
|
80
105
|
url,
|
|
81
106
|
};
|
|
@@ -118,6 +143,35 @@ export class DiffToolHttpServer {
|
|
|
118
143
|
await this.close();
|
|
119
144
|
}
|
|
120
145
|
}
|
|
146
|
+
async restoreReviewState(persistence) {
|
|
147
|
+
const document = await persistence.storage.load();
|
|
148
|
+
if (document !== undefined) {
|
|
149
|
+
this.reviewState.replaceState(parseDiffToolPersistedReviewStateDocument(document, persistence.scopeFingerprint));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await this.persistReviewState(this.reviewState.getState(), persistence);
|
|
153
|
+
}
|
|
154
|
+
async persistReviewState(state, persistence = this.persistence) {
|
|
155
|
+
if (!persistence)
|
|
156
|
+
return;
|
|
157
|
+
await persistence.storage.save(createDiffToolPersistedReviewStateDocument(persistence.scopeFingerprint, state));
|
|
158
|
+
}
|
|
159
|
+
async mutateReviewState(mutation, isApplied = () => true) {
|
|
160
|
+
const operation = this.stateMutationQueue.then(async () => {
|
|
161
|
+
const previousState = this.reviewState.getState();
|
|
162
|
+
const draft = this.reviewState.clone();
|
|
163
|
+
const result = mutation(draft);
|
|
164
|
+
if (!isApplied(result)) {
|
|
165
|
+
return { result, state: previousState };
|
|
166
|
+
}
|
|
167
|
+
const nextState = draft.getState();
|
|
168
|
+
await this.persistReviewState(nextState);
|
|
169
|
+
this.reviewState.replaceStatePreservingConcurrentLoading(nextState, previousState);
|
|
170
|
+
return { result, state: this.reviewState.getState() };
|
|
171
|
+
});
|
|
172
|
+
this.stateMutationQueue = operation.then(() => undefined, () => undefined);
|
|
173
|
+
return await operation;
|
|
174
|
+
}
|
|
121
175
|
async listen() {
|
|
122
176
|
await new Promise((resolve, reject) => {
|
|
123
177
|
const onError = (error) => {
|
|
@@ -126,6 +180,10 @@ export class DiffToolHttpServer {
|
|
|
126
180
|
};
|
|
127
181
|
const onListening = () => {
|
|
128
182
|
this.server.off("error", onError);
|
|
183
|
+
if (this.closed || this.sessionClosing) {
|
|
184
|
+
void this.closeHttpServer().then(() => reject(new Error("diff tool session closed during startup")), reject);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
129
187
|
resolve();
|
|
130
188
|
};
|
|
131
189
|
this.server.once("error", onError);
|
|
@@ -186,6 +244,107 @@ export class DiffToolHttpServer {
|
|
|
186
244
|
async getBootstrapThreadId() {
|
|
187
245
|
return await this.startBootstrapThread();
|
|
188
246
|
}
|
|
247
|
+
startGuideGeneration() {
|
|
248
|
+
if (this.guideGenerationPromise) {
|
|
249
|
+
return this.guideGenerationPromise;
|
|
250
|
+
}
|
|
251
|
+
const currentState = this.reviewState.getState();
|
|
252
|
+
if (currentState.guide.orientation.trim()) {
|
|
253
|
+
return Promise.resolve(currentState);
|
|
254
|
+
}
|
|
255
|
+
if (this.closed) {
|
|
256
|
+
return Promise.reject(new Error("diff tool http server is closed"));
|
|
257
|
+
}
|
|
258
|
+
this.reviewState.setGuideLoading(true);
|
|
259
|
+
const promise = this.generateGuide()
|
|
260
|
+
.catch((error) => {
|
|
261
|
+
this.reviewState.setGuideLoading(false);
|
|
262
|
+
throw error;
|
|
263
|
+
})
|
|
264
|
+
.finally(() => {
|
|
265
|
+
if (this.guideGenerationPromise === promise) {
|
|
266
|
+
this.guideGenerationPromise = undefined;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
this.guideGenerationPromise = promise;
|
|
270
|
+
void promise.catch(() => { });
|
|
271
|
+
return promise;
|
|
272
|
+
}
|
|
273
|
+
async generateGuide() {
|
|
274
|
+
const result = await this.client.submitThreadMessage({
|
|
275
|
+
forkFromThreadId: await this.getBootstrapThreadId(),
|
|
276
|
+
message: buildDiffReviewGuidePrompt(),
|
|
277
|
+
reasoning: DIFF_REVIEW_FORK_REASONING,
|
|
278
|
+
});
|
|
279
|
+
const guide = parseDiffReviewGuideResponse(result.response);
|
|
280
|
+
const { state } = await this.mutateReviewState((draft) => draft.applyGuideResult(result, guide));
|
|
281
|
+
return state;
|
|
282
|
+
}
|
|
283
|
+
enqueueGuideOperation(operation) {
|
|
284
|
+
this.reviewState.setGuideLoading(true);
|
|
285
|
+
const queued = new Promise((resolve, reject) => {
|
|
286
|
+
this.guideOperationQueue.push({ operation, resolve, reject });
|
|
287
|
+
});
|
|
288
|
+
void this.drainGuideOperationQueue();
|
|
289
|
+
return queued;
|
|
290
|
+
}
|
|
291
|
+
async drainGuideOperationQueue() {
|
|
292
|
+
if (this.guideOperationRunning) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.guideOperationRunning = true;
|
|
296
|
+
try {
|
|
297
|
+
if (this.guideGenerationPromise) {
|
|
298
|
+
await this.guideGenerationPromise;
|
|
299
|
+
}
|
|
300
|
+
while (this.guideOperationQueue.length > 0) {
|
|
301
|
+
const queued = this.guideOperationQueue.splice(0);
|
|
302
|
+
try {
|
|
303
|
+
const result = await this.runGuideOperations(queued.map((entry) => entry.operation));
|
|
304
|
+
for (const entry of queued) {
|
|
305
|
+
entry.resolve(result);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.reviewState.setGuideLoading(false);
|
|
310
|
+
for (const entry of queued) {
|
|
311
|
+
entry.reject(error);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.reviewState.setGuideLoading(false);
|
|
318
|
+
for (const entry of this.guideOperationQueue.splice(0)) {
|
|
319
|
+
entry.reject(error);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
this.guideOperationRunning = false;
|
|
324
|
+
if (this.guideOperationQueue.length > 0) {
|
|
325
|
+
void this.drainGuideOperationQueue();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async runGuideOperations(operations) {
|
|
330
|
+
const currentGuide = this.reviewState.getState().guide;
|
|
331
|
+
this.reviewState.setGuideLoading(true);
|
|
332
|
+
const message = buildDiffReviewGuideOperationsPrompt(operations, currentGuide);
|
|
333
|
+
const result = await this.client.submitThreadMessage(currentGuide.threadId
|
|
334
|
+
? { threadId: currentGuide.threadId, message }
|
|
335
|
+
: {
|
|
336
|
+
forkFromThreadId: await this.getBootstrapThreadId(),
|
|
337
|
+
message,
|
|
338
|
+
reasoning: DIFF_REVIEW_FORK_REASONING,
|
|
339
|
+
});
|
|
340
|
+
const contents = parseDiffReviewGuideOperationsResponse(operations, result.response);
|
|
341
|
+
const { result: applied, state } = await this.mutateReviewState((draft) => draft.applyGuideOperationResults(result, operations, contents), (value) => value);
|
|
342
|
+
if (!applied) {
|
|
343
|
+
this.reviewState.setGuideLoading(false);
|
|
344
|
+
return { applied, state: this.reviewState.getState() };
|
|
345
|
+
}
|
|
346
|
+
return { applied, state };
|
|
347
|
+
}
|
|
189
348
|
async handleRequest(request, response) {
|
|
190
349
|
const method = request.method ?? "GET";
|
|
191
350
|
const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? this.host}`);
|
|
@@ -213,8 +372,8 @@ export class DiffToolHttpServer {
|
|
|
213
372
|
}
|
|
214
373
|
if (method === "POST" && requestUrl.pathname === "/api/state") {
|
|
215
374
|
const payload = parseStatePatch(await this.readJsonBody(request));
|
|
216
|
-
this.
|
|
217
|
-
this.sendJson(response, 200, { state
|
|
375
|
+
const { state } = await this.mutateReviewState((draft) => draft.updateState(payload));
|
|
376
|
+
this.sendJson(response, 200, { state });
|
|
218
377
|
return;
|
|
219
378
|
}
|
|
220
379
|
if (method === "POST" && requestUrl.pathname === "/api/thread") {
|
|
@@ -223,9 +382,9 @@ export class DiffToolHttpServer {
|
|
|
223
382
|
this.sendJson(response, 400, { error: "invalid thread payload" });
|
|
224
383
|
return;
|
|
225
384
|
}
|
|
226
|
-
const threadId = this.
|
|
385
|
+
const { result: threadId, state } = await this.mutateReviewState((draft) => draft.createThread(payload));
|
|
227
386
|
this.sendJson(response, 200, {
|
|
228
|
-
state
|
|
387
|
+
state,
|
|
229
388
|
threadId,
|
|
230
389
|
});
|
|
231
390
|
return;
|
|
@@ -234,27 +393,27 @@ export class DiffToolHttpServer {
|
|
|
234
393
|
const payload = await this.readJsonBody(request);
|
|
235
394
|
const id = typeof payload.id === "string" ? payload.id.trim() : "";
|
|
236
395
|
const text = typeof payload.text === "string" ? payload.text.trim() : "";
|
|
237
|
-
if (!this.reviewState.findThread(id)) {
|
|
238
|
-
this.sendJson(response, 404, { error: "thread not found" });
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
396
|
if (!text) {
|
|
242
397
|
this.sendJson(response, 400, { error: "reply text is required" });
|
|
243
398
|
return;
|
|
244
399
|
}
|
|
245
|
-
this.
|
|
246
|
-
|
|
400
|
+
const { result: added, state } = await this.mutateReviewState((draft) => draft.addReply(id, text), (result) => result);
|
|
401
|
+
if (!added) {
|
|
402
|
+
this.sendJson(response, 404, { error: "thread not found" });
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this.sendJson(response, 200, { state });
|
|
247
406
|
return;
|
|
248
407
|
}
|
|
249
408
|
if (method === "POST" && requestUrl.pathname === "/api/thread/delete") {
|
|
250
409
|
const payload = await this.readJsonBody(request);
|
|
251
410
|
const id = typeof payload.id === "string" ? payload.id.trim() : "";
|
|
252
|
-
const removed = this.
|
|
411
|
+
const { result: removed, state } = await this.mutateReviewState((draft) => draft.deleteThread(id), (result) => result);
|
|
253
412
|
if (!removed) {
|
|
254
413
|
this.sendJson(response, 404, { error: "thread not found" });
|
|
255
414
|
return;
|
|
256
415
|
}
|
|
257
|
-
this.sendJson(response, 200, { state
|
|
416
|
+
this.sendJson(response, 200, { state });
|
|
258
417
|
return;
|
|
259
418
|
}
|
|
260
419
|
if (method === "POST" && requestUrl.pathname === "/api/thread-message/delete") {
|
|
@@ -263,46 +422,54 @@ export class DiffToolHttpServer {
|
|
|
263
422
|
const messageIndex = typeof payload.messageIndex === "number" && Number.isInteger(payload.messageIndex)
|
|
264
423
|
? payload.messageIndex
|
|
265
424
|
: -1;
|
|
266
|
-
|
|
425
|
+
const { result, state } = await this.mutateReviewState((draft) => {
|
|
426
|
+
if (!draft.findThread(id))
|
|
427
|
+
return "thread-not-found";
|
|
428
|
+
return draft.deleteThreadMessage(id, messageIndex)
|
|
429
|
+
? "removed"
|
|
430
|
+
: "message-not-found";
|
|
431
|
+
}, (result) => result === "removed");
|
|
432
|
+
if (result === "thread-not-found") {
|
|
267
433
|
this.sendJson(response, 404, { error: "thread not found" });
|
|
268
434
|
return;
|
|
269
435
|
}
|
|
270
|
-
|
|
271
|
-
if (!removed) {
|
|
436
|
+
if (result === "message-not-found") {
|
|
272
437
|
this.sendJson(response, 400, { error: "message not found" });
|
|
273
438
|
return;
|
|
274
439
|
}
|
|
275
|
-
this.sendJson(response, 200, { state
|
|
440
|
+
this.sendJson(response, 200, { state });
|
|
276
441
|
return;
|
|
277
442
|
}
|
|
278
443
|
if (method === "POST" && requestUrl.pathname === "/api/thread/resolve") {
|
|
279
444
|
const payload = await this.readJsonBody(request);
|
|
280
445
|
const id = typeof payload.id === "string" ? payload.id.trim() : "";
|
|
281
|
-
if (!this.reviewState.findThread(id)) {
|
|
282
|
-
this.sendJson(response, 404, { error: "thread not found" });
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
446
|
if (typeof payload.resolved !== "boolean") {
|
|
286
447
|
this.sendJson(response, 400, { error: "resolved flag is required" });
|
|
287
448
|
return;
|
|
288
449
|
}
|
|
289
|
-
|
|
290
|
-
|
|
450
|
+
const resolved = payload.resolved;
|
|
451
|
+
const { result: updated, state } = await this.mutateReviewState((draft) => draft.setThreadResolved(id, resolved), (result) => result);
|
|
452
|
+
if (!updated) {
|
|
453
|
+
this.sendJson(response, 404, { error: "thread not found" });
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
this.sendJson(response, 200, { state });
|
|
291
457
|
return;
|
|
292
458
|
}
|
|
293
459
|
if (method === "POST" && requestUrl.pathname === "/api/thread/collapse") {
|
|
294
460
|
const payload = await this.readJsonBody(request);
|
|
295
461
|
const id = typeof payload.id === "string" ? payload.id.trim() : "";
|
|
296
|
-
if (!this.reviewState.findThread(id)) {
|
|
297
|
-
this.sendJson(response, 404, { error: "thread not found" });
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
462
|
if (typeof payload.collapsed !== "boolean") {
|
|
301
463
|
this.sendJson(response, 400, { error: "collapsed flag is required" });
|
|
302
464
|
return;
|
|
303
465
|
}
|
|
304
|
-
|
|
305
|
-
|
|
466
|
+
const collapsed = payload.collapsed;
|
|
467
|
+
const { result: updated, state } = await this.mutateReviewState((draft) => draft.setThreadCollapsed(id, collapsed), (result) => result);
|
|
468
|
+
if (!updated) {
|
|
469
|
+
this.sendJson(response, 404, { error: "thread not found" });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
this.sendJson(response, 200, { state });
|
|
306
473
|
return;
|
|
307
474
|
}
|
|
308
475
|
if (method === "POST" && requestUrl.pathname === "/api/thread-message") {
|
|
@@ -313,21 +480,35 @@ export class DiffToolHttpServer {
|
|
|
313
480
|
this.sendJson(response, 404, { error: "thread not found" });
|
|
314
481
|
return;
|
|
315
482
|
}
|
|
483
|
+
if (thread.loading) {
|
|
484
|
+
this.sendJson(response, 409, { error: "thread message already in progress" });
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
316
487
|
const message = this.reviewState.buildThreadAgentMessage(id);
|
|
317
488
|
if (!message) {
|
|
318
489
|
this.sendJson(response, 400, { error: "thread has no pending user message" });
|
|
319
490
|
return;
|
|
320
491
|
}
|
|
492
|
+
const guideSnapshot = !thread.threadId &&
|
|
493
|
+
thread.anchor.kind === "detached" &&
|
|
494
|
+
!thread.messages.some((entry) => entry.role === "assistant")
|
|
495
|
+
? this.reviewState.getState().guide
|
|
496
|
+
: undefined;
|
|
321
497
|
this.reviewState.setThreadLoading(id, true);
|
|
322
498
|
try {
|
|
323
499
|
const result = await this.client.submitThreadMessage(thread.threadId
|
|
324
500
|
? { threadId: thread.threadId, message }
|
|
325
501
|
: {
|
|
326
502
|
forkFromThreadId: await this.getBootstrapThreadId(),
|
|
327
|
-
message: buildDiffReviewCommentThreadPrompt(message),
|
|
503
|
+
message: buildDiffReviewCommentThreadPrompt(message, guideSnapshot),
|
|
504
|
+
reasoning: DIFF_REVIEW_FORK_REASONING,
|
|
328
505
|
});
|
|
329
|
-
this.
|
|
330
|
-
|
|
506
|
+
const { result: applied, state } = await this.mutateReviewState((draft) => draft.applyThreadResponse(id, result), (result) => result);
|
|
507
|
+
if (!applied) {
|
|
508
|
+
this.sendJson(response, 404, { error: "thread not found" });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
this.sendJson(response, 200, { state });
|
|
331
512
|
}
|
|
332
513
|
catch (error) {
|
|
333
514
|
this.reviewState.setThreadLoading(id, false);
|
|
@@ -335,32 +516,83 @@ export class DiffToolHttpServer {
|
|
|
335
516
|
}
|
|
336
517
|
return;
|
|
337
518
|
}
|
|
338
|
-
if (method === "POST" && requestUrl.pathname === "/api/
|
|
519
|
+
if (method === "POST" && requestUrl.pathname === "/api/guide/generate") {
|
|
520
|
+
const state = await this.startGuideGeneration();
|
|
521
|
+
this.sendJson(response, 200, { state });
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (method === "POST" && requestUrl.pathname === "/api/guide/operate") {
|
|
525
|
+
const operation = parseGuideOperation(await this.readJsonBody(request));
|
|
526
|
+
if (!operation) {
|
|
527
|
+
this.sendJson(response, 400, { error: "invalid guide operation" });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
339
530
|
const currentState = this.reviewState.getState();
|
|
340
|
-
if (
|
|
341
|
-
|
|
531
|
+
if (operation.kind === "topic.add" &&
|
|
532
|
+
currentState.guide.topics.length >= DIFF_TOOL_GUIDE_TOPIC_LIMIT) {
|
|
533
|
+
this.sendJson(response, 409, { error: "guide topic limit reached" });
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
if (operation.kind === "question.ask" &&
|
|
537
|
+
currentState.guide.questions.length >= DIFF_TOOL_GUIDE_QUESTION_LIMIT) {
|
|
538
|
+
this.sendJson(response, 409, { error: "guide question limit reached" });
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (operation.kind === "topic.revise" &&
|
|
542
|
+
!currentState.guide.topics.some((topic) => topic.id === operation.topicId)) {
|
|
543
|
+
this.sendJson(response, 404, { error: "guide topic not found" });
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const { applied, state } = await this.enqueueGuideOperation(operation);
|
|
547
|
+
if (!applied) {
|
|
548
|
+
this.sendJson(response, 409, { error: "guide changed while it was being updated" });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
this.sendJson(response, 200, { state });
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (method === "POST" && requestUrl.pathname === "/api/guide/comment") {
|
|
555
|
+
const payload = parseGuideCommentPayload(await this.readJsonBody(request));
|
|
556
|
+
if (!payload) {
|
|
557
|
+
this.sendJson(response, 400, { error: "invalid guide comment" });
|
|
342
558
|
return;
|
|
343
559
|
}
|
|
344
|
-
this.reviewState.
|
|
560
|
+
const currentState = this.reviewState.getState();
|
|
561
|
+
if (!guideCommentTargetExists(currentState, payload.target)) {
|
|
562
|
+
this.sendJson(response, 404, { error: "guide comment target not found" });
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const { state } = await this.mutateReviewState((draft) => draft.saveGuideComment(payload.target, payload.body));
|
|
566
|
+
this.sendJson(response, 200, { state });
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (method === "POST" && requestUrl.pathname === "/api/review") {
|
|
570
|
+
await this.readJsonBody(request);
|
|
571
|
+
await this.stateMutationQueue;
|
|
572
|
+
if (this.submissionState !== "active") {
|
|
573
|
+
this.sendJson(response, 409, { error: "diff review has already been submitted" });
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
this.submissionState = "submitting";
|
|
577
|
+
const review = this.reviewState.buildReviewText();
|
|
578
|
+
const context = this.context;
|
|
579
|
+
if (!context) {
|
|
580
|
+
this.submissionState = "active";
|
|
581
|
+
throw new Error("diff tool session context is unavailable");
|
|
582
|
+
}
|
|
345
583
|
try {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
584
|
+
await this.onSubmit?.({
|
|
585
|
+
review,
|
|
586
|
+
context: { ...context, diffArgs: [...context.diffArgs] },
|
|
587
|
+
files: this.files.map((file) => ({ ...file })),
|
|
349
588
|
});
|
|
350
|
-
this.reviewState.applyBriefResult(result);
|
|
351
|
-
this.sendJson(response, 200, { state: this.reviewState.getState() });
|
|
352
589
|
}
|
|
353
590
|
catch (error) {
|
|
354
|
-
this.
|
|
591
|
+
this.submissionState = "active";
|
|
355
592
|
throw error;
|
|
356
593
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (method === "POST" && requestUrl.pathname === "/api/review") {
|
|
360
|
-
const payload = parseReviewPayload(await this.readJsonBody(request));
|
|
361
|
-
const result = await this.client.returnReview({
|
|
362
|
-
review: this.reviewState.buildReviewText(payload.message),
|
|
363
|
-
});
|
|
594
|
+
this.submissionState = "submitted";
|
|
595
|
+
const result = await this.client.returnReview({ review });
|
|
364
596
|
this.sendJson(response, 200, result);
|
|
365
597
|
return;
|
|
366
598
|
}
|
|
@@ -445,10 +677,58 @@ export class DiffToolHttpServer {
|
|
|
445
677
|
response.end(JSON.stringify(payload));
|
|
446
678
|
}
|
|
447
679
|
}
|
|
448
|
-
function
|
|
449
|
-
return
|
|
450
|
-
|
|
451
|
-
|
|
680
|
+
function formatHttpUrlHost(host) {
|
|
681
|
+
return host.includes(":") ? `[${host}]` : host;
|
|
682
|
+
}
|
|
683
|
+
function parseGuideOperation(payload) {
|
|
684
|
+
switch (payload.kind) {
|
|
685
|
+
case "topic.add": {
|
|
686
|
+
const request = typeof payload.request === "string" ? payload.request.trim() : "";
|
|
687
|
+
return request ? { kind: payload.kind, request } : undefined;
|
|
688
|
+
}
|
|
689
|
+
case "topic.revise": {
|
|
690
|
+
const topicId = typeof payload.topicId === "string" ? payload.topicId.trim() : "";
|
|
691
|
+
const request = typeof payload.request === "string" ? payload.request.trim() : "";
|
|
692
|
+
return topicId && request ? { kind: payload.kind, topicId, request } : undefined;
|
|
693
|
+
}
|
|
694
|
+
case "question.ask": {
|
|
695
|
+
const question = typeof payload.question === "string" ? payload.question.trim() : "";
|
|
696
|
+
return question ? { kind: payload.kind, question } : undefined;
|
|
697
|
+
}
|
|
698
|
+
default:
|
|
699
|
+
return undefined;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function parseGuideCommentPayload(payload) {
|
|
703
|
+
const body = typeof payload.body === "string" ? payload.body.trim() : "";
|
|
704
|
+
const target = payload.target && typeof payload.target === "object" && !Array.isArray(payload.target)
|
|
705
|
+
? payload.target
|
|
706
|
+
: undefined;
|
|
707
|
+
if (!body || !target) {
|
|
708
|
+
return undefined;
|
|
709
|
+
}
|
|
710
|
+
if (target.kind === "orientation") {
|
|
711
|
+
return { body, target: { kind: "orientation" } };
|
|
712
|
+
}
|
|
713
|
+
if (target.kind === "topic" && typeof target.topicId === "string" && target.topicId.trim()) {
|
|
714
|
+
return { body, target: { kind: "topic", topicId: target.topicId.trim() } };
|
|
715
|
+
}
|
|
716
|
+
if (target.kind === "question" &&
|
|
717
|
+
typeof target.questionId === "string" &&
|
|
718
|
+
target.questionId.trim()) {
|
|
719
|
+
return { body, target: { kind: "question", questionId: target.questionId.trim() } };
|
|
720
|
+
}
|
|
721
|
+
return undefined;
|
|
722
|
+
}
|
|
723
|
+
function guideCommentTargetExists(state, target) {
|
|
724
|
+
switch (target.kind) {
|
|
725
|
+
case "orientation":
|
|
726
|
+
return Boolean(state.guide.orientation);
|
|
727
|
+
case "topic":
|
|
728
|
+
return state.guide.topics.some((topic) => topic.id === target.topicId);
|
|
729
|
+
case "question":
|
|
730
|
+
return state.guide.questions.some((question) => question.id === target.questionId);
|
|
731
|
+
}
|
|
452
732
|
}
|
|
453
733
|
const codeThemes = new Set(DIFF_TOOL_CODE_THEMES);
|
|
454
734
|
function parseStatePatch(payload) {
|
|
@@ -463,7 +743,6 @@ function parseStatePatch(payload) {
|
|
|
463
743
|
codeThemes.has(payload.codeTheme)
|
|
464
744
|
? { codeTheme: payload.codeTheme }
|
|
465
745
|
: {}),
|
|
466
|
-
...(typeof payload.sidebarOpen === "boolean" ? { sidebarOpen: payload.sidebarOpen } : {}),
|
|
467
746
|
...(Array.isArray(payload.collapsedFileIds)
|
|
468
747
|
? {
|
|
469
748
|
collapsedFileIds: payload.collapsedFileIds.flatMap((value) => typeof value === "string" && value.trim() ? [value] : []),
|