@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.
Files changed (213) hide show
  1. package/README.md +19 -2
  2. package/docs/pt/README.md +62 -2
  3. package/docs/pt/advisor-spec.md +5 -5
  4. package/docs/pt/agentes-customizados.md +670 -0
  5. package/docs/pt/agentes.md +111 -13
  6. package/docs/pt/automacao-squads.md +407 -0
  7. package/docs/pt/cenarios.md +3 -3
  8. package/docs/pt/clientes-ai.md +62 -0
  9. package/docs/pt/comandos-cli.md +167 -17
  10. package/docs/pt/deyvin.md +115 -0
  11. package/docs/pt/genome-3.0-spec.md +11 -11
  12. package/docs/pt/inicio-rapido.md +45 -0
  13. package/docs/pt/memoria-contexto.md +255 -0
  14. package/docs/pt/output-strategy-delivery.md +655 -0
  15. package/docs/pt/profiler-system.md +17 -17
  16. package/docs/pt/runtime-observability.md +5 -1
  17. package/docs/pt/skills.md +175 -0
  18. package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
  19. package/docs/testing/genome-2.0-rollout.md +1 -1
  20. package/package.json +3 -3
  21. package/src/agents.js +21 -5
  22. package/src/backup-provider.js +303 -0
  23. package/src/cli.js +178 -2
  24. package/src/commands/agents.js +22 -4
  25. package/src/commands/backup.js +533 -0
  26. package/src/commands/cloud.js +17 -17
  27. package/src/commands/context-pack.js +45 -0
  28. package/src/commands/implementation-plan.js +340 -0
  29. package/src/commands/learning.js +134 -0
  30. package/src/commands/live.js +1583 -0
  31. package/src/commands/runtime.js +833 -2
  32. package/src/commands/scan-project.js +288 -24
  33. package/src/commands/setup-context.js +23 -0
  34. package/src/commands/skill.js +558 -0
  35. package/src/commands/squad-agent-create.js +788 -0
  36. package/src/commands/squad-doctor.js +51 -1
  37. package/src/commands/squad-investigate.js +261 -0
  38. package/src/commands/squad-learning.js +209 -0
  39. package/src/commands/squad-pipeline.js +247 -1
  40. package/src/commands/squad-plan.js +329 -0
  41. package/src/commands/squad-status.js +1 -1
  42. package/src/commands/squad-validate.js +57 -1
  43. package/src/commands/test-agents.js +6 -1
  44. package/src/commands/workflow-next.js +8 -1
  45. package/src/commands/workflow-status.js +250 -0
  46. package/src/constants.js +80 -16
  47. package/src/context-memory.js +837 -0
  48. package/src/context-writer.js +2 -0
  49. package/src/delivery-runner.js +319 -0
  50. package/src/genome-files.js +1 -1
  51. package/src/genome-format.js +1 -1
  52. package/src/i18n/messages/en.js +206 -7
  53. package/src/i18n/messages/es.js +123 -6
  54. package/src/i18n/messages/fr.js +122 -5
  55. package/src/i18n/messages/pt-BR.js +205 -12
  56. package/src/installer.js +30 -2
  57. package/src/lib/genomes/compat.js +1 -1
  58. package/src/runtime-store.js +780 -42
  59. package/src/session-handoff.js +77 -0
  60. package/template/.aioson/agents/analyst.md +36 -9
  61. package/template/.aioson/agents/architect.md +20 -5
  62. package/template/.aioson/agents/dev.md +135 -15
  63. package/template/.aioson/agents/deyvin.md +166 -0
  64. package/template/.aioson/agents/discovery-design-doc.md +25 -1
  65. package/template/.aioson/agents/{genoma.md → genome.md} +20 -20
  66. package/template/.aioson/agents/orache.md +371 -0
  67. package/template/.aioson/agents/orchestrator.md +37 -2
  68. package/template/.aioson/agents/pair.md +5 -0
  69. package/template/.aioson/agents/pm.md +17 -5
  70. package/template/.aioson/agents/product.md +58 -22
  71. package/template/.aioson/agents/profiler-enricher.md +1 -1
  72. package/template/.aioson/agents/profiler-forge.md +9 -9
  73. package/template/.aioson/agents/profiler-researcher.md +1 -1
  74. package/template/.aioson/agents/qa.md +17 -5
  75. package/template/.aioson/agents/setup.md +81 -5
  76. package/template/.aioson/agents/squad.md +675 -28
  77. package/template/.aioson/agents/ux-ui.md +277 -34
  78. package/template/.aioson/config.md +175 -0
  79. package/template/.aioson/context/spec.md.template +17 -0
  80. package/template/.aioson/genomes/.gitkeep +0 -0
  81. package/template/.aioson/installed-skills/.gitkeep +0 -0
  82. package/template/.aioson/locales/en/agents/analyst.md +26 -4
  83. package/template/.aioson/locales/en/agents/architect.md +10 -0
  84. package/template/.aioson/locales/en/agents/dev.md +89 -4
  85. package/template/.aioson/locales/en/agents/deyvin.md +129 -0
  86. package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
  87. package/template/.aioson/locales/en/agents/orchestrator.md +36 -2
  88. package/template/.aioson/locales/en/agents/pair.md +5 -0
  89. package/template/.aioson/locales/en/agents/pm.md +7 -0
  90. package/template/.aioson/locales/en/agents/product.md +35 -17
  91. package/template/.aioson/locales/en/agents/qa.md +7 -0
  92. package/template/.aioson/locales/en/agents/setup.md +51 -5
  93. package/template/.aioson/locales/en/agents/squad.md +203 -15
  94. package/template/.aioson/locales/en/agents/ux-ui.md +375 -35
  95. package/template/.aioson/locales/es/agents/analyst.md +16 -4
  96. package/template/.aioson/locales/es/agents/architect.md +10 -0
  97. package/template/.aioson/locales/es/agents/dev.md +70 -2
  98. package/template/.aioson/locales/es/agents/deyvin.md +89 -0
  99. package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
  100. package/template/.aioson/locales/es/agents/orache.md +103 -0
  101. package/template/.aioson/locales/es/agents/orchestrator.md +36 -2
  102. package/template/.aioson/locales/es/agents/pair.md +5 -0
  103. package/template/.aioson/locales/es/agents/pm.md +7 -0
  104. package/template/.aioson/locales/es/agents/product.md +13 -3
  105. package/template/.aioson/locales/es/agents/qa.md +7 -0
  106. package/template/.aioson/locales/es/agents/setup.md +28 -5
  107. package/template/.aioson/locales/es/agents/squad.md +221 -15
  108. package/template/.aioson/locales/es/agents/ux-ui.md +26 -25
  109. package/template/.aioson/locales/fr/agents/analyst.md +16 -4
  110. package/template/.aioson/locales/fr/agents/architect.md +10 -0
  111. package/template/.aioson/locales/fr/agents/dev.md +70 -2
  112. package/template/.aioson/locales/fr/agents/deyvin.md +89 -0
  113. package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
  114. package/template/.aioson/locales/fr/agents/orache.md +104 -0
  115. package/template/.aioson/locales/fr/agents/orchestrator.md +36 -2
  116. package/template/.aioson/locales/fr/agents/pair.md +5 -0
  117. package/template/.aioson/locales/fr/agents/pm.md +7 -0
  118. package/template/.aioson/locales/fr/agents/product.md +13 -3
  119. package/template/.aioson/locales/fr/agents/qa.md +7 -0
  120. package/template/.aioson/locales/fr/agents/setup.md +28 -5
  121. package/template/.aioson/locales/fr/agents/squad.md +216 -10
  122. package/template/.aioson/locales/fr/agents/ux-ui.md +26 -25
  123. package/template/.aioson/locales/pt-BR/agents/analyst.md +26 -4
  124. package/template/.aioson/locales/pt-BR/agents/architect.md +10 -0
  125. package/template/.aioson/locales/pt-BR/agents/dev.md +93 -4
  126. package/template/.aioson/locales/pt-BR/agents/deyvin.md +129 -0
  127. package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
  128. package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
  129. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +36 -2
  130. package/template/.aioson/locales/pt-BR/agents/pair.md +5 -0
  131. package/template/.aioson/locales/pt-BR/agents/pm.md +7 -0
  132. package/template/.aioson/locales/pt-BR/agents/product.md +35 -17
  133. package/template/.aioson/locales/pt-BR/agents/qa.md +7 -0
  134. package/template/.aioson/locales/pt-BR/agents/setup.md +51 -5
  135. package/template/.aioson/locales/pt-BR/agents/squad.md +486 -47
  136. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +361 -22
  137. package/template/.aioson/my-agents/.gitkeep +0 -0
  138. package/template/.aioson/rules/.gitkeep +0 -0
  139. package/template/.aioson/rules/squad/.gitkeep +0 -0
  140. package/template/.aioson/rules/squad/README.md +50 -0
  141. package/template/.aioson/schemas/genome-meta.schema.json +1 -1
  142. package/template/.aioson/schemas/genome.schema.json +1 -1
  143. package/template/.aioson/schemas/squad-blueprint.schema.json +11 -0
  144. package/template/.aioson/schemas/squad-manifest.schema.json +257 -1
  145. package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +157 -0
  146. package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
  147. package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +172 -0
  148. package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +490 -0
  149. package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +237 -0
  150. package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +289 -0
  151. package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +350 -0
  152. package/template/.aioson/skills/design/interface-design/SKILL.md +47 -0
  153. package/template/.aioson/skills/design/interface-design/references/components-and-states.md +105 -0
  154. package/template/.aioson/skills/design/interface-design/references/design-directions.md +101 -0
  155. package/template/.aioson/skills/design/interface-design/references/handoff-and-quality.md +71 -0
  156. package/template/.aioson/skills/design/interface-design/references/intent-and-domain.md +74 -0
  157. package/template/.aioson/skills/design/interface-design/references/tokens-and-depth.md +173 -0
  158. package/template/.aioson/skills/design/premium-command-center-ui/SKILL.md +62 -0
  159. package/template/.aioson/skills/design/premium-command-center-ui/references/operations.md +74 -0
  160. package/template/.aioson/skills/design/premium-command-center-ui/references/patterns.md +116 -0
  161. package/template/.aioson/skills/design/premium-command-center-ui/references/validation.md +47 -0
  162. package/template/.aioson/skills/design/premium-command-center-ui/references/visual-system.md +215 -0
  163. package/template/.aioson/skills/design-system/SKILL.md +92 -0
  164. package/template/.aioson/skills/design-system/cognitive-core-ui.skill +0 -0
  165. package/template/.aioson/skills/design-system/components/SKILL.md +274 -0
  166. package/template/.aioson/skills/design-system/components/SKILL.md:Zone.Identifier +0 -0
  167. package/template/.aioson/skills/design-system/dashboards/SKILL.md +184 -0
  168. package/template/.aioson/skills/design-system/dashboards/SKILL.md:Zone.Identifier +0 -0
  169. package/template/.aioson/skills/design-system/foundations/SKILL.md +250 -0
  170. package/template/.aioson/skills/design-system/foundations/SKILL.md:Zone.Identifier +0 -0
  171. package/template/.aioson/skills/design-system/motion/SKILL.md +197 -0
  172. package/template/.aioson/skills/design-system/motion/SKILL.md:Zone.Identifier +0 -0
  173. package/template/.aioson/skills/design-system/patterns/SKILL.md +231 -0
  174. package/template/.aioson/skills/design-system/patterns/SKILL.md:Zone.Identifier +0 -0
  175. package/template/.aioson/skills/squad/SKILL.md +58 -0
  176. package/template/.aioson/skills/squad/domains/.gitkeep +0 -0
  177. package/template/.aioson/skills/squad/formats/.gitkeep +0 -0
  178. package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
  179. package/template/.aioson/skills/squad/references/.gitkeep +0 -0
  180. package/template/.aioson/tasks/implementation-plan.md +288 -0
  181. package/template/.aioson/tasks/squad-create.md +1 -1
  182. package/template/.aioson/tasks/squad-execution-plan.md +279 -0
  183. package/template/.aioson/tasks/squad-export.md +1 -1
  184. package/template/.aioson/tasks/squad-investigate.md +44 -0
  185. package/template/.aioson/tasks/squad-learning-review.md +44 -0
  186. package/template/.aioson/tasks/squad-output-config.md +177 -0
  187. package/template/.aioson/tasks/squad-validate.md +1 -1
  188. package/template/.claude/commands/aioson/agent/deyvin.md +5 -0
  189. package/template/.claude/commands/aioson/agent/discovery-design-doc.md +5 -0
  190. package/template/.claude/commands/aioson/agent/genome.md +5 -0
  191. package/template/.claude/commands/aioson/agent/product.md +5 -0
  192. package/template/.claude/commands/aioson/agent/profiler-enricher.md +5 -0
  193. package/template/.claude/commands/aioson/agent/profiler-forge.md +5 -0
  194. package/template/.claude/commands/aioson/agent/profiler-researcher.md +5 -0
  195. package/template/.claude/commands/aioson/agent/squad.md +5 -0
  196. package/template/.gemini/GEMINI.md +2 -0
  197. package/template/.gemini/commands/aios-deyvin.toml +6 -0
  198. package/template/.gemini/commands/aios-pair.toml +6 -0
  199. package/template/AGENTS.md +34 -6
  200. package/template/CLAUDE.md +31 -4
  201. package/template/OPENCODE.md +6 -2
  202. package/template/squad-searches/.gitkeep +0 -0
  203. package/template/.aioson/skills/static/interface-design.md +0 -372
  204. package/template/.aioson/skills/static/premium-command-center-ui.md +0 -190
  205. /package/template/.aioson/{genomas → docs}/.gitkeep +0 -0
  206. /package/template/.claude/commands/aioson/{analyst.md → agent/analyst.md} +0 -0
  207. /package/template/.claude/commands/aioson/{architect.md → agent/architect.md} +0 -0
  208. /package/template/.claude/commands/aioson/{dev.md → agent/dev.md} +0 -0
  209. /package/template/.claude/commands/aioson/{orchestrator.md → agent/orchestrator.md} +0 -0
  210. /package/template/.claude/commands/aioson/{pm.md → agent/pm.md} +0 -0
  211. /package/template/.claude/commands/aioson/{qa.md → agent/qa.md} +0 -0
  212. /package/template/.claude/commands/aioson/{setup.md → agent/setup.md} +0 -0
  213. /package/template/.claude/commands/aioson/{ux-ui.md → agent/ux-ui.md} +0 -0
@@ -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
  };