@desplega.ai/agent-swarm 1.91.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 +3 -2
- package/openapi.json +1005 -152
- package/package.json +6 -6
- package/plugin/skills/pages/SKILL.md +5 -2
- package/src/be/db.ts +662 -19
- 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/083_script_workflows.sql +51 -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 +42310 -38617
- package/src/be/scripts/typecheck.ts +49 -0
- 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 +310 -6
- 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 +506 -0
- package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
- package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
- package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
- package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
- package/src/be/seed-scripts/index.ts +51 -5
- package/src/be/seed-skills/index.ts +3 -3
- package/src/be/skill-sync.ts +91 -7
- package/src/be/swarm-config-guard.ts +17 -0
- package/src/commands/runner.ts +49 -4
- package/src/heartbeat/templates.ts +20 -16
- package/src/http/db-query.ts +20 -5
- package/src/http/index.ts +51 -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 +557 -0
- package/src/http/scripts.ts +39 -2
- package/src/http/skills.ts +225 -0
- package/src/prompts/session-templates.ts +24 -4
- package/src/providers/claude-adapter.ts +107 -28
- package/src/script-workflows/executor.ts +110 -0
- package/src/script-workflows/harness.ts +73 -0
- package/src/script-workflows/label-lint.ts +51 -0
- package/src/script-workflows/limits.ts +22 -0
- package/src/script-workflows/supervisor.ts +139 -0
- package/src/script-workflows/workflow-ctx.ts +209 -0
- package/src/scripts-runtime/sdk-allowlist.ts +4 -0
- package/src/scripts-runtime/swarm-sdk.ts +13 -0
- package/src/scripts-runtime/types/stdlib.d.ts +61 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
- package/src/server.ts +4 -0
- package/src/slack/handlers.ts +11 -4
- package/src/slack/message-text.ts +98 -0
- package/src/slack/thread-buffer.ts +5 -3
- package/src/tests/claude-adapter-binary.test.ts +271 -74
- package/src/tests/create-page-tool.test.ts +19 -2
- package/src/tests/db-query.test.ts +28 -0
- package/src/tests/error-tracker.test.ts +121 -0
- package/src/tests/harness-provider-resolution.test.ts +33 -0
- package/src/tests/heartbeat-checklist.test.ts +36 -0
- package/src/tests/mcp-tools.test.ts +6 -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/prompt-template-session.test.ts +34 -5
- package/src/tests/script-runs-http.test.ts +278 -0
- package/src/tests/script-workflows-label-lint.test.ts +43 -0
- package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
- package/src/tests/scripts-mcp-e2e.test.ts +102 -2
- package/src/tests/seed-scripts.test.ts +468 -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/tests/slack-message-text.test.ts +250 -0
- package/src/tests/system-default-skills.test.ts +40 -0
- package/src/tools/create-page.ts +2 -2
- package/src/tools/db-query.ts +16 -6
- package/src/tools/script-runs.ts +123 -0
- package/src/tools/skills/index.ts +1 -0
- package/src/tools/skills/skill-get-file.ts +80 -0
- package/src/tools/slack-read.ts +12 -3
- package/src/tools/tool-config.ts +6 -2
- package/src/types.ts +72 -0
- package/src/utils/error-tracker.ts +40 -1
- package/src/utils/internal-ai/complete-structured.ts +10 -4
- package/src/workflows/executors/raw-llm.ts +76 -59
- 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/pages/content.md +205 -55
- package/templates/skills/profile-corruption-escalation/content.md +44 -85
- package/templates/skills/script-workflows/config.json +14 -0
- package/templates/skills/script-workflows/content.md +68 -0
- package/templates/skills/sprite-cli/content.md +4 -5
- package/templates/skills/swarm-scripts/content.md +2 -3
- 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
package/src/be/db.ts
CHANGED
|
@@ -62,12 +62,17 @@ import type {
|
|
|
62
62
|
RepoGuidelines,
|
|
63
63
|
ScheduledTask,
|
|
64
64
|
ScheduledTaskSummary,
|
|
65
|
+
ScriptRun,
|
|
66
|
+
ScriptRunJournalEntry,
|
|
67
|
+
ScriptRunKind,
|
|
68
|
+
ScriptRunStatus,
|
|
65
69
|
Service,
|
|
66
70
|
ServiceStatus,
|
|
67
71
|
SessionCost,
|
|
68
72
|
SessionCostSource,
|
|
69
73
|
SessionLog,
|
|
70
74
|
Skill,
|
|
75
|
+
SkillFile,
|
|
71
76
|
SkillScope,
|
|
72
77
|
SkillType,
|
|
73
78
|
SkillWithInstallInfo,
|
|
@@ -110,6 +115,26 @@ export function isSqliteVecAvailable(): boolean {
|
|
|
110
115
|
return sqliteVecAvailable;
|
|
111
116
|
}
|
|
112
117
|
|
|
118
|
+
function loadSqliteVec(database: Database): void {
|
|
119
|
+
sqliteVecAvailable = false;
|
|
120
|
+
try {
|
|
121
|
+
const extensionPath = process.env.SQLITE_VEC_EXTENSION_PATH;
|
|
122
|
+
if (extensionPath) {
|
|
123
|
+
database.loadExtension(extensionPath);
|
|
124
|
+
} else {
|
|
125
|
+
const sqliteVec = require("sqlite-vec");
|
|
126
|
+
sqliteVec.load(database);
|
|
127
|
+
}
|
|
128
|
+
sqliteVecAvailable = true;
|
|
129
|
+
console.log(`[db] sqlite-vec loaded${extensionPath ? ` from ${extensionPath}` : ""}`);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.warn(
|
|
132
|
+
"[db] sqlite-vec not available, falling back to in-memory cosine:",
|
|
133
|
+
(err as Error).message,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
113
138
|
export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
|
|
114
139
|
if (db) {
|
|
115
140
|
return db;
|
|
@@ -126,6 +151,7 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
|
|
|
126
151
|
db = Database.deserialize(templateBytes);
|
|
127
152
|
db.run("PRAGMA busy_timeout = 5000;");
|
|
128
153
|
db.run("PRAGMA foreign_keys = ON;");
|
|
154
|
+
loadSqliteVec(db);
|
|
129
155
|
configureDbResolver(resolvePromptTemplate);
|
|
130
156
|
// Ensure the encryption key is resolved even when restoring from the test
|
|
131
157
|
// template. The cache may have been cleared via __resetEncryptionKeyForTests
|
|
@@ -151,22 +177,7 @@ export function initDb(dbPath = "./agent-swarm-db.sqlite"): Database {
|
|
|
151
177
|
// `require.resolve("sqlite-vec-<platform>/vec0.so")` can't find the native
|
|
152
178
|
// asset — so we prefer an explicit filesystem path when set, and only fall
|
|
153
179
|
// back to the npm resolver for normal dev runs.
|
|
154
|
-
|
|
155
|
-
const extensionPath = process.env.SQLITE_VEC_EXTENSION_PATH;
|
|
156
|
-
if (extensionPath) {
|
|
157
|
-
database.loadExtension(extensionPath);
|
|
158
|
-
} else {
|
|
159
|
-
const sqliteVec = require("sqlite-vec");
|
|
160
|
-
sqliteVec.load(database);
|
|
161
|
-
}
|
|
162
|
-
sqliteVecAvailable = true;
|
|
163
|
-
console.log(`[db] sqlite-vec loaded${extensionPath ? ` from ${extensionPath}` : ""}`);
|
|
164
|
-
} catch (err) {
|
|
165
|
-
console.warn(
|
|
166
|
-
"[db] sqlite-vec not available, falling back to in-memory cosine:",
|
|
167
|
-
(err as Error).message,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
180
|
+
loadSqliteVec(database);
|
|
170
181
|
|
|
171
182
|
// Run database migrations (schema creation + incremental changes)
|
|
172
183
|
runMigrations(database);
|
|
@@ -344,6 +355,7 @@ export function closeDb(): void {
|
|
|
344
355
|
db.close();
|
|
345
356
|
db = null;
|
|
346
357
|
}
|
|
358
|
+
sqliteVecAvailable = false;
|
|
347
359
|
}
|
|
348
360
|
|
|
349
361
|
// ============================================================================
|
|
@@ -1887,6 +1899,19 @@ export function getInProgressTasksByContextKey(
|
|
|
1887
1899
|
.map(rowToAgentTask);
|
|
1888
1900
|
}
|
|
1889
1901
|
|
|
1902
|
+
export function getLatestTaskByContextKey(contextKey: string): AgentTask | null {
|
|
1903
|
+
if (!contextKey) return null;
|
|
1904
|
+
const row = getDb()
|
|
1905
|
+
.prepare<AgentTaskRow, [string]>(
|
|
1906
|
+
`SELECT * FROM agent_tasks
|
|
1907
|
+
WHERE contextKey = ?
|
|
1908
|
+
ORDER BY createdAt DESC
|
|
1909
|
+
LIMIT 1`,
|
|
1910
|
+
)
|
|
1911
|
+
.get(contextKey);
|
|
1912
|
+
return row ? rowToAgentTask(row) : null;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1890
1915
|
/**
|
|
1891
1916
|
* Find the most recent agent associated with a specific Slack thread.
|
|
1892
1917
|
* No status filter — returns the last agent that touched this thread regardless of task state.
|
|
@@ -7075,7 +7100,7 @@ export function createPage(data: {
|
|
|
7075
7100
|
title: string;
|
|
7076
7101
|
description?: string;
|
|
7077
7102
|
contentType: PageContentType;
|
|
7078
|
-
authMode
|
|
7103
|
+
authMode?: PageAuthMode;
|
|
7079
7104
|
passwordHash?: string;
|
|
7080
7105
|
body: string;
|
|
7081
7106
|
needsCredentials?: string[];
|
|
@@ -7094,7 +7119,7 @@ export function createPage(data: {
|
|
|
7094
7119
|
data.title,
|
|
7095
7120
|
data.description ?? null,
|
|
7096
7121
|
data.contentType,
|
|
7097
|
-
data.authMode,
|
|
7122
|
+
data.authMode ?? "authed",
|
|
7098
7123
|
data.passwordHash ?? null,
|
|
7099
7124
|
data.body,
|
|
7100
7125
|
data.needsCredentials ? JSON.stringify(data.needsCredentials) : null,
|
|
@@ -8676,6 +8701,119 @@ function rowToSkillWithInstall(row: SkillWithInstallRow): SkillWithInstallInfo {
|
|
|
8676
8701
|
};
|
|
8677
8702
|
}
|
|
8678
8703
|
|
|
8704
|
+
type SkillFileRow = {
|
|
8705
|
+
id: string;
|
|
8706
|
+
skillId: string;
|
|
8707
|
+
path: string;
|
|
8708
|
+
content: string;
|
|
8709
|
+
mimeType: string;
|
|
8710
|
+
isBinary: number;
|
|
8711
|
+
size: number | null;
|
|
8712
|
+
createdAt: string;
|
|
8713
|
+
lastUpdatedAt: string;
|
|
8714
|
+
};
|
|
8715
|
+
|
|
8716
|
+
function rowToSkillFile(row: SkillFileRow): SkillFile {
|
|
8717
|
+
return {
|
|
8718
|
+
id: row.id,
|
|
8719
|
+
skillId: row.skillId,
|
|
8720
|
+
path: row.path,
|
|
8721
|
+
content: row.content,
|
|
8722
|
+
mimeType: row.mimeType,
|
|
8723
|
+
isBinary: row.isBinary === 1,
|
|
8724
|
+
size: row.size,
|
|
8725
|
+
createdAt: row.createdAt,
|
|
8726
|
+
lastUpdatedAt: row.lastUpdatedAt,
|
|
8727
|
+
};
|
|
8728
|
+
}
|
|
8729
|
+
|
|
8730
|
+
export type SkillFileInput = {
|
|
8731
|
+
path: string;
|
|
8732
|
+
content: string;
|
|
8733
|
+
mimeType?: string;
|
|
8734
|
+
isBinary?: boolean;
|
|
8735
|
+
size?: number | null;
|
|
8736
|
+
};
|
|
8737
|
+
|
|
8738
|
+
export type SkillFileManifestEntry = Omit<SkillFile, "content">;
|
|
8739
|
+
type NormalizedSkillFileInput = {
|
|
8740
|
+
path: string;
|
|
8741
|
+
content: string;
|
|
8742
|
+
mimeType: string;
|
|
8743
|
+
isBinary: boolean;
|
|
8744
|
+
size: number;
|
|
8745
|
+
};
|
|
8746
|
+
|
|
8747
|
+
export const SKILL_FILE_LIMITS = {
|
|
8748
|
+
maxCount: Number(process.env.SKILL_FILES_MAX_COUNT ?? 100),
|
|
8749
|
+
maxTotalBytes: Number(process.env.SKILL_FILES_MAX_TOTAL_BYTES ?? 10 * 1024 * 1024),
|
|
8750
|
+
maxFileBytes: Number(process.env.SKILL_FILES_MAX_FILE_BYTES ?? 500 * 1024),
|
|
8751
|
+
};
|
|
8752
|
+
|
|
8753
|
+
const BINARY_SKILL_FILE_PLACEHOLDER = "[binary file - not synced]";
|
|
8754
|
+
|
|
8755
|
+
export function normalizeSkillFilePath(path: string): string {
|
|
8756
|
+
const raw = path.trim().replace(/\\/g, "/");
|
|
8757
|
+
if (!raw) throw new Error("File path is required");
|
|
8758
|
+
if (raw.startsWith("/")) throw new Error("File path must be relative");
|
|
8759
|
+
|
|
8760
|
+
const parts = raw.split("/").filter(Boolean);
|
|
8761
|
+
if (parts.length === 0) throw new Error("File path is required");
|
|
8762
|
+
if (parts.some((part) => part === "." || part === "..")) {
|
|
8763
|
+
throw new Error("File path cannot contain traversal segments");
|
|
8764
|
+
}
|
|
8765
|
+
|
|
8766
|
+
const normalized = parts.join("/");
|
|
8767
|
+
if (normalized === "SKILL.md") {
|
|
8768
|
+
throw new Error("SKILL.md is stored on the skill record, not in skill_files");
|
|
8769
|
+
}
|
|
8770
|
+
return normalized;
|
|
8771
|
+
}
|
|
8772
|
+
|
|
8773
|
+
function byteSize(content: string): number {
|
|
8774
|
+
return Buffer.byteLength(content, "utf8");
|
|
8775
|
+
}
|
|
8776
|
+
|
|
8777
|
+
function normalizeSkillFileInput(input: SkillFileInput): NormalizedSkillFileInput {
|
|
8778
|
+
const path = normalizeSkillFilePath(input.path);
|
|
8779
|
+
const isBinary = input.isBinary === true;
|
|
8780
|
+
const content = isBinary ? input.content || BINARY_SKILL_FILE_PLACEHOLDER : input.content;
|
|
8781
|
+
const size = input.size ?? byteSize(content);
|
|
8782
|
+
if (!Number.isFinite(size) || size < 0) {
|
|
8783
|
+
throw new Error("File size must be a non-negative number");
|
|
8784
|
+
}
|
|
8785
|
+
if (size > SKILL_FILE_LIMITS.maxFileBytes) {
|
|
8786
|
+
throw new Error(`File ${path} exceeds max size ${SKILL_FILE_LIMITS.maxFileBytes}`);
|
|
8787
|
+
}
|
|
8788
|
+
|
|
8789
|
+
return {
|
|
8790
|
+
path,
|
|
8791
|
+
content,
|
|
8792
|
+
mimeType: input.mimeType ?? "text/plain",
|
|
8793
|
+
isBinary,
|
|
8794
|
+
size,
|
|
8795
|
+
};
|
|
8796
|
+
}
|
|
8797
|
+
|
|
8798
|
+
function assertSkillFileLimits(skillId: string, incoming: SkillFileInput[], replaceAll: boolean) {
|
|
8799
|
+
const existing = replaceAll ? [] : listSkillFileManifest(skillId);
|
|
8800
|
+
const byPath = new Map(existing.map((file) => [file.path, file.size ?? 0]));
|
|
8801
|
+
|
|
8802
|
+
for (const input of incoming) {
|
|
8803
|
+
const normalized = normalizeSkillFileInput(input);
|
|
8804
|
+
byPath.set(normalized.path, normalized.size);
|
|
8805
|
+
}
|
|
8806
|
+
|
|
8807
|
+
if (byPath.size > SKILL_FILE_LIMITS.maxCount) {
|
|
8808
|
+
throw new Error(`Skill file count exceeds max ${SKILL_FILE_LIMITS.maxCount}`);
|
|
8809
|
+
}
|
|
8810
|
+
|
|
8811
|
+
const total = [...byPath.values()].reduce((sum, size) => sum + size, 0);
|
|
8812
|
+
if (total > SKILL_FILE_LIMITS.maxTotalBytes) {
|
|
8813
|
+
throw new Error(`Skill files exceed max total size ${SKILL_FILE_LIMITS.maxTotalBytes}`);
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
|
|
8679
8817
|
export interface SkillInsert {
|
|
8680
8818
|
name: string;
|
|
8681
8819
|
description: string;
|
|
@@ -8849,6 +8987,124 @@ export function updateSkill(
|
|
|
8849
8987
|
return row ? rowToSkill(row) : null;
|
|
8850
8988
|
}
|
|
8851
8989
|
|
|
8990
|
+
function bumpSkillVersion(skillId: string, now = new Date().toISOString()) {
|
|
8991
|
+
getDb()
|
|
8992
|
+
.prepare("UPDATE skills SET version = version + 1, lastUpdatedAt = ? WHERE id = ?")
|
|
8993
|
+
.run(now, skillId);
|
|
8994
|
+
}
|
|
8995
|
+
|
|
8996
|
+
export function listSkillFileManifest(skillId: string): SkillFileManifestEntry[] {
|
|
8997
|
+
return getDb()
|
|
8998
|
+
.prepare<SkillFileRow, [string]>(
|
|
8999
|
+
`SELECT id, skillId, path, content, mimeType, isBinary, size, createdAt, lastUpdatedAt
|
|
9000
|
+
FROM skill_files
|
|
9001
|
+
WHERE skillId = ?
|
|
9002
|
+
ORDER BY path ASC`,
|
|
9003
|
+
)
|
|
9004
|
+
.all(skillId)
|
|
9005
|
+
.map((row) => {
|
|
9006
|
+
const { content: _content, ...manifest } = rowToSkillFile(row);
|
|
9007
|
+
return manifest;
|
|
9008
|
+
});
|
|
9009
|
+
}
|
|
9010
|
+
|
|
9011
|
+
export function getSkillFiles(skillId: string): SkillFile[] {
|
|
9012
|
+
return getDb()
|
|
9013
|
+
.prepare<SkillFileRow, [string]>(
|
|
9014
|
+
`SELECT id, skillId, path, content, mimeType, isBinary, size, createdAt, lastUpdatedAt
|
|
9015
|
+
FROM skill_files
|
|
9016
|
+
WHERE skillId = ?
|
|
9017
|
+
ORDER BY path ASC`,
|
|
9018
|
+
)
|
|
9019
|
+
.all(skillId)
|
|
9020
|
+
.map(rowToSkillFile);
|
|
9021
|
+
}
|
|
9022
|
+
|
|
9023
|
+
export function getSkillFile(skillId: string, path: string): SkillFile | null {
|
|
9024
|
+
const normalizedPath = normalizeSkillFilePath(path);
|
|
9025
|
+
const row = getDb()
|
|
9026
|
+
.prepare<SkillFileRow, [string, string]>(
|
|
9027
|
+
`SELECT id, skillId, path, content, mimeType, isBinary, size, createdAt, lastUpdatedAt
|
|
9028
|
+
FROM skill_files
|
|
9029
|
+
WHERE skillId = ? AND path = ?`,
|
|
9030
|
+
)
|
|
9031
|
+
.get(skillId, normalizedPath);
|
|
9032
|
+
return row ? rowToSkillFile(row) : null;
|
|
9033
|
+
}
|
|
9034
|
+
|
|
9035
|
+
export function upsertSkillFile(skillId: string, input: SkillFileInput): SkillFile {
|
|
9036
|
+
const payload = normalizeSkillFileInput(input);
|
|
9037
|
+
assertSkillFileLimits(skillId, [payload], false);
|
|
9038
|
+
|
|
9039
|
+
const id = crypto.randomUUID();
|
|
9040
|
+
const now = new Date().toISOString();
|
|
9041
|
+
return upsertSkillFileUnchecked(skillId, payload, id, now, true);
|
|
9042
|
+
}
|
|
9043
|
+
|
|
9044
|
+
function upsertSkillFileUnchecked(
|
|
9045
|
+
skillId: string,
|
|
9046
|
+
payload: NormalizedSkillFileInput,
|
|
9047
|
+
id: string,
|
|
9048
|
+
now: string,
|
|
9049
|
+
bumpVersion: boolean,
|
|
9050
|
+
): SkillFile {
|
|
9051
|
+
const row = getDb()
|
|
9052
|
+
.prepare<SkillFileRow, (string | number | null)[]>(
|
|
9053
|
+
`INSERT INTO skill_files (
|
|
9054
|
+
id, skillId, path, content, mimeType, isBinary, size, createdAt, lastUpdatedAt
|
|
9055
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9056
|
+
ON CONFLICT(skillId, path) DO UPDATE SET
|
|
9057
|
+
content = excluded.content,
|
|
9058
|
+
mimeType = excluded.mimeType,
|
|
9059
|
+
isBinary = excluded.isBinary,
|
|
9060
|
+
size = excluded.size,
|
|
9061
|
+
lastUpdatedAt = excluded.lastUpdatedAt
|
|
9062
|
+
RETURNING *`,
|
|
9063
|
+
)
|
|
9064
|
+
.get(
|
|
9065
|
+
id,
|
|
9066
|
+
skillId,
|
|
9067
|
+
payload.path,
|
|
9068
|
+
payload.content,
|
|
9069
|
+
payload.mimeType,
|
|
9070
|
+
payload.isBinary ? 1 : 0,
|
|
9071
|
+
payload.size,
|
|
9072
|
+
now,
|
|
9073
|
+
now,
|
|
9074
|
+
);
|
|
9075
|
+
|
|
9076
|
+
if (!row) throw new Error("Failed to upsert skill file");
|
|
9077
|
+
if (bumpVersion) bumpSkillVersion(skillId, now);
|
|
9078
|
+
return rowToSkillFile(row);
|
|
9079
|
+
}
|
|
9080
|
+
|
|
9081
|
+
export function upsertSkillFiles(skillId: string, files: SkillFileInput[]): SkillFile[] {
|
|
9082
|
+
if (files.length === 0) return [];
|
|
9083
|
+
const normalized = files.map(normalizeSkillFileInput);
|
|
9084
|
+
assertSkillFileLimits(skillId, normalized, false);
|
|
9085
|
+
|
|
9086
|
+
const now = new Date().toISOString();
|
|
9087
|
+
return getDb().transaction(() => {
|
|
9088
|
+
const rows = normalized.map((file) =>
|
|
9089
|
+
upsertSkillFileUnchecked(skillId, file, crypto.randomUUID(), now, false),
|
|
9090
|
+
);
|
|
9091
|
+
bumpSkillVersion(skillId, now);
|
|
9092
|
+
return rows;
|
|
9093
|
+
})();
|
|
9094
|
+
}
|
|
9095
|
+
|
|
9096
|
+
export function deleteSkillFile(skillId: string, path: string): boolean {
|
|
9097
|
+
const normalizedPath = normalizeSkillFilePath(path);
|
|
9098
|
+
const result = getDb()
|
|
9099
|
+
.prepare("DELETE FROM skill_files WHERE skillId = ? AND path = ?")
|
|
9100
|
+
.run(skillId, normalizedPath);
|
|
9101
|
+
if (result.changes > 0) {
|
|
9102
|
+
bumpSkillVersion(skillId);
|
|
9103
|
+
return true;
|
|
9104
|
+
}
|
|
9105
|
+
return false;
|
|
9106
|
+
}
|
|
9107
|
+
|
|
8852
9108
|
export function deleteSkill(id: string): boolean {
|
|
8853
9109
|
const result = getDb().prepare("DELETE FROM skills WHERE id = ?").run(id);
|
|
8854
9110
|
return result.changes > 0;
|
|
@@ -8991,7 +9247,7 @@ export function getAgentSkills(agentId: string, activeOnly = true): SkillWithIns
|
|
|
8991
9247
|
SELECT s.*, 1 as isActive, s.createdAt as installedAt, 1 as sourceRank,
|
|
8992
9248
|
CASE WHEN s.type = 'personal' THEN 0 ELSE 1 END as typeRank
|
|
8993
9249
|
FROM skills s
|
|
8994
|
-
WHERE s.systemDefault = 1
|
|
9250
|
+
WHERE (s.systemDefault = 1 OR s.scope = 'swarm')
|
|
8995
9251
|
AND s.isEnabled = 1
|
|
8996
9252
|
ORDER BY
|
|
8997
9253
|
sourceRank,
|
|
@@ -11206,3 +11462,390 @@ export function countKv(namespace: string, opts: { prefix?: string }): number {
|
|
|
11206
11462
|
.get(namespace, now);
|
|
11207
11463
|
return row?.n ?? 0;
|
|
11208
11464
|
}
|
|
11465
|
+
|
|
11466
|
+
// ─── Script Runs ────────────────────────────────────────────────────────────
|
|
11467
|
+
|
|
11468
|
+
type ScriptRunRow = {
|
|
11469
|
+
id: string;
|
|
11470
|
+
agentId: string;
|
|
11471
|
+
scriptName: string | null;
|
|
11472
|
+
source: string;
|
|
11473
|
+
args: string;
|
|
11474
|
+
kind: string;
|
|
11475
|
+
status: string;
|
|
11476
|
+
pid: number | null;
|
|
11477
|
+
startedAt: string;
|
|
11478
|
+
finishedAt: string | null;
|
|
11479
|
+
output: string | null;
|
|
11480
|
+
error: string | null;
|
|
11481
|
+
last_heartbeat_at: string | null;
|
|
11482
|
+
idempotencyKey: string | null;
|
|
11483
|
+
requestedByUserId: string | null;
|
|
11484
|
+
created_by: string | null;
|
|
11485
|
+
updated_by: string | null;
|
|
11486
|
+
};
|
|
11487
|
+
|
|
11488
|
+
function parseJsonColumn(value: string | null): unknown | undefined {
|
|
11489
|
+
if (value === null) return undefined;
|
|
11490
|
+
return JSON.parse(value);
|
|
11491
|
+
}
|
|
11492
|
+
|
|
11493
|
+
function rowToScriptRun(row: ScriptRunRow): ScriptRun {
|
|
11494
|
+
return {
|
|
11495
|
+
id: row.id,
|
|
11496
|
+
agentId: row.agentId,
|
|
11497
|
+
scriptName: row.scriptName ?? undefined,
|
|
11498
|
+
source: row.source,
|
|
11499
|
+
args: JSON.parse(row.args),
|
|
11500
|
+
kind: row.kind as ScriptRunKind,
|
|
11501
|
+
status: row.status as ScriptRunStatus,
|
|
11502
|
+
pid: row.pid ?? undefined,
|
|
11503
|
+
startedAt: row.startedAt,
|
|
11504
|
+
finishedAt: row.finishedAt ?? undefined,
|
|
11505
|
+
output: parseJsonColumn(row.output),
|
|
11506
|
+
error: row.error ?? undefined,
|
|
11507
|
+
lastHeartbeatAt: row.last_heartbeat_at ?? undefined,
|
|
11508
|
+
idempotencyKey: row.idempotencyKey ?? undefined,
|
|
11509
|
+
requestedByUserId: row.requestedByUserId ?? undefined,
|
|
11510
|
+
};
|
|
11511
|
+
}
|
|
11512
|
+
|
|
11513
|
+
export function createScriptRun(data: {
|
|
11514
|
+
id: string;
|
|
11515
|
+
agentId: string;
|
|
11516
|
+
source: string;
|
|
11517
|
+
args: unknown;
|
|
11518
|
+
scriptName?: string;
|
|
11519
|
+
idempotencyKey?: string;
|
|
11520
|
+
requestedByUserId?: string;
|
|
11521
|
+
createdBy?: string;
|
|
11522
|
+
updatedBy?: string;
|
|
11523
|
+
}): { run: ScriptRun; existing: boolean } {
|
|
11524
|
+
const db = getDb();
|
|
11525
|
+
if (data.idempotencyKey) {
|
|
11526
|
+
const existing = db
|
|
11527
|
+
.prepare<ScriptRunRow, [string]>("SELECT * FROM script_runs WHERE idempotencyKey = ?")
|
|
11528
|
+
.get(data.idempotencyKey);
|
|
11529
|
+
if (existing) return { run: rowToScriptRun(existing), existing: true };
|
|
11530
|
+
}
|
|
11531
|
+
|
|
11532
|
+
const row = db
|
|
11533
|
+
.prepare<
|
|
11534
|
+
ScriptRunRow,
|
|
11535
|
+
[
|
|
11536
|
+
string,
|
|
11537
|
+
string,
|
|
11538
|
+
string | null,
|
|
11539
|
+
string,
|
|
11540
|
+
string,
|
|
11541
|
+
string | null,
|
|
11542
|
+
string | null,
|
|
11543
|
+
string | null,
|
|
11544
|
+
string | null,
|
|
11545
|
+
]
|
|
11546
|
+
>(
|
|
11547
|
+
`INSERT INTO script_runs
|
|
11548
|
+
(id, agentId, scriptName, source, args, idempotencyKey, requestedByUserId, created_by, updated_by)
|
|
11549
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11550
|
+
RETURNING *`,
|
|
11551
|
+
)
|
|
11552
|
+
.get(
|
|
11553
|
+
data.id,
|
|
11554
|
+
data.agentId,
|
|
11555
|
+
data.scriptName ?? null,
|
|
11556
|
+
data.source,
|
|
11557
|
+
JSON.stringify(data.args ?? null),
|
|
11558
|
+
data.idempotencyKey ?? null,
|
|
11559
|
+
data.requestedByUserId ?? null,
|
|
11560
|
+
data.createdBy ?? null,
|
|
11561
|
+
data.updatedBy ?? data.createdBy ?? null,
|
|
11562
|
+
);
|
|
11563
|
+
if (!row) throw new Error("Failed to create script run");
|
|
11564
|
+
return { run: rowToScriptRun(row), existing: false };
|
|
11565
|
+
}
|
|
11566
|
+
|
|
11567
|
+
// Persist a synchronous inline run (POST /api/scripts/run) as an already-terminal
|
|
11568
|
+
// row. Unlike createScriptRun these never get a journal and never use the
|
|
11569
|
+
// idempotencyKey column (inline idempotency lives in the kv table).
|
|
11570
|
+
export function recordInlineScriptRun(data: {
|
|
11571
|
+
id: string;
|
|
11572
|
+
agentId: string;
|
|
11573
|
+
source: string;
|
|
11574
|
+
args: unknown;
|
|
11575
|
+
scriptName?: string;
|
|
11576
|
+
status: "completed" | "failed";
|
|
11577
|
+
output?: unknown;
|
|
11578
|
+
error?: string;
|
|
11579
|
+
startedAt: string;
|
|
11580
|
+
finishedAt: string;
|
|
11581
|
+
requestedByUserId?: string;
|
|
11582
|
+
createdBy?: string;
|
|
11583
|
+
}): ScriptRun {
|
|
11584
|
+
const row = getDb()
|
|
11585
|
+
.prepare<
|
|
11586
|
+
ScriptRunRow,
|
|
11587
|
+
[
|
|
11588
|
+
string,
|
|
11589
|
+
string,
|
|
11590
|
+
string | null,
|
|
11591
|
+
string,
|
|
11592
|
+
string,
|
|
11593
|
+
string,
|
|
11594
|
+
string | null,
|
|
11595
|
+
string | null,
|
|
11596
|
+
string,
|
|
11597
|
+
string,
|
|
11598
|
+
string | null,
|
|
11599
|
+
string | null,
|
|
11600
|
+
string | null,
|
|
11601
|
+
]
|
|
11602
|
+
>(
|
|
11603
|
+
`INSERT INTO script_runs
|
|
11604
|
+
(id, agentId, scriptName, source, args, kind, status, output, error,
|
|
11605
|
+
startedAt, finishedAt, requestedByUserId, created_by, updated_by)
|
|
11606
|
+
VALUES (?, ?, ?, ?, ?, 'inline', ?, ?, ?, ?, ?, ?, ?, ?)
|
|
11607
|
+
RETURNING *`,
|
|
11608
|
+
)
|
|
11609
|
+
.get(
|
|
11610
|
+
data.id,
|
|
11611
|
+
data.agentId,
|
|
11612
|
+
data.scriptName ?? null,
|
|
11613
|
+
data.source,
|
|
11614
|
+
JSON.stringify(data.args ?? null),
|
|
11615
|
+
data.status,
|
|
11616
|
+
data.output === undefined ? null : JSON.stringify(data.output),
|
|
11617
|
+
data.error ?? null,
|
|
11618
|
+
data.startedAt,
|
|
11619
|
+
data.finishedAt,
|
|
11620
|
+
data.requestedByUserId ?? null,
|
|
11621
|
+
data.createdBy ?? null,
|
|
11622
|
+
data.createdBy ?? null,
|
|
11623
|
+
);
|
|
11624
|
+
if (!row) throw new Error("Failed to record inline script run");
|
|
11625
|
+
return rowToScriptRun(row);
|
|
11626
|
+
}
|
|
11627
|
+
|
|
11628
|
+
export function getScriptRun(id: string): ScriptRun | null {
|
|
11629
|
+
const row = getDb()
|
|
11630
|
+
.prepare<ScriptRunRow, [string]>("SELECT * FROM script_runs WHERE id = ?")
|
|
11631
|
+
.get(id);
|
|
11632
|
+
return row ? rowToScriptRun(row) : null;
|
|
11633
|
+
}
|
|
11634
|
+
|
|
11635
|
+
export function getScriptRunByIdempotencyKey(idempotencyKey: string): ScriptRun | null {
|
|
11636
|
+
const row = getDb()
|
|
11637
|
+
.prepare<ScriptRunRow, [string]>("SELECT * FROM script_runs WHERE idempotencyKey = ?")
|
|
11638
|
+
.get(idempotencyKey);
|
|
11639
|
+
return row ? rowToScriptRun(row) : null;
|
|
11640
|
+
}
|
|
11641
|
+
|
|
11642
|
+
export function listScriptRuns(opts?: {
|
|
11643
|
+
status?: ScriptRunStatus;
|
|
11644
|
+
agentId?: string;
|
|
11645
|
+
limit?: number;
|
|
11646
|
+
offset?: number;
|
|
11647
|
+
}): ScriptRun[] {
|
|
11648
|
+
const conditions: string[] = [];
|
|
11649
|
+
const params: Array<string | number> = [];
|
|
11650
|
+
if (opts?.status) {
|
|
11651
|
+
conditions.push("status = ?");
|
|
11652
|
+
params.push(opts.status);
|
|
11653
|
+
}
|
|
11654
|
+
if (opts?.agentId) {
|
|
11655
|
+
conditions.push("agentId = ?");
|
|
11656
|
+
params.push(opts.agentId);
|
|
11657
|
+
}
|
|
11658
|
+
|
|
11659
|
+
const limit = opts?.limit ?? 50;
|
|
11660
|
+
const offset = opts?.offset ?? 0;
|
|
11661
|
+
params.push(limit, offset);
|
|
11662
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
11663
|
+
const rows = getDb()
|
|
11664
|
+
.prepare<ScriptRunRow, Array<string | number>>(
|
|
11665
|
+
`SELECT * FROM script_runs ${where} ORDER BY startedAt DESC LIMIT ? OFFSET ?`,
|
|
11666
|
+
)
|
|
11667
|
+
.all(...params);
|
|
11668
|
+
return rows.map(rowToScriptRun);
|
|
11669
|
+
}
|
|
11670
|
+
|
|
11671
|
+
export function countScriptRuns(opts?: { status?: ScriptRunStatus; agentId?: string }): number {
|
|
11672
|
+
const conditions: string[] = [];
|
|
11673
|
+
const params: string[] = [];
|
|
11674
|
+
if (opts?.status) {
|
|
11675
|
+
conditions.push("status = ?");
|
|
11676
|
+
params.push(opts.status);
|
|
11677
|
+
}
|
|
11678
|
+
if (opts?.agentId) {
|
|
11679
|
+
conditions.push("agentId = ?");
|
|
11680
|
+
params.push(opts.agentId);
|
|
11681
|
+
}
|
|
11682
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
11683
|
+
const row = getDb()
|
|
11684
|
+
.prepare<{ count: number }, string[]>(`SELECT COUNT(*) AS count FROM script_runs ${where}`)
|
|
11685
|
+
.get(...params);
|
|
11686
|
+
return row?.count ?? 0;
|
|
11687
|
+
}
|
|
11688
|
+
|
|
11689
|
+
export function countActiveScriptRuns(): number {
|
|
11690
|
+
const row = getDb()
|
|
11691
|
+
.prepare<{ count: number }, []>(
|
|
11692
|
+
"SELECT COUNT(*) AS count FROM script_runs WHERE status IN ('running', 'paused')",
|
|
11693
|
+
)
|
|
11694
|
+
.get();
|
|
11695
|
+
return row?.count ?? 0;
|
|
11696
|
+
}
|
|
11697
|
+
|
|
11698
|
+
export function updateScriptRun(
|
|
11699
|
+
id: string,
|
|
11700
|
+
patch: Partial<{
|
|
11701
|
+
status: ScriptRunStatus;
|
|
11702
|
+
pid: number | null;
|
|
11703
|
+
finishedAt: string | null;
|
|
11704
|
+
output: unknown;
|
|
11705
|
+
error: string | null;
|
|
11706
|
+
lastHeartbeatAt: string | null;
|
|
11707
|
+
updatedBy: string | null;
|
|
11708
|
+
}>,
|
|
11709
|
+
): void {
|
|
11710
|
+
const sets: string[] = [];
|
|
11711
|
+
const vals: Array<string | number | null> = [];
|
|
11712
|
+
if (patch.status !== undefined) {
|
|
11713
|
+
sets.push("status = ?");
|
|
11714
|
+
vals.push(patch.status);
|
|
11715
|
+
}
|
|
11716
|
+
if (patch.pid !== undefined) {
|
|
11717
|
+
sets.push("pid = ?");
|
|
11718
|
+
vals.push(patch.pid);
|
|
11719
|
+
}
|
|
11720
|
+
if (patch.finishedAt !== undefined) {
|
|
11721
|
+
sets.push("finishedAt = ?");
|
|
11722
|
+
vals.push(patch.finishedAt);
|
|
11723
|
+
}
|
|
11724
|
+
if ("output" in patch) {
|
|
11725
|
+
sets.push("output = ?");
|
|
11726
|
+
vals.push(patch.output === undefined ? null : JSON.stringify(patch.output));
|
|
11727
|
+
}
|
|
11728
|
+
if (patch.error !== undefined) {
|
|
11729
|
+
sets.push("error = ?");
|
|
11730
|
+
vals.push(patch.error);
|
|
11731
|
+
}
|
|
11732
|
+
if (patch.lastHeartbeatAt !== undefined) {
|
|
11733
|
+
sets.push("last_heartbeat_at = ?");
|
|
11734
|
+
vals.push(patch.lastHeartbeatAt);
|
|
11735
|
+
}
|
|
11736
|
+
if (patch.updatedBy !== undefined) {
|
|
11737
|
+
sets.push("updated_by = ?");
|
|
11738
|
+
vals.push(patch.updatedBy);
|
|
11739
|
+
}
|
|
11740
|
+
if (sets.length === 0) return;
|
|
11741
|
+
vals.push(id);
|
|
11742
|
+
getDb().run(`UPDATE script_runs SET ${sets.join(", ")} WHERE id = ?`, vals);
|
|
11743
|
+
}
|
|
11744
|
+
|
|
11745
|
+
export function getRunningScriptRuns(): ScriptRun[] {
|
|
11746
|
+
const rows = getDb()
|
|
11747
|
+
.prepare<ScriptRunRow, []>("SELECT * FROM script_runs WHERE status IN ('running', 'paused')")
|
|
11748
|
+
.all();
|
|
11749
|
+
return rows.map(rowToScriptRun);
|
|
11750
|
+
}
|
|
11751
|
+
|
|
11752
|
+
// ─── Script Run Journal ─────────────────────────────────────────────────────
|
|
11753
|
+
|
|
11754
|
+
type ScriptRunJournalRow = {
|
|
11755
|
+
id: string;
|
|
11756
|
+
runId: string;
|
|
11757
|
+
stepKey: string;
|
|
11758
|
+
stepType: string;
|
|
11759
|
+
config: string;
|
|
11760
|
+
status: string;
|
|
11761
|
+
result: string | null;
|
|
11762
|
+
error: string | null;
|
|
11763
|
+
startedAt: string;
|
|
11764
|
+
completedAt: string | null;
|
|
11765
|
+
durationMs: number | null;
|
|
11766
|
+
created_by: string | null;
|
|
11767
|
+
updated_by: string | null;
|
|
11768
|
+
};
|
|
11769
|
+
|
|
11770
|
+
function rowToScriptRunJournalEntry(row: ScriptRunJournalRow): ScriptRunJournalEntry {
|
|
11771
|
+
return {
|
|
11772
|
+
id: row.id,
|
|
11773
|
+
runId: row.runId,
|
|
11774
|
+
stepKey: row.stepKey,
|
|
11775
|
+
stepType: row.stepType,
|
|
11776
|
+
config: JSON.parse(row.config),
|
|
11777
|
+
status: row.status as "completed" | "failed",
|
|
11778
|
+
result: parseJsonColumn(row.result),
|
|
11779
|
+
error: row.error ?? undefined,
|
|
11780
|
+
startedAt: row.startedAt,
|
|
11781
|
+
completedAt: row.completedAt ?? undefined,
|
|
11782
|
+
durationMs: row.durationMs ?? undefined,
|
|
11783
|
+
};
|
|
11784
|
+
}
|
|
11785
|
+
|
|
11786
|
+
export function getScriptRunJournalStep(
|
|
11787
|
+
runId: string,
|
|
11788
|
+
stepKey: string,
|
|
11789
|
+
): ScriptRunJournalEntry | null {
|
|
11790
|
+
const row = getDb()
|
|
11791
|
+
.prepare<ScriptRunJournalRow, [string, string]>(
|
|
11792
|
+
"SELECT * FROM script_run_journal WHERE runId = ? AND stepKey = ?",
|
|
11793
|
+
)
|
|
11794
|
+
.get(runId, stepKey);
|
|
11795
|
+
return row ? rowToScriptRunJournalEntry(row) : null;
|
|
11796
|
+
}
|
|
11797
|
+
|
|
11798
|
+
export function upsertScriptRunJournalStep(data: {
|
|
11799
|
+
runId: string;
|
|
11800
|
+
stepKey: string;
|
|
11801
|
+
stepType: string;
|
|
11802
|
+
config: unknown;
|
|
11803
|
+
status: "completed" | "failed";
|
|
11804
|
+
result?: unknown;
|
|
11805
|
+
error?: string;
|
|
11806
|
+
durationMs?: number;
|
|
11807
|
+
}): void {
|
|
11808
|
+
getDb().run(
|
|
11809
|
+
`INSERT OR IGNORE INTO script_run_journal
|
|
11810
|
+
(id, runId, stepKey, stepType, config, status, result, error, durationMs, completedAt)
|
|
11811
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))`,
|
|
11812
|
+
[
|
|
11813
|
+
crypto.randomUUID(),
|
|
11814
|
+
data.runId,
|
|
11815
|
+
data.stepKey,
|
|
11816
|
+
data.stepType,
|
|
11817
|
+
JSON.stringify(data.config ?? {}),
|
|
11818
|
+
data.status,
|
|
11819
|
+
data.result !== undefined ? JSON.stringify(data.result) : null,
|
|
11820
|
+
data.error ?? null,
|
|
11821
|
+
data.durationMs ?? null,
|
|
11822
|
+
],
|
|
11823
|
+
);
|
|
11824
|
+
}
|
|
11825
|
+
|
|
11826
|
+
export function listScriptRunJournalSteps(runId: string): ScriptRunJournalEntry[] {
|
|
11827
|
+
const rows = getDb()
|
|
11828
|
+
.prepare<ScriptRunJournalRow, [string]>(
|
|
11829
|
+
"SELECT * FROM script_run_journal WHERE runId = ? ORDER BY startedAt ASC",
|
|
11830
|
+
)
|
|
11831
|
+
.all(runId);
|
|
11832
|
+
return rows.map(rowToScriptRunJournalEntry);
|
|
11833
|
+
}
|
|
11834
|
+
|
|
11835
|
+
export function countScriptRunJournalSteps(runId: string): number {
|
|
11836
|
+
const row = getDb()
|
|
11837
|
+
.prepare<{ count: number }, [string]>(
|
|
11838
|
+
"SELECT COUNT(*) AS count FROM script_run_journal WHERE runId = ?",
|
|
11839
|
+
)
|
|
11840
|
+
.get(runId);
|
|
11841
|
+
return row?.count ?? 0;
|
|
11842
|
+
}
|
|
11843
|
+
|
|
11844
|
+
export function countScriptRunJournalAgentTaskSteps(runId: string): number {
|
|
11845
|
+
const row = getDb()
|
|
11846
|
+
.prepare<{ count: number }, [string]>(
|
|
11847
|
+
"SELECT COUNT(*) AS count FROM script_run_journal WHERE runId = ? AND stepType = 'agent-task'",
|
|
11848
|
+
)
|
|
11849
|
+
.get(runId);
|
|
11850
|
+
return row?.count ?? 0;
|
|
11851
|
+
}
|