@jaimevalasek/aioson 1.3.0 → 1.4.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/README.md +19 -2
- package/docs/pt/README.md +62 -2
- package/docs/pt/advisor-spec.md +5 -5
- package/docs/pt/agentes-customizados.md +670 -0
- package/docs/pt/agentes.md +111 -13
- package/docs/pt/automacao-squads.md +407 -0
- package/docs/pt/cenarios.md +3 -3
- package/docs/pt/clientes-ai.md +62 -0
- package/docs/pt/comandos-cli.md +167 -17
- package/docs/pt/deyvin.md +115 -0
- package/docs/pt/genome-3.0-spec.md +11 -11
- package/docs/pt/inicio-rapido.md +45 -0
- package/docs/pt/memoria-contexto.md +255 -0
- package/docs/pt/output-strategy-delivery.md +655 -0
- package/docs/pt/profiler-system.md +17 -17
- package/docs/pt/runtime-observability.md +5 -1
- package/docs/pt/skills.md +175 -0
- package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
- package/docs/testing/genome-2.0-rollout.md +1 -1
- package/package.json +3 -3
- package/src/agents.js +21 -5
- package/src/backup-provider.js +303 -0
- package/src/cli.js +178 -2
- package/src/commands/agents.js +22 -4
- package/src/commands/backup.js +533 -0
- package/src/commands/cloud.js +17 -17
- package/src/commands/context-pack.js +45 -0
- package/src/commands/implementation-plan.js +340 -0
- package/src/commands/learning.js +134 -0
- package/src/commands/live.js +1583 -0
- package/src/commands/runtime.js +833 -2
- package/src/commands/scan-project.js +288 -24
- package/src/commands/setup-context.js +23 -0
- package/src/commands/skill.js +558 -0
- package/src/commands/squad-agent-create.js +788 -0
- package/src/commands/squad-doctor.js +51 -1
- package/src/commands/squad-investigate.js +261 -0
- package/src/commands/squad-learning.js +209 -0
- package/src/commands/squad-pipeline.js +247 -1
- package/src/commands/squad-plan.js +329 -0
- package/src/commands/squad-status.js +1 -1
- package/src/commands/squad-validate.js +57 -1
- package/src/commands/test-agents.js +6 -1
- package/src/commands/workflow-next.js +8 -1
- package/src/commands/workflow-status.js +250 -0
- package/src/constants.js +80 -16
- package/src/context-memory.js +837 -0
- package/src/context-writer.js +2 -0
- package/src/delivery-runner.js +319 -0
- package/src/genome-files.js +1 -1
- package/src/genome-format.js +1 -1
- package/src/i18n/messages/en.js +206 -7
- package/src/i18n/messages/es.js +123 -6
- package/src/i18n/messages/fr.js +122 -5
- package/src/i18n/messages/pt-BR.js +205 -12
- package/src/installer.js +30 -2
- package/src/lib/genomes/compat.js +1 -1
- package/src/runtime-store.js +780 -42
- package/src/session-handoff.js +77 -0
- package/template/.aioson/agents/analyst.md +36 -9
- package/template/.aioson/agents/architect.md +20 -5
- package/template/.aioson/agents/dev.md +135 -15
- package/template/.aioson/agents/deyvin.md +166 -0
- package/template/.aioson/agents/discovery-design-doc.md +25 -1
- package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
- package/template/.aioson/agents/orache.md +371 -0
- package/template/.aioson/agents/orchestrator.md +37 -2
- package/template/.aioson/agents/pair.md +5 -0
- package/template/.aioson/agents/pm.md +17 -5
- package/template/.aioson/agents/product.md +58 -22
- package/template/.aioson/agents/profiler-enricher.md +1 -1
- package/template/.aioson/agents/profiler-forge.md +9 -9
- package/template/.aioson/agents/profiler-researcher.md +1 -1
- package/template/.aioson/agents/qa.md +17 -5
- package/template/.aioson/agents/setup.md +81 -5
- package/template/.aioson/agents/squad.md +675 -28
- package/template/.aioson/agents/ux-ui.md +277 -34
- package/template/.aioson/config.md +175 -0
- package/template/.aioson/context/spec.md.template +17 -0
- package/template/.aioson/genomes/.gitkeep +0 -0
- package/template/.aioson/installed-skills/.gitkeep +0 -0
- package/template/.aioson/locales/en/agents/analyst.md +26 -4
- package/template/.aioson/locales/en/agents/architect.md +10 -0
- package/template/.aioson/locales/en/agents/dev.md +89 -4
- package/template/.aioson/locales/en/agents/deyvin.md +129 -0
- package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
- package/template/.aioson/locales/en/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/en/agents/pair.md +5 -0
- package/template/.aioson/locales/en/agents/pm.md +7 -0
- package/template/.aioson/locales/en/agents/product.md +35 -17
- package/template/.aioson/locales/en/agents/qa.md +7 -0
- package/template/.aioson/locales/en/agents/setup.md +51 -5
- package/template/.aioson/locales/en/agents/squad.md +203 -15
- package/template/.aioson/locales/en/agents/ux-ui.md +375 -35
- package/template/.aioson/locales/es/agents/analyst.md +16 -4
- package/template/.aioson/locales/es/agents/architect.md +10 -0
- package/template/.aioson/locales/es/agents/dev.md +70 -2
- package/template/.aioson/locales/es/agents/deyvin.md +89 -0
- package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
- package/template/.aioson/locales/es/agents/orache.md +103 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/es/agents/pair.md +5 -0
- package/template/.aioson/locales/es/agents/pm.md +7 -0
- package/template/.aioson/locales/es/agents/product.md +13 -3
- package/template/.aioson/locales/es/agents/qa.md +7 -0
- package/template/.aioson/locales/es/agents/setup.md +28 -5
- package/template/.aioson/locales/es/agents/squad.md +221 -15
- package/template/.aioson/locales/es/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/fr/agents/analyst.md +16 -4
- package/template/.aioson/locales/fr/agents/architect.md +10 -0
- package/template/.aioson/locales/fr/agents/dev.md +70 -2
- package/template/.aioson/locales/fr/agents/deyvin.md +89 -0
- package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
- package/template/.aioson/locales/fr/agents/orache.md +104 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/fr/agents/pair.md +5 -0
- package/template/.aioson/locales/fr/agents/pm.md +7 -0
- package/template/.aioson/locales/fr/agents/product.md +13 -3
- package/template/.aioson/locales/fr/agents/qa.md +7 -0
- package/template/.aioson/locales/fr/agents/setup.md +28 -5
- package/template/.aioson/locales/fr/agents/squad.md +216 -10
- package/template/.aioson/locales/fr/agents/ux-ui.md +26 -25
- package/template/.aioson/locales/pt-BR/agents/analyst.md +26 -4
- package/template/.aioson/locales/pt-BR/agents/architect.md +10 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +93 -4
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +129 -0
- package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
- package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +36 -2
- package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
- package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +35 -17
- package/template/.aioson/locales/pt-BR/agents/qa.md +7 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +51 -5
- package/template/.aioson/locales/pt-BR/agents/squad.md +486 -47
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +361 -22
- package/template/.aioson/my-agents/.gitkeep +0 -0
- package/template/.aioson/rules/.gitkeep +0 -0
- package/template/.aioson/rules/squad/.gitkeep +0 -0
- package/template/.aioson/rules/squad/README.md +50 -0
- package/template/.aioson/schemas/genome-meta.schema.json +1 -1
- package/template/.aioson/schemas/genome.schema.json +1 -1
- package/template/.aioson/schemas/squad-blueprint.schema.json +11 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +257 -1
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +157 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +172 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +490 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +237 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +350 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
- package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
- package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
- package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
- package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
- package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
- package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
- package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
- package/template/.aioson/skills/design-system/SKILL.md +92 -0
- package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
- package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
- package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
- package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
- package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
- package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
- package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
- package/template/.aioson/skills/squad/SKILL.md +58 -0
- package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
- package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
- package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
- package/template/.aioson/skills/squad/references/.gitkeep +0 -0
- package/template/.aioson/tasks/implementation-plan.md +288 -0
- package/template/.aioson/tasks/squad-create.md +1 -1
- package/template/.aioson/tasks/squad-execution-plan.md +279 -0
- package/template/.aioson/tasks/squad-export.md +1 -1
- package/template/.aioson/tasks/squad-investigate.md +44 -0
- package/template/.aioson/tasks/squad-learning-review.md +44 -0
- package/template/.aioson/tasks/squad-output-config.md +177 -0
- package/template/.aioson/tasks/squad-validate.md +1 -1
- package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
- package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
- package/template/.claude/commands/aioson/agent/genome.md +5 -0
- package/template/.claude/commands/aioson/agent/product.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
- package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
- package/template/.claude/commands/aioson/agent/squad.md +5 -0
- package/template/.gemini/GEMINI.md +2 -0
- package/template/.gemini/commands/aios-deyvin.toml +6 -0
- package/template/.gemini/commands/aios-pair.toml +6 -0
- package/template/AGENTS.md +34 -6
- package/template/CLAUDE.md +31 -4
- package/template/OPENCODE.md +6 -2
- package/template/squad-searches/.gitkeep +0 -0
- package/template/.aioson/skills/static/interface-design.md +0 -372
- package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
- /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
- /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
- /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
- /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
- /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
- /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
- /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
- /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
- /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
package/src/commands/runtime.js
CHANGED
|
@@ -13,8 +13,13 @@ const {
|
|
|
13
13
|
attachArtifact,
|
|
14
14
|
upsertContentItem,
|
|
15
15
|
getStatusSnapshot,
|
|
16
|
-
logAgentEvent
|
|
16
|
+
logAgentEvent,
|
|
17
|
+
appendRunEvent,
|
|
18
|
+
readAgentSession,
|
|
19
|
+
clearAgentSession
|
|
17
20
|
} = require('../runtime-store');
|
|
21
|
+
const { runAutoDelivery } = require('../delivery-runner');
|
|
22
|
+
const { writeHandoff, buildRuntimeLogHandoff } = require('../session-handoff');
|
|
18
23
|
|
|
19
24
|
const ALLOWED_LAYOUTS = new Set(['document', 'tabs', 'accordion', 'stack', 'mixed']);
|
|
20
25
|
const DEFAULT_TEXT_FIELDS = ['content', 'text', 'body', 'lyrics', 'markdown'];
|
|
@@ -31,6 +36,152 @@ function requireOption(options, key, t) {
|
|
|
31
36
|
return String(value).trim();
|
|
32
37
|
}
|
|
33
38
|
|
|
39
|
+
function normalizeAgentHandle(value) {
|
|
40
|
+
const text = String(value || '').trim();
|
|
41
|
+
if (!text) return '';
|
|
42
|
+
return text.startsWith('@') ? text : `@${text}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeDirectSessionKey(agentName) {
|
|
46
|
+
return `direct-session:${Date.now()}:${String(agentName || '').replace(/^@/, '')}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseWatchSeconds(value) {
|
|
50
|
+
if (value === undefined || value === null || value === false) return null;
|
|
51
|
+
if (value === true || value === '') return 2;
|
|
52
|
+
|
|
53
|
+
const parsed = Number(value);
|
|
54
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 2;
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sleep(ms) {
|
|
59
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function collectRuntimeSessionSnapshot(db, runtimeDir, agentName, options = {}) {
|
|
63
|
+
const normalizedAgent = normalizeAgentHandle(agentName);
|
|
64
|
+
const eventLimit = Math.max(1, Math.min(Number(options.limit) || 8, 20));
|
|
65
|
+
const session = await readAgentSession(runtimeDir, normalizedAgent);
|
|
66
|
+
const activeSession = session && !session.finished ? session : null;
|
|
67
|
+
|
|
68
|
+
let run = null;
|
|
69
|
+
if (activeSession && activeSession.runKey) {
|
|
70
|
+
run = db.prepare(`
|
|
71
|
+
SELECT
|
|
72
|
+
run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
73
|
+
title, status, summary, output_path, started_at, updated_at, finished_at
|
|
74
|
+
FROM agent_runs
|
|
75
|
+
WHERE run_key = ?
|
|
76
|
+
LIMIT 1
|
|
77
|
+
`).get(activeSession.runKey);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!run) {
|
|
81
|
+
run = db.prepare(`
|
|
82
|
+
SELECT
|
|
83
|
+
run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
84
|
+
title, status, summary, output_path, started_at, updated_at, finished_at
|
|
85
|
+
FROM agent_runs
|
|
86
|
+
WHERE agent_name = ?
|
|
87
|
+
ORDER BY updated_at DESC, started_at DESC
|
|
88
|
+
LIMIT 1
|
|
89
|
+
`).get(normalizedAgent);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const task = run && run.task_key
|
|
93
|
+
? db.prepare(`
|
|
94
|
+
SELECT
|
|
95
|
+
task_key, squad_slug, session_key, title, goal, status, created_by, created_at, updated_at, finished_at
|
|
96
|
+
FROM tasks
|
|
97
|
+
WHERE task_key = ?
|
|
98
|
+
LIMIT 1
|
|
99
|
+
`).get(run.task_key)
|
|
100
|
+
: null;
|
|
101
|
+
|
|
102
|
+
const recentEvents = run
|
|
103
|
+
? db.prepare(`
|
|
104
|
+
SELECT event_type, phase, status, message, created_at
|
|
105
|
+
FROM execution_events
|
|
106
|
+
WHERE run_key = ?
|
|
107
|
+
ORDER BY created_at DESC, id DESC
|
|
108
|
+
LIMIT ?
|
|
109
|
+
`).all(run.run_key, eventLimit).reverse()
|
|
110
|
+
: [];
|
|
111
|
+
|
|
112
|
+
const open = Boolean(activeSession && run && (run.status === 'running' || run.status === 'queued'));
|
|
113
|
+
const state = open ? 'open' : (run ? 'closed' : 'idle');
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
agent: normalizedAgent,
|
|
117
|
+
state,
|
|
118
|
+
open,
|
|
119
|
+
sessionKey: activeSession?.sessionKey || run?.session_key || task?.session_key || null,
|
|
120
|
+
startedAt: activeSession?.startedAt || run?.started_at || task?.created_at || null,
|
|
121
|
+
updatedAt: run?.updated_at || task?.updated_at || null,
|
|
122
|
+
session: activeSession,
|
|
123
|
+
run,
|
|
124
|
+
task,
|
|
125
|
+
recentEvents
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function getRuntimeSessionSnapshot(targetDir, agentName, t, options = {}) {
|
|
130
|
+
const { dbPath, runtimeDir } = resolveRuntimePaths(targetDir);
|
|
131
|
+
|
|
132
|
+
if (!(await runtimeStoreExists(targetDir))) {
|
|
133
|
+
throw new Error(t('runtime.store_missing', { path: dbPath }));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const { db } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
137
|
+
try {
|
|
138
|
+
const snapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, options);
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
targetDir,
|
|
142
|
+
dbPath,
|
|
143
|
+
...snapshot
|
|
144
|
+
};
|
|
145
|
+
} finally {
|
|
146
|
+
db.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function printRuntimeSessionSnapshot(snapshot, logger) {
|
|
151
|
+
logger.log(`Direct session: ${snapshot.agent}`);
|
|
152
|
+
logger.log(`State: ${snapshot.state}`);
|
|
153
|
+
|
|
154
|
+
if (snapshot.sessionKey) {
|
|
155
|
+
logger.log(`Session: ${snapshot.sessionKey}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (snapshot.task) {
|
|
159
|
+
logger.log(`Task: ${snapshot.task.task_key} | status: ${snapshot.task.status} | work: ${snapshot.task.title || '—'}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (snapshot.run) {
|
|
163
|
+
logger.log(`Run: ${snapshot.run.run_key} | status: ${snapshot.run.status} | work: ${snapshot.run.title || snapshot.run.summary || '—'}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (snapshot.startedAt) {
|
|
167
|
+
logger.log(`Started: ${snapshot.startedAt}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (snapshot.updatedAt) {
|
|
171
|
+
logger.log(`Updated: ${snapshot.updatedAt}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (snapshot.recentEvents.length === 0) {
|
|
175
|
+
logger.log('Recent events: none');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
logger.log('Recent events:');
|
|
180
|
+
for (const event of snapshot.recentEvents) {
|
|
181
|
+
logger.log(`- ${event.created_at} | ${event.event_type} | ${event.message || '—'}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
34
185
|
async function readJsonIfExists(filePath) {
|
|
35
186
|
try {
|
|
36
187
|
const raw = await fs.readFile(filePath, 'utf8');
|
|
@@ -418,6 +569,14 @@ async function ingestContentCandidate(db, targetDir, absolutePath, options = {})
|
|
|
418
569
|
createdByAgent: options.agent || content.createdByAgent || content.created_by_agent || null
|
|
419
570
|
});
|
|
420
571
|
|
|
572
|
+
// Fire auto-delivery if configured (non-blocking)
|
|
573
|
+
runAutoDelivery(db, {
|
|
574
|
+
projectDir: targetDir,
|
|
575
|
+
squadSlug,
|
|
576
|
+
contentKey: content.contentKey,
|
|
577
|
+
contentPayload: content
|
|
578
|
+
}).catch(() => {}); // Swallow errors — delivery failure should not break ingestion
|
|
579
|
+
|
|
421
580
|
return { indexed: true, kind: 'content-json', contentKey: content.contentKey };
|
|
422
581
|
}
|
|
423
582
|
|
|
@@ -458,6 +617,14 @@ async function ingestContentCandidate(db, targetDir, absolutePath, options = {})
|
|
|
458
617
|
createdByAgent: options.agent || null
|
|
459
618
|
});
|
|
460
619
|
|
|
620
|
+
// Fire auto-delivery if configured (non-blocking)
|
|
621
|
+
runAutoDelivery(db, {
|
|
622
|
+
projectDir: targetDir,
|
|
623
|
+
squadSlug,
|
|
624
|
+
contentKey: payload.contentKey,
|
|
625
|
+
contentPayload: payload
|
|
626
|
+
}).catch(() => {}); // Swallow errors — delivery failure should not break ingestion
|
|
627
|
+
|
|
461
628
|
return { indexed: true, kind: path.extname(absolutePath).toLowerCase(), contentKey: payload.contentKey };
|
|
462
629
|
}
|
|
463
630
|
|
|
@@ -820,6 +987,11 @@ async function runRuntimeStatus({ args, options = {}, logger, t }) {
|
|
|
820
987
|
recentTasks: snapshot.recentTasks,
|
|
821
988
|
activeRuns: snapshot.activeRuns,
|
|
822
989
|
recentRuns: snapshot.recentRuns,
|
|
990
|
+
activeLiveSessions: snapshot.activeLiveSessions,
|
|
991
|
+
activeMicroTasks: snapshot.activeMicroTasks,
|
|
992
|
+
recentLiveSessions: snapshot.recentLiveSessions,
|
|
993
|
+
recentMicroTasks: snapshot.recentMicroTasks,
|
|
994
|
+
recentHandoffs: snapshot.recentHandoffs,
|
|
823
995
|
recentArtifacts: snapshot.recentArtifacts,
|
|
824
996
|
recentContentItems: snapshot.recentContentItems,
|
|
825
997
|
recentExecutionEvents: snapshot.recentExecutionEvents
|
|
@@ -874,6 +1046,49 @@ async function runRuntimeStatus({ args, options = {}, logger, t }) {
|
|
|
874
1046
|
);
|
|
875
1047
|
}
|
|
876
1048
|
}
|
|
1049
|
+
if (snapshot.activeLiveSessions.length > 0) {
|
|
1050
|
+
logger.log(t('runtime.status_live_sessions_title'));
|
|
1051
|
+
for (const task of snapshot.activeLiveSessions) {
|
|
1052
|
+
logger.log(
|
|
1053
|
+
t('runtime.status_live_session_line', {
|
|
1054
|
+
task: task.task_key,
|
|
1055
|
+
agent: task.latest_agent_name || task.created_by || '—',
|
|
1056
|
+
status: task.status,
|
|
1057
|
+
plan: task.plan_steps_total > 0 ? `${task.plan_steps_done}/${task.plan_steps_total}` : '—',
|
|
1058
|
+
micro: `${task.completed_child_task_count || 0}/${task.child_task_count || 0}`,
|
|
1059
|
+
handoffs: task.handoff_count || 0,
|
|
1060
|
+
title: task.title || '—'
|
|
1061
|
+
})
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (snapshot.activeMicroTasks.length > 0) {
|
|
1066
|
+
logger.log(t('runtime.status_micro_tasks_title'));
|
|
1067
|
+
for (const task of snapshot.activeMicroTasks) {
|
|
1068
|
+
logger.log(
|
|
1069
|
+
t('runtime.status_micro_task_line', {
|
|
1070
|
+
task: task.task_key,
|
|
1071
|
+
parent: task.parent_task_key || '—',
|
|
1072
|
+
status: task.status,
|
|
1073
|
+
title: task.title || task.goal || '—'
|
|
1074
|
+
})
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
if (snapshot.recentHandoffs.length > 0) {
|
|
1079
|
+
logger.log(t('runtime.status_handoffs_title'));
|
|
1080
|
+
for (const event of snapshot.recentHandoffs.slice(0, 5)) {
|
|
1081
|
+
logger.log(
|
|
1082
|
+
t('runtime.status_handoff_line', {
|
|
1083
|
+
created: event.created_at,
|
|
1084
|
+
from: event.handoff_from || event.agent_name || '—',
|
|
1085
|
+
to: event.handoff_to || '—',
|
|
1086
|
+
session: event.session_key || '—',
|
|
1087
|
+
message: event.message || '—'
|
|
1088
|
+
})
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
877
1092
|
}
|
|
878
1093
|
|
|
879
1094
|
return payload;
|
|
@@ -911,6 +1126,16 @@ async function runRuntimeLog({ args, options = {}, logger, t }) {
|
|
|
911
1126
|
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
912
1127
|
});
|
|
913
1128
|
|
|
1129
|
+
// Generate session handoff on --finish
|
|
1130
|
+
if (options.finish) {
|
|
1131
|
+
const handoffData = buildRuntimeLogHandoff(
|
|
1132
|
+
agentName,
|
|
1133
|
+
options.message || '',
|
|
1134
|
+
options.summary || ''
|
|
1135
|
+
);
|
|
1136
|
+
await writeHandoff(targetDir, handoffData);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
914
1139
|
if (!options.json) {
|
|
915
1140
|
const isFinish = Boolean(options.finish);
|
|
916
1141
|
logger.log(isFinish
|
|
@@ -933,6 +1158,603 @@ async function runRuntimeLog({ args, options = {}, logger, t }) {
|
|
|
933
1158
|
}
|
|
934
1159
|
}
|
|
935
1160
|
|
|
1161
|
+
|
|
1162
|
+
async function runRuntimeSessionStart({ args, options = {}, logger, t }) {
|
|
1163
|
+
const targetDir = resolveTargetDir(args);
|
|
1164
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1165
|
+
|
|
1166
|
+
try {
|
|
1167
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1168
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1169
|
+
|
|
1170
|
+
if (existingSnapshot.session && !existingSnapshot.open) {
|
|
1171
|
+
await clearAgentSession(runtimeDir, agentName);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
if (existingSnapshot.open) {
|
|
1175
|
+
if (!options.json) {
|
|
1176
|
+
logger.log(`Direct session already active: ${agentName} | task: ${existingSnapshot.task?.task_key || '—'} | run: ${existingSnapshot.run?.run_key || '—'} (${dbPath})`);
|
|
1177
|
+
}
|
|
1178
|
+
return {
|
|
1179
|
+
ok: true,
|
|
1180
|
+
targetDir,
|
|
1181
|
+
dbPath,
|
|
1182
|
+
agent: agentName,
|
|
1183
|
+
taskKey: existingSnapshot.task?.task_key || existingSnapshot.session?.taskKey || null,
|
|
1184
|
+
runKey: existingSnapshot.run?.run_key || existingSnapshot.session?.runKey || null,
|
|
1185
|
+
sessionKey: existingSnapshot.sessionKey,
|
|
1186
|
+
status: existingSnapshot.run?.status || 'running',
|
|
1187
|
+
reused: true,
|
|
1188
|
+
open: true
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
const sessionKey = options.session ? String(options.session).trim() : makeDirectSessionKey(agentName);
|
|
1193
|
+
const title = options.title ? String(options.title).trim() : `Direct session ${agentName}`;
|
|
1194
|
+
const message = options.message ? String(options.message).trim() : `Session started for ${agentName}`;
|
|
1195
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1196
|
+
agentName,
|
|
1197
|
+
message,
|
|
1198
|
+
type: options.type || 'session.start',
|
|
1199
|
+
taskTitle: title,
|
|
1200
|
+
sessionKey,
|
|
1201
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
if (!options.json) {
|
|
1205
|
+
logger.log(`Direct session started: ${agentName} | task: ${taskKey} | run: ${runKey} (${dbPath})`);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return {
|
|
1209
|
+
ok: true,
|
|
1210
|
+
targetDir,
|
|
1211
|
+
dbPath,
|
|
1212
|
+
agent: agentName,
|
|
1213
|
+
taskKey,
|
|
1214
|
+
runKey,
|
|
1215
|
+
sessionKey,
|
|
1216
|
+
status: 'running',
|
|
1217
|
+
reused: false,
|
|
1218
|
+
open: true
|
|
1219
|
+
};
|
|
1220
|
+
} finally {
|
|
1221
|
+
db.close();
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
async function runRuntimeSessionLog({ args, options = {}, logger, t }) {
|
|
1226
|
+
const targetDir = resolveTargetDir(args);
|
|
1227
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1228
|
+
|
|
1229
|
+
try {
|
|
1230
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1231
|
+
const message = requireOption(options, 'message', t);
|
|
1232
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1233
|
+
|
|
1234
|
+
if (existingSnapshot.session && !existingSnapshot.open) {
|
|
1235
|
+
await clearAgentSession(runtimeDir, agentName);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
const autoStarted = !existingSnapshot.open;
|
|
1239
|
+
const sessionKey = existingSnapshot.sessionKey || (options.session ? String(options.session).trim() : makeDirectSessionKey(agentName));
|
|
1240
|
+
const title = options.title ? String(options.title).trim() : `Direct session ${agentName}`;
|
|
1241
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1242
|
+
agentName,
|
|
1243
|
+
message,
|
|
1244
|
+
type: options.type || 'session.log',
|
|
1245
|
+
taskTitle: title,
|
|
1246
|
+
sessionKey,
|
|
1247
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
if (!options.json) {
|
|
1251
|
+
logger.log(`Direct session log recorded: ${agentName} | run: ${runKey} (${dbPath})`);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
return {
|
|
1255
|
+
ok: true,
|
|
1256
|
+
targetDir,
|
|
1257
|
+
dbPath,
|
|
1258
|
+
agent: agentName,
|
|
1259
|
+
taskKey,
|
|
1260
|
+
runKey,
|
|
1261
|
+
sessionKey,
|
|
1262
|
+
status: 'running',
|
|
1263
|
+
autoStarted,
|
|
1264
|
+
open: true
|
|
1265
|
+
};
|
|
1266
|
+
} finally {
|
|
1267
|
+
db.close();
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
async function runRuntimeSessionFinish({ args, options = {}, logger, t }) {
|
|
1272
|
+
const targetDir = resolveTargetDir(args);
|
|
1273
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1274
|
+
|
|
1275
|
+
try {
|
|
1276
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1277
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1278
|
+
|
|
1279
|
+
if (!existingSnapshot.open) {
|
|
1280
|
+
throw new Error(`No active direct session for ${agentName}.`);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const summary = options.summary ? String(options.summary).trim() : '';
|
|
1284
|
+
const message = options.message ? String(options.message).trim() : (summary || `Session finished for ${agentName}`);
|
|
1285
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1286
|
+
agentName,
|
|
1287
|
+
message,
|
|
1288
|
+
type: options.type || 'session.finish',
|
|
1289
|
+
finish: true,
|
|
1290
|
+
status: options.status || 'completed',
|
|
1291
|
+
summary,
|
|
1292
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
if (!options.json) {
|
|
1296
|
+
logger.log(`Direct session finished: ${agentName} | run: ${runKey} (${dbPath})`);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
ok: true,
|
|
1301
|
+
targetDir,
|
|
1302
|
+
dbPath,
|
|
1303
|
+
agent: agentName,
|
|
1304
|
+
taskKey,
|
|
1305
|
+
runKey,
|
|
1306
|
+
sessionKey: existingSnapshot.sessionKey,
|
|
1307
|
+
status: options.status || 'completed',
|
|
1308
|
+
finished: true,
|
|
1309
|
+
open: false
|
|
1310
|
+
};
|
|
1311
|
+
} finally {
|
|
1312
|
+
db.close();
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
async function runRuntimeSessionStatus({ args, options = {}, logger, t }) {
|
|
1317
|
+
const targetDir = resolveTargetDir(args);
|
|
1318
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1319
|
+
const watchSeconds = parseWatchSeconds(options.watch);
|
|
1320
|
+
|
|
1321
|
+
if (watchSeconds && options.json) {
|
|
1322
|
+
throw new Error('--watch cannot be combined with --json.');
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if (!watchSeconds) {
|
|
1326
|
+
const snapshot = await getRuntimeSessionSnapshot(targetDir, agentName, t, { limit: options.limit });
|
|
1327
|
+
if (!options.json) {
|
|
1328
|
+
printRuntimeSessionSnapshot(snapshot, logger);
|
|
1329
|
+
}
|
|
1330
|
+
return snapshot;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
while (true) {
|
|
1334
|
+
const snapshot = await getRuntimeSessionSnapshot(targetDir, agentName, t, { limit: options.limit });
|
|
1335
|
+
if (process.stdout && process.stdout.isTTY) {
|
|
1336
|
+
process.stdout.write('\x1Bc');
|
|
1337
|
+
}
|
|
1338
|
+
printRuntimeSessionSnapshot(snapshot, logger);
|
|
1339
|
+
await sleep(Math.round(watchSeconds * 1000));
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
async function runDeliver({ args, options = {}, logger, t }) {
|
|
1344
|
+
const targetDir = resolveTargetDir(args);
|
|
1345
|
+
const squadSlug = requireOption(options, 'squad', t);
|
|
1346
|
+
const contentKey = options['content-key'] || options.contentKey || null;
|
|
1347
|
+
const triggerType = options.trigger || 'manual';
|
|
1348
|
+
|
|
1349
|
+
const { db, dbPath } = await withRuntimeDb(targetDir, t);
|
|
1350
|
+
|
|
1351
|
+
try {
|
|
1352
|
+
const { runManualDelivery } = require('../delivery-runner');
|
|
1353
|
+
|
|
1354
|
+
// Optionally load content payload from DB
|
|
1355
|
+
let contentPayload = null;
|
|
1356
|
+
if (contentKey) {
|
|
1357
|
+
const row = db.prepare('SELECT payload_json FROM content_items WHERE content_key = ? AND squad_slug = ?').get(contentKey, squadSlug);
|
|
1358
|
+
if (row && row.payload_json) {
|
|
1359
|
+
try { contentPayload = JSON.parse(row.payload_json); } catch { /* ignore */ }
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const result = await runManualDelivery(db, {
|
|
1364
|
+
projectDir: targetDir,
|
|
1365
|
+
squadSlug,
|
|
1366
|
+
contentKey,
|
|
1367
|
+
triggerType,
|
|
1368
|
+
contentPayload
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
if (!result.delivered) {
|
|
1372
|
+
logger.log(`Delivery skipped: ${result.reason}`);
|
|
1373
|
+
return { ok: false, ...result };
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
for (const r of result.results || []) {
|
|
1377
|
+
const status = r.ok ? 'OK' : 'FAIL';
|
|
1378
|
+
logger.log(` ${status} ${r.webhookSlug} — ${r.statusCode || 'no response'} (${r.attempts} attempt${r.attempts > 1 ? 's' : ''})`);
|
|
1379
|
+
if (r.error) logger.log(` Error: ${r.error}`);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
logger.log(`\nDelivery ${result.allOk ? 'completed' : 'completed with errors'}.`);
|
|
1383
|
+
return { ok: result.allOk, ...result };
|
|
1384
|
+
} finally {
|
|
1385
|
+
db.close();
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
async function findManifestPath(projectDir, slug) {
|
|
1390
|
+
const candidates = [
|
|
1391
|
+
path.join(projectDir, '.aioson', 'squads', slug, 'squad.manifest.json'),
|
|
1392
|
+
path.join(projectDir, 'agents', slug, 'squad.manifest.json')
|
|
1393
|
+
];
|
|
1394
|
+
for (const p of candidates) {
|
|
1395
|
+
try { await fs.stat(p); return p; } catch { continue; }
|
|
1396
|
+
}
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
async function runOutputStrategyExport({ args, options = {}, logger, t }) {
|
|
1401
|
+
const projectDir = resolveTargetDir(args);
|
|
1402
|
+
const slug = requireOption(options, 'squad', t);
|
|
1403
|
+
const manifestPath = await findManifestPath(projectDir, slug);
|
|
1404
|
+
|
|
1405
|
+
if (!manifestPath) {
|
|
1406
|
+
logger.error(`Manifest not found for squad "${slug}"`);
|
|
1407
|
+
return { ok: false, error: 'Manifest not found' };
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const raw = await fs.readFile(manifestPath, 'utf8');
|
|
1411
|
+
const manifest = JSON.parse(raw);
|
|
1412
|
+
const strategy = manifest.outputStrategy || null;
|
|
1413
|
+
|
|
1414
|
+
if (!strategy) {
|
|
1415
|
+
logger.log(`Squad "${slug}" has no outputStrategy configured.`);
|
|
1416
|
+
return { ok: false, error: 'No outputStrategy found' };
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const exportsDir = path.join(projectDir, '.aioson', 'squads', 'exports');
|
|
1420
|
+
await fs.mkdir(exportsDir, { recursive: true });
|
|
1421
|
+
const outFile = path.join(exportsDir, `${slug}.output-strategy.json`);
|
|
1422
|
+
await fs.writeFile(outFile, JSON.stringify(strategy, null, 2) + '\n', 'utf8');
|
|
1423
|
+
|
|
1424
|
+
const relOut = path.relative(projectDir, outFile).replace(/\\/g, '/');
|
|
1425
|
+
logger.log(`Exported outputStrategy from "${slug}" → ${relOut}`);
|
|
1426
|
+
return { ok: true, file: relOut, strategy };
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
async function runOutputStrategyImport({ args, options = {}, logger, t }) {
|
|
1430
|
+
const projectDir = resolveTargetDir(args);
|
|
1431
|
+
const slug = requireOption(options, 'squad', t);
|
|
1432
|
+
const fromSlug = options.from || null;
|
|
1433
|
+
const fromFile = options.file || null;
|
|
1434
|
+
|
|
1435
|
+
if (!fromSlug && !fromFile) {
|
|
1436
|
+
logger.error('Usage: aioson output-strategy:import --squad=<target> --from=<source-slug> | --file=<path>');
|
|
1437
|
+
return { ok: false, error: 'Provide --from or --file' };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Load source strategy
|
|
1441
|
+
let strategy;
|
|
1442
|
+
if (fromFile) {
|
|
1443
|
+
const absFile = path.resolve(projectDir, fromFile);
|
|
1444
|
+
const raw = await fs.readFile(absFile, 'utf8');
|
|
1445
|
+
strategy = JSON.parse(raw);
|
|
1446
|
+
} else {
|
|
1447
|
+
const srcPath = await findManifestPath(projectDir, fromSlug);
|
|
1448
|
+
if (!srcPath) {
|
|
1449
|
+
logger.error(`Source squad "${fromSlug}" manifest not found`);
|
|
1450
|
+
return { ok: false, error: 'Source manifest not found' };
|
|
1451
|
+
}
|
|
1452
|
+
const srcManifest = JSON.parse(await fs.readFile(srcPath, 'utf8'));
|
|
1453
|
+
strategy = srcManifest.outputStrategy || null;
|
|
1454
|
+
if (!strategy) {
|
|
1455
|
+
logger.error(`Source squad "${fromSlug}" has no outputStrategy`);
|
|
1456
|
+
return { ok: false, error: 'Source has no outputStrategy' };
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Write to target
|
|
1461
|
+
const targetPath = await findManifestPath(projectDir, slug);
|
|
1462
|
+
if (!targetPath) {
|
|
1463
|
+
logger.error(`Target squad "${slug}" manifest not found`);
|
|
1464
|
+
return { ok: false, error: 'Target manifest not found' };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const targetManifest = JSON.parse(await fs.readFile(targetPath, 'utf8'));
|
|
1468
|
+
targetManifest.outputStrategy = strategy;
|
|
1469
|
+
await fs.writeFile(targetPath, JSON.stringify(targetManifest, null, 2) + '\n', 'utf8');
|
|
1470
|
+
|
|
1471
|
+
logger.log(`Imported outputStrategy into "${slug}" from ${fromSlug || fromFile}`);
|
|
1472
|
+
return { ok: true, squad: slug, source: fromSlug || fromFile };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* aioson devlog:sync [targetDir]
|
|
1477
|
+
*
|
|
1478
|
+
* Parses aioson-logs/devlog-*.md files, imports them into SQLite as
|
|
1479
|
+
* task + run + events, then renames each file to .synced so it is not
|
|
1480
|
+
* re-imported on subsequent runs.
|
|
1481
|
+
*/
|
|
1482
|
+
async function runDevlogSync({ args, options = {}, logger, t }) {
|
|
1483
|
+
const targetDir = resolveTargetDir(args);
|
|
1484
|
+
const logsDir = path.join(targetDir, 'aioson-logs');
|
|
1485
|
+
|
|
1486
|
+
let entries;
|
|
1487
|
+
try {
|
|
1488
|
+
entries = await fs.readdir(logsDir);
|
|
1489
|
+
} catch {
|
|
1490
|
+
logger.log('No aioson-logs/ directory found — nothing to sync.');
|
|
1491
|
+
return { ok: true, synced: 0 };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const devlogFiles = entries
|
|
1495
|
+
.filter(f => f.startsWith('devlog-') && f.endsWith('.md'))
|
|
1496
|
+
.sort();
|
|
1497
|
+
|
|
1498
|
+
if (devlogFiles.length === 0) {
|
|
1499
|
+
logger.log('No devlog files to sync.');
|
|
1500
|
+
return { ok: true, synced: 0 };
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
const { db, dbPath } = await openRuntimeDb(targetDir);
|
|
1504
|
+
let synced = 0;
|
|
1505
|
+
const parsedDevlogs = [];
|
|
1506
|
+
|
|
1507
|
+
try {
|
|
1508
|
+
for (const file of devlogFiles) {
|
|
1509
|
+
const filePath = path.join(logsDir, file);
|
|
1510
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
1511
|
+
|
|
1512
|
+
// Parse YAML frontmatter
|
|
1513
|
+
const fm = parseFrontmatter(raw);
|
|
1514
|
+
const agent = fm.agent || 'unknown';
|
|
1515
|
+
const summary = fm.summary || file;
|
|
1516
|
+
const sessionStart = fm.session_start || null;
|
|
1517
|
+
const sessionEnd = fm.session_end || null;
|
|
1518
|
+
const status = fm.status || 'completed';
|
|
1519
|
+
const body = raw.replace(/^---[\s\S]*?---\s*/, '');
|
|
1520
|
+
|
|
1521
|
+
parsedDevlogs.push({ filename: file, agent, summary, sessionStart, sessionEnd, status, body });
|
|
1522
|
+
|
|
1523
|
+
// Create task + run
|
|
1524
|
+
const taskKey = startTask(db, {
|
|
1525
|
+
title: `devlog: ${summary}`,
|
|
1526
|
+
squadSlug: null,
|
|
1527
|
+
status: status === 'partial' ? 'running' : 'completed',
|
|
1528
|
+
createdBy: agent
|
|
1529
|
+
});
|
|
1530
|
+
|
|
1531
|
+
const runKey = startRun(db, {
|
|
1532
|
+
taskKey,
|
|
1533
|
+
agentName: agent,
|
|
1534
|
+
agentKind: 'devlog',
|
|
1535
|
+
squadSlug: null,
|
|
1536
|
+
title: `@${agent} devlog`,
|
|
1537
|
+
message: summary
|
|
1538
|
+
});
|
|
1539
|
+
|
|
1540
|
+
// Extract body sections as events
|
|
1541
|
+
const sections = body.split(/^## /m).filter(Boolean);
|
|
1542
|
+
for (const section of sections) {
|
|
1543
|
+
const firstLine = section.split('\n')[0].trim();
|
|
1544
|
+
const content = section.slice(firstLine.length).trim();
|
|
1545
|
+
if (content) {
|
|
1546
|
+
appendRunEvent(db, {
|
|
1547
|
+
runKey,
|
|
1548
|
+
eventType: 'devlog',
|
|
1549
|
+
phase: firstLine.toLowerCase().replace(/\s+/g, '_'),
|
|
1550
|
+
status: 'completed',
|
|
1551
|
+
message: `## ${firstLine}\n${content}`,
|
|
1552
|
+
createdAt: sessionEnd || new Date().toISOString()
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// Close the run
|
|
1558
|
+
updateRun(db, runKey, {
|
|
1559
|
+
status: status === 'partial' ? 'running' : 'completed',
|
|
1560
|
+
summary,
|
|
1561
|
+
finishedAt: sessionEnd || new Date().toISOString()
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
if (status !== 'partial') {
|
|
1565
|
+
updateTask(db, taskKey, {
|
|
1566
|
+
status: 'completed',
|
|
1567
|
+
finishedAt: sessionEnd || new Date().toISOString()
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Rename to .synced
|
|
1572
|
+
await fs.rename(filePath, filePath.replace(/\.md$/, '.synced.md'));
|
|
1573
|
+
synced++;
|
|
1574
|
+
logger.log(` Synced: ${file} → task=${taskKey} run=${runKey}`);
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
logger.log(`Synced ${synced} devlog(s) into ${dbPath}`);
|
|
1578
|
+
|
|
1579
|
+
// Cloud sync
|
|
1580
|
+
if (options.cloud) {
|
|
1581
|
+
const cloudResult = await syncDevlogsToCloud(targetDir, parsedDevlogs, options, logger);
|
|
1582
|
+
return { ok: true, synced, dbPath, cloud: cloudResult };
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
return { ok: true, synced, dbPath };
|
|
1586
|
+
} finally {
|
|
1587
|
+
db.close();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
/**
|
|
1592
|
+
* Sends parsed devlogs to the cloud endpoint.
|
|
1593
|
+
* Reads cloud config from .aioson/install.json or --url / --token options.
|
|
1594
|
+
*/
|
|
1595
|
+
async function syncDevlogsToCloud(targetDir, devlogs, options, logger) {
|
|
1596
|
+
const cloudUrl = options.url || options['cloud-url'] || await resolveCloudUrl(targetDir);
|
|
1597
|
+
const cloudToken = options.token || options['cloud-token'] || await resolveCloudToken(targetDir);
|
|
1598
|
+
|
|
1599
|
+
if (!cloudUrl) {
|
|
1600
|
+
logger.error('Cloud URL not configured. Use --url or set cloudBaseUrl in dashboard project settings.');
|
|
1601
|
+
return { ok: false, error: 'missing_cloud_url' };
|
|
1602
|
+
}
|
|
1603
|
+
if (!cloudToken) {
|
|
1604
|
+
logger.error('Cloud token not configured. Use --token or set cloudApiToken in dashboard project settings.');
|
|
1605
|
+
return { ok: false, error: 'missing_cloud_token' };
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
const endpoint = `${cloudUrl.replace(/\/+$/, '')}/api/publish/runtime`;
|
|
1609
|
+
const payload = {
|
|
1610
|
+
tasks: [],
|
|
1611
|
+
devlogs: devlogs.map(d => ({
|
|
1612
|
+
filename: d.filename,
|
|
1613
|
+
agent: d.agent,
|
|
1614
|
+
sessionStart: d.sessionStart,
|
|
1615
|
+
sessionEnd: d.sessionEnd,
|
|
1616
|
+
status: d.status,
|
|
1617
|
+
summary: d.summary,
|
|
1618
|
+
body: d.body
|
|
1619
|
+
}))
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
logger.log(` Pushing ${devlogs.length} devlog(s) to ${endpoint}...`);
|
|
1623
|
+
|
|
1624
|
+
const response = await fetch(endpoint, {
|
|
1625
|
+
method: 'POST',
|
|
1626
|
+
headers: {
|
|
1627
|
+
'accept': 'application/json',
|
|
1628
|
+
'content-type': 'application/json',
|
|
1629
|
+
'authorization': `Bearer ${cloudToken}`
|
|
1630
|
+
},
|
|
1631
|
+
body: JSON.stringify(payload),
|
|
1632
|
+
signal: AbortSignal.timeout(15000)
|
|
1633
|
+
});
|
|
1634
|
+
|
|
1635
|
+
const text = await response.text();
|
|
1636
|
+
let result;
|
|
1637
|
+
try { result = JSON.parse(text); } catch { result = { ok: false, error: text }; }
|
|
1638
|
+
|
|
1639
|
+
if (result.ok) {
|
|
1640
|
+
logger.log(` Cloud sync OK: ${result.devlogsStored || 0} devlog(s) stored.`);
|
|
1641
|
+
} else {
|
|
1642
|
+
logger.error(` Cloud sync failed: ${result.error || response.status}`);
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
return result;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
async function resolveCloudUrl(targetDir) {
|
|
1649
|
+
try {
|
|
1650
|
+
const raw = await fs.readFile(path.join(targetDir, '.aioson/install.json'), 'utf8');
|
|
1651
|
+
const meta = JSON.parse(raw);
|
|
1652
|
+
return meta.cloudBaseUrl || null;
|
|
1653
|
+
} catch { return null; }
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
async function resolveCloudToken(targetDir) {
|
|
1657
|
+
try {
|
|
1658
|
+
const raw = await fs.readFile(path.join(targetDir, '.aioson/install.json'), 'utf8');
|
|
1659
|
+
const meta = JSON.parse(raw);
|
|
1660
|
+
return meta.cloudApiToken || null;
|
|
1661
|
+
} catch { return null; }
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
/**
|
|
1665
|
+
* Minimal YAML frontmatter parser (no external deps).
|
|
1666
|
+
* Returns an object with frontmatter keys, or {} if none.
|
|
1667
|
+
*/
|
|
1668
|
+
function parseFrontmatter(content) {
|
|
1669
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1670
|
+
if (!match) return {};
|
|
1671
|
+
const result = {};
|
|
1672
|
+
for (const line of match[1].split('\n')) {
|
|
1673
|
+
const idx = line.indexOf(':');
|
|
1674
|
+
if (idx === -1) continue;
|
|
1675
|
+
const key = line.slice(0, idx).trim();
|
|
1676
|
+
let val = line.slice(idx + 1).trim();
|
|
1677
|
+
// Strip surrounding quotes
|
|
1678
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
1679
|
+
val = val.slice(1, -1);
|
|
1680
|
+
}
|
|
1681
|
+
result[key] = val;
|
|
1682
|
+
}
|
|
1683
|
+
return result;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* aioson runtime:prune [targetDir] --older-than=<days>
|
|
1688
|
+
*
|
|
1689
|
+
* Removes execution_events, agent_events, and completed agent_runs
|
|
1690
|
+
* older than the specified number of days. Tasks are kept but their
|
|
1691
|
+
* events are cleaned up.
|
|
1692
|
+
*/
|
|
1693
|
+
async function runRuntimePrune({ args, options = {}, logger, t }) {
|
|
1694
|
+
const targetDir = resolveTargetDir(args);
|
|
1695
|
+
const days = parseInt(options['older-than'] || options.olderThan || '30', 10);
|
|
1696
|
+
|
|
1697
|
+
if (isNaN(days) || days < 1) {
|
|
1698
|
+
logger.error('Usage: aioson runtime:prune --older-than=<days> (minimum 1)');
|
|
1699
|
+
return { ok: false, error: 'Invalid --older-than value' };
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const { db, dbPath } = await withRuntimeDb(targetDir, t);
|
|
1703
|
+
|
|
1704
|
+
try {
|
|
1705
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1706
|
+
|
|
1707
|
+
const execEvents = db.prepare(
|
|
1708
|
+
`DELETE FROM execution_events WHERE created_at < ?`
|
|
1709
|
+
).run(cutoff);
|
|
1710
|
+
|
|
1711
|
+
const agentEvents = db.prepare(
|
|
1712
|
+
`DELETE FROM agent_events WHERE created_at < ?`
|
|
1713
|
+
).run(cutoff);
|
|
1714
|
+
|
|
1715
|
+
const runs = db.prepare(
|
|
1716
|
+
`DELETE FROM agent_runs WHERE status IN ('completed', 'failed') AND finished_at < ?`
|
|
1717
|
+
).run(cutoff);
|
|
1718
|
+
|
|
1719
|
+
const tasks = db.prepare(
|
|
1720
|
+
`DELETE FROM tasks WHERE status IN ('completed', 'failed') AND finished_at < ?`
|
|
1721
|
+
).run(cutoff);
|
|
1722
|
+
|
|
1723
|
+
const deliveryLogs = db.prepare(
|
|
1724
|
+
`DELETE FROM delivery_log WHERE created_at < ?`
|
|
1725
|
+
).run(cutoff);
|
|
1726
|
+
|
|
1727
|
+
// Reclaim disk space
|
|
1728
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
1729
|
+
|
|
1730
|
+
const total = execEvents.changes + agentEvents.changes + runs.changes + tasks.changes + deliveryLogs.changes;
|
|
1731
|
+
|
|
1732
|
+
logger.log(`Pruned ${total} records older than ${days} days from ${dbPath}:`);
|
|
1733
|
+
logger.log(` execution_events: ${execEvents.changes}`);
|
|
1734
|
+
logger.log(` agent_events: ${agentEvents.changes}`);
|
|
1735
|
+
logger.log(` agent_runs: ${runs.changes}`);
|
|
1736
|
+
logger.log(` tasks: ${tasks.changes}`);
|
|
1737
|
+
logger.log(` delivery_log: ${deliveryLogs.changes}`);
|
|
1738
|
+
|
|
1739
|
+
return {
|
|
1740
|
+
ok: true,
|
|
1741
|
+
dbPath,
|
|
1742
|
+
days,
|
|
1743
|
+
cutoff,
|
|
1744
|
+
deleted: {
|
|
1745
|
+
execution_events: execEvents.changes,
|
|
1746
|
+
agent_events: agentEvents.changes,
|
|
1747
|
+
agent_runs: runs.changes,
|
|
1748
|
+
tasks: tasks.changes,
|
|
1749
|
+
delivery_log: deliveryLogs.changes,
|
|
1750
|
+
total
|
|
1751
|
+
}
|
|
1752
|
+
};
|
|
1753
|
+
} finally {
|
|
1754
|
+
db.close();
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
936
1758
|
module.exports = {
|
|
937
1759
|
runRuntimeInit,
|
|
938
1760
|
runRuntimeIngest,
|
|
@@ -944,5 +1766,14 @@ module.exports = {
|
|
|
944
1766
|
runRuntimeTaskFail,
|
|
945
1767
|
runRuntimeFail,
|
|
946
1768
|
runRuntimeStatus,
|
|
947
|
-
runRuntimeLog
|
|
1769
|
+
runRuntimeLog,
|
|
1770
|
+
runRuntimeSessionStart,
|
|
1771
|
+
runRuntimeSessionLog,
|
|
1772
|
+
runRuntimeSessionFinish,
|
|
1773
|
+
runRuntimeSessionStatus,
|
|
1774
|
+
runDeliver,
|
|
1775
|
+
runOutputStrategyExport,
|
|
1776
|
+
runOutputStrategyImport,
|
|
1777
|
+
runDevlogSync,
|
|
1778
|
+
runRuntimePrune
|
|
948
1779
|
};
|