@jaimevalasek/aioson 1.3.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.
- package/CHANGELOG.md +456 -0
- package/CODE_OF_CONDUCT.md +12 -0
- package/CONTRIBUTING.md +13 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/bin/aioson.js +4 -0
- package/docs/en/cli-reference.md +398 -0
- package/docs/en/i18n.md +52 -0
- package/docs/en/json-schemas.md +41 -0
- package/docs/en/mcp.md +56 -0
- package/docs/en/parallel.md +82 -0
- package/docs/en/qa-browser.md +339 -0
- package/docs/en/release-flow.md +22 -0
- package/docs/en/release-notes-template.md +41 -0
- package/docs/en/release.md +28 -0
- package/docs/en/schemas/agent-prompt.schema.json +17 -0
- package/docs/en/schemas/agents.schema.json +32 -0
- package/docs/en/schemas/context-validate.schema.json +36 -0
- package/docs/en/schemas/doctor.schema.json +89 -0
- package/docs/en/schemas/error.schema.json +24 -0
- package/docs/en/schemas/i18n-add.schema.json +15 -0
- package/docs/en/schemas/index.json +116 -0
- package/docs/en/schemas/info.schema.json +39 -0
- package/docs/en/schemas/init.schema.json +48 -0
- package/docs/en/schemas/install.schema.json +60 -0
- package/docs/en/schemas/locale-apply.schema.json +30 -0
- package/docs/en/schemas/mcp-doctor.schema.json +95 -0
- package/docs/en/schemas/mcp-init.schema.json +122 -0
- package/docs/en/schemas/package-test.schema.json +24 -0
- package/docs/en/schemas/parallel-assign.schema.json +57 -0
- package/docs/en/schemas/parallel-doctor.schema.json +86 -0
- package/docs/en/schemas/parallel-init.schema.json +53 -0
- package/docs/en/schemas/parallel-status.schema.json +94 -0
- package/docs/en/schemas/setup-context.schema.json +39 -0
- package/docs/en/schemas/smoke.schema.json +23 -0
- package/docs/en/schemas/update.schema.json +48 -0
- package/docs/en/schemas/workflow-plan.schema.json +30 -0
- package/docs/en/web3.md +54 -0
- package/docs/pt/README.md +46 -0
- package/docs/pt/advisor-spec.md +335 -0
- package/docs/pt/agentes.md +453 -0
- package/docs/pt/cenarios.md +1230 -0
- package/docs/pt/clientes-ai.md +224 -0
- package/docs/pt/comandos-cli.md +511 -0
- package/docs/pt/genome-3.0-spec.md +296 -0
- package/docs/pt/guia-engineer.md +226 -0
- package/docs/pt/inicio-rapido.md +138 -0
- package/docs/pt/profiler-system.md +214 -0
- package/docs/pt/runtime-observability.md +72 -0
- package/docs/pt/squad-genoma.md +777 -0
- package/docs/pt/web3.md +797 -0
- package/docs/testing/genome-2.0-manual-regression.md +23 -0
- package/docs/testing/genome-2.0-matrix.md +36 -0
- package/docs/testing/genome-2.0-rollout.md +184 -0
- package/package.json +50 -0
- package/src/agents.js +56 -0
- package/src/cli.js +497 -0
- package/src/commands/agents.js +142 -0
- package/src/commands/cloud.js +1767 -0
- package/src/commands/config.js +90 -0
- package/src/commands/context-validate.js +91 -0
- package/src/commands/doctor.js +123 -0
- package/src/commands/genome-doctor.js +41 -0
- package/src/commands/genome-migrate.js +49 -0
- package/src/commands/i18n-add.js +56 -0
- package/src/commands/info.js +41 -0
- package/src/commands/init.js +75 -0
- package/src/commands/install.js +68 -0
- package/src/commands/locale-apply.js +51 -0
- package/src/commands/locale-diff.js +126 -0
- package/src/commands/mcp-doctor.js +406 -0
- package/src/commands/mcp-init.js +379 -0
- package/src/commands/package-e2e.js +273 -0
- package/src/commands/parallel-assign.js +403 -0
- package/src/commands/parallel-doctor.js +437 -0
- package/src/commands/parallel-init.js +249 -0
- package/src/commands/parallel-status.js +290 -0
- package/src/commands/qa-doctor.js +185 -0
- package/src/commands/qa-init.js +161 -0
- package/src/commands/qa-report.js +58 -0
- package/src/commands/qa-run.js +873 -0
- package/src/commands/qa-scan.js +337 -0
- package/src/commands/runtime.js +948 -0
- package/src/commands/scan-project.js +1107 -0
- package/src/commands/setup-context.js +650 -0
- package/src/commands/smoke.js +426 -0
- package/src/commands/squad-doctor.js +358 -0
- package/src/commands/squad-export.js +46 -0
- package/src/commands/squad-pipeline.js +97 -0
- package/src/commands/squad-repair-genomes.js +39 -0
- package/src/commands/squad-status.js +424 -0
- package/src/commands/squad-validate.js +230 -0
- package/src/commands/test-agents.js +194 -0
- package/src/commands/update.js +55 -0
- package/src/commands/workflow-next.js +594 -0
- package/src/commands/workflow-plan.js +108 -0
- package/src/constants.js +314 -0
- package/src/context-parse-reason.js +22 -0
- package/src/context-writer.js +150 -0
- package/src/context.js +217 -0
- package/src/detector.js +261 -0
- package/src/doctor.js +289 -0
- package/src/execution-gateway.js +461 -0
- package/src/genome-files.js +198 -0
- package/src/genome-format.js +442 -0
- package/src/genome-schema.js +215 -0
- package/src/genomes/bindings.js +281 -0
- package/src/genomes.js +467 -0
- package/src/i18n/index.js +103 -0
- package/src/i18n/messages/en.js +784 -0
- package/src/i18n/messages/es.js +718 -0
- package/src/i18n/messages/fr.js +725 -0
- package/src/i18n/messages/pt-BR.js +818 -0
- package/src/i18n/scaffold.js +64 -0
- package/src/installer.js +232 -0
- package/src/lib/genomes/compat.js +206 -0
- package/src/lib/genomes/migrate.js +90 -0
- package/src/lib/squads/genome-repair.js +49 -0
- package/src/locales.js +84 -0
- package/src/onboarding.js +305 -0
- package/src/parser.js +53 -0
- package/src/prompt-tool.js +20 -0
- package/src/qa-html-report.js +472 -0
- package/src/runtime-store.js +1527 -0
- package/src/squads/apply-genome.js +21 -0
- package/src/squads/genome-binding-service.js +154 -0
- package/src/updater.js +32 -0
- package/src/utils.js +46 -0
- package/src/version.js +50 -0
- package/template/.aioson/advisors/.gitkeep +1 -0
- package/template/.aioson/agents/analyst.md +225 -0
- package/template/.aioson/agents/architect.md +221 -0
- package/template/.aioson/agents/dev.md +201 -0
- package/template/.aioson/agents/discovery-design-doc.md +196 -0
- package/template/.aioson/agents/genoma.md +300 -0
- package/template/.aioson/agents/orchestrator.md +107 -0
- package/template/.aioson/agents/pm.md +89 -0
- package/template/.aioson/agents/product.md +361 -0
- package/template/.aioson/agents/profiler-enricher.md +266 -0
- package/template/.aioson/agents/profiler-forge.md +188 -0
- package/template/.aioson/agents/profiler-researcher.md +245 -0
- package/template/.aioson/agents/qa.md +344 -0
- package/template/.aioson/agents/setup.md +381 -0
- package/template/.aioson/agents/squad.md +837 -0
- package/template/.aioson/agents/ux-ui.md +416 -0
- package/template/.aioson/config.md +56 -0
- package/template/.aioson/context/.gitkeep +0 -0
- package/template/.aioson/context/parallel/.gitkeep +0 -0
- package/template/.aioson/context/spec.md.template +37 -0
- package/template/.aioson/genomas/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +214 -0
- package/template/.aioson/locales/en/agents/architect.md +210 -0
- package/template/.aioson/locales/en/agents/dev.md +187 -0
- package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
- package/template/.aioson/locales/en/agents/genoma.md +212 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
- package/template/.aioson/locales/en/agents/pm.md +77 -0
- package/template/.aioson/locales/en/agents/product.md +310 -0
- package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/en/agents/qa.md +214 -0
- package/template/.aioson/locales/en/agents/setup.md +342 -0
- package/template/.aioson/locales/en/agents/squad.md +247 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
- package/template/.aioson/locales/es/agents/analyst.md +203 -0
- package/template/.aioson/locales/es/agents/architect.md +208 -0
- package/template/.aioson/locales/es/agents/dev.md +183 -0
- package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/es/agents/genoma.md +102 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/es/agents/pm.md +81 -0
- package/template/.aioson/locales/es/agents/product.md +310 -0
- package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/es/agents/qa.md +163 -0
- package/template/.aioson/locales/es/agents/setup.md +347 -0
- package/template/.aioson/locales/es/agents/squad.md +247 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/fr/agents/analyst.md +203 -0
- package/template/.aioson/locales/fr/agents/architect.md +208 -0
- package/template/.aioson/locales/fr/agents/dev.md +183 -0
- package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
- package/template/.aioson/locales/fr/agents/genoma.md +102 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/fr/agents/pm.md +81 -0
- package/template/.aioson/locales/fr/agents/product.md +310 -0
- package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/fr/agents/qa.md +163 -0
- package/template/.aioson/locales/fr/agents/setup.md +347 -0
- package/template/.aioson/locales/fr/agents/squad.md +247 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
- package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
- package/template/.aioson/mcp/servers.md +24 -0
- package/template/.aioson/profiler-reports/.gitkeep +1 -0
- package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
- package/template/.aioson/schemas/genome-meta.schema.json +150 -0
- package/template/.aioson/schemas/genome.schema.json +115 -0
- package/template/.aioson/schemas/readiness.schema.json +27 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
- package/template/.aioson/skills/dynamic/README.md +30 -0
- package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
- package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
- package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
- package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
- package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
- package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
- package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
- package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
- package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
- package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
- package/template/.aioson/skills/static/django-patterns.md +342 -0
- package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
- package/template/.aioson/skills/static/filament-patterns.md +267 -0
- package/template/.aioson/skills/static/flux-ui-components.md +262 -0
- package/template/.aioson/skills/static/git-conventions.md +227 -0
- package/template/.aioson/skills/static/interface-design.md +372 -0
- package/template/.aioson/skills/static/jetstream-setup.md +200 -0
- package/template/.aioson/skills/static/laravel-conventions.md +491 -0
- package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
- package/template/.aioson/skills/static/node-express-patterns.md +317 -0
- package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
- package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
- package/template/.aioson/skills/static/rails-conventions.md +307 -0
- package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
- package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
- package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
- package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
- package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
- package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
- package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
- package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
- package/template/.aioson/squads/.artisan/.gitkeep +0 -0
- package/template/.aioson/squads/.gitkeep +0 -0
- package/template/.aioson/squads/memory.md +5 -0
- package/template/.aioson/tasks/squad-analyze.md +83 -0
- package/template/.aioson/tasks/squad-create.md +99 -0
- package/template/.aioson/tasks/squad-design.md +100 -0
- package/template/.aioson/tasks/squad-export.md +20 -0
- package/template/.aioson/tasks/squad-extend.md +68 -0
- package/template/.aioson/tasks/squad-pipeline.md +122 -0
- package/template/.aioson/tasks/squad-repair.md +85 -0
- package/template/.aioson/tasks/squad-validate.md +58 -0
- package/template/.aioson/templates/squads/content-basic/template.json +21 -0
- package/template/.aioson/templates/squads/media-channel/template.json +24 -0
- package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
- package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
- package/template/.claude/commands/aioson/analyst.md +5 -0
- package/template/.claude/commands/aioson/architect.md +5 -0
- package/template/.claude/commands/aioson/dev.md +5 -0
- package/template/.claude/commands/aioson/orchestrator.md +5 -0
- package/template/.claude/commands/aioson/pm.md +5 -0
- package/template/.claude/commands/aioson/qa.md +5 -0
- package/template/.claude/commands/aioson/setup.md +5 -0
- package/template/.claude/commands/aioson/ux-ui.md +5 -0
- package/template/.gemini/GEMINI.md +10 -0
- package/template/.gemini/commands/aios-analyst.toml +4 -0
- package/template/.gemini/commands/aios-architect.toml +7 -0
- package/template/.gemini/commands/aios-dev.toml +8 -0
- package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
- package/template/.gemini/commands/aios-orchestrator.toml +8 -0
- package/template/.gemini/commands/aios-pm.toml +8 -0
- package/template/.gemini/commands/aios-product.toml +4 -0
- package/template/.gemini/commands/aios-qa.toml +6 -0
- package/template/.gemini/commands/aios-setup.toml +3 -0
- package/template/.gemini/commands/aios-ux-ui.toml +8 -0
- package/template/AGENTS.md +67 -0
- package/template/CLAUDE.md +31 -0
- package/template/OPENCODE.md +24 -0
- package/template/aioson-models.json +40 -0
|
@@ -0,0 +1,1527 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const Database = require('better-sqlite3');
|
|
6
|
+
const { ensureDir, exists } = require('./utils');
|
|
7
|
+
const {
|
|
8
|
+
attachBindingsToExecutors,
|
|
9
|
+
flattenGenomeBindings,
|
|
10
|
+
mergeGenomeBindings
|
|
11
|
+
} = require('./genomes/bindings');
|
|
12
|
+
|
|
13
|
+
const RUNTIME_DIR = path.join('.aioson', 'runtime');
|
|
14
|
+
const DB_FILE = 'aios.sqlite';
|
|
15
|
+
const SESSIONS_DIR = '.sessions';
|
|
16
|
+
const VALID_STATUSES = new Set(['queued', 'running', 'completed', 'failed']);
|
|
17
|
+
const VALID_TASK_STATUSES = new Set(['queued', 'running', 'completed', 'failed']);
|
|
18
|
+
|
|
19
|
+
function slugify(value) {
|
|
20
|
+
return String(value || 'run')
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
23
|
+
.replace(/^-+|-+$/g, '')
|
|
24
|
+
.slice(0, 60) || 'run';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function nowIso() {
|
|
28
|
+
return new Date().toISOString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveRuntimePaths(targetDir) {
|
|
32
|
+
const runtimeDir = path.join(targetDir, RUNTIME_DIR);
|
|
33
|
+
return {
|
|
34
|
+
runtimeDir,
|
|
35
|
+
dbPath: path.join(runtimeDir, DB_FILE)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function runtimeStoreExists(targetDir) {
|
|
40
|
+
const { dbPath } = resolveRuntimePaths(targetDir);
|
|
41
|
+
return exists(dbPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function openRuntimeDb(targetDir, options = {}) {
|
|
45
|
+
const { runtimeDir, dbPath } = resolveRuntimePaths(targetDir);
|
|
46
|
+
const mustExist = Boolean(options.mustExist);
|
|
47
|
+
|
|
48
|
+
if (mustExist && !(await exists(dbPath))) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await ensureDir(runtimeDir);
|
|
53
|
+
|
|
54
|
+
const db = new Database(dbPath);
|
|
55
|
+
db.pragma('journal_mode = WAL');
|
|
56
|
+
db.pragma('foreign_keys = ON');
|
|
57
|
+
|
|
58
|
+
db.exec(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS squads (
|
|
60
|
+
squad_slug TEXT PRIMARY KEY,
|
|
61
|
+
name TEXT NOT NULL,
|
|
62
|
+
mode TEXT NOT NULL DEFAULT 'content',
|
|
63
|
+
mission TEXT,
|
|
64
|
+
goal TEXT,
|
|
65
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
66
|
+
visibility TEXT NOT NULL DEFAULT 'private',
|
|
67
|
+
manifest_json TEXT,
|
|
68
|
+
context_json TEXT,
|
|
69
|
+
package_dir TEXT,
|
|
70
|
+
agents_dir TEXT,
|
|
71
|
+
output_dir TEXT,
|
|
72
|
+
logs_dir TEXT,
|
|
73
|
+
media_dir TEXT,
|
|
74
|
+
latest_session_path TEXT,
|
|
75
|
+
created_at TEXT NOT NULL,
|
|
76
|
+
updated_at TEXT NOT NULL
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
CREATE TABLE IF NOT EXISTS squad_executors (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
+
squad_slug TEXT NOT NULL,
|
|
82
|
+
executor_slug TEXT NOT NULL,
|
|
83
|
+
title TEXT,
|
|
84
|
+
role TEXT,
|
|
85
|
+
file_path TEXT,
|
|
86
|
+
skills_json TEXT,
|
|
87
|
+
genomes_json TEXT,
|
|
88
|
+
created_at TEXT NOT NULL,
|
|
89
|
+
updated_at TEXT NOT NULL,
|
|
90
|
+
UNIQUE (squad_slug, executor_slug),
|
|
91
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug) ON DELETE CASCADE
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS squad_skills (
|
|
95
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
96
|
+
squad_slug TEXT NOT NULL,
|
|
97
|
+
skill_slug TEXT NOT NULL,
|
|
98
|
+
title TEXT,
|
|
99
|
+
description TEXT,
|
|
100
|
+
UNIQUE (squad_slug, skill_slug),
|
|
101
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug) ON DELETE CASCADE
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE TABLE IF NOT EXISTS squad_mcps (
|
|
105
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
106
|
+
squad_slug TEXT NOT NULL,
|
|
107
|
+
mcp_slug TEXT NOT NULL,
|
|
108
|
+
required INTEGER NOT NULL DEFAULT 0,
|
|
109
|
+
purpose TEXT,
|
|
110
|
+
UNIQUE (squad_slug, mcp_slug),
|
|
111
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug) ON DELETE CASCADE
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
CREATE TABLE IF NOT EXISTS squad_genomes (
|
|
115
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
116
|
+
squad_slug TEXT NOT NULL,
|
|
117
|
+
genome_slug TEXT NOT NULL,
|
|
118
|
+
scope_type TEXT NOT NULL DEFAULT 'squad',
|
|
119
|
+
agent_slug TEXT,
|
|
120
|
+
UNIQUE (squad_slug, genome_slug, scope_type, agent_slug),
|
|
121
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug) ON DELETE CASCADE
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
125
|
+
task_key TEXT PRIMARY KEY,
|
|
126
|
+
squad_slug TEXT,
|
|
127
|
+
session_key TEXT,
|
|
128
|
+
title TEXT NOT NULL,
|
|
129
|
+
goal TEXT,
|
|
130
|
+
status TEXT NOT NULL,
|
|
131
|
+
created_by TEXT,
|
|
132
|
+
created_at TEXT NOT NULL,
|
|
133
|
+
updated_at TEXT NOT NULL,
|
|
134
|
+
finished_at TEXT
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
138
|
+
run_key TEXT PRIMARY KEY,
|
|
139
|
+
task_key TEXT,
|
|
140
|
+
agent_name TEXT NOT NULL,
|
|
141
|
+
agent_kind TEXT NOT NULL DEFAULT 'official',
|
|
142
|
+
squad_slug TEXT,
|
|
143
|
+
session_key TEXT,
|
|
144
|
+
source TEXT NOT NULL DEFAULT 'direct',
|
|
145
|
+
workflow_id TEXT,
|
|
146
|
+
workflow_stage TEXT,
|
|
147
|
+
parent_run_key TEXT,
|
|
148
|
+
title TEXT,
|
|
149
|
+
status TEXT NOT NULL,
|
|
150
|
+
summary TEXT,
|
|
151
|
+
used_skills_json TEXT,
|
|
152
|
+
output_path TEXT,
|
|
153
|
+
started_at TEXT NOT NULL,
|
|
154
|
+
updated_at TEXT NOT NULL,
|
|
155
|
+
finished_at TEXT,
|
|
156
|
+
FOREIGN KEY (task_key) REFERENCES tasks(task_key) ON DELETE SET NULL
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE IF NOT EXISTS agent_events (
|
|
160
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
161
|
+
run_key TEXT NOT NULL,
|
|
162
|
+
event_type TEXT NOT NULL,
|
|
163
|
+
message TEXT NOT NULL DEFAULT '',
|
|
164
|
+
payload_json TEXT,
|
|
165
|
+
created_at TEXT NOT NULL,
|
|
166
|
+
FOREIGN KEY (run_key) REFERENCES agent_runs(run_key) ON DELETE CASCADE
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
CREATE TABLE IF NOT EXISTS execution_events (
|
|
170
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
171
|
+
task_key TEXT,
|
|
172
|
+
run_key TEXT,
|
|
173
|
+
agent_name TEXT,
|
|
174
|
+
agent_kind TEXT,
|
|
175
|
+
squad_slug TEXT,
|
|
176
|
+
session_key TEXT,
|
|
177
|
+
source TEXT,
|
|
178
|
+
workflow_id TEXT,
|
|
179
|
+
workflow_stage TEXT,
|
|
180
|
+
parent_run_key TEXT,
|
|
181
|
+
event_type TEXT NOT NULL,
|
|
182
|
+
phase TEXT,
|
|
183
|
+
status TEXT,
|
|
184
|
+
tool_name TEXT,
|
|
185
|
+
message TEXT NOT NULL DEFAULT '',
|
|
186
|
+
payload_json TEXT,
|
|
187
|
+
sequence_no INTEGER NOT NULL DEFAULT 1,
|
|
188
|
+
parent_event_id INTEGER,
|
|
189
|
+
created_at TEXT NOT NULL,
|
|
190
|
+
FOREIGN KEY (task_key) REFERENCES tasks(task_key) ON DELETE SET NULL,
|
|
191
|
+
FOREIGN KEY (run_key) REFERENCES agent_runs(run_key) ON DELETE CASCADE
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
195
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
196
|
+
task_key TEXT,
|
|
197
|
+
run_key TEXT,
|
|
198
|
+
squad_slug TEXT,
|
|
199
|
+
agent_name TEXT,
|
|
200
|
+
kind TEXT NOT NULL,
|
|
201
|
+
title TEXT,
|
|
202
|
+
file_path TEXT NOT NULL,
|
|
203
|
+
created_at TEXT NOT NULL,
|
|
204
|
+
FOREIGN KEY (task_key) REFERENCES tasks(task_key) ON DELETE SET NULL,
|
|
205
|
+
FOREIGN KEY (run_key) REFERENCES agent_runs(run_key) ON DELETE SET NULL
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
CREATE TABLE IF NOT EXISTS content_items (
|
|
209
|
+
content_key TEXT PRIMARY KEY,
|
|
210
|
+
task_key TEXT,
|
|
211
|
+
run_key TEXT,
|
|
212
|
+
squad_slug TEXT NOT NULL,
|
|
213
|
+
session_key TEXT,
|
|
214
|
+
title TEXT NOT NULL,
|
|
215
|
+
content_type TEXT NOT NULL,
|
|
216
|
+
layout_type TEXT NOT NULL DEFAULT 'document',
|
|
217
|
+
status TEXT NOT NULL DEFAULT 'completed',
|
|
218
|
+
summary TEXT,
|
|
219
|
+
blueprint_slug TEXT,
|
|
220
|
+
used_skills_json TEXT,
|
|
221
|
+
payload_json TEXT,
|
|
222
|
+
json_path TEXT,
|
|
223
|
+
html_path TEXT,
|
|
224
|
+
created_by_agent TEXT,
|
|
225
|
+
created_at TEXT NOT NULL,
|
|
226
|
+
updated_at TEXT NOT NULL,
|
|
227
|
+
FOREIGN KEY (task_key) REFERENCES tasks(task_key) ON DELETE SET NULL,
|
|
228
|
+
FOREIGN KEY (run_key) REFERENCES agent_runs(run_key) ON DELETE SET NULL
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
CREATE INDEX IF NOT EXISTS idx_squads_updated ON squads(updated_at DESC);
|
|
232
|
+
CREATE INDEX IF NOT EXISTS idx_squad_executors_squad ON squad_executors(squad_slug, updated_at DESC);
|
|
233
|
+
CREATE INDEX IF NOT EXISTS idx_squad_skills_squad ON squad_skills(squad_slug);
|
|
234
|
+
CREATE INDEX IF NOT EXISTS idx_squad_mcps_squad ON squad_mcps(squad_slug);
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_squad_genomes_squad ON squad_genomes(squad_slug);
|
|
236
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status, updated_at DESC);
|
|
237
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_squad ON tasks(squad_slug, updated_at DESC);
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_status ON agent_runs(status, updated_at DESC);
|
|
239
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_task ON agent_runs(task_key, updated_at DESC);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS idx_agent_runs_squad ON agent_runs(squad_slug, updated_at DESC);
|
|
241
|
+
CREATE INDEX IF NOT EXISTS idx_agent_events_run ON agent_events(run_key, created_at DESC);
|
|
242
|
+
CREATE INDEX IF NOT EXISTS idx_execution_events_run ON execution_events(run_key, sequence_no DESC, created_at DESC);
|
|
243
|
+
CREATE INDEX IF NOT EXISTS idx_execution_events_task ON execution_events(task_key, created_at DESC);
|
|
244
|
+
CREATE INDEX IF NOT EXISTS idx_execution_events_created ON execution_events(created_at DESC);
|
|
245
|
+
CREATE INDEX IF NOT EXISTS idx_artifacts_task ON artifacts(task_key, created_at DESC);
|
|
246
|
+
CREATE INDEX IF NOT EXISTS idx_content_items_squad ON content_items(squad_slug, updated_at DESC);
|
|
247
|
+
CREATE INDEX IF NOT EXISTS idx_content_items_task ON content_items(task_key, updated_at DESC);
|
|
248
|
+
|
|
249
|
+
CREATE TABLE IF NOT EXISTS squad_analyses (
|
|
250
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
251
|
+
squad_slug TEXT NOT NULL,
|
|
252
|
+
coverage_json TEXT,
|
|
253
|
+
suggestions_json TEXT,
|
|
254
|
+
metrics_json TEXT,
|
|
255
|
+
created_at TEXT NOT NULL,
|
|
256
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug)
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
CREATE INDEX IF NOT EXISTS idx_squad_analyses_squad ON squad_analyses(squad_slug, created_at DESC);
|
|
260
|
+
|
|
261
|
+
CREATE TABLE IF NOT EXISTS squad_ports (
|
|
262
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
263
|
+
squad_slug TEXT NOT NULL,
|
|
264
|
+
port_type TEXT NOT NULL CHECK(port_type IN ('input', 'output')),
|
|
265
|
+
port_key TEXT NOT NULL,
|
|
266
|
+
data_type TEXT DEFAULT 'any',
|
|
267
|
+
description TEXT,
|
|
268
|
+
required INTEGER DEFAULT 0,
|
|
269
|
+
content_blueprint_slug TEXT,
|
|
270
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug),
|
|
271
|
+
UNIQUE(squad_slug, port_type, port_key)
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
CREATE TABLE IF NOT EXISTS squad_pipelines (
|
|
275
|
+
slug TEXT PRIMARY KEY,
|
|
276
|
+
name TEXT NOT NULL,
|
|
277
|
+
description TEXT,
|
|
278
|
+
status TEXT DEFAULT 'draft' CHECK(status IN ('draft', 'active', 'paused', 'archived')),
|
|
279
|
+
trigger_mode TEXT DEFAULT 'manual' CHECK(trigger_mode IN ('manual', 'on_output', 'scheduled')),
|
|
280
|
+
created_at TEXT NOT NULL,
|
|
281
|
+
updated_at TEXT NOT NULL
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
CREATE TABLE IF NOT EXISTS pipeline_nodes (
|
|
285
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
286
|
+
pipeline_slug TEXT NOT NULL,
|
|
287
|
+
squad_slug TEXT NOT NULL,
|
|
288
|
+
position_x REAL DEFAULT 0,
|
|
289
|
+
position_y REAL DEFAULT 0,
|
|
290
|
+
config_json TEXT,
|
|
291
|
+
FOREIGN KEY (pipeline_slug) REFERENCES squad_pipelines(slug),
|
|
292
|
+
FOREIGN KEY (squad_slug) REFERENCES squads(squad_slug),
|
|
293
|
+
UNIQUE(pipeline_slug, squad_slug)
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
CREATE TABLE IF NOT EXISTS pipeline_edges (
|
|
297
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
298
|
+
pipeline_slug TEXT NOT NULL,
|
|
299
|
+
source_squad TEXT NOT NULL,
|
|
300
|
+
source_port TEXT NOT NULL,
|
|
301
|
+
target_squad TEXT NOT NULL,
|
|
302
|
+
target_port TEXT NOT NULL,
|
|
303
|
+
transform_json TEXT,
|
|
304
|
+
FOREIGN KEY (pipeline_slug) REFERENCES squad_pipelines(slug)
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
CREATE TABLE IF NOT EXISTS squad_handoffs (
|
|
308
|
+
id TEXT PRIMARY KEY,
|
|
309
|
+
pipeline_slug TEXT,
|
|
310
|
+
from_squad TEXT NOT NULL,
|
|
311
|
+
from_port TEXT NOT NULL,
|
|
312
|
+
to_squad TEXT NOT NULL,
|
|
313
|
+
to_port TEXT NOT NULL,
|
|
314
|
+
payload_json TEXT,
|
|
315
|
+
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'consumed', 'failed', 'expired')),
|
|
316
|
+
created_at TEXT NOT NULL,
|
|
317
|
+
consumed_at TEXT,
|
|
318
|
+
FOREIGN KEY (from_squad) REFERENCES squads(squad_slug),
|
|
319
|
+
FOREIGN KEY (to_squad) REFERENCES squads(squad_slug)
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_squad_pipelines_status ON squad_pipelines(status, updated_at DESC);
|
|
323
|
+
CREATE INDEX IF NOT EXISTS idx_pipeline_nodes_pipeline ON pipeline_nodes(pipeline_slug);
|
|
324
|
+
CREATE INDEX IF NOT EXISTS idx_pipeline_edges_pipeline ON pipeline_edges(pipeline_slug);
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_squad_handoffs_to ON squad_handoffs(to_squad, status, created_at DESC);
|
|
326
|
+
|
|
327
|
+
CREATE TABLE IF NOT EXISTS artisan_squads (
|
|
328
|
+
id TEXT PRIMARY KEY,
|
|
329
|
+
slug TEXT NOT NULL UNIQUE,
|
|
330
|
+
title TEXT NOT NULL,
|
|
331
|
+
status TEXT DEFAULT 'draft' CHECK(status IN ('draft', 'refining', 'ready', 'created', 'archived')),
|
|
332
|
+
domain TEXT,
|
|
333
|
+
goal TEXT,
|
|
334
|
+
mode TEXT DEFAULT 'content',
|
|
335
|
+
prd_markdown TEXT,
|
|
336
|
+
summary TEXT,
|
|
337
|
+
confidence REAL DEFAULT 0,
|
|
338
|
+
tags_json TEXT DEFAULT '[]',
|
|
339
|
+
created_at TEXT NOT NULL,
|
|
340
|
+
updated_at TEXT NOT NULL
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
CREATE TABLE IF NOT EXISTS artisan_messages (
|
|
344
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
345
|
+
artisan_id TEXT NOT NULL,
|
|
346
|
+
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
|
|
347
|
+
content TEXT NOT NULL,
|
|
348
|
+
created_at TEXT NOT NULL,
|
|
349
|
+
FOREIGN KEY (artisan_id) REFERENCES artisan_squads(id)
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
CREATE INDEX IF NOT EXISTS idx_artisan_squads_status ON artisan_squads(status, updated_at DESC);
|
|
353
|
+
CREATE INDEX IF NOT EXISTS idx_artisan_messages_artisan ON artisan_messages(artisan_id, created_at ASC);
|
|
354
|
+
`);
|
|
355
|
+
|
|
356
|
+
ensureLegacyColumns(db);
|
|
357
|
+
|
|
358
|
+
return { db, dbPath, runtimeDir };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function createRunKey(agentName) {
|
|
362
|
+
return `${slugify(agentName)}-${Date.now()}`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function createTaskKey(title) {
|
|
366
|
+
return `task-${slugify(title)}-${Date.now()}`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function normalizeStatus(value, fallback) {
|
|
370
|
+
const candidate = String(value || fallback || '')
|
|
371
|
+
.trim()
|
|
372
|
+
.toLowerCase();
|
|
373
|
+
return VALID_STATUSES.has(candidate) ? candidate : fallback;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function normalizeTaskStatus(value, fallback) {
|
|
377
|
+
const candidate = String(value || fallback || '')
|
|
378
|
+
.trim()
|
|
379
|
+
.toLowerCase();
|
|
380
|
+
return VALID_TASK_STATUSES.has(candidate) ? candidate : fallback;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function ensureLegacyColumns(db) {
|
|
384
|
+
const agentRunColumns = db.prepare('PRAGMA table_info(agent_runs)').all();
|
|
385
|
+
const agentRunColumnNames = new Set(agentRunColumns.map((column) => column.name));
|
|
386
|
+
|
|
387
|
+
if (!agentRunColumnNames.has('task_key')) {
|
|
388
|
+
db.exec('ALTER TABLE agent_runs ADD COLUMN task_key TEXT');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (!agentRunColumnNames.has('used_skills_json')) {
|
|
392
|
+
db.exec('ALTER TABLE agent_runs ADD COLUMN used_skills_json TEXT');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!agentRunColumnNames.has('source')) {
|
|
396
|
+
db.exec("ALTER TABLE agent_runs ADD COLUMN source TEXT NOT NULL DEFAULT 'direct'");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!agentRunColumnNames.has('workflow_id')) {
|
|
400
|
+
db.exec('ALTER TABLE agent_runs ADD COLUMN workflow_id TEXT');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!agentRunColumnNames.has('workflow_stage')) {
|
|
404
|
+
db.exec('ALTER TABLE agent_runs ADD COLUMN workflow_stage TEXT');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!agentRunColumnNames.has('parent_run_key')) {
|
|
408
|
+
db.exec('ALTER TABLE agent_runs ADD COLUMN parent_run_key TEXT');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const squadColumns = db.prepare('PRAGMA table_info(squads)').all();
|
|
412
|
+
const squadColumnNames = new Set(squadColumns.map((column) => column.name));
|
|
413
|
+
|
|
414
|
+
if (!squadColumnNames.has('context_json')) {
|
|
415
|
+
db.exec('ALTER TABLE squads ADD COLUMN context_json TEXT');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!squadColumnNames.has('mode')) {
|
|
419
|
+
db.exec("ALTER TABLE squads ADD COLUMN mode TEXT NOT NULL DEFAULT 'content'");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!squadColumnNames.has('package_dir')) {
|
|
423
|
+
db.exec('ALTER TABLE squads ADD COLUMN package_dir TEXT');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const contentItemColumns = db.prepare('PRAGMA table_info(content_items)').all();
|
|
427
|
+
const contentItemColumnNames = new Set(contentItemColumns.map((column) => column.name));
|
|
428
|
+
|
|
429
|
+
if (!contentItemColumnNames.has('blueprint_slug')) {
|
|
430
|
+
db.exec('ALTER TABLE content_items ADD COLUMN blueprint_slug TEXT');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!contentItemColumnNames.has('used_skills_json')) {
|
|
434
|
+
db.exec('ALTER TABLE content_items ADD COLUMN used_skills_json TEXT');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function insertEvent(db, record) {
|
|
439
|
+
db.prepare(`
|
|
440
|
+
INSERT INTO agent_events (run_key, event_type, message, payload_json, created_at)
|
|
441
|
+
VALUES (@run_key, @event_type, @message, @payload_json, @created_at)
|
|
442
|
+
`).run(record);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getRunContext(db, runKey) {
|
|
446
|
+
return db
|
|
447
|
+
.prepare(`
|
|
448
|
+
SELECT
|
|
449
|
+
run_key,
|
|
450
|
+
task_key,
|
|
451
|
+
agent_name,
|
|
452
|
+
agent_kind,
|
|
453
|
+
squad_slug,
|
|
454
|
+
session_key,
|
|
455
|
+
source,
|
|
456
|
+
workflow_id,
|
|
457
|
+
workflow_stage,
|
|
458
|
+
parent_run_key,
|
|
459
|
+
status,
|
|
460
|
+
summary,
|
|
461
|
+
title
|
|
462
|
+
FROM agent_runs
|
|
463
|
+
WHERE run_key = ?
|
|
464
|
+
`)
|
|
465
|
+
.get(runKey);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function nextExecutionSequence(db, runKey) {
|
|
469
|
+
if (!runKey) return 1;
|
|
470
|
+
const row = db
|
|
471
|
+
.prepare('SELECT COALESCE(MAX(sequence_no), 0) AS max_sequence FROM execution_events WHERE run_key = ?')
|
|
472
|
+
.get(runKey);
|
|
473
|
+
return Number(row?.max_sequence || 0) + 1;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function insertExecutionEvent(db, record) {
|
|
477
|
+
db.prepare(`
|
|
478
|
+
INSERT INTO execution_events (
|
|
479
|
+
task_key, run_key, agent_name, agent_kind, squad_slug, session_key,
|
|
480
|
+
source, workflow_id, workflow_stage, parent_run_key,
|
|
481
|
+
event_type, phase, status, tool_name, message, payload_json,
|
|
482
|
+
sequence_no, parent_event_id, created_at
|
|
483
|
+
) VALUES (
|
|
484
|
+
@task_key, @run_key, @agent_name, @agent_kind, @squad_slug, @session_key,
|
|
485
|
+
@source, @workflow_id, @workflow_stage, @parent_run_key,
|
|
486
|
+
@event_type, @phase, @status, @tool_name, @message, @payload_json,
|
|
487
|
+
@sequence_no, @parent_event_id, @created_at
|
|
488
|
+
)
|
|
489
|
+
`).run(record);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function appendRunEvent(db, options) {
|
|
493
|
+
const run = getRunContext(db, options.runKey);
|
|
494
|
+
if (!run) {
|
|
495
|
+
throw new Error(`Run not found: ${options.runKey}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const now = options.createdAt || nowIso();
|
|
499
|
+
const payloadJson = options.payload ? JSON.stringify(options.payload) : null;
|
|
500
|
+
|
|
501
|
+
insertEvent(db, {
|
|
502
|
+
run_key: run.run_key,
|
|
503
|
+
event_type: String(options.eventType || 'update'),
|
|
504
|
+
message: String(options.message || ''),
|
|
505
|
+
payload_json: payloadJson,
|
|
506
|
+
created_at: now
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
insertExecutionEvent(db, {
|
|
510
|
+
task_key: run.task_key,
|
|
511
|
+
run_key: run.run_key,
|
|
512
|
+
agent_name: run.agent_name,
|
|
513
|
+
agent_kind: run.agent_kind,
|
|
514
|
+
squad_slug: run.squad_slug,
|
|
515
|
+
session_key: run.session_key,
|
|
516
|
+
source: run.source,
|
|
517
|
+
workflow_id: run.workflow_id,
|
|
518
|
+
workflow_stage: run.workflow_stage,
|
|
519
|
+
parent_run_key: run.parent_run_key,
|
|
520
|
+
event_type: String(options.eventType || 'update'),
|
|
521
|
+
phase: options.phase ? String(options.phase).trim() : null,
|
|
522
|
+
status: options.status ? String(options.status).trim() : run.status || null,
|
|
523
|
+
tool_name: options.toolName ? String(options.toolName).trim() : null,
|
|
524
|
+
message: String(options.message || ''),
|
|
525
|
+
payload_json: payloadJson,
|
|
526
|
+
sequence_no: nextExecutionSequence(db, run.run_key),
|
|
527
|
+
parent_event_id: options.parentEventId || null,
|
|
528
|
+
created_at: now
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function startTask(db, options) {
|
|
533
|
+
const now = nowIso();
|
|
534
|
+
const taskKey = String(options.taskKey || createTaskKey(options.title));
|
|
535
|
+
const status = normalizeTaskStatus(options.status, 'running');
|
|
536
|
+
|
|
537
|
+
db.prepare(`
|
|
538
|
+
INSERT INTO tasks (
|
|
539
|
+
task_key, squad_slug, session_key, title, goal, status, created_by,
|
|
540
|
+
created_at, updated_at, finished_at
|
|
541
|
+
) VALUES (
|
|
542
|
+
@task_key, @squad_slug, @session_key, @title, @goal, @status, @created_by,
|
|
543
|
+
@created_at, @updated_at, @finished_at
|
|
544
|
+
)
|
|
545
|
+
`).run({
|
|
546
|
+
task_key: taskKey,
|
|
547
|
+
squad_slug: options.squadSlug ? String(options.squadSlug).trim() : null,
|
|
548
|
+
session_key: options.sessionKey ? String(options.sessionKey).trim() : null,
|
|
549
|
+
title: String(options.title).trim(),
|
|
550
|
+
goal: options.goal ? String(options.goal).trim() : null,
|
|
551
|
+
status,
|
|
552
|
+
created_by: options.createdBy ? String(options.createdBy).trim() : null,
|
|
553
|
+
created_at: now,
|
|
554
|
+
updated_at: now,
|
|
555
|
+
finished_at: status === 'completed' || status === 'failed' ? now : null
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
return taskKey;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function updateTask(db, options) {
|
|
562
|
+
const existing = db.prepare('SELECT task_key, status FROM tasks WHERE task_key = ?').get(options.taskKey);
|
|
563
|
+
if (!existing) {
|
|
564
|
+
throw new Error(`Task not found: ${options.taskKey}`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const now = nowIso();
|
|
568
|
+
const nextStatus = normalizeTaskStatus(options.status, existing.status || 'running');
|
|
569
|
+
|
|
570
|
+
db.prepare(`
|
|
571
|
+
UPDATE tasks
|
|
572
|
+
SET
|
|
573
|
+
status = @status,
|
|
574
|
+
goal = COALESCE(@goal, goal),
|
|
575
|
+
updated_at = @updated_at,
|
|
576
|
+
finished_at = CASE
|
|
577
|
+
WHEN @status IN ('completed', 'failed') THEN @updated_at
|
|
578
|
+
ELSE finished_at
|
|
579
|
+
END
|
|
580
|
+
WHERE task_key = @task_key
|
|
581
|
+
`).run({
|
|
582
|
+
task_key: String(options.taskKey),
|
|
583
|
+
status: nextStatus,
|
|
584
|
+
goal: options.goal ? String(options.goal).trim() : null,
|
|
585
|
+
updated_at: now
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
return nextStatus;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function attachArtifact(db, options) {
|
|
592
|
+
const now = nowIso();
|
|
593
|
+
db.prepare(`
|
|
594
|
+
INSERT INTO artifacts (
|
|
595
|
+
task_key, run_key, squad_slug, agent_name, kind, title, file_path, created_at
|
|
596
|
+
) VALUES (
|
|
597
|
+
@task_key, @run_key, @squad_slug, @agent_name, @kind, @title, @file_path, @created_at
|
|
598
|
+
)
|
|
599
|
+
`).run({
|
|
600
|
+
task_key: options.taskKey ? String(options.taskKey) : null,
|
|
601
|
+
run_key: options.runKey ? String(options.runKey) : null,
|
|
602
|
+
squad_slug: options.squadSlug ? String(options.squadSlug) : null,
|
|
603
|
+
agent_name: options.agentName ? String(options.agentName) : null,
|
|
604
|
+
kind: String(options.kind || inferArtifactKind(options.filePath || '')).trim(),
|
|
605
|
+
title: options.title ? String(options.title).trim() : null,
|
|
606
|
+
file_path: String(options.filePath).trim(),
|
|
607
|
+
created_at: now
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function upsertContentItem(db, options) {
|
|
612
|
+
const now = nowIso();
|
|
613
|
+
const contentKey = String(options.contentKey || createTaskKey(options.title || 'content')).trim();
|
|
614
|
+
const usedSkillsJson = normalizeStringArray(options.usedSkills).length > 0 ? JSON.stringify(normalizeStringArray(options.usedSkills)) : null;
|
|
615
|
+
|
|
616
|
+
db.prepare(`
|
|
617
|
+
INSERT INTO content_items (
|
|
618
|
+
content_key, task_key, run_key, squad_slug, session_key, title, content_type, layout_type,
|
|
619
|
+
status, summary, blueprint_slug, used_skills_json, payload_json, json_path, html_path, created_by_agent, created_at, updated_at
|
|
620
|
+
) VALUES (
|
|
621
|
+
@content_key, @task_key, @run_key, @squad_slug, @session_key, @title, @content_type, @layout_type,
|
|
622
|
+
@status, @summary, @blueprint_slug, @used_skills_json, @payload_json, @json_path, @html_path, @created_by_agent, @created_at, @updated_at
|
|
623
|
+
)
|
|
624
|
+
ON CONFLICT(content_key) DO UPDATE SET
|
|
625
|
+
task_key = excluded.task_key,
|
|
626
|
+
run_key = excluded.run_key,
|
|
627
|
+
squad_slug = excluded.squad_slug,
|
|
628
|
+
session_key = excluded.session_key,
|
|
629
|
+
title = excluded.title,
|
|
630
|
+
content_type = excluded.content_type,
|
|
631
|
+
layout_type = excluded.layout_type,
|
|
632
|
+
status = excluded.status,
|
|
633
|
+
summary = excluded.summary,
|
|
634
|
+
blueprint_slug = excluded.blueprint_slug,
|
|
635
|
+
used_skills_json = excluded.used_skills_json,
|
|
636
|
+
payload_json = excluded.payload_json,
|
|
637
|
+
json_path = excluded.json_path,
|
|
638
|
+
html_path = excluded.html_path,
|
|
639
|
+
created_by_agent = excluded.created_by_agent,
|
|
640
|
+
updated_at = excluded.updated_at
|
|
641
|
+
`).run({
|
|
642
|
+
content_key: contentKey,
|
|
643
|
+
task_key: options.taskKey ? String(options.taskKey).trim() : null,
|
|
644
|
+
run_key: options.runKey ? String(options.runKey).trim() : null,
|
|
645
|
+
squad_slug: String(options.squadSlug).trim(),
|
|
646
|
+
session_key: options.sessionKey ? String(options.sessionKey).trim() : null,
|
|
647
|
+
title: String(options.title || contentKey).trim(),
|
|
648
|
+
content_type: String(options.contentType || 'content').trim(),
|
|
649
|
+
layout_type: String(options.layoutType || 'document').trim(),
|
|
650
|
+
status: String(options.status || 'completed').trim(),
|
|
651
|
+
summary: options.summary ? String(options.summary).trim() : null,
|
|
652
|
+
blueprint_slug: options.blueprintSlug ? String(options.blueprintSlug).trim() : null,
|
|
653
|
+
used_skills_json: usedSkillsJson,
|
|
654
|
+
payload_json:
|
|
655
|
+
options.payload && typeof options.payload === 'object'
|
|
656
|
+
? JSON.stringify(options.payload)
|
|
657
|
+
: options.payloadJson
|
|
658
|
+
? String(options.payloadJson)
|
|
659
|
+
: null,
|
|
660
|
+
json_path: options.jsonPath ? String(options.jsonPath).trim() : null,
|
|
661
|
+
html_path: options.htmlPath ? String(options.htmlPath).trim() : null,
|
|
662
|
+
created_by_agent: options.createdByAgent ? String(options.createdByAgent).trim() : null,
|
|
663
|
+
created_at: now,
|
|
664
|
+
updated_at: now
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
return contentKey;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function normalizeArray(value) {
|
|
671
|
+
return Array.isArray(value) ? value : [];
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function normalizeStringArray(value) {
|
|
675
|
+
const values = Array.isArray(value)
|
|
676
|
+
? value
|
|
677
|
+
: typeof value === 'string'
|
|
678
|
+
? value.split(',')
|
|
679
|
+
: [];
|
|
680
|
+
|
|
681
|
+
return Array.from(
|
|
682
|
+
new Set(
|
|
683
|
+
values
|
|
684
|
+
.map((entry) => String(entry || '').trim())
|
|
685
|
+
.filter(Boolean)
|
|
686
|
+
)
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseJsonArray(value) {
|
|
691
|
+
if (!value) return [];
|
|
692
|
+
try {
|
|
693
|
+
return normalizeStringArray(JSON.parse(value));
|
|
694
|
+
} catch {
|
|
695
|
+
return [];
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function upsertSquadManifest(db, options) {
|
|
700
|
+
const now = nowIso();
|
|
701
|
+
const slug = String(options.slug).trim();
|
|
702
|
+
const manifest = options.manifest && typeof options.manifest === 'object' ? options.manifest : {};
|
|
703
|
+
const context =
|
|
704
|
+
options.context && typeof options.context === 'object'
|
|
705
|
+
? options.context
|
|
706
|
+
: manifest.context && typeof manifest.context === 'object'
|
|
707
|
+
? manifest.context
|
|
708
|
+
: null;
|
|
709
|
+
const skills = normalizeArray(manifest.skills);
|
|
710
|
+
const mcps = normalizeArray(manifest.mcps);
|
|
711
|
+
const executors = normalizeArray(manifest.executors);
|
|
712
|
+
const genomeBindings = mergeGenomeBindings({
|
|
713
|
+
blueprintBindings: manifest.genomeBindings,
|
|
714
|
+
manifestBindings: manifest.genomeBindings || manifest.genomes,
|
|
715
|
+
legacyExecutors: executors
|
|
716
|
+
});
|
|
717
|
+
const resolvedExecutors = attachBindingsToExecutors(executors, genomeBindings);
|
|
718
|
+
const genomes = flattenGenomeBindings(genomeBindings);
|
|
719
|
+
|
|
720
|
+
db.prepare(`
|
|
721
|
+
INSERT INTO squads (
|
|
722
|
+
squad_slug, name, mode, mission, goal, status, visibility, manifest_json,
|
|
723
|
+
context_json,
|
|
724
|
+
package_dir, agents_dir, output_dir, logs_dir, media_dir, latest_session_path,
|
|
725
|
+
created_at, updated_at
|
|
726
|
+
) VALUES (
|
|
727
|
+
@squad_slug, @name, @mode, @mission, @goal, @status, @visibility, @manifest_json,
|
|
728
|
+
@context_json,
|
|
729
|
+
@package_dir, @agents_dir, @output_dir, @logs_dir, @media_dir, @latest_session_path,
|
|
730
|
+
@created_at, @updated_at
|
|
731
|
+
)
|
|
732
|
+
ON CONFLICT(squad_slug) DO UPDATE SET
|
|
733
|
+
name = excluded.name,
|
|
734
|
+
mode = excluded.mode,
|
|
735
|
+
mission = excluded.mission,
|
|
736
|
+
goal = excluded.goal,
|
|
737
|
+
status = excluded.status,
|
|
738
|
+
visibility = excluded.visibility,
|
|
739
|
+
manifest_json = excluded.manifest_json,
|
|
740
|
+
context_json = excluded.context_json,
|
|
741
|
+
package_dir = excluded.package_dir,
|
|
742
|
+
agents_dir = excluded.agents_dir,
|
|
743
|
+
output_dir = excluded.output_dir,
|
|
744
|
+
logs_dir = excluded.logs_dir,
|
|
745
|
+
media_dir = excluded.media_dir,
|
|
746
|
+
latest_session_path = excluded.latest_session_path,
|
|
747
|
+
updated_at = excluded.updated_at
|
|
748
|
+
`).run({
|
|
749
|
+
squad_slug: slug,
|
|
750
|
+
name: String(options.name || manifest.name || slug).trim(),
|
|
751
|
+
mode: String(options.mode || manifest.mode || 'content').trim(),
|
|
752
|
+
mission: options.mission ? String(options.mission).trim() : manifest.mission ? String(manifest.mission).trim() : null,
|
|
753
|
+
goal: options.goal ? String(options.goal).trim() : manifest.goal ? String(manifest.goal).trim() : null,
|
|
754
|
+
status: String(options.status || 'active').trim(),
|
|
755
|
+
visibility: String(options.visibility || manifest.visibility || 'private').trim(),
|
|
756
|
+
manifest_json: JSON.stringify(manifest),
|
|
757
|
+
context_json: context ? JSON.stringify(context) : null,
|
|
758
|
+
package_dir: options.packageDir ? String(options.packageDir).trim() : manifest?.package?.rootDir ? String(manifest.package.rootDir).trim() : null,
|
|
759
|
+
agents_dir: options.agentsDir ? String(options.agentsDir).trim() : null,
|
|
760
|
+
output_dir: options.outputDir ? String(options.outputDir).trim() : null,
|
|
761
|
+
logs_dir: options.logsDir ? String(options.logsDir).trim() : null,
|
|
762
|
+
media_dir: options.mediaDir ? String(options.mediaDir).trim() : null,
|
|
763
|
+
latest_session_path: options.latestSessionPath ? String(options.latestSessionPath).trim() : null,
|
|
764
|
+
created_at: now,
|
|
765
|
+
updated_at: now
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
db.prepare('DELETE FROM squad_executors WHERE squad_slug = ?').run(slug);
|
|
769
|
+
db.prepare('DELETE FROM squad_skills WHERE squad_slug = ?').run(slug);
|
|
770
|
+
db.prepare('DELETE FROM squad_mcps WHERE squad_slug = ?').run(slug);
|
|
771
|
+
db.prepare('DELETE FROM squad_genomes WHERE squad_slug = ?').run(slug);
|
|
772
|
+
|
|
773
|
+
const insertExecutor = db.prepare(`
|
|
774
|
+
INSERT INTO squad_executors (
|
|
775
|
+
squad_slug, executor_slug, title, role, file_path, skills_json, genomes_json,
|
|
776
|
+
created_at, updated_at
|
|
777
|
+
) VALUES (
|
|
778
|
+
@squad_slug, @executor_slug, @title, @role, @file_path, @skills_json, @genomes_json,
|
|
779
|
+
@created_at, @updated_at
|
|
780
|
+
)
|
|
781
|
+
`);
|
|
782
|
+
|
|
783
|
+
for (const executor of resolvedExecutors) {
|
|
784
|
+
insertExecutor.run({
|
|
785
|
+
squad_slug: slug,
|
|
786
|
+
executor_slug: String(executor.slug || '').trim(),
|
|
787
|
+
title: executor.title ? String(executor.title).trim() : null,
|
|
788
|
+
role: executor.role ? String(executor.role).trim() : null,
|
|
789
|
+
file_path: executor.file ? String(executor.file).trim() : null,
|
|
790
|
+
skills_json: JSON.stringify(normalizeArray(executor.skills)),
|
|
791
|
+
genomes_json: JSON.stringify(normalizeArray(executor.genomes)),
|
|
792
|
+
created_at: now,
|
|
793
|
+
updated_at: now
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const insertSkill = db.prepare(`
|
|
798
|
+
INSERT INTO squad_skills (squad_slug, skill_slug, title, description)
|
|
799
|
+
VALUES (@squad_slug, @skill_slug, @title, @description)
|
|
800
|
+
`);
|
|
801
|
+
|
|
802
|
+
for (const skill of skills) {
|
|
803
|
+
insertSkill.run({
|
|
804
|
+
squad_slug: slug,
|
|
805
|
+
skill_slug: String(skill.slug || '').trim(),
|
|
806
|
+
title: skill.title ? String(skill.title).trim() : null,
|
|
807
|
+
description: skill.description ? String(skill.description).trim() : null
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
const insertMcp = db.prepare(`
|
|
812
|
+
INSERT INTO squad_mcps (squad_slug, mcp_slug, required, purpose)
|
|
813
|
+
VALUES (@squad_slug, @mcp_slug, @required, @purpose)
|
|
814
|
+
`);
|
|
815
|
+
|
|
816
|
+
for (const mcp of mcps) {
|
|
817
|
+
insertMcp.run({
|
|
818
|
+
squad_slug: slug,
|
|
819
|
+
mcp_slug: String(mcp.slug || '').trim(),
|
|
820
|
+
required: mcp.required ? 1 : 0,
|
|
821
|
+
purpose: mcp.purpose ? String(mcp.purpose).trim() : null
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const insertGenome = db.prepare(`
|
|
826
|
+
INSERT INTO squad_genomes (squad_slug, genome_slug, scope_type, agent_slug)
|
|
827
|
+
VALUES (@squad_slug, @genome_slug, @scope_type, @agent_slug)
|
|
828
|
+
`);
|
|
829
|
+
|
|
830
|
+
for (const genome of genomes) {
|
|
831
|
+
insertGenome.run({
|
|
832
|
+
squad_slug: slug,
|
|
833
|
+
genome_slug: String(genome.slug || '').trim(),
|
|
834
|
+
scope_type: String(genome.scope || 'squad').trim(),
|
|
835
|
+
agent_slug: genome.agentSlug ? String(genome.agentSlug).trim() : null
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return slug;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// ─── Pipeline CRUD ────────────────────────────────────────────────────────────
|
|
843
|
+
|
|
844
|
+
function upsertPipeline(db, options) {
|
|
845
|
+
const now = nowIso();
|
|
846
|
+
const slug = String(options.slug).trim();
|
|
847
|
+
db.prepare(`
|
|
848
|
+
INSERT INTO squad_pipelines (slug, name, description, status, trigger_mode, created_at, updated_at)
|
|
849
|
+
VALUES (@slug, @name, @description, @status, @trigger_mode, @created_at, @updated_at)
|
|
850
|
+
ON CONFLICT(slug) DO UPDATE SET
|
|
851
|
+
name = excluded.name,
|
|
852
|
+
description = COALESCE(excluded.description, description),
|
|
853
|
+
status = excluded.status,
|
|
854
|
+
trigger_mode = excluded.trigger_mode,
|
|
855
|
+
updated_at = excluded.updated_at
|
|
856
|
+
`).run({
|
|
857
|
+
slug,
|
|
858
|
+
name: String(options.name || slug).trim(),
|
|
859
|
+
description: options.description ? String(options.description).trim() : null,
|
|
860
|
+
status: String(options.status || 'draft').trim(),
|
|
861
|
+
trigger_mode: String(options.triggerMode || 'manual').trim(),
|
|
862
|
+
created_at: now,
|
|
863
|
+
updated_at: now
|
|
864
|
+
});
|
|
865
|
+
return slug;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function addPipelineNode(db, options) {
|
|
869
|
+
db.prepare(`
|
|
870
|
+
INSERT INTO pipeline_nodes (pipeline_slug, squad_slug, position_x, position_y, config_json)
|
|
871
|
+
VALUES (@pipeline_slug, @squad_slug, @position_x, @position_y, @config_json)
|
|
872
|
+
ON CONFLICT(pipeline_slug, squad_slug) DO UPDATE SET
|
|
873
|
+
position_x = excluded.position_x,
|
|
874
|
+
position_y = excluded.position_y,
|
|
875
|
+
config_json = COALESCE(excluded.config_json, config_json)
|
|
876
|
+
`).run({
|
|
877
|
+
pipeline_slug: String(options.pipelineSlug).trim(),
|
|
878
|
+
squad_slug: String(options.squadSlug).trim(),
|
|
879
|
+
position_x: Number(options.positionX || 0),
|
|
880
|
+
position_y: Number(options.positionY || 0),
|
|
881
|
+
config_json: options.config ? JSON.stringify(options.config) : null
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function updateNodePosition(db, options) {
|
|
886
|
+
db.prepare(`
|
|
887
|
+
UPDATE pipeline_nodes SET position_x = @position_x, position_y = @position_y
|
|
888
|
+
WHERE pipeline_slug = @pipeline_slug AND squad_slug = @squad_slug
|
|
889
|
+
`).run({
|
|
890
|
+
pipeline_slug: String(options.pipelineSlug).trim(),
|
|
891
|
+
squad_slug: String(options.squadSlug).trim(),
|
|
892
|
+
position_x: Number(options.positionX || 0),
|
|
893
|
+
position_y: Number(options.positionY || 0)
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function addPipelineEdge(db, options) {
|
|
898
|
+
db.prepare(`
|
|
899
|
+
INSERT INTO pipeline_edges (pipeline_slug, source_squad, source_port, target_squad, target_port, transform_json)
|
|
900
|
+
VALUES (@pipeline_slug, @source_squad, @source_port, @target_squad, @target_port, @transform_json)
|
|
901
|
+
`).run({
|
|
902
|
+
pipeline_slug: String(options.pipelineSlug).trim(),
|
|
903
|
+
source_squad: String(options.sourceSquad).trim(),
|
|
904
|
+
source_port: String(options.sourcePort).trim(),
|
|
905
|
+
target_squad: String(options.targetSquad).trim(),
|
|
906
|
+
target_port: String(options.targetPort).trim(),
|
|
907
|
+
transform_json: options.transform ? JSON.stringify(options.transform) : null
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function removePipelineEdge(db, id) {
|
|
912
|
+
db.prepare('DELETE FROM pipeline_edges WHERE id = ?').run(id);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function getPipelineDAG(db, pipelineSlug) {
|
|
916
|
+
const pipeline = db.prepare('SELECT * FROM squad_pipelines WHERE slug = ?').get(pipelineSlug);
|
|
917
|
+
if (!pipeline) return null;
|
|
918
|
+
const nodes = db.prepare('SELECT * FROM pipeline_nodes WHERE pipeline_slug = ?').all(pipelineSlug);
|
|
919
|
+
const edges = db.prepare('SELECT * FROM pipeline_edges WHERE pipeline_slug = ?').all(pipelineSlug);
|
|
920
|
+
return { pipeline, nodes, edges };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function listPipelines(db) {
|
|
924
|
+
return db.prepare('SELECT * FROM squad_pipelines ORDER BY updated_at DESC').all();
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function upsertSquadPorts(db, squadSlug, ports) {
|
|
928
|
+
db.prepare('DELETE FROM squad_ports WHERE squad_slug = ?').run(squadSlug);
|
|
929
|
+
const insert = db.prepare(`
|
|
930
|
+
INSERT INTO squad_ports (squad_slug, port_type, port_key, data_type, description, required, content_blueprint_slug)
|
|
931
|
+
VALUES (@squad_slug, @port_type, @port_key, @data_type, @description, @required, @content_blueprint_slug)
|
|
932
|
+
`);
|
|
933
|
+
const all = [...(ports.inputs || []).map(p => ({ ...p, type: 'input' })), ...(ports.outputs || []).map(p => ({ ...p, type: 'output' }))];
|
|
934
|
+
for (const port of all) {
|
|
935
|
+
insert.run({
|
|
936
|
+
squad_slug: String(squadSlug).trim(),
|
|
937
|
+
port_type: port.type,
|
|
938
|
+
port_key: String(port.key).trim(),
|
|
939
|
+
data_type: String(port.dataType || 'any').trim(),
|
|
940
|
+
description: port.description ? String(port.description).trim() : null,
|
|
941
|
+
required: port.required ? 1 : 0,
|
|
942
|
+
content_blueprint_slug: port.contentBlueprintSlug ? String(port.contentBlueprintSlug).trim() : null
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function getTopologicalOrder(db, pipelineSlug) {
|
|
948
|
+
const nodes = db.prepare('SELECT squad_slug FROM pipeline_nodes WHERE pipeline_slug = ?').all(pipelineSlug);
|
|
949
|
+
const edges = db.prepare('SELECT source_squad, target_squad FROM pipeline_edges WHERE pipeline_slug = ?').all(pipelineSlug);
|
|
950
|
+
|
|
951
|
+
const slugs = nodes.map(n => n.squad_slug);
|
|
952
|
+
const inDegree = Object.fromEntries(slugs.map(s => [s, 0]));
|
|
953
|
+
const adj = Object.fromEntries(slugs.map(s => [s, []]));
|
|
954
|
+
|
|
955
|
+
for (const { source_squad, target_squad } of edges) {
|
|
956
|
+
if (adj[source_squad] !== undefined && inDegree[target_squad] !== undefined) {
|
|
957
|
+
adj[source_squad].push(target_squad);
|
|
958
|
+
inDegree[target_squad]++;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const queue = slugs.filter(s => inDegree[s] === 0);
|
|
963
|
+
const order = [];
|
|
964
|
+
|
|
965
|
+
while (queue.length > 0) {
|
|
966
|
+
const node = queue.shift();
|
|
967
|
+
order.push(node);
|
|
968
|
+
for (const neighbor of (adj[node] || [])) {
|
|
969
|
+
inDegree[neighbor]--;
|
|
970
|
+
if (inDegree[neighbor] === 0) queue.push(neighbor);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return order.length === slugs.length ? order : null; // null = cycle detected
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// ─── Artisan CRUD ─────────────────────────────────────────────────────────────
|
|
978
|
+
|
|
979
|
+
function createArtisanSquad(db, options) {
|
|
980
|
+
const now = nowIso();
|
|
981
|
+
const id = options.id || `artisan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
982
|
+
const slug = options.slug || id;
|
|
983
|
+
db.prepare(`
|
|
984
|
+
INSERT INTO artisan_squads (id, slug, title, status, domain, goal, mode, prd_markdown, summary, confidence, tags_json, created_at, updated_at)
|
|
985
|
+
VALUES (@id, @slug, @title, @status, @domain, @goal, @mode, @prd_markdown, @summary, @confidence, @tags_json, @created_at, @updated_at)
|
|
986
|
+
`).run({
|
|
987
|
+
id, slug,
|
|
988
|
+
title: options.title || 'Nova ideia',
|
|
989
|
+
status: 'draft',
|
|
990
|
+
domain: options.domain || null,
|
|
991
|
+
goal: options.goal || null,
|
|
992
|
+
mode: options.mode || 'content',
|
|
993
|
+
prd_markdown: options.prdMarkdown || null,
|
|
994
|
+
summary: options.summary || null,
|
|
995
|
+
confidence: options.confidence || 0,
|
|
996
|
+
tags_json: JSON.stringify(options.tags || []),
|
|
997
|
+
created_at: now, updated_at: now
|
|
998
|
+
});
|
|
999
|
+
return id;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
function updateArtisanSquad(db, id, updates) {
|
|
1003
|
+
const now = nowIso();
|
|
1004
|
+
const existing = db.prepare('SELECT id FROM artisan_squads WHERE id = ?').get(id);
|
|
1005
|
+
if (!existing) throw new Error(`Artisan squad not found: ${id}`);
|
|
1006
|
+
const fields = [];
|
|
1007
|
+
const values = { id, updated_at: now };
|
|
1008
|
+
const allowed = ['title', 'status', 'domain', 'goal', 'mode', 'prd_markdown', 'summary', 'confidence', 'tags_json'];
|
|
1009
|
+
for (const key of allowed) {
|
|
1010
|
+
if (Object.prototype.hasOwnProperty.call(updates, key)) {
|
|
1011
|
+
fields.push(`${key} = @${key}`);
|
|
1012
|
+
values[key] = updates[key];
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
if (fields.length === 0) return;
|
|
1016
|
+
db.prepare(`UPDATE artisan_squads SET ${fields.join(', ')}, updated_at = @updated_at WHERE id = @id`).run(values);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function getArtisanSquad(db, id) {
|
|
1020
|
+
return db.prepare('SELECT * FROM artisan_squads WHERE id = ?').get(id) || null;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function listArtisanSquads(db) {
|
|
1024
|
+
return db.prepare('SELECT * FROM artisan_squads ORDER BY updated_at DESC').all();
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function deleteArtisanSquad(db, id) {
|
|
1028
|
+
db.prepare('DELETE FROM artisan_messages WHERE artisan_id = ?').run(id);
|
|
1029
|
+
db.prepare('DELETE FROM artisan_squads WHERE id = ?').run(id);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function addArtisanMessage(db, artisanId, role, content) {
|
|
1033
|
+
const now = nowIso();
|
|
1034
|
+
db.prepare(`
|
|
1035
|
+
INSERT INTO artisan_messages (artisan_id, role, content, created_at)
|
|
1036
|
+
VALUES (@artisan_id, @role, @content, @created_at)
|
|
1037
|
+
`).run({ artisan_id: artisanId, role, content, created_at: now });
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
function getArtisanMessages(db, artisanId) {
|
|
1041
|
+
return db.prepare('SELECT * FROM artisan_messages WHERE artisan_id = ? ORDER BY created_at ASC').all(artisanId);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
function insertSquadAnalysis(db, options) {
|
|
1045
|
+
const now = nowIso();
|
|
1046
|
+
db.prepare(`
|
|
1047
|
+
INSERT INTO squad_analyses (squad_slug, coverage_json, suggestions_json, metrics_json, created_at)
|
|
1048
|
+
VALUES (@squad_slug, @coverage_json, @suggestions_json, @metrics_json, @created_at)
|
|
1049
|
+
`).run({
|
|
1050
|
+
squad_slug: String(options.slug).trim(),
|
|
1051
|
+
coverage_json: JSON.stringify(options.coverage || {}),
|
|
1052
|
+
suggestions_json: JSON.stringify(options.suggestions || []),
|
|
1053
|
+
metrics_json: JSON.stringify(options.metrics || {}),
|
|
1054
|
+
created_at: now
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function inferArtifactKind(filePath) {
|
|
1059
|
+
if (/\.html?$/i.test(filePath)) return 'html';
|
|
1060
|
+
if (/\.md$/i.test(filePath)) return 'markdown';
|
|
1061
|
+
return 'file';
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function startRun(db, options) {
|
|
1065
|
+
const now = nowIso();
|
|
1066
|
+
const runKey = String(options.runKey || createRunKey(options.agentName));
|
|
1067
|
+
const status = normalizeStatus(options.status, 'running');
|
|
1068
|
+
const agentKind = String(options.agentKind || (options.squadSlug ? 'squad' : 'official')).trim();
|
|
1069
|
+
const taskKey = options.taskKey ? String(options.taskKey).trim() : null;
|
|
1070
|
+
const source = String(options.source || 'direct').trim() || 'direct';
|
|
1071
|
+
const usedSkillsJson = normalizeStringArray(options.usedSkills).length > 0 ? JSON.stringify(normalizeStringArray(options.usedSkills)) : null;
|
|
1072
|
+
|
|
1073
|
+
if (taskKey) {
|
|
1074
|
+
const taskExists = db.prepare('SELECT task_key FROM tasks WHERE task_key = ?').get(taskKey);
|
|
1075
|
+
if (!taskExists) {
|
|
1076
|
+
throw new Error(`Task not found: ${taskKey}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
db.prepare(`
|
|
1081
|
+
INSERT INTO agent_runs (
|
|
1082
|
+
run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
1083
|
+
workflow_id, workflow_stage, parent_run_key, title,
|
|
1084
|
+
status, summary, used_skills_json, output_path, started_at, updated_at, finished_at
|
|
1085
|
+
) VALUES (
|
|
1086
|
+
@run_key, @task_key, @agent_name, @agent_kind, @squad_slug, @session_key, @source,
|
|
1087
|
+
@workflow_id, @workflow_stage, @parent_run_key, @title,
|
|
1088
|
+
@status, @summary, @used_skills_json, @output_path, @started_at, @updated_at, @finished_at
|
|
1089
|
+
)
|
|
1090
|
+
`).run({
|
|
1091
|
+
run_key: runKey,
|
|
1092
|
+
task_key: taskKey,
|
|
1093
|
+
agent_name: String(options.agentName).trim(),
|
|
1094
|
+
agent_kind: agentKind,
|
|
1095
|
+
squad_slug: options.squadSlug ? String(options.squadSlug).trim() : null,
|
|
1096
|
+
session_key: options.sessionKey ? String(options.sessionKey).trim() : null,
|
|
1097
|
+
source,
|
|
1098
|
+
workflow_id: options.workflowId ? String(options.workflowId).trim() : null,
|
|
1099
|
+
workflow_stage: options.workflowStage ? String(options.workflowStage).trim() : null,
|
|
1100
|
+
parent_run_key: options.parentRunKey ? String(options.parentRunKey).trim() : null,
|
|
1101
|
+
title: options.title ? String(options.title).trim() : null,
|
|
1102
|
+
status,
|
|
1103
|
+
summary: options.summary ? String(options.summary).trim() : null,
|
|
1104
|
+
used_skills_json: usedSkillsJson,
|
|
1105
|
+
output_path: options.outputPath ? String(options.outputPath).trim() : null,
|
|
1106
|
+
started_at: now,
|
|
1107
|
+
updated_at: now,
|
|
1108
|
+
finished_at: status === 'completed' || status === 'failed' ? now : null
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
appendRunEvent(db, {
|
|
1112
|
+
runKey,
|
|
1113
|
+
eventType: 'start',
|
|
1114
|
+
phase: options.phase || 'run',
|
|
1115
|
+
status,
|
|
1116
|
+
message: String(options.message || options.title || 'Agent started'),
|
|
1117
|
+
payload: options.payload,
|
|
1118
|
+
createdAt: now
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
return runKey;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
function updateRun(db, options) {
|
|
1125
|
+
const now = nowIso();
|
|
1126
|
+
const existing = db
|
|
1127
|
+
.prepare('SELECT run_key, status, used_skills_json, source, workflow_id, workflow_stage, parent_run_key FROM agent_runs WHERE run_key = ?')
|
|
1128
|
+
.get(options.runKey);
|
|
1129
|
+
if (!existing) {
|
|
1130
|
+
throw new Error(`Run not found: ${options.runKey}`);
|
|
1131
|
+
}
|
|
1132
|
+
const taskKey = options.taskKey ? String(options.taskKey).trim() : null;
|
|
1133
|
+
|
|
1134
|
+
if (taskKey) {
|
|
1135
|
+
const taskExists = db.prepare('SELECT task_key FROM tasks WHERE task_key = ?').get(taskKey);
|
|
1136
|
+
if (!taskExists) {
|
|
1137
|
+
throw new Error(`Task not found: ${taskKey}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
const nextStatus = normalizeStatus(options.status, existing.status || 'running');
|
|
1142
|
+
const existingUsedSkills = parseJsonArray(existing.used_skills_json);
|
|
1143
|
+
const nextUsedSkills = normalizeStringArray([...(existingUsedSkills || []), ...normalizeStringArray(options.usedSkills)]);
|
|
1144
|
+
db.prepare(`
|
|
1145
|
+
UPDATE agent_runs
|
|
1146
|
+
SET
|
|
1147
|
+
status = @status,
|
|
1148
|
+
summary = COALESCE(@summary, summary),
|
|
1149
|
+
used_skills_json = COALESCE(@used_skills_json, used_skills_json),
|
|
1150
|
+
output_path = COALESCE(@output_path, output_path),
|
|
1151
|
+
task_key = COALESCE(@task_key, task_key),
|
|
1152
|
+
source = COALESCE(@source, source),
|
|
1153
|
+
workflow_id = COALESCE(@workflow_id, workflow_id),
|
|
1154
|
+
workflow_stage = COALESCE(@workflow_stage, workflow_stage),
|
|
1155
|
+
parent_run_key = COALESCE(@parent_run_key, parent_run_key),
|
|
1156
|
+
updated_at = @updated_at,
|
|
1157
|
+
finished_at = CASE
|
|
1158
|
+
WHEN @status IN ('completed', 'failed') THEN @updated_at
|
|
1159
|
+
ELSE finished_at
|
|
1160
|
+
END
|
|
1161
|
+
WHERE run_key = @run_key
|
|
1162
|
+
`).run({
|
|
1163
|
+
run_key: String(options.runKey),
|
|
1164
|
+
status: nextStatus,
|
|
1165
|
+
summary: options.summary ? String(options.summary).trim() : null,
|
|
1166
|
+
used_skills_json: nextUsedSkills.length > 0 ? JSON.stringify(nextUsedSkills) : null,
|
|
1167
|
+
output_path: options.outputPath ? String(options.outputPath).trim() : null,
|
|
1168
|
+
task_key: taskKey,
|
|
1169
|
+
source: options.source ? String(options.source).trim() : null,
|
|
1170
|
+
workflow_id: options.workflowId ? String(options.workflowId).trim() : null,
|
|
1171
|
+
workflow_stage: options.workflowStage ? String(options.workflowStage).trim() : null,
|
|
1172
|
+
parent_run_key: options.parentRunKey ? String(options.parentRunKey).trim() : null,
|
|
1173
|
+
updated_at: now
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
appendRunEvent(db, {
|
|
1177
|
+
runKey: String(options.runKey),
|
|
1178
|
+
eventType: String(options.eventType || 'update'),
|
|
1179
|
+
phase: options.phase || 'run',
|
|
1180
|
+
status: nextStatus,
|
|
1181
|
+
toolName: options.toolName,
|
|
1182
|
+
message: String(options.message || options.summary || 'Run updated'),
|
|
1183
|
+
payload: options.payload,
|
|
1184
|
+
createdAt: now
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
return nextStatus;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function getStatusSnapshot(db) {
|
|
1191
|
+
const taskSummaryRows = db.prepare(`
|
|
1192
|
+
SELECT status, COUNT(*) AS count
|
|
1193
|
+
FROM tasks
|
|
1194
|
+
GROUP BY status
|
|
1195
|
+
`).all();
|
|
1196
|
+
|
|
1197
|
+
const taskCounts = {
|
|
1198
|
+
queued: 0,
|
|
1199
|
+
running: 0,
|
|
1200
|
+
completed: 0,
|
|
1201
|
+
failed: 0
|
|
1202
|
+
};
|
|
1203
|
+
|
|
1204
|
+
for (const row of taskSummaryRows) {
|
|
1205
|
+
if (Object.prototype.hasOwnProperty.call(taskCounts, row.status)) {
|
|
1206
|
+
taskCounts[row.status] = Number(row.count || 0);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const summaryRows = db.prepare(`
|
|
1211
|
+
SELECT status, COUNT(*) AS count
|
|
1212
|
+
FROM agent_runs
|
|
1213
|
+
GROUP BY status
|
|
1214
|
+
`).all();
|
|
1215
|
+
|
|
1216
|
+
const counts = {
|
|
1217
|
+
queued: 0,
|
|
1218
|
+
running: 0,
|
|
1219
|
+
completed: 0,
|
|
1220
|
+
failed: 0
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
for (const row of summaryRows) {
|
|
1224
|
+
if (Object.prototype.hasOwnProperty.call(counts, row.status)) {
|
|
1225
|
+
counts[row.status] = Number(row.count || 0);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
const activeRuns = db.prepare(`
|
|
1230
|
+
SELECT run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source, workflow_id, workflow_stage, parent_run_key, title, status, summary, used_skills_json, output_path, started_at, updated_at
|
|
1231
|
+
FROM agent_runs
|
|
1232
|
+
WHERE status IN ('queued', 'running')
|
|
1233
|
+
ORDER BY updated_at DESC, started_at DESC
|
|
1234
|
+
`).all();
|
|
1235
|
+
|
|
1236
|
+
const recentRuns = db.prepare(`
|
|
1237
|
+
SELECT run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source, workflow_id, workflow_stage, parent_run_key, title, status, summary, used_skills_json, output_path, started_at, updated_at, finished_at
|
|
1238
|
+
FROM agent_runs
|
|
1239
|
+
ORDER BY updated_at DESC, started_at DESC
|
|
1240
|
+
LIMIT 20
|
|
1241
|
+
`).all();
|
|
1242
|
+
|
|
1243
|
+
const activeTasks = db.prepare(`
|
|
1244
|
+
SELECT
|
|
1245
|
+
task_key, squad_slug, session_key, title, goal, status, created_by, created_at, updated_at,
|
|
1246
|
+
(
|
|
1247
|
+
SELECT COUNT(*)
|
|
1248
|
+
FROM agent_runs
|
|
1249
|
+
WHERE agent_runs.task_key = tasks.task_key
|
|
1250
|
+
) AS agent_count,
|
|
1251
|
+
(
|
|
1252
|
+
SELECT COUNT(*)
|
|
1253
|
+
FROM artifacts
|
|
1254
|
+
WHERE artifacts.task_key = tasks.task_key
|
|
1255
|
+
) AS artifact_count
|
|
1256
|
+
FROM tasks
|
|
1257
|
+
WHERE status IN ('queued', 'running')
|
|
1258
|
+
ORDER BY updated_at DESC, created_at DESC
|
|
1259
|
+
`).all();
|
|
1260
|
+
|
|
1261
|
+
const recentTasks = db.prepare(`
|
|
1262
|
+
SELECT
|
|
1263
|
+
task_key, squad_slug, session_key, title, goal, status, created_by, created_at, updated_at, finished_at,
|
|
1264
|
+
(
|
|
1265
|
+
SELECT COUNT(*)
|
|
1266
|
+
FROM agent_runs
|
|
1267
|
+
WHERE agent_runs.task_key = tasks.task_key
|
|
1268
|
+
) AS agent_count,
|
|
1269
|
+
(
|
|
1270
|
+
SELECT COUNT(*)
|
|
1271
|
+
FROM artifacts
|
|
1272
|
+
WHERE artifacts.task_key = tasks.task_key
|
|
1273
|
+
) AS artifact_count
|
|
1274
|
+
FROM tasks
|
|
1275
|
+
ORDER BY updated_at DESC, created_at DESC
|
|
1276
|
+
LIMIT 20
|
|
1277
|
+
`).all();
|
|
1278
|
+
|
|
1279
|
+
const recentArtifacts = db.prepare(`
|
|
1280
|
+
SELECT id, task_key, run_key, squad_slug, agent_name, kind, title, file_path, created_at
|
|
1281
|
+
FROM artifacts
|
|
1282
|
+
ORDER BY created_at DESC
|
|
1283
|
+
LIMIT 20
|
|
1284
|
+
`).all();
|
|
1285
|
+
|
|
1286
|
+
const recentContentItems = db.prepare(`
|
|
1287
|
+
SELECT
|
|
1288
|
+
content_key, task_key, run_key, squad_slug, session_key, title, content_type, layout_type,
|
|
1289
|
+
status, summary, blueprint_slug, used_skills_json, json_path, html_path, created_by_agent, created_at, updated_at
|
|
1290
|
+
FROM content_items
|
|
1291
|
+
ORDER BY updated_at DESC, created_at DESC
|
|
1292
|
+
LIMIT 20
|
|
1293
|
+
`).all();
|
|
1294
|
+
|
|
1295
|
+
const recentExecutionEvents = db.prepare(`
|
|
1296
|
+
SELECT
|
|
1297
|
+
id, task_key, run_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
1298
|
+
workflow_id, workflow_stage, parent_run_key, event_type, phase, status,
|
|
1299
|
+
tool_name, message, payload_json, sequence_no, parent_event_id, created_at
|
|
1300
|
+
FROM execution_events
|
|
1301
|
+
ORDER BY created_at DESC, id DESC
|
|
1302
|
+
LIMIT 40
|
|
1303
|
+
`).all();
|
|
1304
|
+
|
|
1305
|
+
for (const row of activeRuns) {
|
|
1306
|
+
row.used_skills = parseJsonArray(row.used_skills_json);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
for (const row of recentRuns) {
|
|
1310
|
+
row.used_skills = parseJsonArray(row.used_skills_json);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
for (const row of recentContentItems) {
|
|
1314
|
+
row.used_skills = parseJsonArray(row.used_skills_json);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
return {
|
|
1318
|
+
taskCounts,
|
|
1319
|
+
counts,
|
|
1320
|
+
activeTasks,
|
|
1321
|
+
recentTasks,
|
|
1322
|
+
activeRuns,
|
|
1323
|
+
recentRuns,
|
|
1324
|
+
recentArtifacts,
|
|
1325
|
+
recentContentItems,
|
|
1326
|
+
recentExecutionEvents
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// ─── Agent Log Session helpers ───────────────────────────────────────────────
|
|
1331
|
+
|
|
1332
|
+
function resolveSessionsDir(runtimeDir) {
|
|
1333
|
+
return path.join(runtimeDir, SESSIONS_DIR);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function resolveSessionFile(runtimeDir, agentName) {
|
|
1337
|
+
return path.join(resolveSessionsDir(runtimeDir), `${agentName}.json`);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
async function readAgentSession(runtimeDir, agentName) {
|
|
1341
|
+
const filePath = resolveSessionFile(runtimeDir, agentName);
|
|
1342
|
+
try {
|
|
1343
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
1344
|
+
return JSON.parse(raw);
|
|
1345
|
+
} catch {
|
|
1346
|
+
return null;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
async function writeAgentSession(runtimeDir, agentName, data) {
|
|
1351
|
+
const sessionsDir = resolveSessionsDir(runtimeDir);
|
|
1352
|
+
await ensureDir(sessionsDir);
|
|
1353
|
+
const filePath = resolveSessionFile(runtimeDir, agentName);
|
|
1354
|
+
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
async function clearAgentSession(runtimeDir, agentName) {
|
|
1358
|
+
const filePath = resolveSessionFile(runtimeDir, agentName);
|
|
1359
|
+
try { await fs.unlink(filePath); } catch { /* noop */ }
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* Core function for `aioson runtime-log`.
|
|
1364
|
+
*
|
|
1365
|
+
* Squad agents (--squad): state is stored in SQLite, not in session files.
|
|
1366
|
+
* This avoids race conditions when the orquestrador calls runtime-log in
|
|
1367
|
+
* parallel bash commands — SQLite serializes concurrent writes automatically.
|
|
1368
|
+
* Logic: find the most-recent running task for the squad → find or create a
|
|
1369
|
+
* run for this agent → add event. If no running task exists, create one.
|
|
1370
|
+
*
|
|
1371
|
+
* Official agents (no --squad): state is stored in .sessions/{agent}.json.
|
|
1372
|
+
* Single-process by design, no race condition.
|
|
1373
|
+
*/
|
|
1374
|
+
async function logAgentEvent(db, runtimeDir, options) {
|
|
1375
|
+
const agentName = String(options.agentName || 'unknown').trim();
|
|
1376
|
+
const squadSlug = options.squadSlug ? String(options.squadSlug).trim() : null;
|
|
1377
|
+
const isFinish = Boolean(options.finish);
|
|
1378
|
+
const now = nowIso();
|
|
1379
|
+
|
|
1380
|
+
let runKey = null;
|
|
1381
|
+
let taskKey = null;
|
|
1382
|
+
|
|
1383
|
+
if (squadSlug) {
|
|
1384
|
+
// ── Squad agent: look up active task from SQLite ──────────────────────────
|
|
1385
|
+
const activeTask = db.prepare(
|
|
1386
|
+
`SELECT task_key FROM tasks WHERE squad_slug = ? AND status IN ('running', 'queued') ORDER BY created_at DESC LIMIT 1`
|
|
1387
|
+
).get(squadSlug);
|
|
1388
|
+
|
|
1389
|
+
if (activeTask) {
|
|
1390
|
+
taskKey = activeTask.task_key;
|
|
1391
|
+
} else {
|
|
1392
|
+
// No active task — create one (only the first concurrent call wins; the
|
|
1393
|
+
// second will find the task on its next read because SQLite is serialized)
|
|
1394
|
+
const taskTitle = options.taskTitle || `${squadSlug} — sessão`;
|
|
1395
|
+
taskKey = startTask(db, {
|
|
1396
|
+
title: taskTitle,
|
|
1397
|
+
squadSlug,
|
|
1398
|
+
status: 'running',
|
|
1399
|
+
createdBy: agentName
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// Find existing running run for this specific agent under the task
|
|
1404
|
+
const activeRun = db.prepare(
|
|
1405
|
+
`SELECT run_key FROM agent_runs WHERE task_key = ? AND agent_name = ? AND status = 'running' LIMIT 1`
|
|
1406
|
+
).get(taskKey, agentName);
|
|
1407
|
+
|
|
1408
|
+
if (activeRun) {
|
|
1409
|
+
runKey = activeRun.run_key;
|
|
1410
|
+
if (!isFinish) {
|
|
1411
|
+
appendRunEvent(db, {
|
|
1412
|
+
runKey,
|
|
1413
|
+
eventType: options.type || 'status',
|
|
1414
|
+
phase: options.type || 'run',
|
|
1415
|
+
status: 'running',
|
|
1416
|
+
message: String(options.message || ''),
|
|
1417
|
+
payload: options.meta,
|
|
1418
|
+
createdAt: now
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
} else {
|
|
1422
|
+
// First call for this agent — create a run (and emit start event via startRun)
|
|
1423
|
+
// Use taskTitle from --title only if provided, otherwise use agent name
|
|
1424
|
+
const runTitle = options.taskTitle || `@${agentName}`;
|
|
1425
|
+
runKey = startRun(db, {
|
|
1426
|
+
taskKey,
|
|
1427
|
+
agentName,
|
|
1428
|
+
agentKind: 'squad',
|
|
1429
|
+
squadSlug,
|
|
1430
|
+
title: runTitle,
|
|
1431
|
+
message: options.message || 'Iniciando'
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
} else {
|
|
1435
|
+
// ── Official agent: session-file based ───────────────────────────────────
|
|
1436
|
+
const session = await readAgentSession(runtimeDir, agentName);
|
|
1437
|
+
runKey = session && !session.finished ? session.runKey : null;
|
|
1438
|
+
taskKey = session && !session.finished ? session.taskKey : null;
|
|
1439
|
+
|
|
1440
|
+
if (!runKey) {
|
|
1441
|
+
const taskTitle = options.taskTitle || `@${agentName}`;
|
|
1442
|
+
taskKey = startTask(db, {
|
|
1443
|
+
title: taskTitle,
|
|
1444
|
+
squadSlug: null,
|
|
1445
|
+
status: 'running',
|
|
1446
|
+
createdBy: agentName
|
|
1447
|
+
});
|
|
1448
|
+
runKey = startRun(db, {
|
|
1449
|
+
taskKey,
|
|
1450
|
+
agentName,
|
|
1451
|
+
agentKind: 'official',
|
|
1452
|
+
squadSlug: null,
|
|
1453
|
+
title: taskTitle,
|
|
1454
|
+
message: options.message || 'Iniciando'
|
|
1455
|
+
});
|
|
1456
|
+
await writeAgentSession(runtimeDir, agentName, { runKey, taskKey, startedAt: now, finished: false });
|
|
1457
|
+
} else {
|
|
1458
|
+
appendRunEvent(db, {
|
|
1459
|
+
runKey,
|
|
1460
|
+
eventType: options.type || 'status',
|
|
1461
|
+
phase: options.type || 'run',
|
|
1462
|
+
status: 'running',
|
|
1463
|
+
message: String(options.message || ''),
|
|
1464
|
+
payload: options.meta,
|
|
1465
|
+
createdAt: now
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
if (isFinish) {
|
|
1471
|
+
const finalStatus = normalizeStatus(options.status, 'completed');
|
|
1472
|
+
updateRun(db, {
|
|
1473
|
+
runKey,
|
|
1474
|
+
status: finalStatus,
|
|
1475
|
+
summary: options.summary,
|
|
1476
|
+
eventType: finalStatus === 'completed' ? 'finished' : 'failed',
|
|
1477
|
+
message: options.message || (finalStatus === 'completed' ? 'Concluído' : 'Falhou')
|
|
1478
|
+
});
|
|
1479
|
+
// For squad: only finish the task when orquestrador calls --finish
|
|
1480
|
+
const isOrquestrador = agentName === 'orquestrador';
|
|
1481
|
+
if (taskKey && (!squadSlug || isOrquestrador)) {
|
|
1482
|
+
updateTask(db, { taskKey, status: finalStatus });
|
|
1483
|
+
if (!squadSlug) await clearAgentSession(runtimeDir, agentName);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
return { runKey, taskKey };
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
module.exports = {
|
|
1491
|
+
resolveRuntimePaths,
|
|
1492
|
+
runtimeStoreExists,
|
|
1493
|
+
openRuntimeDb,
|
|
1494
|
+
upsertSquadManifest,
|
|
1495
|
+
insertSquadAnalysis,
|
|
1496
|
+
startTask,
|
|
1497
|
+
updateTask,
|
|
1498
|
+
startRun,
|
|
1499
|
+
updateRun,
|
|
1500
|
+
attachArtifact,
|
|
1501
|
+
upsertContentItem,
|
|
1502
|
+
getStatusSnapshot,
|
|
1503
|
+
createRunKey,
|
|
1504
|
+
createTaskKey,
|
|
1505
|
+
appendRunEvent,
|
|
1506
|
+
logAgentEvent,
|
|
1507
|
+
readAgentSession,
|
|
1508
|
+
clearAgentSession,
|
|
1509
|
+
// Pipeline CRUD
|
|
1510
|
+
upsertPipeline,
|
|
1511
|
+
addPipelineNode,
|
|
1512
|
+
updateNodePosition,
|
|
1513
|
+
addPipelineEdge,
|
|
1514
|
+
removePipelineEdge,
|
|
1515
|
+
getPipelineDAG,
|
|
1516
|
+
listPipelines,
|
|
1517
|
+
upsertSquadPorts,
|
|
1518
|
+
getTopologicalOrder,
|
|
1519
|
+
// Artisan CRUD
|
|
1520
|
+
createArtisanSquad,
|
|
1521
|
+
updateArtisanSquad,
|
|
1522
|
+
getArtisanSquad,
|
|
1523
|
+
listArtisanSquads,
|
|
1524
|
+
deleteArtisanSquad,
|
|
1525
|
+
addArtisanMessage,
|
|
1526
|
+
getArtisanMessages
|
|
1527
|
+
};
|