agentweaver 0.1.18 → 0.1.19
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 +8 -0
- package/dist/index.js +6 -2
- package/dist/interactive/controller.js +74 -0
- package/dist/interactive/state.js +9 -0
- package/dist/interactive/web/index.js +149 -2
- package/dist/interactive/web/protocol.js +7 -1
- package/dist/interactive/web/server.js +439 -3
- package/dist/interactive/web/static/app.js +873 -2
- package/dist/interactive/web/static/index.html +37 -0
- package/dist/interactive/web/static/styles.css +1 -1
- package/dist/interactive/web/static/styles.input.css +380 -0
- package/dist/runtime/artifact-catalog.js +379 -0
- package/package.json +1 -1
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildLogicalKeyForPayload } from "../artifact-manifest.js";
|
|
4
|
+
import { artifactIndexFile, artifactManifestSidecarPath, scopeArtifactsDir, scopeWorkspaceDir, } from "../artifacts.js";
|
|
5
|
+
const UNCLASSIFIED_PHASE_ID = "unclassified";
|
|
6
|
+
const ROLE_MAPPINGS = [
|
|
7
|
+
{ prefix: "bug-fix-design", role: "design", title: "Bug Fix Design" },
|
|
8
|
+
{ prefix: "bug-fix-plan", role: "plan", title: "Bug Fix Plan" },
|
|
9
|
+
{ prefix: "bug-analyze", role: "analysis", title: "Bug Analysis" },
|
|
10
|
+
{ prefix: "task-context", role: "context", title: "Task Context" },
|
|
11
|
+
{ prefix: "gitlab-diff", role: "diff", title: "GitLab Diff" },
|
|
12
|
+
{ prefix: "jira-description", role: "context", title: "Jira Description" },
|
|
13
|
+
{ prefix: "design", role: "design", title: "Design" },
|
|
14
|
+
{ prefix: "plan", role: "plan", title: "Plan" },
|
|
15
|
+
{ prefix: "review", role: "review", title: "Review" },
|
|
16
|
+
{ prefix: "qa", role: "qa", title: "QA Plan" },
|
|
17
|
+
{ prefix: "task", role: "summary", title: "Task Summary" },
|
|
18
|
+
];
|
|
19
|
+
const ROLE_ORDER = new Map([
|
|
20
|
+
["context", 10],
|
|
21
|
+
["analysis", 20],
|
|
22
|
+
["design", 30],
|
|
23
|
+
["plan", 40],
|
|
24
|
+
["qa", 50],
|
|
25
|
+
["review", 60],
|
|
26
|
+
["diff", 70],
|
|
27
|
+
["summary", 80],
|
|
28
|
+
["artifact", 90],
|
|
29
|
+
]);
|
|
30
|
+
const BINARY_EXTENSIONS = new Set([
|
|
31
|
+
".avif",
|
|
32
|
+
".bin",
|
|
33
|
+
".bmp",
|
|
34
|
+
".gif",
|
|
35
|
+
".gz",
|
|
36
|
+
".ico",
|
|
37
|
+
".jpeg",
|
|
38
|
+
".jpg",
|
|
39
|
+
".pdf",
|
|
40
|
+
".png",
|
|
41
|
+
".tar",
|
|
42
|
+
".tgz",
|
|
43
|
+
".webp",
|
|
44
|
+
".zip",
|
|
45
|
+
]);
|
|
46
|
+
function normalizePathSeparators(value) {
|
|
47
|
+
return value.replace(/\\/g, "/");
|
|
48
|
+
}
|
|
49
|
+
function normalizedAbsolutePath(filePath) {
|
|
50
|
+
return path.normalize(path.resolve(filePath));
|
|
51
|
+
}
|
|
52
|
+
function isInsideDirectory(parent, candidate) {
|
|
53
|
+
const relative = path.relative(parent, candidate);
|
|
54
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
55
|
+
}
|
|
56
|
+
function scopeRelativePath(scopeKey, payloadPath) {
|
|
57
|
+
const workspaceDir = normalizedAbsolutePath(scopeWorkspaceDir(scopeKey));
|
|
58
|
+
const normalizedPayloadPath = normalizedAbsolutePath(payloadPath);
|
|
59
|
+
if (!isInsideDirectory(workspaceDir, normalizedPayloadPath)) {
|
|
60
|
+
return normalizePathSeparators(path.basename(normalizedPayloadPath));
|
|
61
|
+
}
|
|
62
|
+
const relativePath = path.relative(workspaceDir, normalizedPayloadPath);
|
|
63
|
+
return normalizePathSeparators(relativePath || path.basename(normalizedPayloadPath));
|
|
64
|
+
}
|
|
65
|
+
function fileMetadata(filePath, fallbackIso) {
|
|
66
|
+
try {
|
|
67
|
+
const stats = statSync(filePath);
|
|
68
|
+
return {
|
|
69
|
+
sizeBytes: stats.size,
|
|
70
|
+
updatedAt: stats.mtime.toISOString(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return {
|
|
75
|
+
sizeBytes: 0,
|
|
76
|
+
updatedAt: fallbackIso,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function cleanBaseName(value) {
|
|
81
|
+
const extension = path.extname(value);
|
|
82
|
+
return extension ? value.slice(0, -extension.length) : value;
|
|
83
|
+
}
|
|
84
|
+
function stripScopeAndVersionSuffix(value, scopeKey) {
|
|
85
|
+
const escapedScope = scopeKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
86
|
+
return value
|
|
87
|
+
.replace(new RegExp(`-${escapedScope}-iter-\\d+$`), "")
|
|
88
|
+
.replace(new RegExp(`-${escapedScope}-\\d+$`), "")
|
|
89
|
+
.replace(new RegExp(`-${escapedScope}$`), "")
|
|
90
|
+
.replace(/-\d+$/, "");
|
|
91
|
+
}
|
|
92
|
+
function toTitleCase(value) {
|
|
93
|
+
return value
|
|
94
|
+
.replace(/[/_.-]+/g, " ")
|
|
95
|
+
.trim()
|
|
96
|
+
.split(/\s+/)
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
99
|
+
.join(" ");
|
|
100
|
+
}
|
|
101
|
+
function mappingForKey(logicalKeyOrPath) {
|
|
102
|
+
const normalized = logicalKeyOrPath.replace(/^\.artifacts\//, "").replace(/^artifacts\//, "");
|
|
103
|
+
const fileStem = cleanBaseName(path.posix.basename(normalized));
|
|
104
|
+
const candidates = [normalized, fileStem];
|
|
105
|
+
return ROLE_MAPPINGS.find((mapping) => (candidates.some((candidate) => candidate === mapping.prefix || candidate.startsWith(`${mapping.prefix}-`)))) ?? null;
|
|
106
|
+
}
|
|
107
|
+
export function inferArtifactRole(logicalKey, relativePath) {
|
|
108
|
+
const mapping = mappingForKey(logicalKey ?? relativePath);
|
|
109
|
+
return mapping?.role ?? "artifact";
|
|
110
|
+
}
|
|
111
|
+
export function inferArtifactTitle(scopeKey, logicalKey, relativePath) {
|
|
112
|
+
const source = logicalKey ?? relativePath;
|
|
113
|
+
const mapping = mappingForKey(source);
|
|
114
|
+
if (mapping) {
|
|
115
|
+
return mapping.title;
|
|
116
|
+
}
|
|
117
|
+
const stem = stripScopeAndVersionSuffix(cleanBaseName(path.posix.basename(normalizePathSeparators(source))), scopeKey);
|
|
118
|
+
return toTitleCase(stem) || "Artifact";
|
|
119
|
+
}
|
|
120
|
+
function kindFromPayloadFamily(payloadFamily) {
|
|
121
|
+
if (payloadFamily === "markdown") {
|
|
122
|
+
return "markdown";
|
|
123
|
+
}
|
|
124
|
+
if (payloadFamily === "structured-json" || payloadFamily === "helper-json") {
|
|
125
|
+
return "json";
|
|
126
|
+
}
|
|
127
|
+
if (payloadFamily === "plain-text") {
|
|
128
|
+
return "text";
|
|
129
|
+
}
|
|
130
|
+
if (payloadFamily === "opaque-file") {
|
|
131
|
+
return "binary";
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
function kindFromSchemaId(schemaId) {
|
|
136
|
+
if (!schemaId) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const normalized = schemaId.toLowerCase();
|
|
140
|
+
if (normalized.includes("markdown")) {
|
|
141
|
+
return "markdown";
|
|
142
|
+
}
|
|
143
|
+
if (normalized.includes("diff") || normalized.includes("patch")) {
|
|
144
|
+
return "diff";
|
|
145
|
+
}
|
|
146
|
+
if (normalized.includes("text") || normalized.includes("log")) {
|
|
147
|
+
return "text";
|
|
148
|
+
}
|
|
149
|
+
if (normalized.endsWith("/v1") || normalized.includes("json")) {
|
|
150
|
+
return "json";
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function kindFromExtension(filePath) {
|
|
155
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
156
|
+
if (extension === ".md" || extension === ".markdown") {
|
|
157
|
+
return "markdown";
|
|
158
|
+
}
|
|
159
|
+
if (extension === ".json") {
|
|
160
|
+
return "json";
|
|
161
|
+
}
|
|
162
|
+
if (extension === ".txt" || extension === ".log") {
|
|
163
|
+
return "text";
|
|
164
|
+
}
|
|
165
|
+
if (extension === ".diff" || extension === ".patch") {
|
|
166
|
+
return "diff";
|
|
167
|
+
}
|
|
168
|
+
if (BINARY_EXTENSIONS.has(extension)) {
|
|
169
|
+
return "binary";
|
|
170
|
+
}
|
|
171
|
+
return "unknown";
|
|
172
|
+
}
|
|
173
|
+
export function inferArtifactRenderKind(input) {
|
|
174
|
+
return kindFromPayloadFamily(input.payloadFamily)
|
|
175
|
+
?? kindFromSchemaId(input.schemaId)
|
|
176
|
+
?? kindFromExtension(input.filePath);
|
|
177
|
+
}
|
|
178
|
+
function projectManifestRecord(scopeKey, record) {
|
|
179
|
+
const manifest = record.manifest;
|
|
180
|
+
const metadata = fileMetadata(manifest.payload_path, manifest.created_at);
|
|
181
|
+
const relativePath = scopeRelativePath(scopeKey, manifest.payload_path);
|
|
182
|
+
const logicalKey = record.logical_key || manifest.logical_key;
|
|
183
|
+
return {
|
|
184
|
+
id: record.artifact_id,
|
|
185
|
+
scopeKey,
|
|
186
|
+
runId: manifest.run_id,
|
|
187
|
+
logicalKey,
|
|
188
|
+
title: inferArtifactTitle(scopeKey, logicalKey, relativePath),
|
|
189
|
+
relativePath,
|
|
190
|
+
kind: inferArtifactRenderKind({
|
|
191
|
+
payloadFamily: manifest.payload_family,
|
|
192
|
+
schemaId: record.schema_id || manifest.schema_id,
|
|
193
|
+
filePath: manifest.payload_path,
|
|
194
|
+
}),
|
|
195
|
+
role: inferArtifactRole(logicalKey, relativePath),
|
|
196
|
+
phaseId: manifest.phase_id || null,
|
|
197
|
+
stepId: manifest.step_id || null,
|
|
198
|
+
schemaId: record.schema_id || manifest.schema_id || null,
|
|
199
|
+
sizeBytes: metadata.sizeBytes,
|
|
200
|
+
updatedAt: metadata.updatedAt,
|
|
201
|
+
isLatest: record.is_latest,
|
|
202
|
+
source: "manifest",
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function isExcludedDirectory(name) {
|
|
206
|
+
return name === "manifest-history" || name === "restart-archives";
|
|
207
|
+
}
|
|
208
|
+
function isRegistryTempFile(filePath) {
|
|
209
|
+
const baseName = path.basename(filePath);
|
|
210
|
+
return /\.tmp-[^/\\]+$/.test(baseName)
|
|
211
|
+
|| baseName.endsWith(".tmp")
|
|
212
|
+
|| baseName.endsWith(".temp")
|
|
213
|
+
|| baseName.endsWith(".swp")
|
|
214
|
+
|| baseName.endsWith("~");
|
|
215
|
+
}
|
|
216
|
+
function isInternalIndexFile(filePath) {
|
|
217
|
+
const baseName = path.basename(filePath);
|
|
218
|
+
return baseName === "artifact-index.json"
|
|
219
|
+
|| baseName === "artifact-registry-index.json"
|
|
220
|
+
|| baseName === "manifest-index.json"
|
|
221
|
+
|| baseName === "internal-index.json"
|
|
222
|
+
|| baseName === ".index.json";
|
|
223
|
+
}
|
|
224
|
+
function isExcludedCatalogPath(scopeKey, filePath) {
|
|
225
|
+
const normalizedPath = normalizedAbsolutePath(filePath);
|
|
226
|
+
const workspaceDir = normalizedAbsolutePath(scopeWorkspaceDir(scopeKey));
|
|
227
|
+
if (!isInsideDirectory(workspaceDir, normalizedPath)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
const relativePath = normalizePathSeparators(path.relative(workspaceDir, normalizedPath));
|
|
231
|
+
const segments = relativePath.split("/");
|
|
232
|
+
if (segments.some((segment) => segment === "manifest-history" || segment === "restart-archives")) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
if (normalizedPath === normalizedAbsolutePath(artifactIndexFile(scopeKey))) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (normalizedPath.endsWith(".manifest.json") || normalizedPath === normalizedAbsolutePath(artifactManifestSidecarPath(normalizedPath))) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (isInternalIndexFile(normalizedPath) || isRegistryTempFile(normalizedPath)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
return path.basename(normalizedPath) === ".DS_Store";
|
|
245
|
+
}
|
|
246
|
+
function scanRoot(scopeKey, rootDir, seenDirectories) {
|
|
247
|
+
const normalizedRoot = normalizedAbsolutePath(rootDir);
|
|
248
|
+
const workspaceDir = normalizedAbsolutePath(scopeWorkspaceDir(scopeKey));
|
|
249
|
+
if (!existsSync(normalizedRoot) || !isInsideDirectory(workspaceDir, normalizedRoot)) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
const files = [];
|
|
253
|
+
const queue = [normalizedRoot];
|
|
254
|
+
while (queue.length > 0) {
|
|
255
|
+
const current = queue.shift();
|
|
256
|
+
if (!current) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const normalizedCurrent = normalizedAbsolutePath(current);
|
|
260
|
+
if (seenDirectories.has(normalizedCurrent)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
seenDirectories.add(normalizedCurrent);
|
|
264
|
+
let entries;
|
|
265
|
+
try {
|
|
266
|
+
entries = readdirSync(normalizedCurrent, { withFileTypes: true })
|
|
267
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
const fullPath = path.join(normalizedCurrent, entry.name);
|
|
274
|
+
if (entry.isSymbolicLink()) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (entry.isDirectory()) {
|
|
278
|
+
if (!isExcludedDirectory(entry.name)) {
|
|
279
|
+
queue.push(fullPath);
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (entry.isFile() && !isExcludedCatalogPath(scopeKey, fullPath)) {
|
|
284
|
+
files.push(normalizedAbsolutePath(fullPath));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return files;
|
|
289
|
+
}
|
|
290
|
+
function scanScopeFiles(scopeKey) {
|
|
291
|
+
const seenDirectories = new Set();
|
|
292
|
+
const files = [
|
|
293
|
+
...scanRoot(scopeKey, scopeWorkspaceDir(scopeKey), seenDirectories),
|
|
294
|
+
...scanRoot(scopeKey, scopeArtifactsDir(scopeKey), seenDirectories),
|
|
295
|
+
];
|
|
296
|
+
return [...new Set(files)].sort((left, right) => scopeRelativePath(scopeKey, left).localeCompare(scopeRelativePath(scopeKey, right)));
|
|
297
|
+
}
|
|
298
|
+
function projectScannedFile(scopeKey, filePath) {
|
|
299
|
+
const relativePath = scopeRelativePath(scopeKey, filePath);
|
|
300
|
+
const logicalKey = buildLogicalKeyForPayload(scopeKey, filePath);
|
|
301
|
+
const metadata = fileMetadata(filePath, new Date(0).toISOString());
|
|
302
|
+
return {
|
|
303
|
+
id: `scanner:${scopeKey}:${relativePath}`,
|
|
304
|
+
scopeKey,
|
|
305
|
+
runId: null,
|
|
306
|
+
logicalKey,
|
|
307
|
+
title: inferArtifactTitle(scopeKey, logicalKey, relativePath),
|
|
308
|
+
relativePath,
|
|
309
|
+
kind: inferArtifactRenderKind({ filePath }),
|
|
310
|
+
role: inferArtifactRole(logicalKey, relativePath),
|
|
311
|
+
phaseId: null,
|
|
312
|
+
stepId: null,
|
|
313
|
+
schemaId: null,
|
|
314
|
+
sizeBytes: metadata.sizeBytes,
|
|
315
|
+
updatedAt: metadata.updatedAt,
|
|
316
|
+
isLatest: true,
|
|
317
|
+
source: "scanner",
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
function roleRank(role) {
|
|
321
|
+
return ROLE_ORDER.get(role) ?? 100;
|
|
322
|
+
}
|
|
323
|
+
function phaseSortKey(phaseId) {
|
|
324
|
+
return phaseId ?? UNCLASSIFIED_PHASE_ID;
|
|
325
|
+
}
|
|
326
|
+
function compareCatalogItems(left, right) {
|
|
327
|
+
const phaseComparison = phaseSortKey(left.phaseId).localeCompare(phaseSortKey(right.phaseId));
|
|
328
|
+
if (phaseComparison !== 0) {
|
|
329
|
+
return phaseComparison;
|
|
330
|
+
}
|
|
331
|
+
const roleComparison = roleRank(left.role) - roleRank(right.role);
|
|
332
|
+
if (roleComparison !== 0) {
|
|
333
|
+
return roleComparison;
|
|
334
|
+
}
|
|
335
|
+
const titleComparison = left.title.localeCompare(right.title);
|
|
336
|
+
if (titleComparison !== 0) {
|
|
337
|
+
return titleComparison;
|
|
338
|
+
}
|
|
339
|
+
return left.relativePath.localeCompare(right.relativePath);
|
|
340
|
+
}
|
|
341
|
+
export function groupArtifactCatalog(items) {
|
|
342
|
+
const grouped = new Map();
|
|
343
|
+
for (const item of items) {
|
|
344
|
+
const phaseId = item.phaseId ?? UNCLASSIFIED_PHASE_ID;
|
|
345
|
+
const phaseItems = grouped.get(phaseId) ?? [];
|
|
346
|
+
phaseItems.push(item);
|
|
347
|
+
grouped.set(phaseId, phaseItems);
|
|
348
|
+
}
|
|
349
|
+
return Array.from(grouped.entries())
|
|
350
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
351
|
+
.map(([phaseId, phaseItems]) => ({
|
|
352
|
+
phaseId,
|
|
353
|
+
title: phaseId === UNCLASSIFIED_PHASE_ID ? "Unclassified" : toTitleCase(phaseId),
|
|
354
|
+
items: phaseItems.slice().sort(compareCatalogItems),
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
export function listArtifactCatalog(input) {
|
|
358
|
+
const seenPayloadPaths = new Set();
|
|
359
|
+
const items = [];
|
|
360
|
+
for (const record of input.artifactRegistry.listScopeArtifacts(input.scopeKey)) {
|
|
361
|
+
const payloadPath = normalizedAbsolutePath(record.manifest.payload_path);
|
|
362
|
+
seenPayloadPaths.add(payloadPath);
|
|
363
|
+
items.push(projectManifestRecord(input.scopeKey, record));
|
|
364
|
+
}
|
|
365
|
+
for (const filePath of scanScopeFiles(input.scopeKey)) {
|
|
366
|
+
const normalizedPath = normalizedAbsolutePath(filePath);
|
|
367
|
+
if (seenPayloadPaths.has(normalizedPath)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
seenPayloadPaths.add(normalizedPath);
|
|
371
|
+
items.push(projectScannedFile(input.scopeKey, normalizedPath));
|
|
372
|
+
}
|
|
373
|
+
const sortedItems = items.slice().sort(compareCatalogItems);
|
|
374
|
+
return {
|
|
375
|
+
scopeKey: input.scopeKey,
|
|
376
|
+
items: sortedItems,
|
|
377
|
+
groups: groupArtifactCatalog(sortedItems),
|
|
378
|
+
};
|
|
379
|
+
}
|