@desplega.ai/agent-swarm 1.90.0 → 1.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +2 -1
  2. package/openapi.json +803 -150
  3. package/package.json +5 -5
  4. package/src/artifact-sdk/server.ts +2 -1
  5. package/src/be/db.ts +337 -1
  6. package/src/be/memory/providers/sqlite-store.ts +6 -1
  7. package/src/be/memory/types.ts +1 -0
  8. package/src/be/migrations/083_script_workflows.sql +51 -0
  9. package/src/be/modelsdev-cache.json +42352 -38595
  10. package/src/be/scripts/typecheck.ts +181 -1
  11. package/src/be/seed-scripts/catalog/compound-insights.ts +398 -0
  12. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +911 -0
  13. package/src/be/seed-scripts/catalog/schedule-health.ts +73 -0
  14. package/src/be/seed-scripts/catalog/smart-recall.ts +65 -0
  15. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  16. package/src/be/seed-scripts/catalog/tool-usage.ts +59 -0
  17. package/src/be/seed-scripts/index.ts +54 -0
  18. package/src/be/seed-skills/index.ts +7 -0
  19. package/src/be/swarm-config-guard.ts +17 -0
  20. package/src/commands/artifact.ts +3 -2
  21. package/src/commands/profile-sync.ts +310 -0
  22. package/src/commands/runner.ts +134 -3
  23. package/src/hooks/hook.ts +32 -9
  24. package/src/http/db-query.ts +20 -5
  25. package/src/http/index.ts +57 -0
  26. package/src/http/integrations.ts +6 -1
  27. package/src/http/mcp-bridge.ts +117 -0
  28. package/src/http/mcp-oauth.ts +97 -39
  29. package/src/http/memory.ts +5 -2
  30. package/src/http/openapi.ts +2 -2
  31. package/src/http/pages-public.ts +10 -11
  32. package/src/http/pages.ts +7 -11
  33. package/src/http/script-runs.ts +555 -0
  34. package/src/http/scripts.ts +24 -1
  35. package/src/http/utils.ts +11 -4
  36. package/src/jira/app.ts +2 -3
  37. package/src/jira/webhook-lifecycle.ts +2 -1
  38. package/src/linear/app.ts +2 -3
  39. package/src/prompts/session-templates.ts +24 -4
  40. package/src/providers/claude-adapter.ts +86 -13
  41. package/src/script-workflows/executor.ts +110 -0
  42. package/src/script-workflows/harness.ts +73 -0
  43. package/src/script-workflows/label-lint.ts +51 -0
  44. package/src/script-workflows/limits.ts +22 -0
  45. package/src/script-workflows/supervisor.ts +139 -0
  46. package/src/script-workflows/workflow-ctx.ts +205 -0
  47. package/src/scripts-runtime/executors/native.ts +1 -0
  48. package/src/scripts-runtime/sdk-allowlist.ts +124 -0
  49. package/src/scripts-runtime/swarm-sdk.ts +198 -3
  50. package/src/scripts-runtime/types/stdlib.d.ts +287 -0
  51. package/src/scripts-runtime/types/swarm-sdk.d.ts +287 -0
  52. package/src/server.ts +2 -0
  53. package/src/slack/handlers.ts +11 -4
  54. package/src/slack/message-text.ts +98 -0
  55. package/src/slack/thread-buffer.ts +5 -3
  56. package/src/tests/claude-adapter-binary.test.ts +147 -4
  57. package/src/tests/claude-adapter-otel.test.ts +85 -1
  58. package/src/tests/db-query.test.ts +28 -0
  59. package/src/tests/error-tracker.test.ts +121 -0
  60. package/src/tests/harness-provider-resolution.test.ts +33 -0
  61. package/src/tests/hook-registration-nudge.test.ts +69 -0
  62. package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
  63. package/src/tests/mcp-tools.test.ts +6 -0
  64. package/src/tests/pages-public-html.test.ts +41 -0
  65. package/src/tests/pages-public-json-redirect.test.ts +37 -2
  66. package/src/tests/profile-sync.test.ts +282 -0
  67. package/src/tests/prompt-template-session.test.ts +34 -5
  68. package/src/tests/script-runs-http.test.ts +278 -0
  69. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  70. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  71. package/src/tests/scripts-mcp-e2e.test.ts +49 -2
  72. package/src/tests/scripts-runtime.test.ts +33 -0
  73. package/src/tests/seed-scripts.test.ts +347 -2
  74. package/src/tests/slack-message-text.test.ts +250 -0
  75. package/src/tests/system-default-skills.test.ts +40 -0
  76. package/src/tools/create-metric.ts +2 -3
  77. package/src/tools/create-page.ts +3 -6
  78. package/src/tools/db-query.ts +16 -6
  79. package/src/tools/memory-rate.ts +2 -1
  80. package/src/tools/memory-search.ts +1 -0
  81. package/src/tools/register-kapso-number.ts +2 -4
  82. package/src/tools/request-human-input.ts +2 -1
  83. package/src/tools/script-common.ts +2 -4
  84. package/src/tools/script-run.ts +7 -0
  85. package/src/tools/script-runs.ts +123 -0
  86. package/src/tools/slack-read.ts +12 -3
  87. package/src/tools/tool-config.ts +4 -1
  88. package/src/types.ts +52 -0
  89. package/src/utils/constants.ts +58 -8
  90. package/src/utils/error-tracker.ts +40 -1
  91. package/src/utils/internal-ai/complete-structured.ts +10 -4
  92. package/src/workflows/executors/raw-llm.ts +76 -59
  93. package/templates/skills/pages/content.md +205 -55
  94. package/templates/skills/script-workflows/config.json +14 -0
  95. package/templates/skills/script-workflows/content.md +68 -0
  96. package/templates/skills/swarm-scripts/content.md +45 -7
@@ -16,6 +16,76 @@ export const RawLlmOutputSchema = z.object({
16
16
  model: z.string(),
17
17
  });
18
18
 
19
+ export async function executeRawLlm(
20
+ config: z.infer<typeof RawLlmConfigSchema>,
21
+ ): Promise<
22
+ | { status: "success"; output: z.infer<typeof RawLlmOutputSchema>; error?: string }
23
+ | { status: "failed"; error: string }
24
+ > {
25
+ const modelName = config.model ?? "google/gemini-3-flash-preview";
26
+
27
+ try {
28
+ const { createOpenAI } = await import("@ai-sdk/openai");
29
+ const openrouter = createOpenAI({
30
+ baseURL: "https://openrouter.ai/api/v1",
31
+ apiKey: process.env.OPENROUTER_API_KEY,
32
+ });
33
+ const model = openrouter(modelName);
34
+
35
+ if (config.schema) {
36
+ const { generateObject, jsonSchema } = await import("ai");
37
+ const { object } = await generateObject({
38
+ model,
39
+ schema: jsonSchema(config.schema),
40
+ prompt: config.prompt,
41
+ providerOptions: {
42
+ openai: { strictJsonSchema: false },
43
+ },
44
+ });
45
+ return {
46
+ status: "success",
47
+ output: { result: object, model: modelName },
48
+ };
49
+ }
50
+
51
+ const { generateText } = await import("ai");
52
+ const { text } = await generateText({
53
+ model,
54
+ prompt: config.prompt,
55
+ });
56
+ return {
57
+ status: "success",
58
+ output: { result: text, model: modelName },
59
+ };
60
+ } catch (err) {
61
+ // Re-throw rate-limit errors so executeStep's retry policy handles them
62
+ // via the retry poller (scheduled backoff). Using the fallbackPort for
63
+ // rate limits would trigger the semantic loop-back path instead, causing
64
+ // runaway retries without any backoff.
65
+ const httpStatus =
66
+ (err as { status?: number; statusCode?: number })?.status ??
67
+ (err as { status?: number; statusCode?: number })?.statusCode;
68
+ const isRateLimited =
69
+ httpStatus === 429 ||
70
+ httpStatus === 529 ||
71
+ (err instanceof Error && /rate.?limit|too many requests|529/i.test(err.message));
72
+ if (isRateLimited) {
73
+ throw err;
74
+ }
75
+ if (config.fallbackPort) {
76
+ return {
77
+ status: "success",
78
+ output: { result: null, model: modelName },
79
+ error: `LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`,
80
+ };
81
+ }
82
+ return {
83
+ status: "failed",
84
+ error: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`,
85
+ };
86
+ }
87
+ }
88
+
19
89
  // ─── Executor ───────────────────────────────────────────────
20
90
 
21
91
  export class RawLlmExecutor extends BaseExecutor<
@@ -33,68 +103,15 @@ export class RawLlmExecutor extends BaseExecutor<
33
103
  _meta: ExecutorMeta,
34
104
  ): Promise<ExecutorResult<z.infer<typeof RawLlmOutputSchema>>> {
35
105
  const prompt = this.deps.interpolate(config.prompt, context as Record<string, unknown>);
36
- const modelName = config.model ?? "google/gemini-3-flash-preview";
37
-
38
- try {
39
- const { createOpenAI } = await import("@ai-sdk/openai");
40
- const openrouter = createOpenAI({
41
- baseURL: "https://openrouter.ai/api/v1",
42
- apiKey: process.env.OPENROUTER_API_KEY,
43
- });
44
- const model = openrouter(modelName);
45
-
46
- if (config.schema) {
47
- const { generateObject, jsonSchema } = await import("ai");
48
- const { object } = await generateObject({
49
- model,
50
- schema: jsonSchema(config.schema),
51
- prompt,
52
- providerOptions: {
53
- openai: { strictJsonSchema: false },
54
- },
55
- });
56
- return {
57
- status: "success",
58
- output: { result: object, model: modelName },
59
- };
60
- }
61
-
62
- const { generateText } = await import("ai");
63
- const { text } = await generateText({
64
- model,
65
- prompt,
66
- });
106
+ const result = await executeRawLlm({ ...config, prompt });
107
+ if (result.status === "success" && result.error) {
67
108
  return {
68
109
  status: "success",
69
- output: { result: text, model: modelName },
70
- };
71
- } catch (err) {
72
- // Re-throw rate-limit errors so executeStep's retry policy handles them
73
- // via the retry poller (scheduled backoff). Using the fallbackPort for
74
- // rate limits would trigger the semantic loop-back path instead, causing
75
- // runaway retries without any backoff.
76
- const httpStatus =
77
- (err as { status?: number; statusCode?: number })?.status ??
78
- (err as { status?: number; statusCode?: number })?.statusCode;
79
- const isRateLimited =
80
- httpStatus === 429 ||
81
- httpStatus === 529 ||
82
- (err instanceof Error && /rate.?limit|too many requests|529/i.test(err.message));
83
- if (isRateLimited) {
84
- throw err;
85
- }
86
- if (config.fallbackPort) {
87
- return {
88
- status: "success",
89
- output: { result: null, model: modelName },
90
- nextPort: config.fallbackPort,
91
- error: `LLM call failed, using fallback port: ${err instanceof Error ? err.message : String(err)}`,
92
- };
93
- }
94
- return {
95
- status: "failed",
96
- error: `LLM call failed: ${err instanceof Error ? err.message : String(err)}`,
110
+ output: result.output,
111
+ nextPort: config.fallbackPort,
112
+ error: result.error,
97
113
  };
98
114
  }
115
+ return result;
99
116
  }
100
117
  }
@@ -1,85 +1,235 @@
1
1
  # Pages
2
2
 
3
- Pages are persistent, shareable HTML documents created via the swarm's `create_page` MCP tool. Use them when the output benefits from layout, tables, headers, and persistent sharing unlike Slack messages, pages don't expire and can be bookmarked.
3
+ Pages are persistent, shareable HTML documents created via the swarm's page tooling. Use them when the output benefits from layout, tables, headers, and persistent sharing. A page should be a clean human-facing report, not a raw dump with a URL.
4
+
5
+ This skill covers both how to publish and how to design the page. It distills the current external design guidance from Anthropic's `frontend-design`, Vercel's `composition-patterns`, and Vercel's `web-design-guidelines`: start from content hierarchy, use a small spacing system, keep typography readable, prefer restraint over decoration, and make responsive behavior intentional.
4
6
 
5
7
  ## When to Create a Page
6
8
 
7
9
  - A report, dashboard, or summary that benefits from structured layout
8
10
  - Analysis that should be linkable and bookmarkable
9
- - Results that need to be reviewed asynchronously (not in a Slack thread)
10
- - Content that's too long or rich for a `store-progress.output` string
11
+ - Results that need to be reviewed asynchronously
12
+ - Content that is too long or rich for a `store-progress.output` string
11
13
 
12
14
  Do NOT use pages for:
13
- - In-flight progress notes (use `store-progress.progress`)
14
- - Sensitive data (credentials, private customer data)
15
- - Large binary files (use agent-fs for PNG/MP4)
15
+
16
+ - In-flight progress notes; use `store-progress.progress`
17
+ - Secrets, private credentials, or unapproved personal data
18
+ - Large binary files; use agent-fs for PNG/MP4
19
+ - Raw verbose logs; summarize them and link to artifacts
20
+
21
+ ## Page Design Defaults
22
+
23
+ Every page should be useful at a glance.
24
+
25
+ - Put the page's point in the first viewport: title, one-sentence summary, and 3-6 key numbers or statuses.
26
+ - Use a single-column reading spine with `max-width: 1120px`; keep prose measure around 65-75 characters.
27
+ - Use system fonts unless there is a clear reason not to: `Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`.
28
+ - Use a restrained palette: neutral background, high-contrast text, white panels, one accent color, and semantic colors for statuses only.
29
+ - Use a consistent spacing scale: `8, 12, 16, 24, 32, 48, 72`.
30
+ - Use clear type hierarchy: page title 36-48px desktop / 30-36px mobile, section titles 22-28px, body 15-16px, supporting text 13-14px.
31
+ - Keep tables readable: sticky/scannable headers where useful, padded cells, zebra-free or very subtle row borders, `tabular-nums` for numbers, horizontal scroll on narrow screens.
32
+ - Prefer cards only for repeated records or metrics. Do not nest cards inside cards.
33
+ - Hide raw JSON behind a collapsed `<details>` block at the bottom.
34
+ - Make mobile explicit with media queries: single-column grids, reduced padding, no overflow except intentional table scroll.
35
+
36
+ ## Content Structure
37
+
38
+ Use this order unless the task gives a better domain-specific structure:
39
+
40
+ 1. Hero: title, short summary, timestamp/source context
41
+ 2. Key metrics: 3-6 tiles that answer "how big / how bad / what changed?"
42
+ 3. Findings or sections grouped by theme, owner, severity, or stage
43
+ 4. Evidence tables or samples under each finding
44
+ 5. Next actions or recommendations
45
+ 6. Raw evidence links / collapsed JSON appendix
46
+
47
+ Write section headings as labels, not slogans. Favor "Critical Routing Gaps" over "Things We Found".
16
48
 
17
49
  ## Creating a Page
18
50
 
51
+ Use the page tool with an HTML body. Prefer `contentType: "text/html"` and an explicit `authMode` for internal reports.
52
+
19
53
  ```javascript
20
- // Via MCP tool
21
- create_page({
22
- title: "Q2 SEO Performance Report",
23
- content: `<h1>Q2 SEO Performance</h1>
24
- <p>Analysis period: 2026-04-01 to 2026-06-30</p>
25
- <h2>Summary</h2>
26
- <table>
27
- <tr><th>Metric</th><th>Q1</th><th>Q2</th><th>Change</th></tr>
28
- <tr><td>Organic clicks</td><td>12,400</td><td>18,600</td><td>+50%</td></tr>
29
- </table>
30
- <h2>Next Actions</h2>
31
- <ul>
32
- <li>Publish 3 new pillar pages targeting high-intent queries</li>
33
- <li>Fix 23 pages with missing meta descriptions</li>
34
- </ul>`
35
- })
54
+ const html = `<!doctype html>
55
+ <html lang="en">
56
+ <head>
57
+ <meta charset="utf-8">
58
+ <meta name="viewport" content="width=device-width, initial-scale=1">
59
+ <title>Q2 SEO Performance</title>
60
+ <style>
61
+ :root {
62
+ color-scheme: light;
63
+ --bg: #f6f4f0;
64
+ --panel: #ffffff;
65
+ --ink: #18181b;
66
+ --muted: #62646a;
67
+ --line: #dedbd2;
68
+ --accent: #2563eb;
69
+ --danger: #b42318;
70
+ --warn: #b54708;
71
+ --ok: #067647;
72
+ --radius: 8px;
73
+ --shadow: 0 1px 2px rgba(24, 24, 27, 0.06), 0 12px 32px rgba(24, 24, 27, 0.07);
74
+ }
75
+ * { box-sizing: border-box; }
76
+ body {
77
+ margin: 0;
78
+ background: var(--bg);
79
+ color: var(--ink);
80
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
81
+ font-size: 16px;
82
+ line-height: 1.55;
83
+ }
84
+ main {
85
+ width: min(1120px, calc(100% - 32px));
86
+ margin: 0 auto;
87
+ padding: 48px 0 72px;
88
+ }
89
+ header { margin-bottom: 32px; }
90
+ .eyebrow {
91
+ margin: 0 0 8px;
92
+ color: var(--muted);
93
+ font-size: 13px;
94
+ font-weight: 700;
95
+ letter-spacing: 0.08em;
96
+ text-transform: uppercase;
97
+ }
98
+ h1 {
99
+ margin: 0;
100
+ max-width: 780px;
101
+ font-size: clamp(2rem, 4vw, 3rem);
102
+ line-height: 1.05;
103
+ letter-spacing: 0;
104
+ }
105
+ .lede {
106
+ max-width: 760px;
107
+ margin: 16px 0 0;
108
+ color: var(--muted);
109
+ font-size: 18px;
110
+ }
111
+ .metrics {
112
+ display: grid;
113
+ grid-template-columns: repeat(4, minmax(0, 1fr));
114
+ gap: 12px;
115
+ margin: 32px 0;
116
+ }
117
+ .metric, .section {
118
+ background: var(--panel);
119
+ border: 1px solid var(--line);
120
+ border-radius: var(--radius);
121
+ box-shadow: var(--shadow);
122
+ }
123
+ .metric { padding: 18px; }
124
+ .metric strong {
125
+ display: block;
126
+ font-size: 32px;
127
+ line-height: 1;
128
+ font-variant-numeric: tabular-nums;
129
+ }
130
+ .metric span {
131
+ display: block;
132
+ margin-top: 8px;
133
+ color: var(--muted);
134
+ font-size: 13px;
135
+ font-weight: 650;
136
+ }
137
+ .section {
138
+ margin-top: 18px;
139
+ padding: 24px;
140
+ }
141
+ h2 { margin: 0 0 12px; font-size: 24px; line-height: 1.2; }
142
+ h3 { margin: 0 0 8px; font-size: 17px; line-height: 1.3; }
143
+ p { margin: 0 0 12px; }
144
+ .table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); }
145
+ table { width: 100%; border-collapse: collapse; min-width: 640px; }
146
+ th, td { padding: 10px 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
147
+ th { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
148
+ td { font-size: 14px; }
149
+ code, pre { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
150
+ details { margin-top: 24px; }
151
+ summary { cursor: pointer; font-weight: 700; }
152
+ pre { overflow: auto; padding: 16px; background: #111827; color: #f9fafb; border-radius: var(--radius); }
153
+ @media (max-width: 760px) {
154
+ main { width: min(100% - 24px, 1120px); padding-top: 32px; }
155
+ .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
156
+ .section { padding: 18px; }
157
+ }
158
+ @media (max-width: 520px) {
159
+ .metrics { grid-template-columns: 1fr; }
160
+ .lede { font-size: 16px; }
161
+ }
162
+ </style>
163
+ </head>
164
+ <body>
165
+ <main>
166
+ <header>
167
+ <p class="eyebrow">Generated 2026-06-04</p>
168
+ <h1>Q2 SEO Performance</h1>
169
+ <p class="lede">Organic traffic grew sharply, but the next gains depend on fixing thin page metadata and publishing three high-intent pillar pages.</p>
170
+ </header>
171
+
172
+ <section class="metrics" aria-label="Key metrics">
173
+ <div class="metric"><strong>18.6k</strong><span>Organic clicks</span></div>
174
+ <div class="metric"><strong>+50%</strong><span>Quarter over quarter</span></div>
175
+ <div class="metric"><strong>23</strong><span>Metadata fixes</span></div>
176
+ <div class="metric"><strong>3</strong><span>Priority pages</span></div>
177
+ </section>
178
+
179
+ <section class="section">
180
+ <h2>Summary</h2>
181
+ <p>Start with the conclusion. Add tables only after the reader understands what changed and what to do next.</p>
182
+ <div class="table-wrap">
183
+ <table>
184
+ <thead><tr><th>Metric</th><th>Q1</th><th>Q2</th><th>Change</th></tr></thead>
185
+ <tbody><tr><td>Organic clicks</td><td>12,400</td><td>18,600</td><td>+50%</td></tr></tbody>
186
+ </table>
187
+ </div>
188
+ </section>
189
+ </main>
190
+ </body>
191
+ </html>`;
192
+
193
+ await create_page({
194
+ title: "Q2 SEO Performance",
195
+ slug: "q2-seo-performance",
196
+ description: "Human-readable SEO performance report with summary metrics and next actions.",
197
+ contentType: "text/html",
198
+ authMode: "authed",
199
+ body: html,
200
+ });
36
201
  ```
37
202
 
38
- Returns a page ID. Build the share URL:
39
- ```
203
+ Returns a page ID. Build share URLs from environment:
204
+
205
+ ```text
40
206
  ${APP_URL}/pages/<pageId> # opens in SPA with chrome
41
207
  ${APP_URL}/pages/<pageId>?mode=full # slim header, full viewport
42
- ${MCP_BASE_URL}/p/<pageId> # direct HTML (no SPA)
208
+ ${MCP_BASE_URL}/p/<pageId> # direct HTML
43
209
  ```
44
210
 
45
- Read `APP_URL` and `MCP_BASE_URL` from environment never hardcode.
211
+ Read `APP_URL` and `MCP_BASE_URL` from environment. Never hardcode localhost or example hosts in shared output.
46
212
 
47
- ## Content Guidelines
213
+ ## Design Checklist
48
214
 
49
- **Keep raw evidence in artifacts and link to it.** The page should contain:
50
- 1. **Short summary** (1 paragraph) — what this covers and the key finding
51
- 2. **Source links** — links to the data, agent-fs artifacts, or upstream systems
52
- 3. **Structured content** — tables, headers, numbered lists
53
- 4. **Next actions** — what should happen next, who owns it
215
+ Before publishing:
54
216
 
55
- Do NOT embed:
56
- - Secrets or private credentials
57
- - Personal data of individuals without approval
58
- - Raw verbose logs (summarize them)
217
+ - The first viewport states what the page is, why it matters, and the key numbers.
218
+ - The page has a clear hierarchy: `h1`, short lede, metrics, sections, evidence.
219
+ - Body text is readable on mobile and desktop.
220
+ - Tables scroll horizontally on mobile instead of crushing columns.
221
+ - Status colors are semantic and not the whole visual identity.
222
+ - Raw JSON/logs are collapsed or linked, not the primary experience.
223
+ - No nested cards, decorative gradients, oversized hero art, or cramped default browser styles.
224
+ - No text overlaps, clipped buttons, or unreadable low-contrast text.
59
225
 
60
- ## Page vs. Agent-fs
226
+ ## Page vs Agent-fs
61
227
 
62
228
  | Use pages for | Use agent-fs for |
63
229
  |---|---|
64
230
  | Reports, dashboards, human-readable summaries | Markdown research notes, code files, recordings |
65
231
  | Content that benefits from HTML layout | Searchable knowledge base entries |
66
- | Quick share links to non-technical stakeholders | Binary artifacts (PNG, MP4) |
232
+ | Quick share links to non-technical stakeholders | Binary artifacts such as PNG or MP4 |
67
233
  | Time-bounded deliverables | Long-lived reference documentation |
68
234
 
69
- ## Sharing Pages
70
-
71
- Always use the platform share URL (from `APP_URL` env var) rather than hardcoded local hosts. Append `?mode=full` for a standalone view (hides sidebar/header — good for screenshots or embedding in Slack previews).
72
-
73
- ```bash
74
- # Get the share URL
75
- PAGE_URL="${APP_URL}/pages/${pageId}?mode=full"
76
-
77
- # Post to Slack
78
- slack-reply --taskId <id> --message "Report ready: ${PAGE_URL}"
79
- ```
80
-
81
- ## Trade-offs
82
-
83
- **Pages vs Slack messages:** Slack messages are ephemeral and scroll out of view. Pages are persistent and bookmarkable. Use pages for anything you'd want to reference in 3 months; use Slack for in-the-moment communication.
84
-
85
- **Pages vs agent-fs:** Pages are rendered HTML with a share URL — great for non-technical stakeholders. Agent-fs files are raw content — great for other agents and developers who need the source data. For a research memo, write the source to agent-fs and create a page for the human-facing summary.
235
+ For a research memo, write the source to agent-fs and create a page for the human-facing summary.
@@ -0,0 +1,14 @@
1
+ {
2
+ "kind": "skill",
3
+ "name": "script-workflows",
4
+ "displayName": "Script Workflows",
5
+ "slug": "script-workflows",
6
+ "title": "Script Workflows",
7
+ "description": "Launch and inspect durable one-off script workflow runs with journaled swarm-script, raw-llm, and agent-task steps.",
8
+ "version": "1.0.0",
9
+ "category": "skills",
10
+ "placeholders": [],
11
+ "runAllSeedersCandidate": true,
12
+ "systemDefault": true,
13
+ "tags": ["scripts", "workflows", "automation"]
14
+ }
@@ -0,0 +1,68 @@
1
+ Use this skill when a user asks to launch, monitor, inspect, or debug a durable script workflow run. This is for the Script Workflows v1 runtime: one-off TypeScript workflow source with journaled `swarm-script`, `raw-llm`, and `agent-task` steps.
2
+
3
+ ## Tool Flow
4
+
5
+ Load the script workflow tools with ToolSearch when they are not already visible:
6
+
7
+ ```text
8
+ launch-script-run
9
+ get-script-run
10
+ list-script-runs
11
+ ```
12
+
13
+ Use `launch-script-run` to start a one-off run. It calls the same `/api/script-runs` API as the dashboard, preserves the invoking agent identity, and starts the run in the background.
14
+
15
+ Use `get-script-run` to read terminal status and journal entries. Poll it when needed, but keep polling bounded and report progress for long runs.
16
+
17
+ Use `list-script-runs` to find recent runs or filter by `status` / `agentId`.
18
+
19
+ Do not hand-roll raw HTTP for this flow unless the tool itself is broken and you are explicitly debugging the API. The tool handles auth and `X-Agent-ID` like the existing inline `script-run` tool family.
20
+
21
+ ## Source Shape
22
+
23
+ Author TypeScript workflow source as a default export. The runtime provides `args` and `ctx`.
24
+
25
+ ```ts
26
+ export default async function main(args, ctx) {
27
+ const lookup = await ctx.step.swarmScript("lookup-data", {
28
+ scriptName: "fetch-readable",
29
+ args: { url: args.url },
30
+ });
31
+
32
+ const summary = await ctx.step.rawLlm("summarize", {
33
+ prompt: `Summarize this for an operator:\n${JSON.stringify(lookup)}`,
34
+ });
35
+
36
+ const task = await ctx.step.agentTask("operator-review", {
37
+ task: `Review this summary and flag risks:\n${JSON.stringify(summary)}`,
38
+ tags: ["script-run"],
39
+ priority: 50,
40
+ });
41
+
42
+ return { lookup, summary, task };
43
+ }
44
+ ```
45
+
46
+ ## Label Rules
47
+
48
+ Step labels are durability keys. They must be stable and unique for each logical step. Do not reuse the same literal label inside a loop; launch will fail with `label_lint_violation`.
49
+
50
+ For looped work, include an item identifier in the label:
51
+
52
+ ```ts
53
+ for (const item of args.items) {
54
+ await ctx.step.agentTask(`review-${item.id}`, { task: item.prompt });
55
+ }
56
+ ```
57
+
58
+ ## Statuses
59
+
60
+ Terminal statuses to surface clearly:
61
+
62
+ - `completed` — run finished and `output` may be present.
63
+ - `failed` — run ended with `error`.
64
+ - `cancelled` — run was cancelled before completion.
65
+ - `aborted_limit` — runtime guardrail stopped the run, usually step count, agent-task count, or wall-clock cap.
66
+ - `label_lint_violation` — launch-time rejection, not a persisted run status.
67
+
68
+ When a run is not terminal, report the current status, journal count, and latest heartbeat if present.
@@ -32,16 +32,18 @@ Use `script-query-types` before non-trivial work so the script matches the live
32
32
  Use `script-run` with inline source for one-off work:
33
33
 
34
34
  ```typescript
35
- export default async function main(args: { status: string; limit: number }, ctx) {
35
+ export default async function main(args: any, ctx: any) {
36
36
  const { swarm, logger } = ctx;
37
- const result = await swarm.task_list({ status: args.status, limit: args.limit });
38
- logger.info(`Fetched ${result.tasks.length} tasks`);
37
+ // All SDK methods return Promise<unknown> unwrap defensively.
38
+ const res: any = await swarm.task_list({ status: args?.status, limit: args?.limit ?? 50 });
39
+ const tasks: any[] = res?.data?.tasks ?? res?.tasks ?? [];
40
+ logger.info(`Fetched ${tasks.length} tasks`);
39
41
  return {
40
- total: result.tasks.length,
41
- tasks: result.tasks.map((task) => ({
42
+ total: tasks.length,
43
+ tasks: tasks.map((task: any) => ({
42
44
  id: task.id,
43
45
  status: task.status,
44
- title: task.task.slice(0, 120),
46
+ title: task.task?.slice(0, 120),
45
47
  })),
46
48
  };
47
49
  }
@@ -60,8 +62,44 @@ Good named scripts:
60
62
  - Fan out over many swarm tasks, memories, repos, or schedules.
61
63
  - Convert noisy JSON or HTML into a compact summary.
62
64
 
65
+ ## Using `db_query` For Aggregation
66
+
67
+ For scripts that aggregate over tasks, sessions, or memory, `ctx.swarm.db_query` with direct SQL is far more efficient than fetching lists client-side.
68
+
69
+ **The parameter is `sql`:**
70
+
71
+ ```typescript
72
+ // CORRECT
73
+ const res = await ctx.swarm.db_query({ sql: "SELECT status, count(*) as cnt FROM agent_tasks GROUP BY status" });
74
+
75
+ // Legacy scripts may still run with `query`, but new code should not use it.
76
+ ```
77
+
78
+ **`db_query` returns positional rows, not objects.** The response shape is `{ rows: unknown[][], columns: string[] }`. Zip them into objects:
79
+
80
+ ```typescript
81
+ function rowsToObjects(res: any): any[] {
82
+ const p = res?.data ?? res;
83
+ const cols: string[] = p?.columns ?? [];
84
+ return (p?.rows ?? []).map((r: any) =>
85
+ Array.isArray(r) ? Object.fromEntries(cols.map((c, i) => [c, r[i]])) : r,
86
+ );
87
+ }
88
+
89
+ const rows = rowsToObjects(await ctx.swarm.db_query({
90
+ sql: `SELECT status, count(*) as cnt FROM agent_tasks WHERE createdAt > datetime('now','-3 days') GROUP BY status`,
91
+ }));
92
+ // rows = [{ status: "completed", cnt: 42 }, ...]
93
+ ```
94
+
95
+ **Common tables:** `agent_tasks` (tasks), `session_logs` (tool call logs), `agent_memory` (memories), `scheduled_tasks` (schedules), `agents` (agent registry).
96
+
97
+ **`session_logs` has no `tool_name` column.** Tool names are embedded in the `content` JSON column. Extract them SQL-side with `instr`/`substr` or parse JSON in JS after fetching.
98
+
63
99
  ## SDK And Context Gotchas
64
100
 
101
+ - **`args` can be undefined.** When a script is called without arguments, `args` is `undefined`. Always guard: `argsSchema.safeParse(args || {})` or use optional chaining (`args?.field`).
102
+ - **All SDK methods return `Promise<unknown>`.** Never assume a specific return shape without defensive unwrapping (`res?.data?.tasks ?? res?.tasks ?? []`). Run `script-query-types` to see live type signatures — return types are `unknown` and actual shapes vary by endpoint.
65
103
  - `agentId` is propagated to scripts via the `X-Agent-ID` header, so SDK calls run as the invoking agent.
66
104
  - `taskId` is not ambient. If a script needs to call `ctx.swarm.task_storeProgress`, pass `taskId` explicitly in `args`.
67
105
  - Scripts invoked from a workflow script node may run with a workflow identity rather than a human or worker agent identity.
@@ -73,7 +111,7 @@ Good named scripts:
73
111
  Thread task identity explicitly:
74
112
 
75
113
  ```typescript
76
- export default async function main(args: { taskId: string; items: string[] }, ctx) {
114
+ export default async function main(args: { taskId: string; items: string[] }, ctx: any) {
77
115
  const { swarm } = ctx;
78
116
  await swarm.task_storeProgress({
79
117
  taskId: args.taskId,