@jaimevalasek/aioson 1.3.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +22 -3
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- 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 +235 -23
- package/docs/pt/automacao-squads.md +407 -0
- package/docs/pt/cenarios.md +49 -5
- package/docs/pt/clientes-ai.md +62 -0
- package/docs/pt/comandos-cli.md +226 -17
- package/docs/pt/deyvin.md +115 -0
- package/docs/pt/genome-3.0-spec.md +11 -11
- package/docs/pt/inicio-rapido.md +63 -2
- 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-dashboard.md +373 -0
- package/docs/pt/{squad-genoma.md → squad-genome.md} +81 -75
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +10 -10
- package/package.json +4 -4
- package/src/agents.js +21 -5
- package/src/backup-local.js +74 -0
- package/src/backup-provider.js +303 -0
- package/src/cli.js +276 -2
- package/src/commands/agents.js +22 -4
- package/src/commands/backup-local-cmd.js +25 -0
- 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 +1075 -2
- package/src/commands/scan-project.js +288 -24
- package/src/commands/setup-context.js +30 -2
- package/src/commands/skill.js +558 -0
- package/src/commands/squad-agent-create.js +788 -0
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +103 -1
- package/src/commands/squad-investigate.js +261 -0
- package/src/commands/squad-learning.js +209 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-pipeline.js +247 -1
- package/src/commands/squad-plan.js +329 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +38 -2
- package/src/commands/squad-validate.js +118 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/test-agents.js +6 -1
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/commands/workflow-next.js +8 -1
- package/src/commands/workflow-status.js +250 -0
- package/src/constants.js +88 -16
- package/src/context-memory.js +837 -0
- package/src/context-writer.js +47 -1
- 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 +333 -8
- package/src/i18n/messages/es.js +240 -6
- package/src/i18n/messages/fr.js +239 -5
- package/src/i18n/messages/pt-BR.js +330 -12
- package/src/installer.js +30 -2
- package/src/lib/genomes/compat.js +1 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +1037 -42
- package/src/session-handoff.js +77 -0
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +40 -9
- package/template/.aioson/agents/architect.md +24 -5
- package/template/.aioson/agents/dev.md +254 -25
- package/template/.aioson/agents/deyvin.md +174 -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/neo.md +152 -0
- package/template/.aioson/agents/orache.md +388 -0
- package/template/.aioson/agents/orchestrator.md +63 -2
- package/template/.aioson/agents/pair.md +5 -0
- package/template/.aioson/agents/pm.md +17 -5
- package/template/.aioson/agents/product.md +113 -29
- 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 +18 -5
- package/template/.aioson/agents/setup.md +138 -18
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +866 -28
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +289 -34
- package/template/.aioson/config.md +181 -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 +34 -4
- package/template/.aioson/locales/en/agents/architect.md +18 -0
- package/template/.aioson/locales/en/agents/dev.md +155 -11
- package/template/.aioson/locales/en/agents/deyvin.md +137 -0
- package/template/.aioson/locales/en/agents/{genoma.md → genome.md} +14 -14
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +62 -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 +56 -0
- package/template/.aioson/locales/en/agents/setup.md +53 -6
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/squad.md +203 -15
- package/template/.aioson/locales/en/agents/ux-ui.md +383 -35
- package/template/.aioson/locales/es/agents/analyst.md +24 -4
- package/template/.aioson/locales/es/agents/architect.md +18 -0
- package/template/.aioson/locales/es/agents/dev.md +136 -9
- package/template/.aioson/locales/es/agents/deyvin.md +97 -0
- package/template/.aioson/locales/es/agents/{genoma.md → genome.md} +13 -13
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orache.md +103 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +62 -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 +33 -0
- package/template/.aioson/locales/es/agents/setup.md +30 -6
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +284 -15
- package/template/.aioson/locales/es/agents/ux-ui.md +34 -25
- package/template/.aioson/locales/fr/agents/analyst.md +24 -4
- package/template/.aioson/locales/fr/agents/architect.md +18 -0
- package/template/.aioson/locales/fr/agents/dev.md +136 -9
- package/template/.aioson/locales/fr/agents/deyvin.md +97 -0
- package/template/.aioson/locales/fr/agents/{genoma.md → genome.md} +7 -7
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orache.md +104 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +62 -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 +33 -0
- package/template/.aioson/locales/fr/agents/setup.md +30 -6
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +279 -10
- package/template/.aioson/locales/fr/agents/ux-ui.md +34 -25
- package/template/.aioson/locales/pt-BR/agents/analyst.md +45 -4
- package/template/.aioson/locales/pt-BR/agents/architect.md +29 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +167 -15
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/{genoma.md → genome.md} +49 -49
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orache.md +137 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +62 -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 +43 -20
- package/template/.aioson/locales/pt-BR/agents/qa.md +67 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +53 -6
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +591 -47
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +369 -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 +32 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +434 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +203 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +407 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +272 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +524 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +277 -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 +437 -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/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/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/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/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/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/.gitkeep +0 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/.gitkeep +0 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +307 -0
- package/template/.aioson/tasks/squad-create.md +1 -1
- package/template/.aioson/tasks/squad-design.md +28 -0
- 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-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.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/neo.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/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +3 -0
- package/template/.gemini/commands/aios-deyvin.toml +6 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-pair.toml +6 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +37 -6
- package/template/CLAUDE.md +34 -4
- package/template/OPENCODE.md +8 -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,14 @@ 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');
|
|
23
|
+
const { backupAiosonDocs, isDocCreatingAgent } = require('../backup-local');
|
|
18
24
|
|
|
19
25
|
const ALLOWED_LAYOUTS = new Set(['document', 'tabs', 'accordion', 'stack', 'mixed']);
|
|
20
26
|
const DEFAULT_TEXT_FIELDS = ['content', 'text', 'body', 'lyrics', 'markdown'];
|
|
@@ -31,6 +37,152 @@ function requireOption(options, key, t) {
|
|
|
31
37
|
return String(value).trim();
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
function normalizeAgentHandle(value) {
|
|
41
|
+
const text = String(value || '').trim();
|
|
42
|
+
if (!text) return '';
|
|
43
|
+
return text.startsWith('@') ? text : `@${text}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeDirectSessionKey(agentName) {
|
|
47
|
+
return `direct-session:${Date.now()}:${String(agentName || '').replace(/^@/, '')}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseWatchSeconds(value) {
|
|
51
|
+
if (value === undefined || value === null || value === false) return null;
|
|
52
|
+
if (value === true || value === '') return 2;
|
|
53
|
+
|
|
54
|
+
const parsed = Number(value);
|
|
55
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return 2;
|
|
56
|
+
return parsed;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sleep(ms) {
|
|
60
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function collectRuntimeSessionSnapshot(db, runtimeDir, agentName, options = {}) {
|
|
64
|
+
const normalizedAgent = normalizeAgentHandle(agentName);
|
|
65
|
+
const eventLimit = Math.max(1, Math.min(Number(options.limit) || 8, 20));
|
|
66
|
+
const session = await readAgentSession(runtimeDir, normalizedAgent);
|
|
67
|
+
const activeSession = session && !session.finished ? session : null;
|
|
68
|
+
|
|
69
|
+
let run = null;
|
|
70
|
+
if (activeSession && activeSession.runKey) {
|
|
71
|
+
run = db.prepare(`
|
|
72
|
+
SELECT
|
|
73
|
+
run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
74
|
+
title, status, summary, output_path, started_at, updated_at, finished_at
|
|
75
|
+
FROM agent_runs
|
|
76
|
+
WHERE run_key = ?
|
|
77
|
+
LIMIT 1
|
|
78
|
+
`).get(activeSession.runKey);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!run) {
|
|
82
|
+
run = db.prepare(`
|
|
83
|
+
SELECT
|
|
84
|
+
run_key, task_key, agent_name, agent_kind, squad_slug, session_key, source,
|
|
85
|
+
title, status, summary, output_path, started_at, updated_at, finished_at
|
|
86
|
+
FROM agent_runs
|
|
87
|
+
WHERE agent_name = ?
|
|
88
|
+
ORDER BY updated_at DESC, started_at DESC
|
|
89
|
+
LIMIT 1
|
|
90
|
+
`).get(normalizedAgent);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const task = run && run.task_key
|
|
94
|
+
? db.prepare(`
|
|
95
|
+
SELECT
|
|
96
|
+
task_key, squad_slug, session_key, title, goal, status, created_by, created_at, updated_at, finished_at
|
|
97
|
+
FROM tasks
|
|
98
|
+
WHERE task_key = ?
|
|
99
|
+
LIMIT 1
|
|
100
|
+
`).get(run.task_key)
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
const recentEvents = run
|
|
104
|
+
? db.prepare(`
|
|
105
|
+
SELECT event_type, phase, status, message, created_at
|
|
106
|
+
FROM execution_events
|
|
107
|
+
WHERE run_key = ?
|
|
108
|
+
ORDER BY created_at DESC, id DESC
|
|
109
|
+
LIMIT ?
|
|
110
|
+
`).all(run.run_key, eventLimit).reverse()
|
|
111
|
+
: [];
|
|
112
|
+
|
|
113
|
+
const open = Boolean(activeSession && run && (run.status === 'running' || run.status === 'queued'));
|
|
114
|
+
const state = open ? 'open' : (run ? 'closed' : 'idle');
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
agent: normalizedAgent,
|
|
118
|
+
state,
|
|
119
|
+
open,
|
|
120
|
+
sessionKey: activeSession?.sessionKey || run?.session_key || task?.session_key || null,
|
|
121
|
+
startedAt: activeSession?.startedAt || run?.started_at || task?.created_at || null,
|
|
122
|
+
updatedAt: run?.updated_at || task?.updated_at || null,
|
|
123
|
+
session: activeSession,
|
|
124
|
+
run,
|
|
125
|
+
task,
|
|
126
|
+
recentEvents
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function getRuntimeSessionSnapshot(targetDir, agentName, t, options = {}) {
|
|
131
|
+
const { dbPath, runtimeDir } = resolveRuntimePaths(targetDir);
|
|
132
|
+
|
|
133
|
+
if (!(await runtimeStoreExists(targetDir))) {
|
|
134
|
+
throw new Error(t('runtime.store_missing', { path: dbPath }));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { db } = await openRuntimeDb(targetDir, { mustExist: true });
|
|
138
|
+
try {
|
|
139
|
+
const snapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, options);
|
|
140
|
+
return {
|
|
141
|
+
ok: true,
|
|
142
|
+
targetDir,
|
|
143
|
+
dbPath,
|
|
144
|
+
...snapshot
|
|
145
|
+
};
|
|
146
|
+
} finally {
|
|
147
|
+
db.close();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function printRuntimeSessionSnapshot(snapshot, logger) {
|
|
152
|
+
logger.log(`Direct session: ${snapshot.agent}`);
|
|
153
|
+
logger.log(`State: ${snapshot.state}`);
|
|
154
|
+
|
|
155
|
+
if (snapshot.sessionKey) {
|
|
156
|
+
logger.log(`Session: ${snapshot.sessionKey}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (snapshot.task) {
|
|
160
|
+
logger.log(`Task: ${snapshot.task.task_key} | status: ${snapshot.task.status} | work: ${snapshot.task.title || '—'}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (snapshot.run) {
|
|
164
|
+
logger.log(`Run: ${snapshot.run.run_key} | status: ${snapshot.run.status} | work: ${snapshot.run.title || snapshot.run.summary || '—'}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (snapshot.startedAt) {
|
|
168
|
+
logger.log(`Started: ${snapshot.startedAt}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (snapshot.updatedAt) {
|
|
172
|
+
logger.log(`Updated: ${snapshot.updatedAt}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (snapshot.recentEvents.length === 0) {
|
|
176
|
+
logger.log('Recent events: none');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
logger.log('Recent events:');
|
|
181
|
+
for (const event of snapshot.recentEvents) {
|
|
182
|
+
logger.log(`- ${event.created_at} | ${event.event_type} | ${event.message || '—'}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
34
186
|
async function readJsonIfExists(filePath) {
|
|
35
187
|
try {
|
|
36
188
|
const raw = await fs.readFile(filePath, 'utf8');
|
|
@@ -418,6 +570,14 @@ async function ingestContentCandidate(db, targetDir, absolutePath, options = {})
|
|
|
418
570
|
createdByAgent: options.agent || content.createdByAgent || content.created_by_agent || null
|
|
419
571
|
});
|
|
420
572
|
|
|
573
|
+
// Fire auto-delivery if configured (non-blocking)
|
|
574
|
+
runAutoDelivery(db, {
|
|
575
|
+
projectDir: targetDir,
|
|
576
|
+
squadSlug,
|
|
577
|
+
contentKey: content.contentKey,
|
|
578
|
+
contentPayload: content
|
|
579
|
+
}).catch(() => {}); // Swallow errors — delivery failure should not break ingestion
|
|
580
|
+
|
|
421
581
|
return { indexed: true, kind: 'content-json', contentKey: content.contentKey };
|
|
422
582
|
}
|
|
423
583
|
|
|
@@ -458,6 +618,14 @@ async function ingestContentCandidate(db, targetDir, absolutePath, options = {})
|
|
|
458
618
|
createdByAgent: options.agent || null
|
|
459
619
|
});
|
|
460
620
|
|
|
621
|
+
// Fire auto-delivery if configured (non-blocking)
|
|
622
|
+
runAutoDelivery(db, {
|
|
623
|
+
projectDir: targetDir,
|
|
624
|
+
squadSlug,
|
|
625
|
+
contentKey: payload.contentKey,
|
|
626
|
+
contentPayload: payload
|
|
627
|
+
}).catch(() => {}); // Swallow errors — delivery failure should not break ingestion
|
|
628
|
+
|
|
461
629
|
return { indexed: true, kind: path.extname(absolutePath).toLowerCase(), contentKey: payload.contentKey };
|
|
462
630
|
}
|
|
463
631
|
|
|
@@ -820,6 +988,11 @@ async function runRuntimeStatus({ args, options = {}, logger, t }) {
|
|
|
820
988
|
recentTasks: snapshot.recentTasks,
|
|
821
989
|
activeRuns: snapshot.activeRuns,
|
|
822
990
|
recentRuns: snapshot.recentRuns,
|
|
991
|
+
activeLiveSessions: snapshot.activeLiveSessions,
|
|
992
|
+
activeMicroTasks: snapshot.activeMicroTasks,
|
|
993
|
+
recentLiveSessions: snapshot.recentLiveSessions,
|
|
994
|
+
recentMicroTasks: snapshot.recentMicroTasks,
|
|
995
|
+
recentHandoffs: snapshot.recentHandoffs,
|
|
823
996
|
recentArtifacts: snapshot.recentArtifacts,
|
|
824
997
|
recentContentItems: snapshot.recentContentItems,
|
|
825
998
|
recentExecutionEvents: snapshot.recentExecutionEvents
|
|
@@ -874,6 +1047,49 @@ async function runRuntimeStatus({ args, options = {}, logger, t }) {
|
|
|
874
1047
|
);
|
|
875
1048
|
}
|
|
876
1049
|
}
|
|
1050
|
+
if (snapshot.activeLiveSessions.length > 0) {
|
|
1051
|
+
logger.log(t('runtime.status_live_sessions_title'));
|
|
1052
|
+
for (const task of snapshot.activeLiveSessions) {
|
|
1053
|
+
logger.log(
|
|
1054
|
+
t('runtime.status_live_session_line', {
|
|
1055
|
+
task: task.task_key,
|
|
1056
|
+
agent: task.latest_agent_name || task.created_by || '—',
|
|
1057
|
+
status: task.status,
|
|
1058
|
+
plan: task.plan_steps_total > 0 ? `${task.plan_steps_done}/${task.plan_steps_total}` : '—',
|
|
1059
|
+
micro: `${task.completed_child_task_count || 0}/${task.child_task_count || 0}`,
|
|
1060
|
+
handoffs: task.handoff_count || 0,
|
|
1061
|
+
title: task.title || '—'
|
|
1062
|
+
})
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (snapshot.activeMicroTasks.length > 0) {
|
|
1067
|
+
logger.log(t('runtime.status_micro_tasks_title'));
|
|
1068
|
+
for (const task of snapshot.activeMicroTasks) {
|
|
1069
|
+
logger.log(
|
|
1070
|
+
t('runtime.status_micro_task_line', {
|
|
1071
|
+
task: task.task_key,
|
|
1072
|
+
parent: task.parent_task_key || '—',
|
|
1073
|
+
status: task.status,
|
|
1074
|
+
title: task.title || task.goal || '—'
|
|
1075
|
+
})
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (snapshot.recentHandoffs.length > 0) {
|
|
1080
|
+
logger.log(t('runtime.status_handoffs_title'));
|
|
1081
|
+
for (const event of snapshot.recentHandoffs.slice(0, 5)) {
|
|
1082
|
+
logger.log(
|
|
1083
|
+
t('runtime.status_handoff_line', {
|
|
1084
|
+
created: event.created_at,
|
|
1085
|
+
from: event.handoff_from || event.agent_name || '—',
|
|
1086
|
+
to: event.handoff_to || '—',
|
|
1087
|
+
session: event.session_key || '—',
|
|
1088
|
+
message: event.message || '—'
|
|
1089
|
+
})
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
877
1093
|
}
|
|
878
1094
|
|
|
879
1095
|
return payload;
|
|
@@ -911,6 +1127,16 @@ async function runRuntimeLog({ args, options = {}, logger, t }) {
|
|
|
911
1127
|
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
912
1128
|
});
|
|
913
1129
|
|
|
1130
|
+
// Generate session handoff on --finish
|
|
1131
|
+
if (options.finish) {
|
|
1132
|
+
const handoffData = buildRuntimeLogHandoff(
|
|
1133
|
+
agentName,
|
|
1134
|
+
options.message || '',
|
|
1135
|
+
options.summary || ''
|
|
1136
|
+
);
|
|
1137
|
+
await writeHandoff(targetDir, handoffData);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
914
1140
|
if (!options.json) {
|
|
915
1141
|
const isFinish = Boolean(options.finish);
|
|
916
1142
|
logger.log(isFinish
|
|
@@ -933,6 +1159,842 @@ async function runRuntimeLog({ args, options = {}, logger, t }) {
|
|
|
933
1159
|
}
|
|
934
1160
|
}
|
|
935
1161
|
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* aioson agent:done . --agent=<name> --summary="..." [--title="..."] [--status=completed|failed]
|
|
1165
|
+
*
|
|
1166
|
+
* Safe self-registration for official agents invoked directly (not via workflow:next or live:start).
|
|
1167
|
+
* - If an active live session exists for the agent: appends a completion event without closing the session.
|
|
1168
|
+
* - If no session exists: creates a standalone task+run and immediately marks it completed.
|
|
1169
|
+
*
|
|
1170
|
+
* Intended to be called ONCE at the very end of an agent session, after delivering the main artifact.
|
|
1171
|
+
*/
|
|
1172
|
+
async function runAgentDone({ args, options = {}, logger, t }) {
|
|
1173
|
+
const targetDir = resolveTargetDir(args);
|
|
1174
|
+
const agentName = String(options.agent || '').trim();
|
|
1175
|
+
if (!agentName) {
|
|
1176
|
+
throw new Error('--agent is required');
|
|
1177
|
+
}
|
|
1178
|
+
const normalizedAgent = agentName.startsWith('@') ? agentName : `@${agentName}`;
|
|
1179
|
+
const summary = String(options.summary || options.message || `${normalizedAgent} session completed`).trim();
|
|
1180
|
+
const title = options.title ? String(options.title).trim() : null;
|
|
1181
|
+
const status = options.status || 'completed';
|
|
1182
|
+
|
|
1183
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1184
|
+
|
|
1185
|
+
try {
|
|
1186
|
+
const session = await readAgentSession(runtimeDir, normalizedAgent);
|
|
1187
|
+
const hasActiveSession = session && !session.finished && session.runKey;
|
|
1188
|
+
|
|
1189
|
+
if (hasActiveSession) {
|
|
1190
|
+
// Live or tracked session is already open — only append a completion note.
|
|
1191
|
+
// Do NOT close the session: live:handoff or live:close owns the lifecycle.
|
|
1192
|
+
appendRunEvent(db, {
|
|
1193
|
+
runKey: session.runKey,
|
|
1194
|
+
eventType: 'agent_done',
|
|
1195
|
+
phase: 'live',
|
|
1196
|
+
status: 'running',
|
|
1197
|
+
message: summary
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
if (!options.json) {
|
|
1201
|
+
logger.log(`agent:done — ${normalizedAgent} | live session active, event logged | run: ${session.runKey} (${dbPath})`);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1205
|
+
backupAiosonDocs(targetDir).catch(() => {});
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'live_event', runKey: session.runKey };
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// No active session — create a standalone task+run and immediately complete it.
|
|
1212
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1213
|
+
agentName: normalizedAgent,
|
|
1214
|
+
message: summary,
|
|
1215
|
+
type: 'completed',
|
|
1216
|
+
taskTitle: title || normalizedAgent,
|
|
1217
|
+
finish: true,
|
|
1218
|
+
status,
|
|
1219
|
+
summary
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
if (!options.json) {
|
|
1223
|
+
logger.log(`agent:done — ${normalizedAgent} | task: ${taskKey} | run: ${runKey} (${dbPath})`);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1227
|
+
backupAiosonDocs(targetDir).catch(() => {});
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'standalone', runKey, taskKey };
|
|
1231
|
+
} finally {
|
|
1232
|
+
db.close();
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
async function runRuntimeSessionStart({ args, options = {}, logger, t }) {
|
|
1238
|
+
const targetDir = resolveTargetDir(args);
|
|
1239
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1240
|
+
|
|
1241
|
+
try {
|
|
1242
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1243
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1244
|
+
|
|
1245
|
+
if (existingSnapshot.session && !existingSnapshot.open) {
|
|
1246
|
+
await clearAgentSession(runtimeDir, agentName);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
if (existingSnapshot.open) {
|
|
1250
|
+
if (!options.json) {
|
|
1251
|
+
logger.log(`Direct session already active: ${agentName} | task: ${existingSnapshot.task?.task_key || '—'} | run: ${existingSnapshot.run?.run_key || '—'} (${dbPath})`);
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
ok: true,
|
|
1255
|
+
targetDir,
|
|
1256
|
+
dbPath,
|
|
1257
|
+
agent: agentName,
|
|
1258
|
+
taskKey: existingSnapshot.task?.task_key || existingSnapshot.session?.taskKey || null,
|
|
1259
|
+
runKey: existingSnapshot.run?.run_key || existingSnapshot.session?.runKey || null,
|
|
1260
|
+
sessionKey: existingSnapshot.sessionKey,
|
|
1261
|
+
status: existingSnapshot.run?.status || 'running',
|
|
1262
|
+
reused: true,
|
|
1263
|
+
open: true
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const sessionKey = options.session ? String(options.session).trim() : makeDirectSessionKey(agentName);
|
|
1268
|
+
const title = options.title ? String(options.title).trim() : `Direct session ${agentName}`;
|
|
1269
|
+
const message = options.message ? String(options.message).trim() : `Session started for ${agentName}`;
|
|
1270
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1271
|
+
agentName,
|
|
1272
|
+
message,
|
|
1273
|
+
type: options.type || 'session.start',
|
|
1274
|
+
taskTitle: title,
|
|
1275
|
+
sessionKey,
|
|
1276
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
if (!options.json) {
|
|
1280
|
+
logger.log(`Direct session started: ${agentName} | task: ${taskKey} | run: ${runKey} (${dbPath})`);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
return {
|
|
1284
|
+
ok: true,
|
|
1285
|
+
targetDir,
|
|
1286
|
+
dbPath,
|
|
1287
|
+
agent: agentName,
|
|
1288
|
+
taskKey,
|
|
1289
|
+
runKey,
|
|
1290
|
+
sessionKey,
|
|
1291
|
+
status: 'running',
|
|
1292
|
+
reused: false,
|
|
1293
|
+
open: true
|
|
1294
|
+
};
|
|
1295
|
+
} finally {
|
|
1296
|
+
db.close();
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
async function runRuntimeSessionLog({ args, options = {}, logger, t }) {
|
|
1301
|
+
const targetDir = resolveTargetDir(args);
|
|
1302
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1303
|
+
|
|
1304
|
+
try {
|
|
1305
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1306
|
+
const message = requireOption(options, 'message', t);
|
|
1307
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1308
|
+
|
|
1309
|
+
if (existingSnapshot.session && !existingSnapshot.open) {
|
|
1310
|
+
await clearAgentSession(runtimeDir, agentName);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const autoStarted = !existingSnapshot.open;
|
|
1314
|
+
const sessionKey = existingSnapshot.sessionKey || (options.session ? String(options.session).trim() : makeDirectSessionKey(agentName));
|
|
1315
|
+
const title = options.title ? String(options.title).trim() : `Direct session ${agentName}`;
|
|
1316
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1317
|
+
agentName,
|
|
1318
|
+
message,
|
|
1319
|
+
type: options.type || 'session.log',
|
|
1320
|
+
taskTitle: title,
|
|
1321
|
+
sessionKey,
|
|
1322
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
if (!options.json) {
|
|
1326
|
+
logger.log(`Direct session log recorded: ${agentName} | run: ${runKey} (${dbPath})`);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
return {
|
|
1330
|
+
ok: true,
|
|
1331
|
+
targetDir,
|
|
1332
|
+
dbPath,
|
|
1333
|
+
agent: agentName,
|
|
1334
|
+
taskKey,
|
|
1335
|
+
runKey,
|
|
1336
|
+
sessionKey,
|
|
1337
|
+
status: 'running',
|
|
1338
|
+
autoStarted,
|
|
1339
|
+
open: true
|
|
1340
|
+
};
|
|
1341
|
+
} finally {
|
|
1342
|
+
db.close();
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
async function runRuntimeSessionFinish({ args, options = {}, logger, t }) {
|
|
1347
|
+
const targetDir = resolveTargetDir(args);
|
|
1348
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1349
|
+
|
|
1350
|
+
try {
|
|
1351
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1352
|
+
const existingSnapshot = await collectRuntimeSessionSnapshot(db, runtimeDir, agentName, { limit: options.limit });
|
|
1353
|
+
|
|
1354
|
+
if (!existingSnapshot.open) {
|
|
1355
|
+
throw new Error(`No active direct session for ${agentName}.`);
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const summary = options.summary ? String(options.summary).trim() : '';
|
|
1359
|
+
const message = options.message ? String(options.message).trim() : (summary || `Session finished for ${agentName}`);
|
|
1360
|
+
const { runKey, taskKey } = await logAgentEvent(db, runtimeDir, {
|
|
1361
|
+
agentName,
|
|
1362
|
+
message,
|
|
1363
|
+
type: options.type || 'session.finish',
|
|
1364
|
+
finish: true,
|
|
1365
|
+
status: options.status || 'completed',
|
|
1366
|
+
summary,
|
|
1367
|
+
meta: options.meta ? (() => { try { return JSON.parse(options.meta); } catch { return { raw: options.meta }; } })() : undefined
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
if (!options.json) {
|
|
1371
|
+
logger.log(`Direct session finished: ${agentName} | run: ${runKey} (${dbPath})`);
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
return {
|
|
1375
|
+
ok: true,
|
|
1376
|
+
targetDir,
|
|
1377
|
+
dbPath,
|
|
1378
|
+
agent: agentName,
|
|
1379
|
+
taskKey,
|
|
1380
|
+
runKey,
|
|
1381
|
+
sessionKey: existingSnapshot.sessionKey,
|
|
1382
|
+
status: options.status || 'completed',
|
|
1383
|
+
finished: true,
|
|
1384
|
+
open: false
|
|
1385
|
+
};
|
|
1386
|
+
} finally {
|
|
1387
|
+
db.close();
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
async function runRuntimeSessionStatus({ args, options = {}, logger, t }) {
|
|
1392
|
+
const targetDir = resolveTargetDir(args);
|
|
1393
|
+
const agentName = normalizeAgentHandle(requireOption(options, 'agent', t));
|
|
1394
|
+
const watchSeconds = parseWatchSeconds(options.watch);
|
|
1395
|
+
|
|
1396
|
+
if (watchSeconds && options.json) {
|
|
1397
|
+
throw new Error('--watch cannot be combined with --json.');
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
if (!watchSeconds) {
|
|
1401
|
+
const snapshot = await getRuntimeSessionSnapshot(targetDir, agentName, t, { limit: options.limit });
|
|
1402
|
+
if (!options.json) {
|
|
1403
|
+
printRuntimeSessionSnapshot(snapshot, logger);
|
|
1404
|
+
}
|
|
1405
|
+
return snapshot;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
while (true) {
|
|
1409
|
+
const snapshot = await getRuntimeSessionSnapshot(targetDir, agentName, t, { limit: options.limit });
|
|
1410
|
+
if (process.stdout && process.stdout.isTTY) {
|
|
1411
|
+
process.stdout.write('\x1Bc');
|
|
1412
|
+
}
|
|
1413
|
+
printRuntimeSessionSnapshot(snapshot, logger);
|
|
1414
|
+
await sleep(Math.round(watchSeconds * 1000));
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
async function runDeliver({ args, options = {}, logger, t }) {
|
|
1419
|
+
const targetDir = resolveTargetDir(args);
|
|
1420
|
+
const squadSlug = requireOption(options, 'squad', t);
|
|
1421
|
+
const contentKey = options['content-key'] || options.contentKey || null;
|
|
1422
|
+
const triggerType = options.trigger || 'manual';
|
|
1423
|
+
|
|
1424
|
+
const { db, dbPath } = await withRuntimeDb(targetDir, t);
|
|
1425
|
+
|
|
1426
|
+
try {
|
|
1427
|
+
const { runManualDelivery } = require('../delivery-runner');
|
|
1428
|
+
|
|
1429
|
+
// Optionally load content payload from DB
|
|
1430
|
+
let contentPayload = null;
|
|
1431
|
+
if (contentKey) {
|
|
1432
|
+
const row = db.prepare('SELECT payload_json FROM content_items WHERE content_key = ? AND squad_slug = ?').get(contentKey, squadSlug);
|
|
1433
|
+
if (row && row.payload_json) {
|
|
1434
|
+
try { contentPayload = JSON.parse(row.payload_json); } catch { /* ignore */ }
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const result = await runManualDelivery(db, {
|
|
1439
|
+
projectDir: targetDir,
|
|
1440
|
+
squadSlug,
|
|
1441
|
+
contentKey,
|
|
1442
|
+
triggerType,
|
|
1443
|
+
contentPayload
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
if (!result.delivered) {
|
|
1447
|
+
logger.log(`Delivery skipped: ${result.reason}`);
|
|
1448
|
+
return { ok: false, ...result };
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
for (const r of result.results || []) {
|
|
1452
|
+
const status = r.ok ? 'OK' : 'FAIL';
|
|
1453
|
+
logger.log(` ${status} ${r.webhookSlug} — ${r.statusCode || 'no response'} (${r.attempts} attempt${r.attempts > 1 ? 's' : ''})`);
|
|
1454
|
+
if (r.error) logger.log(` Error: ${r.error}`);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
logger.log(`\nDelivery ${result.allOk ? 'completed' : 'completed with errors'}.`);
|
|
1458
|
+
return { ok: result.allOk, ...result };
|
|
1459
|
+
} finally {
|
|
1460
|
+
db.close();
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
async function findManifestPath(projectDir, slug) {
|
|
1465
|
+
const candidates = [
|
|
1466
|
+
path.join(projectDir, '.aioson', 'squads', slug, 'squad.manifest.json'),
|
|
1467
|
+
path.join(projectDir, 'agents', slug, 'squad.manifest.json')
|
|
1468
|
+
];
|
|
1469
|
+
for (const p of candidates) {
|
|
1470
|
+
try { await fs.stat(p); return p; } catch { continue; }
|
|
1471
|
+
}
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
async function runOutputStrategyExport({ args, options = {}, logger, t }) {
|
|
1476
|
+
const projectDir = resolveTargetDir(args);
|
|
1477
|
+
const slug = requireOption(options, 'squad', t);
|
|
1478
|
+
const manifestPath = await findManifestPath(projectDir, slug);
|
|
1479
|
+
|
|
1480
|
+
if (!manifestPath) {
|
|
1481
|
+
logger.error(`Manifest not found for squad "${slug}"`);
|
|
1482
|
+
return { ok: false, error: 'Manifest not found' };
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
const raw = await fs.readFile(manifestPath, 'utf8');
|
|
1486
|
+
const manifest = JSON.parse(raw);
|
|
1487
|
+
const strategy = manifest.outputStrategy || null;
|
|
1488
|
+
|
|
1489
|
+
if (!strategy) {
|
|
1490
|
+
logger.log(`Squad "${slug}" has no outputStrategy configured.`);
|
|
1491
|
+
return { ok: false, error: 'No outputStrategy found' };
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const exportsDir = path.join(projectDir, '.aioson', 'squads', 'exports');
|
|
1495
|
+
await fs.mkdir(exportsDir, { recursive: true });
|
|
1496
|
+
const outFile = path.join(exportsDir, `${slug}.output-strategy.json`);
|
|
1497
|
+
await fs.writeFile(outFile, JSON.stringify(strategy, null, 2) + '\n', 'utf8');
|
|
1498
|
+
|
|
1499
|
+
const relOut = path.relative(projectDir, outFile).replace(/\\/g, '/');
|
|
1500
|
+
logger.log(`Exported outputStrategy from "${slug}" → ${relOut}`);
|
|
1501
|
+
return { ok: true, file: relOut, strategy };
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
async function runOutputStrategyImport({ args, options = {}, logger, t }) {
|
|
1505
|
+
const projectDir = resolveTargetDir(args);
|
|
1506
|
+
const slug = requireOption(options, 'squad', t);
|
|
1507
|
+
const fromSlug = options.from || null;
|
|
1508
|
+
const fromFile = options.file || null;
|
|
1509
|
+
|
|
1510
|
+
if (!fromSlug && !fromFile) {
|
|
1511
|
+
logger.error('Usage: aioson output-strategy:import --squad=<target> --from=<source-slug> | --file=<path>');
|
|
1512
|
+
return { ok: false, error: 'Provide --from or --file' };
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
// Load source strategy
|
|
1516
|
+
let strategy;
|
|
1517
|
+
if (fromFile) {
|
|
1518
|
+
const absFile = path.resolve(projectDir, fromFile);
|
|
1519
|
+
const raw = await fs.readFile(absFile, 'utf8');
|
|
1520
|
+
strategy = JSON.parse(raw);
|
|
1521
|
+
} else {
|
|
1522
|
+
const srcPath = await findManifestPath(projectDir, fromSlug);
|
|
1523
|
+
if (!srcPath) {
|
|
1524
|
+
logger.error(`Source squad "${fromSlug}" manifest not found`);
|
|
1525
|
+
return { ok: false, error: 'Source manifest not found' };
|
|
1526
|
+
}
|
|
1527
|
+
const srcManifest = JSON.parse(await fs.readFile(srcPath, 'utf8'));
|
|
1528
|
+
strategy = srcManifest.outputStrategy || null;
|
|
1529
|
+
if (!strategy) {
|
|
1530
|
+
logger.error(`Source squad "${fromSlug}" has no outputStrategy`);
|
|
1531
|
+
return { ok: false, error: 'Source has no outputStrategy' };
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
// Write to target
|
|
1536
|
+
const targetPath = await findManifestPath(projectDir, slug);
|
|
1537
|
+
if (!targetPath) {
|
|
1538
|
+
logger.error(`Target squad "${slug}" manifest not found`);
|
|
1539
|
+
return { ok: false, error: 'Target manifest not found' };
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
const targetManifest = JSON.parse(await fs.readFile(targetPath, 'utf8'));
|
|
1543
|
+
targetManifest.outputStrategy = strategy;
|
|
1544
|
+
await fs.writeFile(targetPath, JSON.stringify(targetManifest, null, 2) + '\n', 'utf8');
|
|
1545
|
+
|
|
1546
|
+
logger.log(`Imported outputStrategy into "${slug}" from ${fromSlug || fromFile}`);
|
|
1547
|
+
return { ok: true, squad: slug, source: fromSlug || fromFile };
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* aioson devlog:sync [targetDir]
|
|
1552
|
+
*
|
|
1553
|
+
* Parses aioson-logs/devlog-*.md files, imports them into SQLite as
|
|
1554
|
+
* task + run + events, then renames each file to .synced so it is not
|
|
1555
|
+
* re-imported on subsequent runs.
|
|
1556
|
+
*/
|
|
1557
|
+
async function runDevlogSync({ args, options = {}, logger, t }) {
|
|
1558
|
+
const targetDir = resolveTargetDir(args);
|
|
1559
|
+
const logsDir = path.join(targetDir, 'aioson-logs');
|
|
1560
|
+
|
|
1561
|
+
let entries;
|
|
1562
|
+
try {
|
|
1563
|
+
entries = await fs.readdir(logsDir);
|
|
1564
|
+
} catch {
|
|
1565
|
+
logger.log('No aioson-logs/ directory found — nothing to sync.');
|
|
1566
|
+
return { ok: true, synced: 0 };
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const devlogFiles = entries
|
|
1570
|
+
.filter(f => f.startsWith('devlog-') && f.endsWith('.md'))
|
|
1571
|
+
.sort();
|
|
1572
|
+
|
|
1573
|
+
if (devlogFiles.length === 0) {
|
|
1574
|
+
logger.log('No devlog files to sync.');
|
|
1575
|
+
return { ok: true, synced: 0 };
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
const { db, dbPath } = await openRuntimeDb(targetDir);
|
|
1579
|
+
let synced = 0;
|
|
1580
|
+
const parsedDevlogs = [];
|
|
1581
|
+
|
|
1582
|
+
try {
|
|
1583
|
+
for (const file of devlogFiles) {
|
|
1584
|
+
const filePath = path.join(logsDir, file);
|
|
1585
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
1586
|
+
|
|
1587
|
+
// Parse YAML frontmatter
|
|
1588
|
+
const fm = parseFrontmatter(raw);
|
|
1589
|
+
const agent = fm.agent || 'unknown';
|
|
1590
|
+
const summary = fm.summary || file;
|
|
1591
|
+
const sessionStart = fm.session_start || null;
|
|
1592
|
+
const sessionEnd = fm.session_end || null;
|
|
1593
|
+
const status = fm.status || 'completed';
|
|
1594
|
+
const body = raw.replace(/^---[\s\S]*?---\s*/, '');
|
|
1595
|
+
|
|
1596
|
+
parsedDevlogs.push({ filename: file, agent, summary, sessionStart, sessionEnd, status, body });
|
|
1597
|
+
|
|
1598
|
+
// Create task + run
|
|
1599
|
+
const taskKey = startTask(db, {
|
|
1600
|
+
title: `devlog: ${summary}`,
|
|
1601
|
+
squadSlug: null,
|
|
1602
|
+
status: status === 'partial' ? 'running' : 'completed',
|
|
1603
|
+
createdBy: agent
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
const runKey = startRun(db, {
|
|
1607
|
+
taskKey,
|
|
1608
|
+
agentName: agent,
|
|
1609
|
+
agentKind: 'devlog',
|
|
1610
|
+
squadSlug: null,
|
|
1611
|
+
title: `@${agent} devlog`,
|
|
1612
|
+
message: summary
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
// Extract body sections as events
|
|
1616
|
+
const sections = body.split(/^## /m).filter(Boolean);
|
|
1617
|
+
for (const section of sections) {
|
|
1618
|
+
const firstLine = section.split('\n')[0].trim();
|
|
1619
|
+
const content = section.slice(firstLine.length).trim();
|
|
1620
|
+
if (content) {
|
|
1621
|
+
appendRunEvent(db, {
|
|
1622
|
+
runKey,
|
|
1623
|
+
eventType: 'devlog',
|
|
1624
|
+
phase: firstLine.toLowerCase().replace(/\s+/g, '_'),
|
|
1625
|
+
status: 'completed',
|
|
1626
|
+
message: `## ${firstLine}\n${content}`,
|
|
1627
|
+
createdAt: sessionEnd || new Date().toISOString()
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Close the run
|
|
1633
|
+
updateRun(db, runKey, {
|
|
1634
|
+
status: status === 'partial' ? 'running' : 'completed',
|
|
1635
|
+
summary,
|
|
1636
|
+
finishedAt: sessionEnd || new Date().toISOString()
|
|
1637
|
+
});
|
|
1638
|
+
|
|
1639
|
+
if (status !== 'partial') {
|
|
1640
|
+
updateTask(db, taskKey, {
|
|
1641
|
+
status: 'completed',
|
|
1642
|
+
finishedAt: sessionEnd || new Date().toISOString()
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
// Rename to .synced
|
|
1647
|
+
await fs.rename(filePath, filePath.replace(/\.md$/, '.synced.md'));
|
|
1648
|
+
synced++;
|
|
1649
|
+
logger.log(` Synced: ${file} → task=${taskKey} run=${runKey}`);
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
logger.log(`Synced ${synced} devlog(s) into ${dbPath}`);
|
|
1653
|
+
|
|
1654
|
+
// Cloud sync
|
|
1655
|
+
if (options.cloud) {
|
|
1656
|
+
const cloudResult = await syncDevlogsToCloud(targetDir, parsedDevlogs, options, logger);
|
|
1657
|
+
return { ok: true, synced, dbPath, cloud: cloudResult };
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
return { ok: true, synced, dbPath };
|
|
1661
|
+
} finally {
|
|
1662
|
+
db.close();
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Sends parsed devlogs to the cloud endpoint.
|
|
1668
|
+
* Reads cloud config from .aioson/install.json or --url / --token options.
|
|
1669
|
+
*/
|
|
1670
|
+
async function syncDevlogsToCloud(targetDir, devlogs, options, logger) {
|
|
1671
|
+
const cloudUrl = options.url || options['cloud-url'] || await resolveCloudUrl(targetDir);
|
|
1672
|
+
const cloudToken = options.token || options['cloud-token'] || await resolveCloudToken(targetDir);
|
|
1673
|
+
|
|
1674
|
+
if (!cloudUrl) {
|
|
1675
|
+
logger.error('Cloud URL not configured. Use --url or set cloudBaseUrl in dashboard project settings.');
|
|
1676
|
+
return { ok: false, error: 'missing_cloud_url' };
|
|
1677
|
+
}
|
|
1678
|
+
if (!cloudToken) {
|
|
1679
|
+
logger.error('Cloud token not configured. Use --token or set cloudApiToken in dashboard project settings.');
|
|
1680
|
+
return { ok: false, error: 'missing_cloud_token' };
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
const endpoint = `${cloudUrl.replace(/\/+$/, '')}/api/publish/runtime`;
|
|
1684
|
+
const payload = {
|
|
1685
|
+
tasks: [],
|
|
1686
|
+
devlogs: devlogs.map(d => ({
|
|
1687
|
+
filename: d.filename,
|
|
1688
|
+
agent: d.agent,
|
|
1689
|
+
sessionStart: d.sessionStart,
|
|
1690
|
+
sessionEnd: d.sessionEnd,
|
|
1691
|
+
status: d.status,
|
|
1692
|
+
summary: d.summary,
|
|
1693
|
+
body: d.body
|
|
1694
|
+
}))
|
|
1695
|
+
};
|
|
1696
|
+
|
|
1697
|
+
logger.log(` Pushing ${devlogs.length} devlog(s) to ${endpoint}...`);
|
|
1698
|
+
|
|
1699
|
+
const response = await fetch(endpoint, {
|
|
1700
|
+
method: 'POST',
|
|
1701
|
+
headers: {
|
|
1702
|
+
'accept': 'application/json',
|
|
1703
|
+
'content-type': 'application/json',
|
|
1704
|
+
'authorization': `Bearer ${cloudToken}`
|
|
1705
|
+
},
|
|
1706
|
+
body: JSON.stringify(payload),
|
|
1707
|
+
signal: AbortSignal.timeout(15000)
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
const text = await response.text();
|
|
1711
|
+
let result;
|
|
1712
|
+
try { result = JSON.parse(text); } catch { result = { ok: false, error: text }; }
|
|
1713
|
+
|
|
1714
|
+
if (result.ok) {
|
|
1715
|
+
logger.log(` Cloud sync OK: ${result.devlogsStored || 0} devlog(s) stored.`);
|
|
1716
|
+
} else {
|
|
1717
|
+
logger.error(` Cloud sync failed: ${result.error || response.status}`);
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
return result;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
async function resolveCloudUrl(targetDir) {
|
|
1724
|
+
try {
|
|
1725
|
+
const raw = await fs.readFile(path.join(targetDir, '.aioson/install.json'), 'utf8');
|
|
1726
|
+
const meta = JSON.parse(raw);
|
|
1727
|
+
return meta.cloudBaseUrl || null;
|
|
1728
|
+
} catch { return null; }
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
async function resolveCloudToken(targetDir) {
|
|
1732
|
+
try {
|
|
1733
|
+
const raw = await fs.readFile(path.join(targetDir, '.aioson/install.json'), 'utf8');
|
|
1734
|
+
const meta = JSON.parse(raw);
|
|
1735
|
+
return meta.cloudApiToken || null;
|
|
1736
|
+
} catch { return null; }
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Minimal YAML frontmatter parser (no external deps).
|
|
1741
|
+
* Returns an object with frontmatter keys, or {} if none.
|
|
1742
|
+
*/
|
|
1743
|
+
function parseFrontmatter(content) {
|
|
1744
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
1745
|
+
if (!match) return {};
|
|
1746
|
+
const result = {};
|
|
1747
|
+
for (const line of match[1].split('\n')) {
|
|
1748
|
+
const idx = line.indexOf(':');
|
|
1749
|
+
if (idx === -1) continue;
|
|
1750
|
+
const key = line.slice(0, idx).trim();
|
|
1751
|
+
let val = line.slice(idx + 1).trim();
|
|
1752
|
+
// Strip surrounding quotes
|
|
1753
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
1754
|
+
val = val.slice(1, -1);
|
|
1755
|
+
}
|
|
1756
|
+
result[key] = val;
|
|
1757
|
+
}
|
|
1758
|
+
return result;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
/**
|
|
1762
|
+
* Parses a duration string like "24h", "30m", "7d" into milliseconds.
|
|
1763
|
+
* Falls back to treating the raw value as hours.
|
|
1764
|
+
*/
|
|
1765
|
+
function parseDurationMs(value, defaultHours = 24) {
|
|
1766
|
+
const text = String(value || '').trim().toLowerCase();
|
|
1767
|
+
if (!text) return defaultHours * 60 * 60 * 1000;
|
|
1768
|
+
|
|
1769
|
+
const match = text.match(/^(\d+(?:\.\d+)?)\s*([hmd]?)$/);
|
|
1770
|
+
if (!match) return defaultHours * 60 * 60 * 1000;
|
|
1771
|
+
|
|
1772
|
+
const n = parseFloat(match[1]);
|
|
1773
|
+
const unit = match[2] || 'h';
|
|
1774
|
+
if (unit === 'd') return n * 24 * 60 * 60 * 1000;
|
|
1775
|
+
if (unit === 'm') return n * 60 * 1000;
|
|
1776
|
+
return n * 60 * 60 * 1000; // hours (default)
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* aioson agent:recover [targetDir] [--older-than=<duration>] [--dry-run]
|
|
1781
|
+
*
|
|
1782
|
+
* Detects and closes agent sessions that were abandoned (Claude Code closed before
|
|
1783
|
+
* agent:done was called, or live:start session was never closed).
|
|
1784
|
+
*
|
|
1785
|
+
* Sources checked:
|
|
1786
|
+
* 1. Session files in .aioson/.sessions/ with finished=false older than threshold.
|
|
1787
|
+
* 2. agent_runs rows with status='running'/'queued' and started_at older than threshold
|
|
1788
|
+
* that have no corresponding live session file (orphaned DB records).
|
|
1789
|
+
*
|
|
1790
|
+
* --older-than Duration threshold. Accepts: 24h (default), 8h, 30m, 7d.
|
|
1791
|
+
* --dry-run Report what would be recovered without making any changes.
|
|
1792
|
+
* --json Output JSON result.
|
|
1793
|
+
*/
|
|
1794
|
+
async function runAgentRecover({ args, options = {}, logger }) {
|
|
1795
|
+
const targetDir = resolveTargetDir(args);
|
|
1796
|
+
const dryRun = Boolean(options['dry-run'] || options.dryRun);
|
|
1797
|
+
const olderThanMs = parseDurationMs(options['older-than'] || options.olderThan, 24);
|
|
1798
|
+
const cutoffMs = Date.now() - olderThanMs;
|
|
1799
|
+
const cutoffIso = new Date(cutoffMs).toISOString();
|
|
1800
|
+
const now = new Date().toISOString();
|
|
1801
|
+
|
|
1802
|
+
const { db, dbPath, runtimeDir } = await openRuntimeDb(targetDir);
|
|
1803
|
+
|
|
1804
|
+
const recovered = [];
|
|
1805
|
+
const skipped = [];
|
|
1806
|
+
|
|
1807
|
+
try {
|
|
1808
|
+
// ── 1. Scan session files ─────────────────────────────────────────────────
|
|
1809
|
+
const sessionsDir = path.join(runtimeDir, '.sessions');
|
|
1810
|
+
let sessionFiles = [];
|
|
1811
|
+
try {
|
|
1812
|
+
const entries = await fs.readdir(sessionsDir);
|
|
1813
|
+
sessionFiles = entries.filter((f) => f.endsWith('.json'));
|
|
1814
|
+
} catch {
|
|
1815
|
+
// .sessions dir may not exist — that's fine
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
for (const file of sessionFiles) {
|
|
1819
|
+
const filePath = path.join(sessionsDir, file);
|
|
1820
|
+
let session;
|
|
1821
|
+
try {
|
|
1822
|
+
session = JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
1823
|
+
} catch {
|
|
1824
|
+
continue;
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
if (session.finished) continue;
|
|
1828
|
+
|
|
1829
|
+
const startedAt = session.startedAt ? new Date(session.startedAt).getTime() : 0;
|
|
1830
|
+
if (startedAt > cutoffMs) {
|
|
1831
|
+
skipped.push({ source: 'session_file', file, reason: 'within_threshold', startedAt: session.startedAt });
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
const agentName = file.replace(/\.json$/, '');
|
|
1836
|
+
const runKey = session.runKey || null;
|
|
1837
|
+
const taskKey = session.taskKey || null;
|
|
1838
|
+
|
|
1839
|
+
if (!dryRun) {
|
|
1840
|
+
// Mark run as abandoned
|
|
1841
|
+
if (runKey) {
|
|
1842
|
+
const runRow = db.prepare('SELECT run_key, status FROM agent_runs WHERE run_key = ?').get(runKey);
|
|
1843
|
+
if (runRow && (runRow.status === 'running' || runRow.status === 'queued')) {
|
|
1844
|
+
db.prepare(`
|
|
1845
|
+
UPDATE agent_runs
|
|
1846
|
+
SET status = 'abandoned', summary = 'Recovered: session abandoned without close', updated_at = ?, finished_at = ?
|
|
1847
|
+
WHERE run_key = ?
|
|
1848
|
+
`).run(now, now, runKey);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
// Mark task as abandoned
|
|
1852
|
+
if (taskKey) {
|
|
1853
|
+
const taskRow = db.prepare('SELECT task_key, status FROM tasks WHERE task_key = ?').get(taskKey);
|
|
1854
|
+
if (taskRow && (taskRow.status === 'running' || taskRow.status === 'queued')) {
|
|
1855
|
+
db.prepare(`
|
|
1856
|
+
UPDATE tasks
|
|
1857
|
+
SET status = 'abandoned', updated_at = ?, finished_at = ?
|
|
1858
|
+
WHERE task_key = ?
|
|
1859
|
+
`).run(now, now, taskKey);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
// Remove session file
|
|
1863
|
+
try { await fs.unlink(filePath); } catch { /* noop */ }
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
recovered.push({ source: 'session_file', agent: agentName, runKey, taskKey, startedAt: session.startedAt });
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
// ── 2. Scan DB for orphaned running runs (no session file) ────────────────
|
|
1870
|
+
const orphanedRuns = db.prepare(`
|
|
1871
|
+
SELECT run_key, task_key, agent_name, started_at
|
|
1872
|
+
FROM agent_runs
|
|
1873
|
+
WHERE status IN ('running', 'queued')
|
|
1874
|
+
AND source = 'direct'
|
|
1875
|
+
AND started_at < ?
|
|
1876
|
+
`).all(cutoffIso);
|
|
1877
|
+
|
|
1878
|
+
for (const run of orphanedRuns) {
|
|
1879
|
+
// Skip if already recovered via session file
|
|
1880
|
+
if (recovered.some((r) => r.runKey === run.run_key)) continue;
|
|
1881
|
+
|
|
1882
|
+
if (!dryRun) {
|
|
1883
|
+
db.prepare(`
|
|
1884
|
+
UPDATE agent_runs
|
|
1885
|
+
SET status = 'abandoned', summary = 'Recovered: orphaned run with no session file', updated_at = ?, finished_at = ?
|
|
1886
|
+
WHERE run_key = ?
|
|
1887
|
+
`).run(now, now, run.run_key);
|
|
1888
|
+
|
|
1889
|
+
if (run.task_key) {
|
|
1890
|
+
const taskRow = db.prepare('SELECT task_key, status FROM tasks WHERE task_key = ?').get(run.task_key);
|
|
1891
|
+
if (taskRow && (taskRow.status === 'running' || taskRow.status === 'queued')) {
|
|
1892
|
+
db.prepare(`
|
|
1893
|
+
UPDATE tasks
|
|
1894
|
+
SET status = 'abandoned', updated_at = ?, finished_at = ?
|
|
1895
|
+
WHERE task_key = ?
|
|
1896
|
+
`).run(now, now, run.task_key);
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
recovered.push({ source: 'orphaned_run', agent: run.agent_name, runKey: run.run_key, taskKey: run.task_key, startedAt: run.started_at });
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// ── Output ────────────────────────────────────────────────────────────────
|
|
1905
|
+
const olderThanLabel = options['older-than'] || options.olderThan || '24h';
|
|
1906
|
+
if (recovered.length === 0) {
|
|
1907
|
+
logger.log(`agent:recover — no abandoned sessions found older than ${olderThanLabel} (${dbPath})`);
|
|
1908
|
+
} else {
|
|
1909
|
+
const verb = dryRun ? '[dry-run] would recover' : 'recovered';
|
|
1910
|
+
logger.log(`agent:recover — ${verb} ${recovered.length} abandoned session(s) older than ${olderThanLabel} (${dbPath})`);
|
|
1911
|
+
for (const r of recovered) {
|
|
1912
|
+
logger.log(` ${r.agent} started: ${r.startedAt || '?'} run: ${r.runKey || '—'} [${r.source}]`);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (skipped.length > 0) {
|
|
1916
|
+
logger.log(` skipped ${skipped.length} session(s) within threshold.`);
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
return { ok: true, targetDir, dbPath, dryRun, cutoff: cutoffIso, recovered, skipped };
|
|
1920
|
+
} finally {
|
|
1921
|
+
db.close();
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
|
|
1926
|
+
/**
|
|
1927
|
+
* aioson runtime:prune [targetDir] --older-than=<days>
|
|
1928
|
+
*
|
|
1929
|
+
* Removes execution_events, agent_events, and completed agent_runs
|
|
1930
|
+
* older than the specified number of days. Tasks are kept but their
|
|
1931
|
+
* events are cleaned up.
|
|
1932
|
+
*/
|
|
1933
|
+
async function runRuntimePrune({ args, options = {}, logger, t }) {
|
|
1934
|
+
const targetDir = resolveTargetDir(args);
|
|
1935
|
+
const days = parseInt(options['older-than'] || options.olderThan || '30', 10);
|
|
1936
|
+
|
|
1937
|
+
if (isNaN(days) || days < 1) {
|
|
1938
|
+
logger.error('Usage: aioson runtime:prune --older-than=<days> (minimum 1)');
|
|
1939
|
+
return { ok: false, error: 'Invalid --older-than value' };
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
const { db, dbPath } = await withRuntimeDb(targetDir, t);
|
|
1943
|
+
|
|
1944
|
+
try {
|
|
1945
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
|
1946
|
+
|
|
1947
|
+
const execEvents = db.prepare(
|
|
1948
|
+
`DELETE FROM execution_events WHERE created_at < ?`
|
|
1949
|
+
).run(cutoff);
|
|
1950
|
+
|
|
1951
|
+
const agentEvents = db.prepare(
|
|
1952
|
+
`DELETE FROM agent_events WHERE created_at < ?`
|
|
1953
|
+
).run(cutoff);
|
|
1954
|
+
|
|
1955
|
+
const runs = db.prepare(
|
|
1956
|
+
`DELETE FROM agent_runs WHERE status IN ('completed', 'failed') AND finished_at < ?`
|
|
1957
|
+
).run(cutoff);
|
|
1958
|
+
|
|
1959
|
+
const tasks = db.prepare(
|
|
1960
|
+
`DELETE FROM tasks WHERE status IN ('completed', 'failed') AND finished_at < ?`
|
|
1961
|
+
).run(cutoff);
|
|
1962
|
+
|
|
1963
|
+
const deliveryLogs = db.prepare(
|
|
1964
|
+
`DELETE FROM delivery_log WHERE created_at < ?`
|
|
1965
|
+
).run(cutoff);
|
|
1966
|
+
|
|
1967
|
+
// Reclaim disk space
|
|
1968
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
1969
|
+
|
|
1970
|
+
const total = execEvents.changes + agentEvents.changes + runs.changes + tasks.changes + deliveryLogs.changes;
|
|
1971
|
+
|
|
1972
|
+
logger.log(`Pruned ${total} records older than ${days} days from ${dbPath}:`);
|
|
1973
|
+
logger.log(` execution_events: ${execEvents.changes}`);
|
|
1974
|
+
logger.log(` agent_events: ${agentEvents.changes}`);
|
|
1975
|
+
logger.log(` agent_runs: ${runs.changes}`);
|
|
1976
|
+
logger.log(` tasks: ${tasks.changes}`);
|
|
1977
|
+
logger.log(` delivery_log: ${deliveryLogs.changes}`);
|
|
1978
|
+
|
|
1979
|
+
return {
|
|
1980
|
+
ok: true,
|
|
1981
|
+
dbPath,
|
|
1982
|
+
days,
|
|
1983
|
+
cutoff,
|
|
1984
|
+
deleted: {
|
|
1985
|
+
execution_events: execEvents.changes,
|
|
1986
|
+
agent_events: agentEvents.changes,
|
|
1987
|
+
agent_runs: runs.changes,
|
|
1988
|
+
tasks: tasks.changes,
|
|
1989
|
+
delivery_log: deliveryLogs.changes,
|
|
1990
|
+
total
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1993
|
+
} finally {
|
|
1994
|
+
db.close();
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
936
1998
|
module.exports = {
|
|
937
1999
|
runRuntimeInit,
|
|
938
2000
|
runRuntimeIngest,
|
|
@@ -944,5 +2006,16 @@ module.exports = {
|
|
|
944
2006
|
runRuntimeTaskFail,
|
|
945
2007
|
runRuntimeFail,
|
|
946
2008
|
runRuntimeStatus,
|
|
947
|
-
runRuntimeLog
|
|
2009
|
+
runRuntimeLog,
|
|
2010
|
+
runAgentDone,
|
|
2011
|
+
runAgentRecover,
|
|
2012
|
+
runRuntimeSessionStart,
|
|
2013
|
+
runRuntimeSessionLog,
|
|
2014
|
+
runRuntimeSessionFinish,
|
|
2015
|
+
runRuntimeSessionStatus,
|
|
2016
|
+
runDeliver,
|
|
2017
|
+
runOutputStrategyExport,
|
|
2018
|
+
runOutputStrategyImport,
|
|
2019
|
+
runDevlogSync,
|
|
2020
|
+
runRuntimePrune
|
|
948
2021
|
};
|