@desplega.ai/agent-swarm 1.89.0 → 1.91.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 (63) hide show
  1. package/README.md +4 -0
  2. package/openapi.json +74 -1
  3. package/package.json +6 -6
  4. package/plugin/skills/composio/SKILL.md +138 -63
  5. package/plugin/skills/composio-gmail/SKILL.md +83 -0
  6. package/plugin/skills/composio-google-calendar/SKILL.md +81 -0
  7. package/plugin/skills/composio-google-docs/SKILL.md +71 -0
  8. package/src/artifact-sdk/server.ts +2 -1
  9. package/src/be/db.ts +28 -0
  10. package/src/be/memory/providers/sqlite-store.ts +6 -1
  11. package/src/be/memory/types.ts +1 -0
  12. package/src/be/modelsdev-cache.json +752 -81
  13. package/src/be/scripts/typecheck.ts +132 -1
  14. package/src/be/seed-scripts/catalog/compound-insights.ts +188 -0
  15. package/src/be/seed-scripts/catalog/schedule-health.ts +73 -0
  16. package/src/be/seed-scripts/catalog/smart-recall.ts +65 -0
  17. package/src/be/seed-scripts/catalog/tool-usage.ts +56 -0
  18. package/src/be/seed-scripts/index.ts +36 -0
  19. package/src/commands/artifact.ts +3 -2
  20. package/src/commands/profile-sync.ts +310 -0
  21. package/src/commands/runner.ts +91 -1
  22. package/src/heartbeat/heartbeat.ts +54 -7
  23. package/src/hooks/hook.ts +32 -9
  24. package/src/http/index.ts +47 -0
  25. package/src/http/integrations.ts +6 -1
  26. package/src/http/mcp-bridge.ts +117 -0
  27. package/src/http/mcp-oauth.ts +97 -39
  28. package/src/http/memory.ts +5 -2
  29. package/src/http/openapi.ts +2 -2
  30. package/src/http/pages-public.ts +10 -11
  31. package/src/http/pages.ts +7 -11
  32. package/src/http/scripts.ts +24 -1
  33. package/src/http/tasks.ts +2 -0
  34. package/src/http/utils.ts +11 -4
  35. package/src/jira/app.ts +2 -3
  36. package/src/jira/webhook-lifecycle.ts +2 -1
  37. package/src/linear/app.ts +2 -3
  38. package/src/providers/claude-adapter.ts +26 -0
  39. package/src/scripts-runtime/executors/native.ts +1 -0
  40. package/src/scripts-runtime/sdk-allowlist.ts +121 -0
  41. package/src/scripts-runtime/swarm-sdk.ts +198 -3
  42. package/src/scripts-runtime/types/stdlib.d.ts +227 -0
  43. package/src/scripts-runtime/types/swarm-sdk.d.ts +227 -0
  44. package/src/tasks/worker-follow-up.ts +19 -1
  45. package/src/tests/claude-adapter-otel.test.ts +85 -1
  46. package/src/tests/heartbeat-supersede-resume.test.ts +91 -1
  47. package/src/tests/hook-registration-nudge.test.ts +69 -0
  48. package/src/tests/mcp-oauth-manual-client.test.ts +213 -0
  49. package/src/tests/pages-public-html.test.ts +41 -0
  50. package/src/tests/pages-public-json-redirect.test.ts +37 -2
  51. package/src/tests/profile-sync.test.ts +282 -0
  52. package/src/tests/scripts-runtime.test.ts +33 -0
  53. package/src/tests/seed-scripts.test.ts +2 -2
  54. package/src/tools/create-metric.ts +2 -3
  55. package/src/tools/create-page.ts +3 -6
  56. package/src/tools/memory-rate.ts +2 -1
  57. package/src/tools/memory-search.ts +1 -0
  58. package/src/tools/register-kapso-number.ts +2 -4
  59. package/src/tools/request-human-input.ts +2 -1
  60. package/src/tools/script-common.ts +2 -4
  61. package/src/tools/script-run.ts +7 -0
  62. package/src/utils/constants.ts +58 -8
  63. package/templates/skills/swarm-scripts/content.md +46 -7
@@ -50,21 +50,152 @@ export interface SwarmConfig {
50
50
  }
51
51
 
52
52
  export interface SwarmSdk {
53
+ // --- memory ---
53
54
  memory_search(args: { query: string; scope?: "all" | "agent" | "swarm"; limit?: number; source?: string }): Promise<unknown>;
54
55
  memory_get(args: { memoryId: string }): Promise<unknown>;
55
56
  memory_rate(args: { id: string; useful: boolean; note?: string }): Promise<unknown>;
57
+ // --- tasks ---
56
58
  task_list(args?: Record<string, unknown>): Promise<unknown>;
57
59
  task_get(args: { taskId: string }): Promise<unknown>;
58
60
  task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
61
+ task_poll(args?: Record<string, unknown>): Promise<unknown>;
62
+ // --- kv ---
59
63
  kv_get(args: { key: string; namespace?: string }): Promise<unknown>;
60
64
  kv_set(args: { key: string; value: unknown; namespace?: string; ttlSeconds?: number; valueType?: "string" | "json" | "integer" }): Promise<unknown>;
61
65
  kv_del(args: { key: string; namespace?: string }): Promise<unknown>;
62
66
  kv_incr(args: { key: string; by?: number; namespace?: string }): Promise<unknown>;
63
67
  kv_list(args?: { prefix?: string; namespace?: string; limit?: number }): Promise<unknown>;
68
+ // --- repos ---
64
69
  repo_list(args?: Record<string, unknown>): Promise<unknown>;
70
+ // --- schedules ---
65
71
  schedule_list(args?: Record<string, unknown>): Promise<unknown>;
72
+ // --- scripts ---
66
73
  script_search(args: { query?: string; scope?: ScriptScope; limit?: number }): Promise<unknown>;
67
- script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode }): Promise<unknown>;
74
+ script_run(args: { name?: string; source?: string; args?: unknown; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode; idempotencyKey?: string }): Promise<unknown>;
75
+ // --- swarm / agent ---
76
+ swarm_get(args?: { includeFull?: boolean }): Promise<unknown>;
77
+ agent_info(args?: Record<string, unknown>): Promise<unknown>;
78
+ metrics_get(args?: Record<string, unknown>): Promise<unknown>;
79
+ user_resolve(args?: { kind?: string; externalId?: string; email?: string; userId?: string }): Promise<unknown>;
80
+ db_query(args: { sql: string; params?: unknown[] }): Promise<unknown>;
81
+ // --- config ---
82
+ config_get(args?: { agentId?: string; repoId?: string; key?: string; includeSecrets?: boolean }): Promise<unknown>;
83
+ config_list(args?: { scope?: "global" | "agent" | "repo"; scopeId?: string; key?: string; includeSecrets?: boolean }): Promise<unknown>;
84
+ // --- slack ---
85
+ slack_read(args?: { inboxMessageId?: string; taskId?: string; channelId?: string; threadTs?: string; limit?: number; includeFiles?: boolean }): Promise<unknown>;
86
+ slack_listChannels(args?: { types?: Array<"public" | "private" | "dm" | "mpim">; limit?: number }): Promise<unknown>;
87
+ // --- messaging ---
88
+ message_read(args?: { channel?: string; limit?: number; since?: string; unreadOnly?: boolean; mentionsOnly?: boolean; markAsRead?: boolean }): Promise<unknown>;
89
+ // --- services ---
90
+ service_list(args?: { agentId?: string; name?: string; status?: "starting" | "healthy" | "unhealthy" | "stopped"; includeOwn?: boolean }): Promise<unknown>;
91
+ // --- context / profiles ---
92
+ context_history(args?: { agentId?: string; field?: "soulMd" | "identityMd" | "toolsMd" | "claudeMd" | "setupScript"; limit?: number }): Promise<unknown>;
93
+ context_diff(args: { versionId: string; compareToVersionId?: string }): Promise<unknown>;
94
+ // --- workflows ---
95
+ workflow_list(args?: { enabled?: boolean; includeFull?: boolean }): Promise<unknown>;
96
+ workflow_get(args: { id: string }): Promise<unknown>;
97
+ workflow_listRuns(args: { workflowId: string; status?: "running" | "waiting" | "completed" | "failed" | "skipped" | "cancelled" }): Promise<unknown>;
98
+ workflow_getRun(args: { id: string }): Promise<unknown>;
99
+ // --- prompt templates ---
100
+ prompt_list(args?: { eventType?: string; scope?: "global" | "agent" | "repo"; scopeId?: string; isDefault?: boolean }): Promise<unknown>;
101
+ prompt_get(args: { id: string }): Promise<unknown>;
102
+ // --- tracker ---
103
+ tracker_status(args?: Record<string, unknown>): Promise<unknown>;
104
+ tracker_syncStatus(args?: Record<string, unknown>): Promise<unknown>;
105
+ tracker_linkTask(args: { taskId: string; externalId: string; provider?: string }): Promise<unknown>;
106
+ tracker_unlink(args: { taskId: string }): Promise<unknown>;
107
+ tracker_mapAgent(args: { agentId: string; externalId: string; provider?: string }): Promise<unknown>;
108
+
109
+ // --- write: memory ---
110
+ memory_delete(args: { id: string }): Promise<unknown>;
111
+ inject_learning(args: { content: string; name?: string; scope?: "agent" | "swarm"; source?: string; tags?: string[] }): Promise<unknown>;
112
+
113
+ // --- write: tasks ---
114
+ task_send(args: Record<string, unknown>): Promise<unknown>;
115
+ task_cancel(args: { taskId: string }): Promise<unknown>;
116
+ task_action(args: Record<string, unknown>): Promise<unknown>;
117
+
118
+ // --- write: config ---
119
+ config_set(args: { key: string; value: unknown; scope?: "global" | "agent" | "repo"; scopeId?: string; isSecret?: boolean }): Promise<unknown>;
120
+ config_delete(args: { id: string }): Promise<unknown>;
121
+
122
+ // --- write: slack ---
123
+ slack_post(args: { channelId: string; message: string; blocks?: unknown }): Promise<unknown>;
124
+ slack_reply(args: { channelId?: string; threadTs?: string; message: string; taskId?: string }): Promise<unknown>;
125
+ slack_startThread(args: { channelId: string; message: string }): Promise<unknown>;
126
+ slack_uploadFile(args: Record<string, unknown>): Promise<unknown>;
127
+ slack_downloadFile(args: { url: string }): Promise<unknown>;
128
+
129
+ // --- write: messaging (internal) ---
130
+ message_post(args: { channel?: string; content: string; to?: string }): Promise<unknown>;
131
+
132
+ // --- write: profiles ---
133
+ profile_update(args: Record<string, unknown>): Promise<unknown>;
134
+
135
+ // --- write: services ---
136
+ service_register(args: Record<string, unknown>): Promise<unknown>;
137
+ service_unregister(args: { name: string }): Promise<unknown>;
138
+ service_updateStatus(args: { name: string; status: "starting" | "healthy" | "unhealthy" | "stopped" }): Promise<unknown>;
139
+
140
+ // --- write: schedules ---
141
+ schedule_create(args: Record<string, unknown>): Promise<unknown>;
142
+ schedule_update(args: Record<string, unknown>): Promise<unknown>;
143
+ schedule_delete(args: { id: string }): Promise<unknown>;
144
+ schedule_runNow(args: { id: string }): Promise<unknown>;
145
+
146
+ // --- write: workflows ---
147
+ workflow_create(args: Record<string, unknown>): Promise<unknown>;
148
+ workflow_update(args: Record<string, unknown>): Promise<unknown>;
149
+ workflow_patch(args: Record<string, unknown>): Promise<unknown>;
150
+ workflow_patchNode(args: Record<string, unknown>): Promise<unknown>;
151
+ workflow_delete(args: { id: string }): Promise<unknown>;
152
+ workflow_trigger(args: { id: string; input?: Record<string, unknown> }): Promise<unknown>;
153
+ workflow_retryRun(args: { id: string }): Promise<unknown>;
154
+ workflow_cancelRun(args: { id: string }): Promise<unknown>;
155
+
156
+ // --- write: prompt templates ---
157
+ prompt_set(args: Record<string, unknown>): Promise<unknown>;
158
+ prompt_delete(args: { id: string }): Promise<unknown>;
159
+ prompt_preview(args: Record<string, unknown>): Promise<unknown>;
160
+
161
+ // --- write: scripts ---
162
+ script_upsert(args: { name: string; source: string; description?: string; intent?: string; scope?: ScriptScope; fsMode?: ScriptFsMode }): Promise<unknown>;
163
+ script_delete(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
164
+ script_queryTypes(args: { name: string; scope?: ScriptScope }): Promise<unknown>;
165
+
166
+ // --- write: repos ---
167
+ repo_update(args: Record<string, unknown>): Promise<unknown>;
168
+
169
+ // --- write: agent ---
170
+ agent_join(args: { name: string; role?: string; description?: string; capabilities?: string[]; requestedId?: string; lead?: boolean }): Promise<unknown>;
171
+ user_manage(args: Record<string, unknown>): Promise<unknown>;
172
+
173
+ // --- skills ---
174
+ skill_list(args?: { scope?: string; scopeId?: string; includeBuiltin?: boolean }): Promise<unknown>;
175
+ skill_get(args: { id: string }): Promise<unknown>;
176
+ skill_search(args: { query: string; limit?: number }): Promise<unknown>;
177
+ skill_create(args: Record<string, unknown>): Promise<unknown>;
178
+ skill_update(args: Record<string, unknown>): Promise<unknown>;
179
+ skill_delete(args: { id: string }): Promise<unknown>;
180
+ skill_install(args: Record<string, unknown>): Promise<unknown>;
181
+ skill_uninstall(args: Record<string, unknown>): Promise<unknown>;
182
+ skill_publish(args: Record<string, unknown>): Promise<unknown>;
183
+
184
+ // --- mcp servers ---
185
+ mcpServer_list(args?: Record<string, unknown>): Promise<unknown>;
186
+ mcpServer_get(args: { id: string }): Promise<unknown>;
187
+ mcpServer_create(args: Record<string, unknown>): Promise<unknown>;
188
+ mcpServer_update(args: Record<string, unknown>): Promise<unknown>;
189
+ mcpServer_delete(args: { id: string }): Promise<unknown>;
190
+ mcpServer_install(args: Record<string, unknown>): Promise<unknown>;
191
+ mcpServer_uninstall(args: Record<string, unknown>): Promise<unknown>;
192
+
193
+ // --- pages & metrics ---
194
+ page_create(args: Record<string, unknown>): Promise<unknown>;
195
+ metric_create(args: Record<string, unknown>): Promise<unknown>;
196
+
197
+ // --- human input ---
198
+ request_humanInput(args: Record<string, unknown>): Promise<unknown>;
68
199
  }
69
200
 
70
201
  export interface ScriptStdlib {
@@ -0,0 +1,188 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ days: z
5
+ .number()
6
+ .int()
7
+ .positive()
8
+ .optional()
9
+ .describe("Look back this many days (default 3)"),
10
+ includeToolUsage: z.boolean().optional().describe("Include tool usage histogram (default true)"),
11
+ includeScheduleHealth: z
12
+ .boolean()
13
+ .optional()
14
+ .describe("Include schedule health flags (default true)"),
15
+ includeMemoryHealth: z.boolean().optional().describe("Include memory health stats (default true)"),
16
+ includeByAgent: z
17
+ .boolean()
18
+ .optional()
19
+ .describe("Include per-agent task/completion/failure breakdown (default true)"),
20
+ });
21
+
22
+ /**
23
+ * Failure reasons that are swarm bookkeeping, not real failures. Excluded from
24
+ * failureClusters, scheduleHealth and byAgent failure counts (Lead Rule #16):
25
+ * the run engine collapses redundant sibling tasks into these statuses, so
26
+ * counting them produces phantom failure spikes.
27
+ */
28
+ const EXCLUDED_FAIL = ["superseded_workflow_task", "cancelled"];
29
+
30
+ /**
31
+ * `db_query` returns positional rows (`rows: unknown[][]`) plus a `columns`
32
+ * array — NOT an array of objects. Zip them back into objects so callers can
33
+ * read by column name.
34
+ */
35
+ function rowsToObjects(res: any): any[] {
36
+ const p = res?.data ?? res;
37
+ const cols: string[] = p?.columns ?? [];
38
+ return (p?.rows ?? []).map((r: any) =>
39
+ Array.isArray(r) ? Object.fromEntries(cols.map((c, i) => [c, r[i]])) : r,
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Daily compounding insights — compressed JSON for Phase 0 evolution.
45
+ *
46
+ * Swarm-wide by design: every section aggregates across ALL agents via direct
47
+ * read-only SQL (no per-agent scoping), so a single call replaces ~25 raw tool
48
+ * roundtrips. Parametric via `days` + the `include*` flags.
49
+ */
50
+ export default async function compoundInsights(args: any, ctx: any) {
51
+ const parsed = argsSchema.safeParse(args || {});
52
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
53
+ const days = parsed.data.days || 3;
54
+ const includeToolUsage = parsed.data.includeToolUsage !== false;
55
+ const includeScheduleHealth = parsed.data.includeScheduleHealth !== false;
56
+ const includeMemoryHealth = parsed.data.includeMemoryHealth !== false;
57
+ const includeByAgent = parsed.data.includeByAgent !== false;
58
+
59
+ // `days` is a validated positive int, so it is safe to interpolate into the
60
+ // SQLite datetime modifier. EXCLUDED_FAIL is a fixed constant list.
61
+ const w = `datetime('now','-${days} days')`;
62
+ const exclList = EXCLUDED_FAIL.map((r) => `'${r}'`).join(",");
63
+ // A "real" failure = status failed AND not one of the bookkeeping reasons.
64
+ const realFail = `t.status='failed' AND (t.failureReason IS NULL OR t.failureReason NOT IN (${exclList}))`;
65
+
66
+ const insights: any = { days, generatedAt: new Date().toISOString() };
67
+
68
+ // Task summary (all agents, direct SQL).
69
+ const statusRows = rowsToObjects(
70
+ await ctx.swarm.db_query({
71
+ sql: `SELECT status, count(*) as cnt FROM agent_tasks t WHERE t.createdAt > ${w} GROUP BY status`,
72
+ }),
73
+ );
74
+ const statusCounts: Record<string, number> = {};
75
+ let total = 0;
76
+ for (const r of statusRows) {
77
+ statusCounts[r.status] = r.cnt;
78
+ total += r.cnt;
79
+ }
80
+ const completed = statusCounts.completed ?? 0;
81
+ const failed = statusCounts.failed ?? 0;
82
+ insights.taskSummary = {
83
+ total,
84
+ completed,
85
+ failed,
86
+ completionRate: total > 0 ? Math.round((completed / total) * 1000) / 10 : 0,
87
+ failureRate: total > 0 ? Math.round((failed / total) * 1000) / 10 : 0,
88
+ statusCounts,
89
+ };
90
+
91
+ // Failure clusters (real failures only, normalized to a 60-char lowercased prefix).
92
+ insights.failureClusters = rowsToObjects(
93
+ await ctx.swarm.db_query({
94
+ sql: `SELECT substr(lower(t.failureReason),1,60) as reason, count(*) as count
95
+ FROM agent_tasks t
96
+ WHERE ${realFail} AND t.failureReason IS NOT NULL AND t.createdAt > ${w}
97
+ GROUP BY reason ORDER BY count DESC LIMIT 10`,
98
+ }),
99
+ );
100
+
101
+ // Schedule health (>= 2 runs, > 20% real-failure rate).
102
+ if (includeScheduleHealth) {
103
+ const sh = rowsToObjects(
104
+ await ctx.swarm.db_query({
105
+ sql: `SELECT s.name as name, s.id as id, count(t.id) as runs,
106
+ sum(case when ${realFail} then 1 else 0 end) as failed
107
+ FROM scheduled_tasks s
108
+ JOIN agent_tasks t ON t.scheduleId = s.id
109
+ WHERE t.createdAt > ${w} AND t.status != 'cancelled'
110
+ GROUP BY s.id, s.name HAVING runs >= 2`,
111
+ }),
112
+ );
113
+ insights.scheduleHealth = sh
114
+ .map((r: any) => ({
115
+ name: r.name,
116
+ id: r.id,
117
+ runs: r.runs,
118
+ failureRate: r.runs > 0 ? Math.round((r.failed / r.runs) * 100) : 0,
119
+ }))
120
+ .filter((r: any) => r.failureRate > 20)
121
+ .sort((a: any, b: any) => b.failureRate - a.failureRate);
122
+ }
123
+
124
+ // Tool usage (top 25). Tool names live inside the `content` JSON of
125
+ // session_logs (no dedicated column), so extract the name SQL-side: the
126
+ // `'%"type":"tool_use"%'` filter excludes tool_result rows (which only carry
127
+ // `tool_use_id`), and instr/substr pull the first tool name per log line.
128
+ // Approximate: a log line with parallel tool_use blocks counts only its first.
129
+ if (includeToolUsage) {
130
+ insights.toolUsage = rowsToObjects(
131
+ await ctx.swarm.db_query({
132
+ sql: `WITH tu AS (
133
+ SELECT substr(content, instr(content,'"type":"tool_use"')) AS tail
134
+ FROM session_logs
135
+ WHERE content LIKE '%"type":"tool_use"%' AND createdAt > ${w}
136
+ ),
137
+ nm AS (
138
+ SELECT substr(tail, instr(tail,'"name":"')+8) AS rest
139
+ FROM tu WHERE instr(tail,'"name":"') > 0
140
+ )
141
+ SELECT substr(rest,1,instr(rest,'"')-1) AS tool, count(*) AS calls
142
+ FROM nm GROUP BY tool ORDER BY calls DESC LIMIT 25`,
143
+ }),
144
+ ).map((r: any) => ({ tool: r.tool, calls: r.calls }));
145
+ }
146
+
147
+ // Memory health (whole store, by scope + source).
148
+ if (includeMemoryHealth) {
149
+ const memRows = rowsToObjects(
150
+ await ctx.swarm.db_query({
151
+ sql: `SELECT scope, source, count(*) as cnt FROM agent_memory GROUP BY scope, source`,
152
+ }),
153
+ );
154
+ const totalMem = memRows.reduce((s: number, r: any) => s + (r.cnt ?? 0), 0);
155
+ insights.memoryHealth = {
156
+ total: totalMem,
157
+ byScope: memRows.reduce((m: any, r: any) => {
158
+ m[r.scope] = (m[r.scope] ?? 0) + r.cnt;
159
+ return m;
160
+ }, {}),
161
+ bySource: memRows.reduce((m: any, r: any) => {
162
+ m[r.source] = (m[r.source] ?? 0) + r.cnt;
163
+ return m;
164
+ }, {}),
165
+ };
166
+ }
167
+
168
+ // Per-agent breakdown — covers every agent that ran a task in the window.
169
+ if (includeByAgent) {
170
+ insights.byAgent = rowsToObjects(
171
+ await ctx.swarm.db_query({
172
+ sql: `SELECT a.name as agent, count(*) as total,
173
+ sum(case when t.status='completed' then 1 else 0 end) as completed,
174
+ sum(case when ${realFail} then 1 else 0 end) as failed
175
+ FROM agent_tasks t LEFT JOIN agents a ON a.id = t.agentId
176
+ WHERE t.createdAt > ${w} AND t.agentId IS NOT NULL
177
+ GROUP BY t.agentId, a.name ORDER BY total DESC LIMIT 30`,
178
+ }),
179
+ ).map((r: any) => ({
180
+ agent: r.agent,
181
+ total: r.total,
182
+ completed: r.completed,
183
+ failed: r.failed,
184
+ }));
185
+ }
186
+
187
+ return insights;
188
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ days: z
5
+ .number()
6
+ .int()
7
+ .positive()
8
+ .optional()
9
+ .describe("Look back this many days (default 7)"),
10
+ failureThreshold: z
11
+ .number()
12
+ .optional()
13
+ .describe("Flag schedules with failure rate above this (0-1, default 0.2)"),
14
+ });
15
+
16
+ /** Per-schedule health check: failure rates and flagging unhealthy schedules. */
17
+ export default async function scheduleHealth(args: any, ctx: any) {
18
+ const parsed = argsSchema.safeParse(args);
19
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
20
+ const days = parsed.data.days || 7;
21
+ const threshold = parsed.data.failureThreshold ?? 0.2;
22
+ const since = new Date(Date.now() - days * 86400000).toISOString();
23
+
24
+ // Get schedules
25
+ const schedRes: any = await ctx.swarm.schedule_list({});
26
+ const schedPayload = schedRes?.data ?? schedRes;
27
+ const schedules: any[] = schedPayload?.schedules ?? [];
28
+
29
+ if (!schedules.length) return { days, schedules: [], flagged: [] };
30
+
31
+ // Get recent tasks to correlate with schedules
32
+ const taskRes: any = await ctx.swarm.task_list({ createdAfter: since, limit: 2000 });
33
+ const taskPayload = taskRes?.data ?? taskRes;
34
+ const tasks: any[] = taskPayload?.tasks ?? [];
35
+
36
+ // Group by scheduleId
37
+ const bySchedule = new Map<string, { total: number; failed: number; completed: number }>();
38
+ for (const t of tasks) {
39
+ if (!t.scheduleId) continue;
40
+ const entry = bySchedule.get(t.scheduleId) ?? { total: 0, failed: 0, completed: 0 };
41
+ entry.total++;
42
+ if (t.status === "failed") entry.failed++;
43
+ if (t.status === "completed") entry.completed++;
44
+ bySchedule.set(t.scheduleId, entry);
45
+ }
46
+
47
+ const results = schedules.map((s: any) => {
48
+ const stats = bySchedule.get(s.id) ?? { total: 0, failed: 0, completed: 0 };
49
+ const failureRate = stats.total > 0 ? stats.failed / stats.total : 0;
50
+ return {
51
+ id: s.id,
52
+ name: s.name,
53
+ enabled: s.enabled,
54
+ targetAgentId: s.targetAgentId,
55
+ runs: stats.total,
56
+ failed: stats.failed,
57
+ completed: stats.completed,
58
+ failureRate: Math.round(failureRate * 1000) / 1000,
59
+ flagged: stats.total >= 3 && failureRate > threshold,
60
+ };
61
+ });
62
+
63
+ const flagged = results.filter((r: any) => r.flagged);
64
+
65
+ return {
66
+ days,
67
+ threshold,
68
+ totalSchedules: schedules.length,
69
+ flaggedCount: flagged.length,
70
+ schedules: results.sort((a: any, b: any) => b.failureRate - a.failureRate),
71
+ flagged,
72
+ };
73
+ }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ queries: z
5
+ .array(z.string())
6
+ .min(1)
7
+ .describe("One or more search queries to fan out against the memory store"),
8
+ scope: z
9
+ .enum(["all", "agent", "swarm"])
10
+ .optional()
11
+ .describe("Memory scope filter (default all)"),
12
+ limit: z
13
+ .number()
14
+ .int()
15
+ .positive()
16
+ .optional()
17
+ .describe("Max results per query (default 10)"),
18
+ dedupThreshold: z
19
+ .number()
20
+ .optional()
21
+ .describe("Similarity threshold for dedup — memories above this are merged (default 0.92)"),
22
+ });
23
+
24
+ /** Multi-query fan-out memory recall with dedup and composite reranking. */
25
+ export default async function smartRecall(args: any, ctx: any) {
26
+ const parsed = argsSchema.safeParse(args);
27
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
28
+ const { queries, scope = "all", limit = 10, dedupThreshold = 0.92 } = parsed.data;
29
+
30
+ const allResults: any[] = [];
31
+ for (const q of queries) {
32
+ const res: any = await ctx.swarm.memory_search({ query: q, limit, scope });
33
+ const payload = res?.data ?? res;
34
+ const results = payload?.results ?? [];
35
+ for (const r of results) {
36
+ allResults.push({ ...r, querySource: q });
37
+ }
38
+ }
39
+
40
+ // Dedup by ID, keeping best similarity per memory
41
+ const seen = new Map<string, any>();
42
+ const hitCounts = new Map<string, number>();
43
+ for (const r of allResults) {
44
+ hitCounts.set(r.id, (hitCounts.get(r.id) ?? 0) + 1);
45
+ const existing = seen.get(r.id);
46
+ if (!existing || r.similarity > existing.similarity) {
47
+ seen.set(r.id, r);
48
+ }
49
+ }
50
+
51
+ // Composite rerank: bestSimilarity + 0.05 * hitCount
52
+ const deduped = Array.from(seen.values()).map((r) => ({
53
+ ...r,
54
+ hits: hitCounts.get(r.id) ?? 1,
55
+ compositeScore: r.similarity + 0.05 * (hitCounts.get(r.id) ?? 1),
56
+ }));
57
+ deduped.sort((a: any, b: any) => b.compositeScore - a.compositeScore);
58
+
59
+ return {
60
+ queriesRun: queries.length,
61
+ totalCandidates: allResults.length,
62
+ uniqueMemories: deduped.length,
63
+ memories: deduped.slice(0, limit * 2),
64
+ };
65
+ }
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+
3
+ export const argsSchema = z.object({
4
+ days: z
5
+ .number()
6
+ .int()
7
+ .positive()
8
+ .optional()
9
+ .describe("Look back this many days (default 7)"),
10
+ agentId: z.string().optional().describe("Filter by agent ID (default: all agents)"),
11
+ limit: z
12
+ .number()
13
+ .int()
14
+ .positive()
15
+ .optional()
16
+ .describe("Top N tools to return (default 20)"),
17
+ });
18
+
19
+ /** Tool usage histogram from session_logs — top tools by call count. */
20
+ export default async function toolUsage(args: any, ctx: any) {
21
+ const parsed = argsSchema.safeParse(args);
22
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
23
+ const days = parsed.data.days || 7;
24
+ const limit = parsed.data.limit || 20;
25
+ const agentId = parsed.data.agentId;
26
+
27
+ const agentFilter = agentId ? `AND agent_id = '${agentId}'` : "";
28
+ const query = `
29
+ SELECT tool_name, count(*) as calls
30
+ FROM session_logs
31
+ WHERE tool_name IS NOT NULL
32
+ AND created_at > datetime('now', '-${days} days')
33
+ ${agentFilter}
34
+ GROUP BY tool_name
35
+ ORDER BY calls DESC
36
+ LIMIT ${limit}
37
+ `;
38
+
39
+ const res: any = await ctx.swarm.db_query({ query });
40
+ const payload = res?.data ?? res;
41
+ const rows: any[] = payload?.rows ?? [];
42
+
43
+ const totalCalls = rows.reduce((sum: number, r: any) => sum + (r.calls ?? 0), 0);
44
+
45
+ return {
46
+ days,
47
+ agentId: agentId ?? "all",
48
+ totalDistinctTools: rows.length,
49
+ totalCalls,
50
+ tools: rows.map((r: any) => ({
51
+ tool: r.tool_name,
52
+ calls: r.calls,
53
+ pct: totalCalls > 0 ? Math.round((r.calls / totalCalls) * 1000) / 10 : 0,
54
+ })),
55
+ };
56
+ }
@@ -22,6 +22,7 @@ import { getScript, upsertScriptByName } from "../scripts/db";
22
22
  import { extractArgsJsonSchema } from "../scripts/extract-schema";
23
23
  import { typecheckScript } from "../scripts/typecheck";
24
24
  import type { Seeder, SeedItem } from "../seed/types";
25
+ import compoundInsightsSrc from "./catalog/compound-insights.ts" with { type: "text" };
25
26
  import dateResolveSrc from "./catalog/date-resolve.ts" with { type: "text" };
26
27
  import fetchReadableSrc from "./catalog/fetch-readable.ts" with { type: "text" };
27
28
  import ghPrSnapshotSrc from "./catalog/gh-pr-snapshot.ts" with { type: "text" };
@@ -29,9 +30,12 @@ import groupCountSrc from "./catalog/group-count.ts" with { type: "text" };
29
30
  import jsonQuerySrc from "./catalog/json-query.ts" with { type: "text" };
30
31
  import linearIssueSrc from "./catalog/linear-issue.ts" with { type: "text" };
31
32
  import memoryDedupCheckSrc from "./catalog/memory-dedup-check.ts" with { type: "text" };
33
+ import scheduleHealthSrc from "./catalog/schedule-health.ts" with { type: "text" };
32
34
  import slackThreadFlattenSrc from "./catalog/slack-thread-flatten.ts" with { type: "text" };
35
+ import smartRecallSrc from "./catalog/smart-recall.ts" with { type: "text" };
33
36
  import taskFailureAuditSrc from "./catalog/task-failure-audit.ts" with { type: "text" };
34
37
  import textDiffSrc from "./catalog/text-diff.ts" with { type: "text" };
38
+ import toolUsageSrc from "./catalog/tool-usage.ts" with { type: "text" };
35
39
 
36
40
  export type SeedScript = {
37
41
  name: string;
@@ -122,6 +126,38 @@ export const SEED_SCRIPTS: SeedScript[] = [
122
126
  intent: "Turn a Slack thread into plain text for summarizing or as task context.",
123
127
  source: asText(slackThreadFlattenSrc),
124
128
  },
129
+ {
130
+ name: "smart-recall",
131
+ description:
132
+ "Multi-query fan-out memory search with dedup and composite reranking (bestSimilarity + 0.05 * hitCount). Returns unique memories across all queries.",
133
+ intent:
134
+ "Recall relevant memories using multiple search angles — better coverage than a single query. Use for task onboarding, context gathering, or before writing new memories.",
135
+ source: asText(smartRecallSrc),
136
+ },
137
+ {
138
+ name: "schedule-health",
139
+ description:
140
+ "Per-schedule failure rate check over recent tasks — flags schedules with failure rates above a configurable threshold.",
141
+ intent:
142
+ "Find unhealthy schedules that keep failing — for daily compounding, reliability reviews, or ops triage.",
143
+ source: asText(scheduleHealthSrc),
144
+ },
145
+ {
146
+ name: "tool-usage",
147
+ description:
148
+ "Tool usage histogram from session_logs — top tools by call count over a time window, optionally filtered by agent.",
149
+ intent:
150
+ "See which MCP tools agents use most — for SDK gap analysis, optimization, or daily ops snapshots.",
151
+ source: asText(toolUsageSrc),
152
+ },
153
+ {
154
+ name: "compound-insights",
155
+ description:
156
+ "All-in-one swarm-wide daily ops snapshot: task completion/failure summary, real failure clusters (excludes superseded/cancelled bookkeeping), schedule health flags, tool usage top-25, memory health stats, and a per-agent breakdown. Aggregates across ALL agents via direct read-only SQL.",
157
+ intent:
158
+ "Single-call daily compounding Phase 0 helper — replaces ~25 raw tool roundtrips with one compressed JSON result covering every agent. For daily evolution, ops reviews, or heartbeat context.",
159
+ source: asText(compoundInsightsSrc),
160
+ },
125
161
  ];
126
162
 
127
163
  /** A catalog entry resolved into a generic {@link SeedItem}. */
@@ -1,6 +1,7 @@
1
1
  import { Hono } from "hono";
2
2
  import { createArtifactServer } from "../artifact-sdk";
3
3
  import { getApiKey } from "../utils/api-key";
4
+ import { getMcpBaseUrl } from "../utils/constants";
4
5
 
5
6
  interface ArtifactArgs {
6
7
  additionalArgs: string[];
@@ -139,7 +140,7 @@ async function artifactServe(args: ArtifactArgs) {
139
140
 
140
141
  async function artifactList() {
141
142
  const apiKey = getApiKey();
142
- const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
143
+ const mcpBaseUrl = getMcpBaseUrl();
143
144
  const agentId = process.env.AGENT_ID || "";
144
145
 
145
146
  try {
@@ -197,7 +198,7 @@ async function artifactStop(args: ArtifactArgs) {
197
198
  }
198
199
 
199
200
  const apiKey = getApiKey();
200
- const mcpBaseUrl = process.env.MCP_BASE_URL || `http://localhost:${process.env.PORT || "3013"}`;
201
+ const mcpBaseUrl = getMcpBaseUrl();
201
202
  const agentId = process.env.AGENT_ID || "";
202
203
 
203
204
  // 1. Try to stop PM2 process