agentweaver 0.1.19 → 0.1.20
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/README.md +47 -7
- package/dist/artifacts.js +9 -0
- package/dist/executors/git-commit-executor.js +24 -6
- package/dist/flow-state.js +3 -8
- package/dist/git/git-diff-parser.js +223 -0
- package/dist/git/git-service.js +562 -0
- package/dist/git/git-stage-selection.js +24 -0
- package/dist/git/git-status-parser.js +171 -0
- package/dist/git/git-types.js +1 -0
- package/dist/index.js +450 -108
- package/dist/interactive/auto-flow.js +644 -0
- package/dist/interactive/controller.js +417 -9
- package/dist/interactive/progress.js +194 -1
- package/dist/interactive/state.js +25 -0
- package/dist/interactive/web/index.js +97 -12
- package/dist/interactive/web/protocol.js +216 -1
- package/dist/interactive/web/server.js +72 -14
- package/dist/interactive/web/static/app.js +1603 -49
- package/dist/interactive/web/static/index.html +76 -11
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +901 -47
- package/dist/pipeline/auto-flow-blocks.js +307 -0
- package/dist/pipeline/auto-flow-config.js +273 -0
- package/dist/pipeline/auto-flow-identity.js +49 -0
- package/dist/pipeline/auto-flow-presets.js +52 -0
- package/dist/pipeline/auto-flow-resolver.js +830 -0
- package/dist/pipeline/auto-flow-types.js +17 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flows.js +27 -1
- package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
- package/dist/pipeline/flow-specs/auto-golang.json +12 -1
- package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
- package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
- package/dist/pipeline/flow-specs/review/review-project.json +19 -1
- package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
- package/dist/pipeline/node-registry.js +9 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
- package/dist/pipeline/nodes/flow-run-node.js +5 -3
- package/dist/pipeline/nodes/git-status-node.js +2 -168
- package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
- package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
- package/dist/pipeline/nodes/plan-codex-node.js +8 -1
- package/dist/pipeline/spec-loader.js +14 -4
- package/dist/runtime/artifact-catalog.js +29 -5
- package/dist/runtime/settings.js +114 -0
- package/dist/scope.js +14 -4
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/auto-common.json +0 -179
- package/dist/pipeline/flow-specs/auto-simple.json +0 -141
|
@@ -14,6 +14,25 @@ const ACTION_TYPES = new Set([
|
|
|
14
14
|
"log.clear",
|
|
15
15
|
"artifactExplorer.open",
|
|
16
16
|
"artifactExplorer.close",
|
|
17
|
+
"autoFlow.selectPreset",
|
|
18
|
+
"autoFlow.loadConfig",
|
|
19
|
+
"autoFlow.save",
|
|
20
|
+
"autoFlow.reset",
|
|
21
|
+
"autoFlow.toggleBlock",
|
|
22
|
+
"autoFlow.updateParam",
|
|
23
|
+
"autoFlow.insertBlock",
|
|
24
|
+
"autoFlow.removeBlock",
|
|
25
|
+
"git.refresh",
|
|
26
|
+
"git.createBranch",
|
|
27
|
+
"git.checkout",
|
|
28
|
+
"git.fetch",
|
|
29
|
+
"git.pullFfOnly",
|
|
30
|
+
"git.stage",
|
|
31
|
+
"git.unstage",
|
|
32
|
+
"git.updateCommitMessage",
|
|
33
|
+
"git.commit",
|
|
34
|
+
"git.push",
|
|
35
|
+
"settings.update",
|
|
17
36
|
"help.toggle",
|
|
18
37
|
"scroll",
|
|
19
38
|
]);
|
|
@@ -53,6 +72,62 @@ function optionalInteger(value, fieldName) {
|
|
|
53
72
|
}
|
|
54
73
|
return field;
|
|
55
74
|
}
|
|
75
|
+
function optionalBoolean(value, fieldName) {
|
|
76
|
+
if (value[fieldName] === undefined) {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const field = value[fieldName];
|
|
80
|
+
if (typeof field !== "boolean") {
|
|
81
|
+
throw new Error(`${fieldName} must be a boolean.`);
|
|
82
|
+
}
|
|
83
|
+
return field;
|
|
84
|
+
}
|
|
85
|
+
function requireSettingsPatch(value) {
|
|
86
|
+
const settings = value.settings;
|
|
87
|
+
if (!isRecord(settings)) {
|
|
88
|
+
throw new Error("settings must be an object.");
|
|
89
|
+
}
|
|
90
|
+
const allowed = new Set(["theme", "autoFlowHeight", "workspaceSplit", "logAutoscroll"]);
|
|
91
|
+
for (const key of Object.keys(settings)) {
|
|
92
|
+
if (!allowed.has(key)) {
|
|
93
|
+
throw new Error(`Unsupported settings key: ${key}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const patch = {};
|
|
97
|
+
if ("theme" in settings) {
|
|
98
|
+
const theme = settings.theme;
|
|
99
|
+
if (theme !== "dark" && theme !== "light") {
|
|
100
|
+
throw new Error("settings.theme must be light or dark.");
|
|
101
|
+
}
|
|
102
|
+
patch.theme = theme;
|
|
103
|
+
}
|
|
104
|
+
if ("autoFlowHeight" in settings) {
|
|
105
|
+
const autoFlowHeight = settings.autoFlowHeight;
|
|
106
|
+
if (autoFlowHeight !== null
|
|
107
|
+
&& (typeof autoFlowHeight !== "number" || !Number.isFinite(autoFlowHeight))) {
|
|
108
|
+
throw new Error("settings.autoFlowHeight must be a finite number or null.");
|
|
109
|
+
}
|
|
110
|
+
patch.autoFlowHeight = autoFlowHeight;
|
|
111
|
+
}
|
|
112
|
+
if ("workspaceSplit" in settings) {
|
|
113
|
+
const workspaceSplit = settings.workspaceSplit;
|
|
114
|
+
if (typeof workspaceSplit !== "number" || !Number.isFinite(workspaceSplit)) {
|
|
115
|
+
throw new Error("settings.workspaceSplit must be a finite number.");
|
|
116
|
+
}
|
|
117
|
+
patch.workspaceSplit = workspaceSplit;
|
|
118
|
+
}
|
|
119
|
+
if ("logAutoscroll" in settings) {
|
|
120
|
+
const logAutoscroll = settings.logAutoscroll;
|
|
121
|
+
if (typeof logAutoscroll !== "boolean") {
|
|
122
|
+
throw new Error("settings.logAutoscroll must be a boolean.");
|
|
123
|
+
}
|
|
124
|
+
patch.logAutoscroll = logAutoscroll;
|
|
125
|
+
}
|
|
126
|
+
if (Object.keys(patch).length === 0) {
|
|
127
|
+
throw new Error("settings.update requires at least one setting.");
|
|
128
|
+
}
|
|
129
|
+
return patch;
|
|
130
|
+
}
|
|
56
131
|
function requireValues(value, fieldName = "values") {
|
|
57
132
|
const values = value[fieldName];
|
|
58
133
|
if (!isRecord(values)) {
|
|
@@ -60,6 +135,26 @@ function requireValues(value, fieldName = "values") {
|
|
|
60
135
|
}
|
|
61
136
|
return values;
|
|
62
137
|
}
|
|
138
|
+
function requireStringArray(value, fieldName) {
|
|
139
|
+
const field = value[fieldName];
|
|
140
|
+
if (!Array.isArray(field) || field.some((item) => typeof item !== "string" || item.length === 0)) {
|
|
141
|
+
throw new Error(`${fieldName} must be an array of non-empty strings.`);
|
|
142
|
+
}
|
|
143
|
+
return field;
|
|
144
|
+
}
|
|
145
|
+
function optionalStringArray(value, fieldName) {
|
|
146
|
+
if (value[fieldName] === undefined) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return requireStringArray(value, fieldName);
|
|
150
|
+
}
|
|
151
|
+
function requireCommitMessage(value) {
|
|
152
|
+
const message = value.message;
|
|
153
|
+
if (typeof message !== "string" || message.trim().length === 0) {
|
|
154
|
+
throw new Error("message must be a non-empty string.");
|
|
155
|
+
}
|
|
156
|
+
return message;
|
|
157
|
+
}
|
|
63
158
|
export function parseClientAction(raw) {
|
|
64
159
|
let parsed;
|
|
65
160
|
try {
|
|
@@ -102,7 +197,11 @@ export function parseClientAction(raw) {
|
|
|
102
197
|
|| parsed.type === "form.cancel"
|
|
103
198
|
|| parsed.type === "log.clear"
|
|
104
199
|
|| parsed.type === "artifactExplorer.open"
|
|
105
|
-
|| parsed.type === "artifactExplorer.close"
|
|
200
|
+
|| parsed.type === "artifactExplorer.close"
|
|
201
|
+
|| parsed.type === "git.refresh"
|
|
202
|
+
|| parsed.type === "git.fetch"
|
|
203
|
+
|| parsed.type === "git.pullFfOnly"
|
|
204
|
+
|| parsed.type === "git.push") {
|
|
106
205
|
return { type: parsed.type, ...(actionId ? { actionId } : {}) };
|
|
107
206
|
}
|
|
108
207
|
if (parsed.type === "form.update") {
|
|
@@ -135,12 +234,128 @@ export function parseClientAction(raw) {
|
|
|
135
234
|
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
136
235
|
return { type: "flow.interrupt", ...(flowId ? { flowId } : {}), ...(actionId ? { actionId } : {}) };
|
|
137
236
|
}
|
|
237
|
+
if (parsed.type === "autoFlow.selectPreset") {
|
|
238
|
+
const preset = requireNonEmptyString(parsed, "preset");
|
|
239
|
+
if (preset !== "simple" && preset !== "standard") {
|
|
240
|
+
throw new Error("preset must be simple or standard.");
|
|
241
|
+
}
|
|
242
|
+
return { type: "autoFlow.selectPreset", preset, ...(actionId ? { actionId } : {}) };
|
|
243
|
+
}
|
|
244
|
+
if (parsed.type === "autoFlow.loadConfig") {
|
|
245
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
246
|
+
return {
|
|
247
|
+
type: "autoFlow.loadConfig",
|
|
248
|
+
name: requireNonEmptyString(parsed, "name"),
|
|
249
|
+
...(flowId ? { flowId } : {}),
|
|
250
|
+
...(actionId ? { actionId } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (parsed.type === "autoFlow.save") {
|
|
254
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
255
|
+
const name = optionalNonEmptyString(parsed, "name");
|
|
256
|
+
const location = optionalNonEmptyString(parsed, "location");
|
|
257
|
+
if (location !== undefined && location !== "project" && location !== "user") {
|
|
258
|
+
throw new Error("location must be project or user.");
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
type: "autoFlow.save",
|
|
262
|
+
...(flowId ? { flowId } : {}),
|
|
263
|
+
...(name ? { name } : {}),
|
|
264
|
+
...(location ? { location } : {}),
|
|
265
|
+
...(actionId ? { actionId } : {}),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (parsed.type === "autoFlow.reset") {
|
|
269
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
270
|
+
return { type: "autoFlow.reset", ...(flowId ? { flowId } : {}), ...(actionId ? { actionId } : {}) };
|
|
271
|
+
}
|
|
272
|
+
if (parsed.type === "autoFlow.toggleBlock") {
|
|
273
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
274
|
+
const slotId = optionalNonEmptyString(parsed, "slotId");
|
|
275
|
+
const enabled = optionalBoolean(parsed, "enabled");
|
|
276
|
+
return {
|
|
277
|
+
type: "autoFlow.toggleBlock",
|
|
278
|
+
...(flowId ? { flowId } : {}),
|
|
279
|
+
...(slotId ? { slotId } : {}),
|
|
280
|
+
blockId: requireNonEmptyString(parsed, "blockId"),
|
|
281
|
+
...(enabled !== undefined ? { enabled } : {}),
|
|
282
|
+
...(actionId ? { actionId } : {}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (parsed.type === "autoFlow.updateParam") {
|
|
286
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
287
|
+
const slotId = optionalNonEmptyString(parsed, "slotId");
|
|
288
|
+
return {
|
|
289
|
+
type: "autoFlow.updateParam",
|
|
290
|
+
...(flowId ? { flowId } : {}),
|
|
291
|
+
...(slotId ? { slotId } : {}),
|
|
292
|
+
blockId: requireNonEmptyString(parsed, "blockId"),
|
|
293
|
+
paramName: requireNonEmptyString(parsed, "paramName"),
|
|
294
|
+
value: optionalInteger(parsed, "value") ?? (() => {
|
|
295
|
+
throw new Error("value must be an integer.");
|
|
296
|
+
})(),
|
|
297
|
+
...(actionId ? { actionId } : {}),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (parsed.type === "autoFlow.insertBlock") {
|
|
301
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
302
|
+
return {
|
|
303
|
+
type: "autoFlow.insertBlock",
|
|
304
|
+
...(flowId ? { flowId } : {}),
|
|
305
|
+
slotId: requireNonEmptyString(parsed, "slotId"),
|
|
306
|
+
blockId: requireNonEmptyString(parsed, "blockId"),
|
|
307
|
+
...(actionId ? { actionId } : {}),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (parsed.type === "autoFlow.removeBlock") {
|
|
311
|
+
const flowId = optionalNonEmptyString(parsed, "flowId");
|
|
312
|
+
return {
|
|
313
|
+
type: "autoFlow.removeBlock",
|
|
314
|
+
...(flowId ? { flowId } : {}),
|
|
315
|
+
slotId: requireNonEmptyString(parsed, "slotId"),
|
|
316
|
+
blockId: requireNonEmptyString(parsed, "blockId"),
|
|
317
|
+
...(actionId ? { actionId } : {}),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
138
320
|
if (parsed.type === "help.toggle") {
|
|
139
321
|
if (parsed.visible !== undefined && typeof parsed.visible !== "boolean") {
|
|
140
322
|
throw new Error("visible must be a boolean when provided.");
|
|
141
323
|
}
|
|
142
324
|
return { type: "help.toggle", ...(parsed.visible !== undefined ? { visible: parsed.visible } : {}), ...(actionId ? { actionId } : {}) };
|
|
143
325
|
}
|
|
326
|
+
if (parsed.type === "git.createBranch") {
|
|
327
|
+
if ("selectedBase" in parsed || "base" in parsed || "baseBranch" in parsed) {
|
|
328
|
+
throw new Error("git.createBranch does not accept a selected base in the MVP.");
|
|
329
|
+
}
|
|
330
|
+
return { type: "git.createBranch", branchName: requireNonEmptyString(parsed, "branchName"), ...(actionId ? { actionId } : {}) };
|
|
331
|
+
}
|
|
332
|
+
if (parsed.type === "git.checkout") {
|
|
333
|
+
return { type: "git.checkout", branchName: requireNonEmptyString(parsed, "branchName"), ...(actionId ? { actionId } : {}) };
|
|
334
|
+
}
|
|
335
|
+
if (parsed.type === "git.stage") {
|
|
336
|
+
return { type: "git.stage", paths: requireStringArray(parsed, "paths"), ...(actionId ? { actionId } : {}) };
|
|
337
|
+
}
|
|
338
|
+
if (parsed.type === "git.unstage") {
|
|
339
|
+
return { type: "git.unstage", paths: requireStringArray(parsed, "paths"), ...(actionId ? { actionId } : {}) };
|
|
340
|
+
}
|
|
341
|
+
if (parsed.type === "git.updateCommitMessage") {
|
|
342
|
+
if (typeof parsed.message !== "string") {
|
|
343
|
+
throw new Error("message must be a string.");
|
|
344
|
+
}
|
|
345
|
+
return { type: "git.updateCommitMessage", message: parsed.message, ...(actionId ? { actionId } : {}) };
|
|
346
|
+
}
|
|
347
|
+
if (parsed.type === "git.commit") {
|
|
348
|
+
const paths = optionalStringArray(parsed, "paths");
|
|
349
|
+
return {
|
|
350
|
+
type: "git.commit",
|
|
351
|
+
message: requireCommitMessage(parsed),
|
|
352
|
+
...(paths !== undefined ? { paths } : {}),
|
|
353
|
+
...(actionId ? { actionId } : {}),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (parsed.type === "settings.update") {
|
|
357
|
+
return { type: "settings.update", settings: requireSettingsPatch(parsed), ...(actionId ? { actionId } : {}) };
|
|
358
|
+
}
|
|
144
359
|
const pane = requireNonEmptyString(parsed, "pane");
|
|
145
360
|
if (!SCROLL_PANES.has(pane)) {
|
|
146
361
|
throw new Error("scroll pane must be one of flows, progress, summary, log, or help.");
|
|
@@ -7,6 +7,7 @@ import process from "node:process";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { parseArtifactReference } from "../../artifact-manifest.js";
|
|
9
9
|
import { scopeArtifactsDir, scopeWorkspaceDir } from "../../artifacts.js";
|
|
10
|
+
import { GitDiffError } from "../../git/git-service.js";
|
|
10
11
|
import { groupArtifactCatalog, inferArtifactRenderKind, inferArtifactRole, inferArtifactTitle, } from "../../runtime/artifact-catalog.js";
|
|
11
12
|
import { createArtifactRegistry } from "../../runtime/artifact-registry.js";
|
|
12
13
|
import { parseClientAction } from "./protocol.js";
|
|
@@ -22,6 +23,17 @@ const CONTENT_TYPES = new Map([
|
|
|
22
23
|
]);
|
|
23
24
|
const BASIC_AUTH_REALM = "AgentWeaver Web UI";
|
|
24
25
|
const ARTIFACT_API_PREFIX = "/__agentweaver/api/artifacts";
|
|
26
|
+
const GIT_DIFF_API_PATH = "/__agentweaver/api/git/diff";
|
|
27
|
+
const GIT_DIFF_ERROR_STATUSES = {
|
|
28
|
+
missing_path: 400,
|
|
29
|
+
invalid_path: 403,
|
|
30
|
+
invalid_mode: 400,
|
|
31
|
+
missing_provider: 503,
|
|
32
|
+
repository_unavailable: 503,
|
|
33
|
+
forbidden_path: 403,
|
|
34
|
+
read_failed: 500,
|
|
35
|
+
git_failed: 500,
|
|
36
|
+
};
|
|
25
37
|
const ARTIFACT_ERROR_STATUSES = {
|
|
26
38
|
invalid_id: 400,
|
|
27
39
|
missing_scope: 400,
|
|
@@ -136,6 +148,9 @@ function writeArtifactApiError(response, code, message, artifact) {
|
|
|
136
148
|
...(artifact ? { artifact } : {}),
|
|
137
149
|
});
|
|
138
150
|
}
|
|
151
|
+
function writeGitDiffApiError(response, code, message) {
|
|
152
|
+
writeJson(response, GIT_DIFF_ERROR_STATUSES[code], { code, message });
|
|
153
|
+
}
|
|
139
154
|
function safeArtifactMetadata(item) {
|
|
140
155
|
return {
|
|
141
156
|
id: item.id,
|
|
@@ -195,13 +210,9 @@ function parseArtifactApiRoute(requestUrl) {
|
|
|
195
210
|
}
|
|
196
211
|
return { kind: "content", artifactId: "", action: "preview", scopeKey, runIds };
|
|
197
212
|
}
|
|
198
|
-
function filterCatalog(catalog, scopeKey
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
if (runIdSet.size > 0) {
|
|
202
|
-
items = items.filter((item) => item.runId !== null && runIdSet.has(item.runId));
|
|
203
|
-
}
|
|
204
|
-
const sortedItems = items.slice();
|
|
213
|
+
function filterCatalog(catalog, scopeKey) {
|
|
214
|
+
const markdownItems = catalog.items.filter((item) => item.scopeKey === scopeKey && item.kind === "markdown");
|
|
215
|
+
const sortedItems = markdownItems.slice();
|
|
205
216
|
return {
|
|
206
217
|
scopeKey,
|
|
207
218
|
items: sortedItems,
|
|
@@ -460,11 +471,8 @@ function handleArtifactApiRequest(request, response, options, auth) {
|
|
|
460
471
|
writeArtifactApiError(response, "missing_scope", "A scope query parameter is required.");
|
|
461
472
|
return true;
|
|
462
473
|
}
|
|
463
|
-
const primaryRunId = route.runIds[0];
|
|
464
474
|
void loadArtifactCatalog(options, {
|
|
465
475
|
scopeKey: route.scopeKey,
|
|
466
|
-
...(route.runIds.length === 1 && primaryRunId ? { runId: primaryRunId, runIds: route.runIds } : {}),
|
|
467
|
-
...(route.runIds.length > 1 ? { runIds: route.runIds } : {}),
|
|
468
476
|
})
|
|
469
477
|
.then((catalog) => {
|
|
470
478
|
if (!catalog) {
|
|
@@ -475,7 +483,7 @@ function handleArtifactApiRequest(request, response, options, auth) {
|
|
|
475
483
|
writeArtifactApiError(response, "scope_mismatch", "Requested scope does not match the active Web UI scope.");
|
|
476
484
|
return;
|
|
477
485
|
}
|
|
478
|
-
writeJson(response, 200, filterCatalog(catalog, route.scopeKey
|
|
486
|
+
writeJson(response, 200, filterCatalog(catalog, route.scopeKey));
|
|
479
487
|
})
|
|
480
488
|
.catch(() => {
|
|
481
489
|
writeArtifactApiError(response, "read_failed", "Artifact catalog could not be loaded.");
|
|
@@ -486,11 +494,8 @@ function handleArtifactApiRequest(request, response, options, auth) {
|
|
|
486
494
|
writeArtifactApiError(response, "invalid_id", "Artifact identifier is invalid.");
|
|
487
495
|
return true;
|
|
488
496
|
}
|
|
489
|
-
const primaryRunId = route.runIds[0];
|
|
490
497
|
void loadArtifactCatalog(options, {
|
|
491
498
|
...(route.scopeKey ? { scopeKey: route.scopeKey } : {}),
|
|
492
|
-
...(route.runIds.length === 1 && primaryRunId ? { runId: primaryRunId, runIds: route.runIds } : {}),
|
|
493
|
-
...(route.runIds.length > 1 ? { runIds: route.runIds } : {}),
|
|
494
499
|
})
|
|
495
500
|
.then((catalog) => {
|
|
496
501
|
if (!catalog) {
|
|
@@ -521,6 +526,56 @@ function handleArtifactApiRequest(request, response, options, auth) {
|
|
|
521
526
|
});
|
|
522
527
|
return true;
|
|
523
528
|
}
|
|
529
|
+
function isGitDiffMode(value) {
|
|
530
|
+
return value === "head" || value === "staged" || value === "worktree";
|
|
531
|
+
}
|
|
532
|
+
function gitDiffErrorCode(error) {
|
|
533
|
+
if (error instanceof GitDiffError && error.code in GIT_DIFF_ERROR_STATUSES) {
|
|
534
|
+
return error.code;
|
|
535
|
+
}
|
|
536
|
+
return "git_failed";
|
|
537
|
+
}
|
|
538
|
+
function handleGitDiffApiRequest(request, response, options, auth) {
|
|
539
|
+
const parsed = new URL(request.url ?? "/", "http://agentweaver.local");
|
|
540
|
+
if (parsed.pathname !== GIT_DIFF_API_PATH) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
if (request.method !== "GET") {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
if (!isAuthorized(request, auth)) {
|
|
547
|
+
writeAuthRequired(response);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
if (!options.gitService || !options.getGitWorkspaceSnapshot) {
|
|
551
|
+
writeGitDiffApiError(response, "missing_provider", "Git diff provider is not configured.");
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
const filePath = parsed.searchParams.get("path");
|
|
555
|
+
const mode = parsed.searchParams.get("mode");
|
|
556
|
+
if (!filePath) {
|
|
557
|
+
writeGitDiffApiError(response, "missing_path", "A path query parameter is required.");
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
if (!isGitDiffMode(mode)) {
|
|
561
|
+
writeGitDiffApiError(response, "invalid_mode", "Mode must be head, staged, or worktree.");
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
const snapshot = options.getGitWorkspaceSnapshot();
|
|
565
|
+
if (!snapshot || !snapshot.available || !snapshot.repositoryRoot) {
|
|
566
|
+
writeGitDiffApiError(response, "repository_unavailable", snapshot?.error ?? "Git repository is not available.");
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
void options.gitService.diffFile(filePath, mode, snapshot)
|
|
570
|
+
.then((diff) => {
|
|
571
|
+
writeJson(response, 200, diff);
|
|
572
|
+
})
|
|
573
|
+
.catch((error) => {
|
|
574
|
+
const code = gitDiffErrorCode(error);
|
|
575
|
+
writeGitDiffApiError(response, code, error.message || "Git diff request failed.");
|
|
576
|
+
});
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
524
579
|
function htmlShell() {
|
|
525
580
|
return `<!doctype html>
|
|
526
581
|
<html lang="en">
|
|
@@ -843,6 +898,9 @@ export async function startWebServer(options) {
|
|
|
843
898
|
writeJson(response, 200, { ok: true });
|
|
844
899
|
return;
|
|
845
900
|
}
|
|
901
|
+
if (handleGitDiffApiRequest(request, response, options, auth)) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
846
904
|
if (handleArtifactApiRequest(request, response, options, auth)) {
|
|
847
905
|
return;
|
|
848
906
|
}
|