@jaimevalasek/aioson 1.4.0 → 1.5.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/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +3 -1
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/agentes.md +124 -10
- package/docs/pt/cenarios.md +46 -2
- package/docs/pt/comandos-cli.md +60 -1
- package/docs/pt/inicio-rapido.md +18 -2
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +9 -9
- package/package.json +2 -2
- package/src/backup-local.js +74 -0
- package/src/cli.js +98 -0
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/runtime.js +242 -0
- package/src/commands/setup-context.js +7 -2
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +52 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +37 -1
- package/src/commands/squad-validate.js +62 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/constants.js +8 -0
- package/src/context-writer.js +45 -1
- package/src/i18n/messages/en.js +127 -1
- package/src/i18n/messages/es.js +117 -0
- package/src/i18n/messages/fr.js +117 -0
- package/src/i18n/messages/pt-BR.js +126 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +259 -2
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +4 -0
- package/template/.aioson/agents/architect.md +4 -0
- package/template/.aioson/agents/dev.md +120 -11
- package/template/.aioson/agents/deyvin.md +8 -0
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +17 -0
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/product.md +60 -12
- package/template/.aioson/agents/qa.md +1 -0
- package/template/.aioson/agents/setup.md +63 -19
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +191 -0
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +12 -0
- package/template/.aioson/config.md +6 -0
- package/template/.aioson/locales/en/agents/analyst.md +8 -0
- package/template/.aioson/locales/en/agents/architect.md +8 -0
- package/template/.aioson/locales/en/agents/dev.md +66 -7
- package/template/.aioson/locales/en/agents/deyvin.md +8 -0
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/en/agents/qa.md +49 -0
- package/template/.aioson/locales/en/agents/setup.md +2 -1
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/es/agents/analyst.md +8 -0
- package/template/.aioson/locales/es/agents/architect.md +8 -0
- package/template/.aioson/locales/es/agents/dev.md +66 -7
- package/template/.aioson/locales/es/agents/deyvin.md +8 -0
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/qa.md +26 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -1
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +63 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/fr/agents/analyst.md +8 -0
- package/template/.aioson/locales/fr/agents/architect.md +8 -0
- package/template/.aioson/locales/fr/agents/dev.md +66 -7
- package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/qa.md +26 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +63 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
- package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +19 -0
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +1 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +3 -0
- package/template/CLAUDE.md +5 -2
- package/template/OPENCODE.md +2 -0
package/src/runtime-store.js
CHANGED
|
@@ -496,6 +496,100 @@ async function openRuntimeDb(targetDir, options = {}) {
|
|
|
496
496
|
CREATE INDEX IF NOT EXISTS idx_squad_learnings_status ON squad_learnings(status);
|
|
497
497
|
CREATE INDEX IF NOT EXISTS idx_project_learnings_type ON project_learnings(type);
|
|
498
498
|
CREATE INDEX IF NOT EXISTS idx_project_learnings_status ON project_learnings(status);
|
|
499
|
+
|
|
500
|
+
CREATE TABLE IF NOT EXISTS squad_metrics (
|
|
501
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
502
|
+
squad_slug TEXT NOT NULL,
|
|
503
|
+
metric_key TEXT NOT NULL,
|
|
504
|
+
metric_value REAL NOT NULL,
|
|
505
|
+
metric_unit TEXT,
|
|
506
|
+
period TEXT,
|
|
507
|
+
baseline REAL,
|
|
508
|
+
target REAL,
|
|
509
|
+
source TEXT DEFAULT 'manual',
|
|
510
|
+
notes TEXT,
|
|
511
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
512
|
+
UNIQUE(squad_slug, metric_key, period)
|
|
513
|
+
);
|
|
514
|
+
CREATE INDEX IF NOT EXISTS idx_squad_metrics_squad ON squad_metrics(squad_slug, period DESC);
|
|
515
|
+
|
|
516
|
+
CREATE TABLE IF NOT EXISTS worker_runs (
|
|
517
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
518
|
+
squad_slug TEXT NOT NULL,
|
|
519
|
+
worker_slug TEXT NOT NULL,
|
|
520
|
+
trigger_type TEXT NOT NULL DEFAULT 'manual',
|
|
521
|
+
input_json TEXT,
|
|
522
|
+
output_json TEXT,
|
|
523
|
+
status TEXT DEFAULT 'running',
|
|
524
|
+
error_message TEXT,
|
|
525
|
+
duration_ms INTEGER,
|
|
526
|
+
attempt INTEGER DEFAULT 1,
|
|
527
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
528
|
+
completed_at TEXT
|
|
529
|
+
);
|
|
530
|
+
CREATE INDEX IF NOT EXISTS idx_worker_runs_squad ON worker_runs(squad_slug, created_at DESC);
|
|
531
|
+
|
|
532
|
+
CREATE TABLE IF NOT EXISTS squad_daemons (
|
|
533
|
+
squad_slug TEXT PRIMARY KEY,
|
|
534
|
+
status TEXT DEFAULT 'stopped',
|
|
535
|
+
pid INTEGER,
|
|
536
|
+
port INTEGER,
|
|
537
|
+
started_at TEXT,
|
|
538
|
+
last_heartbeat TEXT,
|
|
539
|
+
config_json TEXT,
|
|
540
|
+
error_message TEXT
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
CREATE TABLE IF NOT EXISTS mcp_status (
|
|
544
|
+
squad_slug TEXT NOT NULL,
|
|
545
|
+
mcp_slug TEXT NOT NULL,
|
|
546
|
+
connector TEXT NOT NULL,
|
|
547
|
+
status TEXT DEFAULT 'unconfigured',
|
|
548
|
+
last_check TEXT,
|
|
549
|
+
last_error TEXT,
|
|
550
|
+
calls_total INTEGER DEFAULT 0,
|
|
551
|
+
calls_failed INTEGER DEFAULT 0,
|
|
552
|
+
PRIMARY KEY (squad_slug, mcp_slug)
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
CREATE TABLE IF NOT EXISTS workflow_reviews (
|
|
556
|
+
review_id TEXT PRIMARY KEY,
|
|
557
|
+
squad_slug TEXT NOT NULL,
|
|
558
|
+
workflow_slug TEXT NOT NULL,
|
|
559
|
+
phase_id TEXT NOT NULL,
|
|
560
|
+
attempt_number INTEGER DEFAULT 1,
|
|
561
|
+
reviewer_slug TEXT NOT NULL,
|
|
562
|
+
verdict TEXT NOT NULL,
|
|
563
|
+
score REAL,
|
|
564
|
+
feedback TEXT,
|
|
565
|
+
veto_triggered TEXT,
|
|
566
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
567
|
+
);
|
|
568
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_reviews_squad ON workflow_reviews(squad_slug, workflow_slug);
|
|
569
|
+
|
|
570
|
+
CREATE TABLE IF NOT EXISTS squad_scores (
|
|
571
|
+
squad_slug TEXT NOT NULL,
|
|
572
|
+
dimension TEXT NOT NULL,
|
|
573
|
+
score INTEGER NOT NULL,
|
|
574
|
+
max_score INTEGER NOT NULL,
|
|
575
|
+
details_json TEXT,
|
|
576
|
+
scored_at TEXT DEFAULT (datetime('now')),
|
|
577
|
+
PRIMARY KEY (squad_slug, dimension, scored_at)
|
|
578
|
+
);
|
|
579
|
+
CREATE INDEX IF NOT EXISTS idx_squad_scores_squad ON squad_scores(squad_slug);
|
|
580
|
+
|
|
581
|
+
CREATE TABLE IF NOT EXISTS squad_roi_config (
|
|
582
|
+
squad_slug TEXT PRIMARY KEY,
|
|
583
|
+
pricing_model TEXT DEFAULT 'fixed',
|
|
584
|
+
setup_fee REAL,
|
|
585
|
+
monthly_fee REAL,
|
|
586
|
+
percentage_fee REAL,
|
|
587
|
+
percentage_base TEXT,
|
|
588
|
+
currency TEXT DEFAULT 'BRL',
|
|
589
|
+
contract_months INTEGER DEFAULT 12,
|
|
590
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
591
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
592
|
+
);
|
|
499
593
|
`);
|
|
500
594
|
|
|
501
595
|
ensureLegacyColumns(db);
|
|
@@ -597,6 +691,8 @@ function ensureLegacyColumns(db) {
|
|
|
597
691
|
if (!contentItemColumnNames.has('used_skills_json')) {
|
|
598
692
|
db.exec('ALTER TABLE content_items ADD COLUMN used_skills_json TEXT');
|
|
599
693
|
}
|
|
694
|
+
|
|
695
|
+
try { db.exec('ALTER TABLE worker_runs ADD COLUMN conversation_id TEXT'); } catch { /* já existe */ }
|
|
600
696
|
}
|
|
601
697
|
|
|
602
698
|
function insertEvent(db, record) {
|
|
@@ -2019,7 +2115,7 @@ function getSquadPlanRounds(db, planSlug) {
|
|
|
2019
2115
|
// --- Squad Learnings CRUD ---
|
|
2020
2116
|
|
|
2021
2117
|
function insertSquadLearning(db, options = {}) {
|
|
2022
|
-
const learningId = options.learningId || `sl-${slugify(options.squadSlug || 'squad')}-${Date.now()}`;
|
|
2118
|
+
const learningId = options.learningId || `sl-${slugify(options.squadSlug || 'squad')}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
2023
2119
|
const now = nowIso();
|
|
2024
2120
|
db.prepare(`
|
|
2025
2121
|
INSERT OR REPLACE INTO squad_learnings
|
|
@@ -2185,6 +2281,150 @@ function getProjectLearningStats(db) {
|
|
|
2185
2281
|
`).all();
|
|
2186
2282
|
}
|
|
2187
2283
|
|
|
2284
|
+
// --- Squad Metrics CRUD ---
|
|
2285
|
+
|
|
2286
|
+
function upsertSquadMetric(db, { squadSlug, metricKey, value, unit, period, baseline, target, source, notes }) {
|
|
2287
|
+
db.prepare(`
|
|
2288
|
+
INSERT INTO squad_metrics (squad_slug, metric_key, metric_value, metric_unit, period, baseline, target, source, notes)
|
|
2289
|
+
VALUES (@squad_slug, @metric_key, @metric_value, @metric_unit, @period, @baseline, @target, @source, @notes)
|
|
2290
|
+
ON CONFLICT(squad_slug, metric_key, period) DO UPDATE SET
|
|
2291
|
+
metric_value = excluded.metric_value,
|
|
2292
|
+
metric_unit = excluded.metric_unit,
|
|
2293
|
+
baseline = COALESCE(excluded.baseline, squad_metrics.baseline),
|
|
2294
|
+
target = COALESCE(excluded.target, squad_metrics.target),
|
|
2295
|
+
source = excluded.source,
|
|
2296
|
+
notes = COALESCE(excluded.notes, squad_metrics.notes)
|
|
2297
|
+
`).run({
|
|
2298
|
+
squad_slug: String(squadSlug).trim(),
|
|
2299
|
+
metric_key: String(metricKey).trim(),
|
|
2300
|
+
metric_value: Number(value),
|
|
2301
|
+
metric_unit: unit ? String(unit).trim() : null,
|
|
2302
|
+
period: period ? String(period).trim() : null,
|
|
2303
|
+
baseline: baseline != null ? Number(baseline) : null,
|
|
2304
|
+
target: target != null ? Number(target) : null,
|
|
2305
|
+
source: source ? String(source).trim() : 'manual',
|
|
2306
|
+
notes: notes ? String(notes).trim() : null
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
function listSquadMetrics(db, squadSlug, period) {
|
|
2311
|
+
if (period) {
|
|
2312
|
+
return db.prepare(
|
|
2313
|
+
'SELECT * FROM squad_metrics WHERE squad_slug = ? AND period = ? ORDER BY metric_key ASC'
|
|
2314
|
+
).all(squadSlug, period);
|
|
2315
|
+
}
|
|
2316
|
+
return db.prepare(
|
|
2317
|
+
'SELECT * FROM squad_metrics WHERE squad_slug = ? ORDER BY period DESC, metric_key ASC'
|
|
2318
|
+
).all(squadSlug);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
function deleteSquadMetric(db, squadSlug, metricKey, period) {
|
|
2322
|
+
db.prepare(
|
|
2323
|
+
'DELETE FROM squad_metrics WHERE squad_slug = ? AND metric_key = ? AND period = ?'
|
|
2324
|
+
).run(squadSlug, metricKey, period);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// --- Worker Runs CRUD ---
|
|
2328
|
+
|
|
2329
|
+
function insertWorkerRun(db, { squadSlug, workerSlug, triggerType, inputJson, outputJson, status, errorMessage, durationMs, attempt, conversationId }) {
|
|
2330
|
+
const now = nowIso();
|
|
2331
|
+
return db.prepare(`
|
|
2332
|
+
INSERT INTO worker_runs (squad_slug, worker_slug, trigger_type, input_json, output_json, status, error_message, duration_ms, attempt, conversation_id, created_at, completed_at)
|
|
2333
|
+
VALUES (@squad_slug, @worker_slug, @trigger_type, @input_json, @output_json, @status, @error_message, @duration_ms, @attempt, @conversation_id, @created_at, @completed_at)
|
|
2334
|
+
`).run({
|
|
2335
|
+
squad_slug: String(squadSlug).trim(),
|
|
2336
|
+
worker_slug: String(workerSlug).trim(),
|
|
2337
|
+
trigger_type: String(triggerType || 'manual').trim(),
|
|
2338
|
+
input_json: inputJson || null,
|
|
2339
|
+
output_json: outputJson || null,
|
|
2340
|
+
status: String(status || 'running').trim(),
|
|
2341
|
+
error_message: errorMessage || null,
|
|
2342
|
+
duration_ms: durationMs != null ? Number(durationMs) : null,
|
|
2343
|
+
attempt: Number(attempt || 1),
|
|
2344
|
+
conversation_id: conversationId || null,
|
|
2345
|
+
created_at: now,
|
|
2346
|
+
completed_at: (status === 'completed' || status === 'failed') ? now : null
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
function listWorkerRuns(db, squadSlug, limit = 50) {
|
|
2351
|
+
return db.prepare(
|
|
2352
|
+
'SELECT * FROM worker_runs WHERE squad_slug = ? ORDER BY created_at DESC, id DESC LIMIT ?'
|
|
2353
|
+
).all(squadSlug, limit);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
function getWorkerRunStats(db, squadSlug) {
|
|
2357
|
+
return db.prepare(`
|
|
2358
|
+
SELECT worker_slug, status, COUNT(*) as count,
|
|
2359
|
+
AVG(duration_ms) as avg_duration_ms
|
|
2360
|
+
FROM worker_runs WHERE squad_slug = ?
|
|
2361
|
+
GROUP BY worker_slug, status
|
|
2362
|
+
ORDER BY worker_slug, status
|
|
2363
|
+
`).all(squadSlug);
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
// --- MCP Status CRUD ---
|
|
2367
|
+
|
|
2368
|
+
function upsertMcpStatus(db, { squadSlug, mcpSlug, connector, status, lastError }) {
|
|
2369
|
+
db.prepare(`
|
|
2370
|
+
INSERT INTO mcp_status (squad_slug, mcp_slug, connector, status, last_check, last_error)
|
|
2371
|
+
VALUES (?, ?, ?, ?, datetime('now'), ?)
|
|
2372
|
+
ON CONFLICT(squad_slug, mcp_slug) DO UPDATE SET
|
|
2373
|
+
connector = excluded.connector,
|
|
2374
|
+
status = excluded.status,
|
|
2375
|
+
last_check = excluded.last_check,
|
|
2376
|
+
last_error = excluded.last_error
|
|
2377
|
+
`).run(squadSlug, mcpSlug, connector, status || 'unconfigured', lastError || null);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function incrementMcpCalls(db, squadSlug, mcpSlug, failed) {
|
|
2381
|
+
if (failed) {
|
|
2382
|
+
db.prepare(`
|
|
2383
|
+
UPDATE mcp_status SET calls_total = calls_total + 1, calls_failed = calls_failed + 1
|
|
2384
|
+
WHERE squad_slug = ? AND mcp_slug = ?
|
|
2385
|
+
`).run(squadSlug, mcpSlug);
|
|
2386
|
+
} else {
|
|
2387
|
+
db.prepare(`
|
|
2388
|
+
UPDATE mcp_status SET calls_total = calls_total + 1
|
|
2389
|
+
WHERE squad_slug = ? AND mcp_slug = ?
|
|
2390
|
+
`).run(squadSlug, mcpSlug);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
function listMcpStatus(db, squadSlug) {
|
|
2395
|
+
return db.prepare('SELECT * FROM mcp_status WHERE squad_slug = ? ORDER BY mcp_slug').all(squadSlug);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
function getMcpStatus(db, squadSlug, mcpSlug) {
|
|
2399
|
+
return db.prepare('SELECT * FROM mcp_status WHERE squad_slug = ? AND mcp_slug = ?').get(squadSlug, mcpSlug);
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// --- ROI Config CRUD ---
|
|
2403
|
+
|
|
2404
|
+
function upsertROIConfig(db, { squadSlug, pricingModel, setupFee, monthlyFee, percentageFee, percentageBase, currency, contractMonths }) {
|
|
2405
|
+
db.prepare(`
|
|
2406
|
+
INSERT INTO squad_roi_config (squad_slug, pricing_model, setup_fee, monthly_fee, percentage_fee, percentage_base, currency, contract_months)
|
|
2407
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
2408
|
+
ON CONFLICT(squad_slug) DO UPDATE SET
|
|
2409
|
+
pricing_model = excluded.pricing_model,
|
|
2410
|
+
setup_fee = excluded.setup_fee,
|
|
2411
|
+
monthly_fee = excluded.monthly_fee,
|
|
2412
|
+
percentage_fee = excluded.percentage_fee,
|
|
2413
|
+
percentage_base = excluded.percentage_base,
|
|
2414
|
+
currency = excluded.currency,
|
|
2415
|
+
contract_months = excluded.contract_months,
|
|
2416
|
+
updated_at = datetime('now')
|
|
2417
|
+
`).run(squadSlug, pricingModel || 'fixed', setupFee || null, monthlyFee || null, percentageFee || null, percentageBase || null, currency || 'BRL', contractMonths || 12);
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
function getROIConfig(db, squadSlug) {
|
|
2421
|
+
return db.prepare('SELECT * FROM squad_roi_config WHERE squad_slug = ?').get(squadSlug);
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
function deleteROIConfig(db, squadSlug) {
|
|
2425
|
+
return db.prepare('DELETE FROM squad_roi_config WHERE squad_slug = ?').run(squadSlug);
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2188
2428
|
module.exports = {
|
|
2189
2429
|
resolveRuntimePaths,
|
|
2190
2430
|
runtimeStoreExists,
|
|
@@ -2261,5 +2501,22 @@ module.exports = {
|
|
|
2261
2501
|
updateProjectLearningStatus,
|
|
2262
2502
|
reinforceProjectLearning,
|
|
2263
2503
|
promoteProjectLearning,
|
|
2264
|
-
getProjectLearningStats
|
|
2504
|
+
getProjectLearningStats,
|
|
2505
|
+
// Squad Metrics CRUD
|
|
2506
|
+
upsertSquadMetric,
|
|
2507
|
+
listSquadMetrics,
|
|
2508
|
+
deleteSquadMetric,
|
|
2509
|
+
// Worker Runs CRUD
|
|
2510
|
+
insertWorkerRun,
|
|
2511
|
+
listWorkerRuns,
|
|
2512
|
+
getWorkerRunStats,
|
|
2513
|
+
// MCP Status CRUD
|
|
2514
|
+
upsertMcpStatus,
|
|
2515
|
+
incrementMcpCalls,
|
|
2516
|
+
listMcpStatus,
|
|
2517
|
+
getMcpStatus,
|
|
2518
|
+
// ROI Config CRUD
|
|
2519
|
+
upsertROIConfig,
|
|
2520
|
+
getROIConfig,
|
|
2521
|
+
deleteROIConfig
|
|
2265
2522
|
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SESSIONS_DIR = path.join('.aioson', 'sessions');
|
|
7
|
+
const DEFAULT_TTL_HOURS = 24;
|
|
8
|
+
|
|
9
|
+
function sanitizeSessionId(sessionId) {
|
|
10
|
+
// Replace anything that isn't alphanumeric, hyphen, or underscore with a dash
|
|
11
|
+
return String(sessionId)
|
|
12
|
+
.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
13
|
+
.replace(/-{2,}/g, '-')
|
|
14
|
+
.replace(/^-|-$/g, '')
|
|
15
|
+
.slice(0, 128);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sessionPath(projectDir, sessionId) {
|
|
19
|
+
return path.join(projectDir, SESSIONS_DIR, `${sanitizeSessionId(sessionId)}.json`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function ensureSessionsDir(projectDir) {
|
|
23
|
+
const dir = path.join(projectDir, SESSIONS_DIR);
|
|
24
|
+
await fs.mkdir(dir, { recursive: true });
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load a session by ID. Returns null if not found or expired.
|
|
30
|
+
* @param {string} projectDir
|
|
31
|
+
* @param {string} sessionId
|
|
32
|
+
* @param {number} [ttlHours]
|
|
33
|
+
* @returns {Promise<object|null>}
|
|
34
|
+
*/
|
|
35
|
+
async function loadSession(projectDir, sessionId, ttlHours = DEFAULT_TTL_HOURS) {
|
|
36
|
+
const filePath = sessionPath(projectDir, sessionId);
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = await fs.readFile(filePath, 'utf8');
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let session;
|
|
45
|
+
try {
|
|
46
|
+
session = JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check TTL
|
|
52
|
+
if (ttlHours > 0 && session.last_active) {
|
|
53
|
+
const idleMs = Date.now() - new Date(session.last_active).getTime();
|
|
54
|
+
if (idleMs > ttlHours * 3_600_000) {
|
|
55
|
+
// Expired — delete and return null
|
|
56
|
+
await fs.unlink(filePath).catch(() => {});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return session;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Save (overwrite) a session file atomically.
|
|
66
|
+
* @param {string} projectDir
|
|
67
|
+
* @param {object} session Must have session.session_id
|
|
68
|
+
*/
|
|
69
|
+
async function saveSession(projectDir, session) {
|
|
70
|
+
await ensureSessionsDir(projectDir);
|
|
71
|
+
const filePath = sessionPath(projectDir, session.session_id);
|
|
72
|
+
const tmpPath = filePath + '.tmp';
|
|
73
|
+
await fs.writeFile(tmpPath, JSON.stringify(session, null, 2), 'utf8');
|
|
74
|
+
await fs.rename(tmpPath, filePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Append a turn to a session. Creates the session file if it doesn't exist.
|
|
79
|
+
* @param {string} projectDir
|
|
80
|
+
* @param {string} sessionId
|
|
81
|
+
* @param {'user'|'assistant'} role
|
|
82
|
+
* @param {string} content
|
|
83
|
+
* @param {object} [metadata] Merged into session.metadata on first creation
|
|
84
|
+
* @param {number} [ttlHours]
|
|
85
|
+
* @returns {Promise<object>} Updated session
|
|
86
|
+
*/
|
|
87
|
+
async function appendTurn(projectDir, sessionId, role, content, metadata = {}, ttlHours = DEFAULT_TTL_HOURS) {
|
|
88
|
+
let session = await loadSession(projectDir, sessionId, ttlHours);
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
|
|
91
|
+
if (!session) {
|
|
92
|
+
session = {
|
|
93
|
+
session_id: sessionId,
|
|
94
|
+
channel: metadata.channel || 'webhook',
|
|
95
|
+
created_at: now,
|
|
96
|
+
last_active: now,
|
|
97
|
+
turns: [],
|
|
98
|
+
metadata: { ...metadata }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
session.last_active = now;
|
|
103
|
+
session.turns.push({ role, content, ts: now });
|
|
104
|
+
|
|
105
|
+
// Merge any new metadata fields without overwriting existing ones
|
|
106
|
+
if (metadata && typeof metadata === 'object') {
|
|
107
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
108
|
+
if (session.metadata[k] === undefined) session.metadata[k] = v;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await saveSession(projectDir, session);
|
|
113
|
+
return session;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a context string from session history to inject into the squad input.
|
|
118
|
+
* @param {object} session
|
|
119
|
+
* @param {string} currentInput
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
function buildContextualInput(session, currentInput) {
|
|
123
|
+
if (!session || !session.turns || session.turns.length === 0) {
|
|
124
|
+
return currentInput;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const history = session.turns
|
|
128
|
+
.map(t => `${t.role}: ${t.content}`)
|
|
129
|
+
.join('\n');
|
|
130
|
+
|
|
131
|
+
return `[Conversation history]\n${history}\n\n[Current message]\n${currentInput}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Delete all session files that have been inactive for longer than ttlHours.
|
|
136
|
+
* @param {string} projectDir
|
|
137
|
+
* @param {number} [ttlHours]
|
|
138
|
+
* @returns {Promise<number>} Number of sessions deleted
|
|
139
|
+
*/
|
|
140
|
+
async function cleanExpiredSessions(projectDir, ttlHours = DEFAULT_TTL_HOURS) {
|
|
141
|
+
const dir = path.join(projectDir, SESSIONS_DIR);
|
|
142
|
+
let entries;
|
|
143
|
+
try {
|
|
144
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
145
|
+
} catch {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let deleted = 0;
|
|
150
|
+
const cutoff = Date.now() - ttlHours * 3_600_000;
|
|
151
|
+
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
154
|
+
const filePath = path.join(dir, entry.name);
|
|
155
|
+
try {
|
|
156
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
157
|
+
const session = JSON.parse(raw);
|
|
158
|
+
const lastActive = session.last_active ? new Date(session.last_active).getTime() : 0;
|
|
159
|
+
if (lastActive < cutoff) {
|
|
160
|
+
await fs.unlink(filePath);
|
|
161
|
+
deleted++;
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Corrupt file — remove it
|
|
165
|
+
await fs.unlink(filePath).catch(() => {});
|
|
166
|
+
deleted++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return deleted;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
loadSession,
|
|
175
|
+
saveSession,
|
|
176
|
+
appendTurn,
|
|
177
|
+
buildContextualInput,
|
|
178
|
+
cleanExpiredSessions,
|
|
179
|
+
sanitizeSessionId
|
|
180
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { randomUUID } = require('node:crypto');
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb, insertWorkerRun } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
const INBOX_DIR = (projectDir, squadSlug) =>
|
|
8
|
+
path.join(projectDir, '.aioson', 'squads', squadSlug, 'inbox');
|
|
9
|
+
|
|
10
|
+
async function callSquad({ projectDir, from, to, worker, payload, conversationId, depth = 0 }) {
|
|
11
|
+
// 1. Cascade guard
|
|
12
|
+
if (depth > 5) return { ok: false, error: 'cascade_guard' };
|
|
13
|
+
|
|
14
|
+
conversationId = conversationId || randomUUID();
|
|
15
|
+
|
|
16
|
+
// 2. Resolver porta do squad destino no SQLite (manter DB aberto para log)
|
|
17
|
+
const handle = await openRuntimeDb(projectDir);
|
|
18
|
+
const db = handle?.db;
|
|
19
|
+
const port = db
|
|
20
|
+
?.prepare("SELECT port FROM squad_daemons WHERE squad_slug = ? AND status = 'running'")
|
|
21
|
+
.get(to)?.port;
|
|
22
|
+
|
|
23
|
+
let result;
|
|
24
|
+
|
|
25
|
+
// 3. Tentar chamada direta
|
|
26
|
+
if (port) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`http://127.0.0.1:${port}/webhook/${worker}`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify({ ...payload, _inter_squad: { from, conversationId, depth: depth + 1 } }),
|
|
32
|
+
signal: AbortSignal.timeout(10000)
|
|
33
|
+
});
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
result = { ok: res.ok, result: json, conversationId };
|
|
36
|
+
} catch {
|
|
37
|
+
// cai para inbox
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 4. Enfileirar na inbox do squad destino
|
|
42
|
+
if (!result) {
|
|
43
|
+
const inboxDir = INBOX_DIR(projectDir, to);
|
|
44
|
+
await fs.mkdir(inboxDir, { recursive: true });
|
|
45
|
+
const id = randomUUID();
|
|
46
|
+
await fs.writeFile(
|
|
47
|
+
path.join(inboxDir, `${id}.json`),
|
|
48
|
+
JSON.stringify({ id, from, to, worker, payload, conversationId, depth, created_at: new Date().toISOString() })
|
|
49
|
+
);
|
|
50
|
+
result = { ok: false, error: 'offline_queued', conversationId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 5. Gravar chamada emitida no runtime store (trigger_type = 'inter-squad')
|
|
54
|
+
if (db) {
|
|
55
|
+
try {
|
|
56
|
+
insertWorkerRun(db, {
|
|
57
|
+
squadSlug: from,
|
|
58
|
+
workerSlug: worker,
|
|
59
|
+
triggerType: 'inter-squad',
|
|
60
|
+
inputJson: JSON.stringify({ to, payload, conversationId }),
|
|
61
|
+
outputJson: result.ok ? JSON.stringify(result.result) : null,
|
|
62
|
+
status: result.ok ? 'completed' : 'failed',
|
|
63
|
+
errorMessage: result.ok ? null : result.error,
|
|
64
|
+
durationMs: 0,
|
|
65
|
+
attempt: 1
|
|
66
|
+
});
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
db.close();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { callSquad, INBOX_DIR };
|