@desplega.ai/agent-swarm 1.92.0 → 1.92.1
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 +1 -1
- package/openapi.json +276 -3
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +327 -20
- package/src/be/memory/constants.ts +2 -1
- package/src/be/memory/providers/openai-embedding.ts +2 -5
- package/src/be/memory/providers/sqlite-store.ts +293 -76
- package/src/be/memory/types.ts +35 -0
- package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
- package/src/be/migrations/085_script_runs_kind.sql +9 -0
- package/src/be/migrations/086_pages_default_authed.sql +64 -0
- package/src/be/migrations/087_skill_files.sql +19 -0
- package/src/be/modelsdev-cache.json +264 -328
- package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
- package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
- package/src/be/seed-scripts/catalog/compound-insights.ts +94 -0
- package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
- package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
- package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +34 -439
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/index.ts +32 -4
- package/src/be/seed-skills/index.ts +0 -7
- package/src/be/skill-sync.ts +91 -7
- package/src/commands/runner.ts +6 -2
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/index.ts +41 -7
- package/src/http/mcp-user.ts +23 -0
- package/src/http/mcp.ts +58 -0
- package/src/http/memory.ts +58 -0
- package/src/http/pages.ts +1 -1
- package/src/http/script-runs.ts +2 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/providers/claude-adapter.ts +56 -24
- package/src/script-workflows/workflow-ctx.ts +7 -3
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +1 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +1 -0
- package/src/server.ts +2 -0
- package/src/tests/claude-adapter-binary.test.ts +135 -81
- package/src/tests/create-page-tool.test.ts +19 -2
- package/src/tests/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-transport-gc.test.ts +58 -0
- package/src/tests/memory-health-endpoint.test.ts +78 -0
- package/src/tests/memory-store.test.ts +221 -1
- package/src/tests/pages-http.test.ts +20 -2
- package/src/tests/pages-storage.test.ts +26 -0
- package/src/tests/scripts-mcp-e2e.test.ts +53 -0
- package/src/tests/seed-scripts.test.ts +123 -3
- package/src/tests/skill-files-http.test.ts +171 -0
- package/src/tests/skill-files.test.ts +162 -0
- package/src/tests/skill-get-file-tool.test.ts +110 -0
- package/src/tests/skill-sync.test.ts +125 -6
- package/src/tools/create-page.ts +2 -2
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/tool-config.ts +2 -1
- package/src/types.ts +20 -0
- package/src/utils/internal-ai/complete-structured.ts +2 -2
- package/templates/schedules/daily-blocker-digest/content.md +68 -54
- package/templates/schedules/daily-compounding-reflection/content.md +4 -4
- package/templates/schedules/daily-hn-briefing/content.md +5 -5
- package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
- package/templates/schedules/gtm-weekly-review/content.md +9 -9
- package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
- package/templates/skills/agentmail-sending/content.md +6 -7
- package/templates/skills/desloppify/content.md +8 -9
- package/templates/skills/jira-interaction/content.md +25 -33
- package/templates/skills/kapso-whatsapp/content.md +29 -30
- package/templates/skills/linear-interaction/content.md +8 -9
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/turso-interaction/content.md +14 -17
- package/templates/skills/workflow-iterate/content.md +38 -391
- package/templates/skills/x-api-interactions/content.md +4 -6
- package/templates/skills/scheduled-task-resilience/config.json +0 -14
- package/templates/skills/scheduled-task-resilience/content.md +0 -95
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
type CatalogReport,
|
|
4
|
+
publishCatalogReportPage,
|
|
5
|
+
renderCatalogReportPage,
|
|
6
|
+
} from "./catalog-report";
|
|
2
7
|
|
|
3
8
|
export const argsSchema = z.object({
|
|
4
9
|
nowIso: z.string().optional().describe("Audit clock override (default: current time)"),
|
|
@@ -22,7 +27,7 @@ export const argsSchema = z.object({
|
|
|
22
27
|
const CODE_WORK_RE =
|
|
23
28
|
/\b(git|github|gh\b|gh-cli|docker|docker-compose|bun|npm|pnpm|yarn|tsc|eslint|lint|test|pr\b|pull request|branch|commit|repo|worktree|typescript|javascript)\b/i;
|
|
24
29
|
const CODE_AGENT_RE =
|
|
25
|
-
/\b(code|coder|coding|implement|implementation|engineer|software|typescript|javascript|repo|github
|
|
30
|
+
/\b(code|coder|coding|implement|implementation|engineer|software|typescript|javascript|repo|github)\b/i;
|
|
26
31
|
const NON_CODE_AGENT_RE = /\b(content|reviewer|research|sales|gtm|support|ops|lead)\b/i;
|
|
27
32
|
const SMOKE_WORKFLOW_RE =
|
|
28
33
|
/\b(smoke|demo|litmus-smoke|one[- ]shot|validation|des-462-gate-validation|gsc-runtime-smoke)\b/i;
|
|
@@ -74,6 +79,11 @@ function compactText(value: unknown, max = 180): string {
|
|
|
74
79
|
return asText(value).replace(/\s+/g, " ").trim().slice(0, max);
|
|
75
80
|
}
|
|
76
81
|
|
|
82
|
+
function formatMetric(value: unknown): string {
|
|
83
|
+
if (typeof value === "number") return new Intl.NumberFormat("en-US").format(value);
|
|
84
|
+
return asText(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
function daysSince(iso: string | null | undefined, nowMs: number): number | null {
|
|
78
88
|
if (!iso) return null;
|
|
79
89
|
const t = Date.parse(iso);
|
|
@@ -115,449 +125,34 @@ function hasStructuredOutput(value: any): boolean {
|
|
|
115
125
|
return /"outputSchema"|"schema"|"jsonSchema"|"structured"/i.test(JSON.stringify(value ?? {}));
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
function
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
129
|
-
.replace(/[-_.]+/g, " ")
|
|
130
|
-
.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function formatMetric(value: unknown): string {
|
|
134
|
-
if (typeof value === "number") return new Intl.NumberFormat("en-US").format(value);
|
|
135
|
-
return asText(value);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function severityTone(value: string): string {
|
|
139
|
-
if (value === "critical") return "danger";
|
|
140
|
-
if (value === "high") return "warn";
|
|
141
|
-
if (value === "medium") return "note";
|
|
142
|
-
return "low";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function renderSampleValue(value: unknown): string {
|
|
146
|
-
if (Array.isArray(value)) {
|
|
147
|
-
return value
|
|
148
|
-
.map((item) => (typeof item === "object" && item !== null ? JSON.stringify(item) : asText(item)))
|
|
149
|
-
.join(", ");
|
|
150
|
-
}
|
|
151
|
-
if (value && typeof value === "object") return JSON.stringify(value);
|
|
152
|
-
return asText(value);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function renderSamples(samples: any[]): string {
|
|
156
|
-
if (!Array.isArray(samples) || samples.length === 0) return "";
|
|
157
|
-
const normalized = samples.map((sampleRow) =>
|
|
158
|
-
sampleRow && typeof sampleRow === "object" && !Array.isArray(sampleRow)
|
|
159
|
-
? sampleRow
|
|
160
|
-
: { value: sampleRow },
|
|
161
|
-
);
|
|
162
|
-
const columns = Array.from(
|
|
163
|
-
new Set(normalized.flatMap((sampleRow) => Object.keys(sampleRow).slice(0, 6))),
|
|
164
|
-
).slice(0, 6);
|
|
165
|
-
if (columns.length === 0) return "";
|
|
166
|
-
const rows = normalized
|
|
167
|
-
.map(
|
|
168
|
-
(sampleRow) =>
|
|
169
|
-
`<tr>${columns
|
|
170
|
-
.map((column) => `<td>${htmlEscape(renderSampleValue(sampleRow[column]))}</td>`)
|
|
171
|
-
.join("")}</tr>`,
|
|
172
|
-
)
|
|
173
|
-
.join("");
|
|
174
|
-
return `<div class="sample-table" aria-label="Sample rows">
|
|
175
|
-
<table>
|
|
176
|
-
<thead><tr>${columns.map((column) => `<th>${htmlEscape(humanLabel(column))}</th>`).join("")}</tr></thead>
|
|
177
|
-
<tbody>${rows}</tbody>
|
|
178
|
-
</table>
|
|
179
|
-
</div>`;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function renderPage(result: any): string {
|
|
183
|
-
const metrics = [
|
|
128
|
+
export function buildReport(result: any): CatalogReport {
|
|
129
|
+
return {
|
|
130
|
+
title: "Ops Catalog Audit",
|
|
131
|
+
slug: "ops-catalog-audit",
|
|
132
|
+
description: "Clustered audit-as-code report for schedules, workflows, and prompts/templates.",
|
|
133
|
+
generatedAt: result.generatedAt,
|
|
134
|
+
lede: `A re-runnable audit of schedules, workflows, and prompt/template catalogs. It found ${formatMetric(
|
|
135
|
+
result.summary.findingsTotal,
|
|
136
|
+
)} actionable issue cluster(s), with the highest-risk items called out first inside each group.`,
|
|
137
|
+
metrics: [
|
|
184
138
|
["Findings", result.summary.findingsTotal],
|
|
185
139
|
["Schedules enabled", result.summary.schedulesEnabled],
|
|
186
140
|
["Workflows enabled", result.summary.workflowsEnabled],
|
|
187
141
|
["Prompt templates", result.summary.promptTemplates],
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<h3>${htmlEscape(finding.summary)}</h3>
|
|
199
|
-
</div>
|
|
200
|
-
<span class="pill ${htmlEscape(severityTone(finding.severity))}">${htmlEscape(
|
|
201
|
-
finding.severity,
|
|
202
|
-
)}</span>
|
|
203
|
-
</div>
|
|
204
|
-
<p class="action">${htmlEscape(finding.action)}</p>
|
|
205
|
-
${renderSamples(finding.samples)}
|
|
206
|
-
</article>`,
|
|
207
|
-
)
|
|
208
|
-
.join("");
|
|
209
|
-
const checks = Object.entries(group.checks || {})
|
|
210
|
-
.map(
|
|
211
|
-
([label, value]) =>
|
|
212
|
-
`<div class="check"><span>${htmlEscape(humanLabel(label))}</span><strong>${htmlEscape(
|
|
213
|
-
formatMetric(value),
|
|
214
|
-
)}</strong></div>`,
|
|
215
|
-
)
|
|
216
|
-
.join("");
|
|
217
|
-
return `<section class="section">
|
|
218
|
-
<div class="section-grid">
|
|
219
|
-
<aside class="checks">
|
|
220
|
-
<p class="section-kicker">${htmlEscape(humanLabel(key))}</p>
|
|
221
|
-
<div class="check-list">${checks}</div>
|
|
222
|
-
</aside>
|
|
223
|
-
<div>
|
|
224
|
-
<div class="section-head">
|
|
225
|
-
<h2>${htmlEscape(group.goal)}</h2>
|
|
226
|
-
<span>${htmlEscape(formatMetric(group.findingCount))} finding(s)</span>
|
|
227
|
-
</div>
|
|
228
|
-
<div class="findings">
|
|
229
|
-
${findings || '<p class="empty">No actionable findings in this cluster.</p>'}
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
</section>`;
|
|
234
|
-
})
|
|
235
|
-
.join("\n");
|
|
236
|
-
return `<!doctype html>
|
|
237
|
-
<html lang="en">
|
|
238
|
-
<head>
|
|
239
|
-
<meta charset="utf-8">
|
|
240
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
241
|
-
<meta name="theme-color" content="#f5f2ea">
|
|
242
|
-
<title>Ops Catalog Audit</title>
|
|
243
|
-
<style>
|
|
244
|
-
:root {
|
|
245
|
-
color-scheme: light;
|
|
246
|
-
--bg: #f5f2ea;
|
|
247
|
-
--panel: #ffffff;
|
|
248
|
-
--ink: #18181b;
|
|
249
|
-
--muted: #5f6368;
|
|
250
|
-
--line: #ded8cb;
|
|
251
|
-
--accent: #255c99;
|
|
252
|
-
--danger: #b42318;
|
|
253
|
-
--danger-bg: #fff1f0;
|
|
254
|
-
--warn: #b54708;
|
|
255
|
-
--warn-bg: #fff7ed;
|
|
256
|
-
--note: #175cd3;
|
|
257
|
-
--note-bg: #eff6ff;
|
|
258
|
-
--low: #067647;
|
|
259
|
-
--low-bg: #ecfdf3;
|
|
260
|
-
--radius: 8px;
|
|
261
|
-
--shadow: 0 1px 2px rgba(24, 24, 27, 0.06), 0 14px 36px rgba(24, 24, 27, 0.07);
|
|
262
|
-
}
|
|
263
|
-
* { box-sizing: border-box; }
|
|
264
|
-
body {
|
|
265
|
-
margin: 0;
|
|
266
|
-
background: var(--bg);
|
|
267
|
-
color: var(--ink);
|
|
268
|
-
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
269
|
-
font-size: 16px;
|
|
270
|
-
line-height: 1.55;
|
|
271
|
-
}
|
|
272
|
-
main {
|
|
273
|
-
width: min(1120px, calc(100% - 32px));
|
|
274
|
-
margin: 0 auto;
|
|
275
|
-
padding: 48px 0 72px;
|
|
276
|
-
}
|
|
277
|
-
header { margin-bottom: 28px; }
|
|
278
|
-
.eyebrow {
|
|
279
|
-
margin: 0 0 8px;
|
|
280
|
-
color: var(--muted);
|
|
281
|
-
font-size: 13px;
|
|
282
|
-
font-weight: 750;
|
|
283
|
-
letter-spacing: 0.08em;
|
|
284
|
-
text-transform: uppercase;
|
|
285
|
-
}
|
|
286
|
-
h1 {
|
|
287
|
-
margin: 0;
|
|
288
|
-
max-width: 860px;
|
|
289
|
-
font-size: clamp(2rem, 4vw, 3rem);
|
|
290
|
-
line-height: 1.05;
|
|
291
|
-
letter-spacing: 0;
|
|
292
|
-
}
|
|
293
|
-
.lede {
|
|
294
|
-
max-width: 780px;
|
|
295
|
-
margin: 16px 0 0;
|
|
296
|
-
color: var(--muted);
|
|
297
|
-
font-size: 18px;
|
|
298
|
-
}
|
|
299
|
-
.metrics {
|
|
300
|
-
display: grid;
|
|
301
|
-
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
302
|
-
gap: 12px;
|
|
303
|
-
margin: 32px 0;
|
|
304
|
-
}
|
|
305
|
-
.metric, .section, details {
|
|
306
|
-
background: var(--panel);
|
|
307
|
-
border: 1px solid var(--line);
|
|
308
|
-
border-radius: var(--radius);
|
|
309
|
-
box-shadow: var(--shadow);
|
|
310
|
-
}
|
|
311
|
-
.metric { padding: 18px; }
|
|
312
|
-
.metric strong {
|
|
313
|
-
display: block;
|
|
314
|
-
font-size: 32px;
|
|
315
|
-
line-height: 1;
|
|
316
|
-
font-variant-numeric: tabular-nums;
|
|
317
|
-
}
|
|
318
|
-
.metric span {
|
|
319
|
-
display: block;
|
|
320
|
-
margin-top: 8px;
|
|
321
|
-
color: var(--muted);
|
|
322
|
-
font-size: 13px;
|
|
323
|
-
font-weight: 650;
|
|
324
|
-
}
|
|
325
|
-
.section {
|
|
326
|
-
margin-top: 18px;
|
|
327
|
-
padding: 24px;
|
|
328
|
-
}
|
|
329
|
-
.section-grid {
|
|
330
|
-
display: grid;
|
|
331
|
-
grid-template-columns: 260px minmax(0, 1fr);
|
|
332
|
-
gap: 28px;
|
|
333
|
-
}
|
|
334
|
-
.section-kicker {
|
|
335
|
-
margin: 0 0 12px;
|
|
336
|
-
color: var(--accent);
|
|
337
|
-
font-size: 13px;
|
|
338
|
-
font-weight: 800;
|
|
339
|
-
letter-spacing: 0.08em;
|
|
340
|
-
text-transform: uppercase;
|
|
341
|
-
}
|
|
342
|
-
.check-list {
|
|
343
|
-
display: grid;
|
|
344
|
-
gap: 8px;
|
|
345
|
-
}
|
|
346
|
-
.check {
|
|
347
|
-
display: flex;
|
|
348
|
-
align-items: baseline;
|
|
349
|
-
justify-content: space-between;
|
|
350
|
-
gap: 12px;
|
|
351
|
-
padding: 10px 0;
|
|
352
|
-
border-bottom: 1px solid var(--line);
|
|
353
|
-
}
|
|
354
|
-
.check span {
|
|
355
|
-
color: var(--muted);
|
|
356
|
-
font-size: 13px;
|
|
357
|
-
}
|
|
358
|
-
.check strong {
|
|
359
|
-
font-size: 18px;
|
|
360
|
-
font-variant-numeric: tabular-nums;
|
|
361
|
-
}
|
|
362
|
-
.section-head {
|
|
363
|
-
display: flex;
|
|
364
|
-
align-items: start;
|
|
365
|
-
justify-content: space-between;
|
|
366
|
-
gap: 16px;
|
|
367
|
-
margin-bottom: 16px;
|
|
368
|
-
}
|
|
369
|
-
.section-head h2 {
|
|
370
|
-
max-width: 680px;
|
|
371
|
-
margin: 0;
|
|
372
|
-
font-size: 24px;
|
|
373
|
-
line-height: 1.2;
|
|
374
|
-
letter-spacing: 0;
|
|
375
|
-
}
|
|
376
|
-
.section-head > span {
|
|
377
|
-
flex: 0 0 auto;
|
|
378
|
-
color: var(--muted);
|
|
379
|
-
font-size: 13px;
|
|
380
|
-
font-weight: 700;
|
|
381
|
-
white-space: nowrap;
|
|
382
|
-
}
|
|
383
|
-
.findings {
|
|
384
|
-
display: grid;
|
|
385
|
-
gap: 12px;
|
|
386
|
-
}
|
|
387
|
-
.finding {
|
|
388
|
-
border: 1px solid var(--line);
|
|
389
|
-
border-left: 4px solid var(--note);
|
|
390
|
-
border-radius: var(--radius);
|
|
391
|
-
padding: 16px;
|
|
392
|
-
background: #fffdf8;
|
|
393
|
-
}
|
|
394
|
-
.finding.danger { border-left-color: var(--danger); }
|
|
395
|
-
.finding.warn { border-left-color: var(--warn); }
|
|
396
|
-
.finding.low { border-left-color: var(--low); }
|
|
397
|
-
.finding-head {
|
|
398
|
-
display: flex;
|
|
399
|
-
align-items: start;
|
|
400
|
-
justify-content: space-between;
|
|
401
|
-
gap: 16px;
|
|
402
|
-
}
|
|
403
|
-
.finding-id {
|
|
404
|
-
margin: 0 0 4px;
|
|
405
|
-
color: var(--muted);
|
|
406
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
407
|
-
font-size: 12px;
|
|
408
|
-
}
|
|
409
|
-
h3 {
|
|
410
|
-
margin: 0;
|
|
411
|
-
font-size: 17px;
|
|
412
|
-
line-height: 1.3;
|
|
413
|
-
letter-spacing: 0;
|
|
414
|
-
}
|
|
415
|
-
.pill {
|
|
416
|
-
display: inline-flex;
|
|
417
|
-
align-items: center;
|
|
418
|
-
min-height: 26px;
|
|
419
|
-
padding: 4px 9px;
|
|
420
|
-
border-radius: 999px;
|
|
421
|
-
font-size: 12px;
|
|
422
|
-
font-weight: 800;
|
|
423
|
-
text-transform: uppercase;
|
|
424
|
-
white-space: nowrap;
|
|
425
|
-
}
|
|
426
|
-
.pill.danger { background: var(--danger-bg); color: var(--danger); }
|
|
427
|
-
.pill.warn { background: var(--warn-bg); color: var(--warn); }
|
|
428
|
-
.pill.note { background: var(--note-bg); color: var(--note); }
|
|
429
|
-
.pill.low { background: var(--low-bg); color: var(--low); }
|
|
430
|
-
.action {
|
|
431
|
-
margin: 10px 0 0;
|
|
432
|
-
color: var(--muted);
|
|
433
|
-
}
|
|
434
|
-
.sample-table {
|
|
435
|
-
margin-top: 14px;
|
|
436
|
-
overflow-x: auto;
|
|
437
|
-
border: 1px solid var(--line);
|
|
438
|
-
border-radius: var(--radius);
|
|
439
|
-
background: var(--panel);
|
|
440
|
-
}
|
|
441
|
-
table {
|
|
442
|
-
width: 100%;
|
|
443
|
-
min-width: 640px;
|
|
444
|
-
border-collapse: collapse;
|
|
445
|
-
}
|
|
446
|
-
th, td {
|
|
447
|
-
padding: 10px 12px;
|
|
448
|
-
border-bottom: 1px solid var(--line);
|
|
449
|
-
text-align: left;
|
|
450
|
-
vertical-align: top;
|
|
451
|
-
}
|
|
452
|
-
th {
|
|
453
|
-
color: var(--muted);
|
|
454
|
-
font-size: 12px;
|
|
455
|
-
font-weight: 800;
|
|
456
|
-
letter-spacing: 0.06em;
|
|
457
|
-
text-transform: uppercase;
|
|
458
|
-
}
|
|
459
|
-
td {
|
|
460
|
-
max-width: 360px;
|
|
461
|
-
color: #27272a;
|
|
462
|
-
font-size: 13px;
|
|
463
|
-
overflow-wrap: anywhere;
|
|
464
|
-
}
|
|
465
|
-
tr:last-child td { border-bottom: 0; }
|
|
466
|
-
.empty {
|
|
467
|
-
margin: 0;
|
|
468
|
-
color: var(--muted);
|
|
469
|
-
}
|
|
470
|
-
details {
|
|
471
|
-
margin-top: 24px;
|
|
472
|
-
padding: 18px;
|
|
473
|
-
}
|
|
474
|
-
summary {
|
|
475
|
-
cursor: pointer;
|
|
476
|
-
font-weight: 800;
|
|
477
|
-
}
|
|
478
|
-
pre {
|
|
479
|
-
margin: 16px 0 0;
|
|
480
|
-
max-height: 560px;
|
|
481
|
-
overflow: auto;
|
|
482
|
-
padding: 16px;
|
|
483
|
-
border-radius: var(--radius);
|
|
484
|
-
background: #111827;
|
|
485
|
-
color: #f9fafb;
|
|
486
|
-
font-size: 12px;
|
|
487
|
-
line-height: 1.45;
|
|
488
|
-
}
|
|
489
|
-
@media (max-width: 860px) {
|
|
490
|
-
main { width: min(100% - 24px, 1120px); padding-top: 32px; }
|
|
491
|
-
.metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
492
|
-
.section-grid { grid-template-columns: 1fr; gap: 18px; }
|
|
493
|
-
.section { padding: 18px; }
|
|
494
|
-
.section-head { display: block; }
|
|
495
|
-
.section-head > span { display: block; margin-top: 8px; }
|
|
496
|
-
}
|
|
497
|
-
@media (max-width: 520px) {
|
|
498
|
-
.metrics { grid-template-columns: 1fr; }
|
|
499
|
-
.lede { font-size: 16px; }
|
|
500
|
-
.finding-head { display: block; }
|
|
501
|
-
.pill { margin-top: 10px; }
|
|
502
|
-
}
|
|
503
|
-
</style>
|
|
504
|
-
</head>
|
|
505
|
-
<body>
|
|
506
|
-
<main>
|
|
507
|
-
<header>
|
|
508
|
-
<p class="eyebrow">Generated ${htmlEscape(result.generatedAt)}</p>
|
|
509
|
-
<h1>Ops Catalog Audit</h1>
|
|
510
|
-
<p class="lede">A re-runnable audit of schedules, workflows, and prompt/template catalogs. It found ${htmlEscape(
|
|
511
|
-
formatMetric(result.summary.findingsTotal),
|
|
512
|
-
)} actionable issue cluster(s), with the highest-risk items called out first inside each group.</p>
|
|
513
|
-
</header>
|
|
514
|
-
<section class="metrics" aria-label="Audit summary">
|
|
515
|
-
${metrics
|
|
516
|
-
.map(
|
|
517
|
-
([label, value]) =>
|
|
518
|
-
`<div class="metric"><strong>${htmlEscape(formatMetric(value))}</strong><span>${htmlEscape(
|
|
519
|
-
label,
|
|
520
|
-
)}</span></div>`,
|
|
521
|
-
)
|
|
522
|
-
.join("")}
|
|
523
|
-
</section>
|
|
524
|
-
${sections}
|
|
525
|
-
<details>
|
|
526
|
-
<summary>Compressed JSON appendix</summary>
|
|
527
|
-
<pre>${htmlEscape(JSON.stringify(result, null, 2))}</pre>
|
|
528
|
-
</details>
|
|
529
|
-
</main>
|
|
530
|
-
</body>
|
|
531
|
-
</html>`;
|
|
142
|
+
],
|
|
143
|
+
sections: ["schedules", "workflows", "promptsTemplates"].map((key) => ({
|
|
144
|
+
key,
|
|
145
|
+
goal: result.goals[key].goal,
|
|
146
|
+
findingCount: result.goals[key].findingCount,
|
|
147
|
+
checks: result.goals[key].checks,
|
|
148
|
+
findings: result.goals[key].findings,
|
|
149
|
+
})),
|
|
150
|
+
appendix: result,
|
|
151
|
+
};
|
|
532
152
|
}
|
|
533
153
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const value = (v: any) => (redacted?.value ? redacted.value(v) : String(v));
|
|
537
|
-
const response = await ctx.stdlib.fetch(`${value(ctx.swarm.config.mcpBaseUrl)}/api/pages`, {
|
|
538
|
-
method: "POST",
|
|
539
|
-
headers: {
|
|
540
|
-
Authorization: `Bearer ${value(ctx.swarm.config.apiKey)}`,
|
|
541
|
-
"Content-Type": "application/json",
|
|
542
|
-
"X-Agent-ID": value(ctx.swarm.config.agentId),
|
|
543
|
-
},
|
|
544
|
-
body: JSON.stringify({
|
|
545
|
-
title: "Ops Catalog Audit",
|
|
546
|
-
slug: "ops-catalog-audit",
|
|
547
|
-
description: "Clustered audit-as-code report for schedules, workflows, and prompts/templates.",
|
|
548
|
-
contentType: "text/html",
|
|
549
|
-
authMode: "authed",
|
|
550
|
-
body: renderPage(result),
|
|
551
|
-
}),
|
|
552
|
-
});
|
|
553
|
-
const body = await response.json().catch(async () => ({ error: await response.text() }));
|
|
554
|
-
if (!response.ok) return { error: body?.error || `page create failed: ${response.status}` };
|
|
555
|
-
return {
|
|
556
|
-
id: body?.id ?? null,
|
|
557
|
-
appUrl: body?.app_url ?? null,
|
|
558
|
-
apiUrl: body?.api_url ?? null,
|
|
559
|
-
version: body?.version ?? null,
|
|
560
|
-
};
|
|
154
|
+
export function renderPage(result: any): string {
|
|
155
|
+
return renderCatalogReportPage(buildReport(result));
|
|
561
156
|
}
|
|
562
157
|
|
|
563
158
|
/** Audit schedules, workflows, and prompt/template catalogs by goal, with optional authed page output. */
|
|
@@ -906,6 +501,6 @@ export default async function opsCatalogAudit(args: any, ctx: any) {
|
|
|
906
501
|
},
|
|
907
502
|
};
|
|
908
503
|
|
|
909
|
-
if (publishPage) result.page = await
|
|
504
|
+
if (publishPage) result.page = await publishCatalogReportPage(buildReport(result), ctx);
|
|
910
505
|
return result;
|
|
911
506
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { publishCatalogReportPage } from "./catalog-report";
|
|
2
3
|
|
|
3
4
|
export const argsSchema = z.object({
|
|
4
5
|
days: z
|
|
@@ -11,6 +12,7 @@ export const argsSchema = z.object({
|
|
|
11
12
|
.number()
|
|
12
13
|
.optional()
|
|
13
14
|
.describe("Flag schedules with failure rate above this (0-1, default 0.2)"),
|
|
15
|
+
publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
/** Per-schedule health check: failure rates and flagging unhealthy schedules. */
|
|
@@ -19,6 +21,7 @@ export default async function scheduleHealth(args: any, ctx: any) {
|
|
|
19
21
|
if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
|
|
20
22
|
const days = parsed.data.days || 7;
|
|
21
23
|
const threshold = parsed.data.failureThreshold ?? 0.2;
|
|
24
|
+
const publishPage = parsed.data.publishPage !== false;
|
|
22
25
|
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
23
26
|
|
|
24
27
|
// Get schedules
|
|
@@ -26,7 +29,38 @@ export default async function scheduleHealth(args: any, ctx: any) {
|
|
|
26
29
|
const schedPayload = schedRes?.data ?? schedRes;
|
|
27
30
|
const schedules: any[] = schedPayload?.schedules ?? [];
|
|
28
31
|
|
|
29
|
-
if (!schedules.length)
|
|
32
|
+
if (!schedules.length) {
|
|
33
|
+
const result: any = { days, threshold, totalSchedules: 0, flaggedCount: 0, schedules: [], flagged: [] };
|
|
34
|
+
if (publishPage) {
|
|
35
|
+
result.page = await publishCatalogReportPage(
|
|
36
|
+
{
|
|
37
|
+
title: "Schedule Health Audit",
|
|
38
|
+
slug: "schedule-health",
|
|
39
|
+
description: "Per-schedule failure rate audit.",
|
|
40
|
+
generatedAt: new Date().toISOString(),
|
|
41
|
+
lede: "No schedules were returned for the selected window.",
|
|
42
|
+
metrics: [
|
|
43
|
+
["Schedules", 0],
|
|
44
|
+
["Flagged", 0],
|
|
45
|
+
["Days", days],
|
|
46
|
+
["Threshold", threshold],
|
|
47
|
+
],
|
|
48
|
+
sections: [
|
|
49
|
+
{
|
|
50
|
+
key: "schedule-health",
|
|
51
|
+
goal: "Keep recurring schedules healthy and visible.",
|
|
52
|
+
findingCount: 0,
|
|
53
|
+
checks: { schedules: 0, flagged: 0 },
|
|
54
|
+
findings: [],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
appendix: result,
|
|
58
|
+
},
|
|
59
|
+
ctx,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
30
64
|
|
|
31
65
|
// Get recent tasks to correlate with schedules
|
|
32
66
|
const taskRes: any = await ctx.swarm.task_list({ createdAfter: since, limit: 2000 });
|
|
@@ -62,7 +96,7 @@ export default async function scheduleHealth(args: any, ctx: any) {
|
|
|
62
96
|
|
|
63
97
|
const flagged = results.filter((r: any) => r.flagged);
|
|
64
98
|
|
|
65
|
-
|
|
99
|
+
const result: any = {
|
|
66
100
|
days,
|
|
67
101
|
threshold,
|
|
68
102
|
totalSchedules: schedules.length,
|
|
@@ -70,4 +104,46 @@ export default async function scheduleHealth(args: any, ctx: any) {
|
|
|
70
104
|
schedules: results.sort((a: any, b: any) => b.failureRate - a.failureRate),
|
|
71
105
|
flagged,
|
|
72
106
|
};
|
|
107
|
+
|
|
108
|
+
if (publishPage) {
|
|
109
|
+
result.page = await publishCatalogReportPage(
|
|
110
|
+
{
|
|
111
|
+
title: "Schedule Health Audit",
|
|
112
|
+
slug: "schedule-health",
|
|
113
|
+
description: "Per-schedule failure rate audit.",
|
|
114
|
+
generatedAt: new Date().toISOString(),
|
|
115
|
+
lede: `Checked ${schedules.length} schedule(s) over ${days} day(s); ${flagged.length} exceeded the configured failure threshold.`,
|
|
116
|
+
metrics: [
|
|
117
|
+
["Schedules", schedules.length],
|
|
118
|
+
["Flagged", flagged.length],
|
|
119
|
+
["Days", days],
|
|
120
|
+
["Threshold", threshold],
|
|
121
|
+
],
|
|
122
|
+
sections: [
|
|
123
|
+
{
|
|
124
|
+
key: "schedule-health",
|
|
125
|
+
goal: "Keep recurring schedules healthy and visible.",
|
|
126
|
+
findingCount: flagged.length,
|
|
127
|
+
checks: {
|
|
128
|
+
totalSchedules: schedules.length,
|
|
129
|
+
flaggedSchedules: flagged.length,
|
|
130
|
+
threshold,
|
|
131
|
+
scannedTasks: tasks.length,
|
|
132
|
+
},
|
|
133
|
+
findings: flagged.map((schedule: any) => ({
|
|
134
|
+
id: `schedule.${schedule.id}`,
|
|
135
|
+
severity: schedule.failureRate >= 0.5 ? "high" : "medium",
|
|
136
|
+
summary: `${schedule.name} failed ${schedule.failed}/${schedule.runs} recent run(s).`,
|
|
137
|
+
action: "Review the latest failed tasks and disable, repair, or retarget this schedule.",
|
|
138
|
+
samples: [schedule],
|
|
139
|
+
})),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
appendix: result,
|
|
143
|
+
},
|
|
144
|
+
ctx,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
73
149
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { publishCatalogReportPage } from "./catalog-report";
|
|
2
3
|
|
|
3
4
|
export const argsSchema = z.object({
|
|
4
5
|
days: z
|
|
@@ -17,6 +18,7 @@ export const argsSchema = z.object({
|
|
|
17
18
|
.positive()
|
|
18
19
|
.optional()
|
|
19
20
|
.describe("Max failed tasks to scan (default 500)"),
|
|
21
|
+
publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
const REASON_PATTERNS: any[] = [
|
|
@@ -46,6 +48,7 @@ export default async function taskFailureAudit(args: any, ctx: any) {
|
|
|
46
48
|
const days = parsed.data.days || 7;
|
|
47
49
|
const groupBy = parsed.data.groupBy || "reason";
|
|
48
50
|
const limit = parsed.data.limit || 500;
|
|
51
|
+
const publishPage = parsed.data.publishPage !== false;
|
|
49
52
|
|
|
50
53
|
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
51
54
|
const res: any = await ctx.swarm.task_list({
|
|
@@ -77,11 +80,55 @@ export default async function taskFailureAudit(args: any, ctx: any) {
|
|
|
77
80
|
.map((k: string) => groups[k])
|
|
78
81
|
.sort((a: any, b: any) => b.count - a.count);
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
const result: any = {
|
|
81
84
|
days,
|
|
82
85
|
groupBy,
|
|
83
86
|
totalFailed: tasks.length,
|
|
84
87
|
clusterCount: rows.length,
|
|
85
88
|
groups: rows,
|
|
86
89
|
};
|
|
90
|
+
|
|
91
|
+
if (publishPage) {
|
|
92
|
+
result.page = await publishCatalogReportPage(
|
|
93
|
+
{
|
|
94
|
+
title: "Task Failure Audit",
|
|
95
|
+
slug: "task-failure-audit",
|
|
96
|
+
description: "Clustered audit of recently failed swarm tasks.",
|
|
97
|
+
generatedAt: new Date().toISOString(),
|
|
98
|
+
lede: `Clustered ${tasks.length} failed task(s) over ${days} day(s) by ${groupBy}.`,
|
|
99
|
+
metrics: [
|
|
100
|
+
["Failed tasks", tasks.length],
|
|
101
|
+
["Clusters", rows.length],
|
|
102
|
+
["Days", days],
|
|
103
|
+
["Limit", limit],
|
|
104
|
+
],
|
|
105
|
+
sections: [
|
|
106
|
+
{
|
|
107
|
+
key: "failure-clusters",
|
|
108
|
+
goal: "Surface repeated failure modes before they become operational drift.",
|
|
109
|
+
findingCount: rows.length,
|
|
110
|
+
checks: { totalFailed: tasks.length, clusterCount: rows.length, groupBy },
|
|
111
|
+
findings: rows.map((group: any) => ({
|
|
112
|
+
id: `failure.${group.key}`,
|
|
113
|
+
severity: group.count >= 5 ? "high" : group.count >= 2 ? "medium" : "low",
|
|
114
|
+
summary: `${group.count} failed task(s) in ${group.key}.`,
|
|
115
|
+
action: "Inspect the sample task IDs and decide whether this needs a fix, retry, or HEARTBEAT watch item.",
|
|
116
|
+
samples: [
|
|
117
|
+
{
|
|
118
|
+
key: group.key,
|
|
119
|
+
count: group.count,
|
|
120
|
+
taskIds: group.taskIds,
|
|
121
|
+
sampleReason: group.sampleReason,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
appendix: result,
|
|
128
|
+
},
|
|
129
|
+
ctx,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return result;
|
|
87
134
|
}
|