@jaimevalasek/aioson 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +9 -1
- package/docs/design-previews/aurora-command-ui-website.html +884 -0
- package/docs/design-previews/aurora-command-ui.html +682 -0
- package/docs/design-previews/bold-editorial-ui-website.html +658 -0
- package/docs/design-previews/bold-editorial-ui.html +717 -0
- package/docs/design-previews/clean-saas-ui-website.html +1202 -0
- package/docs/design-previews/clean-saas-ui.html +549 -0
- package/docs/design-previews/cognitive-core-ui-website.html +1009 -0
- package/docs/design-previews/cognitive-core-ui.html +463 -0
- package/docs/design-previews/glassmorphism-ui-website.html +572 -0
- package/docs/design-previews/glassmorphism-ui.html +886 -0
- package/docs/design-previews/index.html +699 -0
- package/docs/design-previews/interface-design-website.html +1187 -0
- package/docs/design-previews/interface-design.html +513 -0
- package/docs/design-previews/neo-brutalist-ui-website.html +621 -0
- package/docs/design-previews/neo-brutalist-ui.html +797 -0
- package/docs/design-previews/premium-command-center-ui-website.html +1217 -0
- package/docs/design-previews/premium-command-center-ui.html +552 -0
- package/docs/design-previews/warm-craft-ui-website.html +684 -0
- package/docs/design-previews/warm-craft-ui.html +739 -0
- package/docs/en/cli-reference.md +20 -9
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/README.md +7 -0
- package/docs/pt/agent-sharding.md +132 -0
- package/docs/pt/agentes.md +131 -11
- package/docs/pt/busca-de-contexto.md +129 -0
- package/docs/pt/cache-de-contexto.md +156 -0
- package/docs/pt/cenarios.md +46 -2
- package/docs/pt/comandos-cli.md +88 -1
- package/docs/pt/design-hybrid-forge.md +107 -0
- package/docs/pt/inicio-rapido.md +72 -5
- package/docs/pt/inteligencia-adaptativa.md +324 -0
- package/docs/pt/monitor-de-contexto.md +104 -0
- package/docs/pt/recuperacao-de-sessao.md +125 -0
- package/docs/pt/sandbox.md +125 -0
- package/docs/pt/skills.md +98 -6
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +9 -9
- package/package.json +2 -2
- package/src/agent-loader.js +280 -0
- package/src/backup-local.js +74 -0
- package/src/cli.js +192 -0
- package/src/commands/agent-loader.js +85 -0
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/context-cache.js +90 -0
- package/src/commands/context-monitor.js +92 -0
- package/src/commands/context-search.js +66 -0
- package/src/commands/design-hybrid-options.js +385 -0
- package/src/commands/health.js +214 -0
- package/src/commands/init.js +54 -13
- package/src/commands/install.js +52 -13
- package/src/commands/learning-evolve.js +355 -0
- package/src/commands/live.js +34 -0
- package/src/commands/recovery.js +43 -0
- package/src/commands/runtime.js +242 -0
- package/src/commands/sandbox.js +37 -0
- package/src/commands/setup-context.js +29 -4
- package/src/commands/setup.js +178 -0
- package/src/commands/skill.js +79 -32
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +52 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +37 -1
- package/src/commands/squad-validate.js +62 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/tool-registry-cmd.js +232 -0
- package/src/commands/update.js +7 -0
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/constants.js +17 -0
- package/src/context-cache.js +159 -0
- package/src/context-search.js +326 -0
- package/src/context-writer.js +45 -1
- package/src/design-variation-catalog.js +503 -0
- package/src/i18n/messages/en.js +159 -3
- package/src/i18n/messages/es.js +147 -2
- package/src/i18n/messages/fr.js +147 -2
- package/src/i18n/messages/pt-BR.js +158 -3
- package/src/install-animation.js +260 -0
- package/src/install-profile.js +143 -0
- package/src/install-wizard.js +474 -0
- package/src/installer.js +38 -10
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/parser.js +7 -1
- package/src/recovery-context-session.js +154 -0
- package/src/runtime-store.js +355 -2
- package/src/sandbox.js +177 -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/tool-executor.js +94 -0
- package/src/updater.js +11 -3
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +62 -3
- package/template/.aioson/agents/architect.md +42 -0
- package/template/.aioson/agents/design-hybrid-forge.md +127 -0
- package/template/.aioson/agents/dev.md +223 -11
- package/template/.aioson/agents/deyvin.md +65 -0
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +17 -0
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/pm.md +58 -0
- package/template/.aioson/agents/product.md +88 -12
- package/template/.aioson/agents/qa.md +80 -0
- package/template/.aioson/agents/setup.md +128 -22
- package/template/.aioson/agents/sheldon.md +704 -0
- package/template/.aioson/agents/squad.md +191 -0
- package/template/.aioson/agents/tester.md +410 -0
- package/template/.aioson/agents/ux-ui.md +12 -0
- package/template/.aioson/config.md +21 -0
- package/template/.aioson/context/forensics/.gitkeep +0 -0
- package/template/.aioson/context/seeds/seed-example.md +27 -0
- package/template/.aioson/context/user-profile.md +42 -0
- package/template/.aioson/locales/en/agents/analyst.md +8 -0
- package/template/.aioson/locales/en/agents/architect.md +8 -0
- package/template/.aioson/locales/en/agents/dev.md +66 -7
- package/template/.aioson/locales/en/agents/deyvin.md +8 -0
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/en/agents/qa.md +49 -0
- package/template/.aioson/locales/en/agents/setup.md +35 -2
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/es/agents/analyst.md +8 -0
- package/template/.aioson/locales/es/agents/architect.md +8 -0
- package/template/.aioson/locales/es/agents/dev.md +66 -7
- package/template/.aioson/locales/es/agents/deyvin.md +8 -0
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/qa.md +26 -0
- package/template/.aioson/locales/es/agents/setup.md +35 -2
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +63 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/fr/agents/analyst.md +8 -0
- package/template/.aioson/locales/fr/agents/architect.md +8 -0
- package/template/.aioson/locales/fr/agents/dev.md +66 -7
- package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/qa.md +26 -0
- package/template/.aioson/locales/fr/agents/setup.md +35 -2
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +63 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
- package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +35 -2
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
- package/template/.aioson/skills/design/aurora-command-ui/SKILL.md +243 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/art-direction.md +293 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/components.md +827 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/dashboards.md +250 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/design-tokens.md +585 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/motion.md +365 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/patterns.md +482 -0
- package/template/.aioson/skills/design/aurora-command-ui/references/websites.md +387 -0
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
- package/template/.aioson/skills/design/glassmorphism-ui/SKILL.md +222 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/art-direction.md +159 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/components.md +498 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/dashboards.md +236 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/design-tokens.md +274 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/motion.md +355 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/patterns.md +198 -0
- package/template/.aioson/skills/design/glassmorphism-ui/references/websites.md +307 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/SKILL.md +213 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/art-direction.md +228 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/components.md +855 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/dashboards.md +334 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/design-tokens.md +342 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/motion.md +286 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/patterns.md +458 -0
- package/template/.aioson/skills/design/neo-brutalist-ui/references/websites.md +723 -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/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/process/aioson-spec-driven/SKILL.md +45 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +109 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +44 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/classification-map.md +37 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/hardening-lane.md +49 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/maintenance-and-state.md +66 -0
- package/template/.aioson/skills/process/aioson-spec-driven/references/ui-language.md +75 -0
- package/template/.aioson/skills/process/design-hybrid-forge/SKILL.md +144 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/crossover-protocol.md +221 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/naming-registry.md +88 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/output-contract.md +291 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/pair-compatibility.md +117 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/quality-gates.md +188 -0
- package/template/.aioson/skills/process/design-hybrid-forge/references/variation-library.md +125 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +19 -0
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +1 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +26 -1
- package/template/CLAUDE.md +6 -2
- package/template/OPENCODE.md +2 -0
package/src/sandbox.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
6
|
+
const MAX_OUTPUT_BYTES = 5 * 1024; // 5KB before summarization
|
|
7
|
+
|
|
8
|
+
// Credential redaction patterns — covers the most common secret formats
|
|
9
|
+
const REDACTION_PATTERNS = [
|
|
10
|
+
// GitHub tokens (classic and fine-grained)
|
|
11
|
+
{ pattern: /ghp_[A-Za-z0-9]{36}/g, replacement: 'ghp_[REDACTED]' },
|
|
12
|
+
{ pattern: /github_pat_[A-Za-z0-9_]{82}/g, replacement: 'github_pat_[REDACTED]' },
|
|
13
|
+
// AWS access keys
|
|
14
|
+
{ pattern: /AKIA[0-9A-Z]{16}/g, replacement: 'AKIA[REDACTED]' },
|
|
15
|
+
// Google OAuth tokens
|
|
16
|
+
{ pattern: /ya29\.[A-Za-z0-9_\-]{50,}/g, replacement: 'ya29.[REDACTED]' },
|
|
17
|
+
// Generic Bearer tokens in headers
|
|
18
|
+
{ pattern: /Bearer\s+[A-Za-z0-9\-._~+\/]+=*/gi, replacement: 'Bearer [REDACTED]' },
|
|
19
|
+
// Password in URL (e.g. postgres://user:password@host)
|
|
20
|
+
{ pattern: /:[^/:@\s]{4,}@[a-z0-9.\-]+(?::\d+)?/gi, replacement: ':[REDACTED]@host' },
|
|
21
|
+
// Generic password= key=value pairs
|
|
22
|
+
{ pattern: /password\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'password=[REDACTED]' },
|
|
23
|
+
{ pattern: /passwd\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'passwd=[REDACTED]' },
|
|
24
|
+
// secret= key=value pairs
|
|
25
|
+
{ pattern: /secret\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'secret=[REDACTED]' },
|
|
26
|
+
// api_key= or apikey= patterns
|
|
27
|
+
{ pattern: /api[_-]?key\s*=\s*["']?[^\s"'&;,]{4,}["']?/gi, replacement: 'api_key=[REDACTED]' },
|
|
28
|
+
// Private key blocks
|
|
29
|
+
{ pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g, replacement: '-----BEGIN PRIVATE KEY [REDACTED] END PRIVATE KEY-----' },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Redact known credential patterns from a string.
|
|
34
|
+
* @param {string} text
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function redactCredentials(text) {
|
|
38
|
+
if (!text) return text;
|
|
39
|
+
let result = text;
|
|
40
|
+
for (const { pattern, replacement } of REDACTION_PATTERNS) {
|
|
41
|
+
result = result.replace(pattern, replacement);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Summarize long output to stay within size budget.
|
|
48
|
+
* @param {string} output
|
|
49
|
+
* @param {string} intent — optional context about what was executed
|
|
50
|
+
* @param {number} maxSize — max bytes to return
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function summarizeOutput(output, intent = '', maxSize = MAX_OUTPUT_BYTES) {
|
|
54
|
+
if (!output || output.length <= maxSize) return output;
|
|
55
|
+
|
|
56
|
+
const half = Math.floor(maxSize / 2);
|
|
57
|
+
const head = output.slice(0, half);
|
|
58
|
+
const tail = output.slice(-half);
|
|
59
|
+
const omitted = output.length - maxSize;
|
|
60
|
+
const intentNote = intent ? ` (${intent})` : '';
|
|
61
|
+
|
|
62
|
+
return `${head}\n\n[... ${omitted} bytes omitted${intentNote} ...]\n\n${tail}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Execute a shell command in a sandboxed subprocess with timeout and redaction.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} command — shell command to run
|
|
69
|
+
* @param {object} opts — { cwd?, timeout?, env?, maxOutput?, intent?, shell? }
|
|
70
|
+
* @returns {{ ok: boolean, stdout: string, stderr: string, exitCode: number|null, timedOut: boolean }}
|
|
71
|
+
*/
|
|
72
|
+
async function executeInSandbox(command, opts = {}) {
|
|
73
|
+
const timeout = opts.timeout || DEFAULT_TIMEOUT_MS;
|
|
74
|
+
const cwd = opts.cwd || process.cwd();
|
|
75
|
+
const maxOutput = opts.maxOutput || MAX_OUTPUT_BYTES;
|
|
76
|
+
const intent = opts.intent || command.slice(0, 60);
|
|
77
|
+
const shell = opts.shell !== false; // default true
|
|
78
|
+
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
let timedOut = false;
|
|
82
|
+
let killed = false;
|
|
83
|
+
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
timedOut = true;
|
|
86
|
+
controller.abort();
|
|
87
|
+
}, timeout);
|
|
88
|
+
|
|
89
|
+
const stdoutChunks = [];
|
|
90
|
+
const stderrChunks = [];
|
|
91
|
+
let stdoutSize = 0;
|
|
92
|
+
let stderrSize = 0;
|
|
93
|
+
|
|
94
|
+
const baseOpts = {
|
|
95
|
+
cwd,
|
|
96
|
+
env: { ...process.env, ...(opts.env || {}) },
|
|
97
|
+
signal: controller.signal
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
let child;
|
|
101
|
+
try {
|
|
102
|
+
if (shell) {
|
|
103
|
+
// Use Node's built-in shell wrapping
|
|
104
|
+
child = spawn(command, [], { ...baseOpts, shell: true });
|
|
105
|
+
} else {
|
|
106
|
+
const parts = command.split(/\s+/);
|
|
107
|
+
child = spawn(parts[0], parts.slice(1), { ...baseOpts, shell: false });
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
resolve({ ok: false, stdout: '', stderr: err.message, exitCode: null, timedOut: false, error: err.message });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
child.stdout.on('data', (chunk) => {
|
|
116
|
+
if (stdoutSize < maxOutput * 2) {
|
|
117
|
+
stdoutChunks.push(chunk);
|
|
118
|
+
stdoutSize += chunk.length;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
child.stderr.on('data', (chunk) => {
|
|
123
|
+
if (stderrSize < maxOutput * 2) {
|
|
124
|
+
stderrChunks.push(chunk);
|
|
125
|
+
stderrSize += chunk.length;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
child.on('close', (code, signal) => {
|
|
130
|
+
if (killed) return;
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
|
|
133
|
+
const rawStdout = Buffer.concat(stdoutChunks).toString('utf8');
|
|
134
|
+
const rawStderr = Buffer.concat(stderrChunks).toString('utf8');
|
|
135
|
+
|
|
136
|
+
const stdout = redactCredentials(summarizeOutput(rawStdout, intent, maxOutput));
|
|
137
|
+
const stderr = redactCredentials(summarizeOutput(rawStderr, intent, maxOutput));
|
|
138
|
+
|
|
139
|
+
resolve({
|
|
140
|
+
ok: !timedOut && code === 0,
|
|
141
|
+
stdout,
|
|
142
|
+
stderr,
|
|
143
|
+
exitCode: code,
|
|
144
|
+
timedOut,
|
|
145
|
+
signal: signal || null
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
child.on('error', (err) => {
|
|
150
|
+
if (killed) return;
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
|
|
153
|
+
if (err.code === 'ABORT_ERR' || timedOut) {
|
|
154
|
+
resolve({
|
|
155
|
+
ok: false,
|
|
156
|
+
stdout: '',
|
|
157
|
+
stderr: `Command timed out after ${timeout}ms`,
|
|
158
|
+
exitCode: null,
|
|
159
|
+
timedOut: true,
|
|
160
|
+
signal: null
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
resolve({
|
|
164
|
+
ok: false,
|
|
165
|
+
stdout: '',
|
|
166
|
+
stderr: err.message,
|
|
167
|
+
exitCode: null,
|
|
168
|
+
timedOut: false,
|
|
169
|
+
error: err.message
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
killed = true;
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { executeInSandbox, redactCredentials, summarizeOutput };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SESSIONS_DIR = path.join('.aioson', 'sessions');
|
|
7
|
+
const DEFAULT_TTL_HOURS = 24;
|
|
8
|
+
|
|
9
|
+
function sanitizeSessionId(sessionId) {
|
|
10
|
+
// Replace anything that isn't alphanumeric, hyphen, or underscore with a dash
|
|
11
|
+
return String(sessionId)
|
|
12
|
+
.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
13
|
+
.replace(/-{2,}/g, '-')
|
|
14
|
+
.replace(/^-|-$/g, '')
|
|
15
|
+
.slice(0, 128);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sessionPath(projectDir, sessionId) {
|
|
19
|
+
return path.join(projectDir, SESSIONS_DIR, `${sanitizeSessionId(sessionId)}.json`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function ensureSessionsDir(projectDir) {
|
|
23
|
+
const dir = path.join(projectDir, SESSIONS_DIR);
|
|
24
|
+
await fs.mkdir(dir, { recursive: true });
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Load a session by ID. Returns null if not found or expired.
|
|
30
|
+
* @param {string} projectDir
|
|
31
|
+
* @param {string} sessionId
|
|
32
|
+
* @param {number} [ttlHours]
|
|
33
|
+
* @returns {Promise<object|null>}
|
|
34
|
+
*/
|
|
35
|
+
async function loadSession(projectDir, sessionId, ttlHours = DEFAULT_TTL_HOURS) {
|
|
36
|
+
const filePath = sessionPath(projectDir, sessionId);
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = await fs.readFile(filePath, 'utf8');
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let session;
|
|
45
|
+
try {
|
|
46
|
+
session = JSON.parse(raw);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check TTL
|
|
52
|
+
if (ttlHours > 0 && session.last_active) {
|
|
53
|
+
const idleMs = Date.now() - new Date(session.last_active).getTime();
|
|
54
|
+
if (idleMs > ttlHours * 3_600_000) {
|
|
55
|
+
// Expired — delete and return null
|
|
56
|
+
await fs.unlink(filePath).catch(() => {});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return session;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Save (overwrite) a session file atomically.
|
|
66
|
+
* @param {string} projectDir
|
|
67
|
+
* @param {object} session Must have session.session_id
|
|
68
|
+
*/
|
|
69
|
+
async function saveSession(projectDir, session) {
|
|
70
|
+
await ensureSessionsDir(projectDir);
|
|
71
|
+
const filePath = sessionPath(projectDir, session.session_id);
|
|
72
|
+
const tmpPath = filePath + '.tmp';
|
|
73
|
+
await fs.writeFile(tmpPath, JSON.stringify(session, null, 2), 'utf8');
|
|
74
|
+
await fs.rename(tmpPath, filePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Append a turn to a session. Creates the session file if it doesn't exist.
|
|
79
|
+
* @param {string} projectDir
|
|
80
|
+
* @param {string} sessionId
|
|
81
|
+
* @param {'user'|'assistant'} role
|
|
82
|
+
* @param {string} content
|
|
83
|
+
* @param {object} [metadata] Merged into session.metadata on first creation
|
|
84
|
+
* @param {number} [ttlHours]
|
|
85
|
+
* @returns {Promise<object>} Updated session
|
|
86
|
+
*/
|
|
87
|
+
async function appendTurn(projectDir, sessionId, role, content, metadata = {}, ttlHours = DEFAULT_TTL_HOURS) {
|
|
88
|
+
let session = await loadSession(projectDir, sessionId, ttlHours);
|
|
89
|
+
const now = new Date().toISOString();
|
|
90
|
+
|
|
91
|
+
if (!session) {
|
|
92
|
+
session = {
|
|
93
|
+
session_id: sessionId,
|
|
94
|
+
channel: metadata.channel || 'webhook',
|
|
95
|
+
created_at: now,
|
|
96
|
+
last_active: now,
|
|
97
|
+
turns: [],
|
|
98
|
+
metadata: { ...metadata }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
session.last_active = now;
|
|
103
|
+
session.turns.push({ role, content, ts: now });
|
|
104
|
+
|
|
105
|
+
// Merge any new metadata fields without overwriting existing ones
|
|
106
|
+
if (metadata && typeof metadata === 'object') {
|
|
107
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
108
|
+
if (session.metadata[k] === undefined) session.metadata[k] = v;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await saveSession(projectDir, session);
|
|
113
|
+
return session;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Build a context string from session history to inject into the squad input.
|
|
118
|
+
* @param {object} session
|
|
119
|
+
* @param {string} currentInput
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
function buildContextualInput(session, currentInput) {
|
|
123
|
+
if (!session || !session.turns || session.turns.length === 0) {
|
|
124
|
+
return currentInput;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const history = session.turns
|
|
128
|
+
.map(t => `${t.role}: ${t.content}`)
|
|
129
|
+
.join('\n');
|
|
130
|
+
|
|
131
|
+
return `[Conversation history]\n${history}\n\n[Current message]\n${currentInput}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Delete all session files that have been inactive for longer than ttlHours.
|
|
136
|
+
* @param {string} projectDir
|
|
137
|
+
* @param {number} [ttlHours]
|
|
138
|
+
* @returns {Promise<number>} Number of sessions deleted
|
|
139
|
+
*/
|
|
140
|
+
async function cleanExpiredSessions(projectDir, ttlHours = DEFAULT_TTL_HOURS) {
|
|
141
|
+
const dir = path.join(projectDir, SESSIONS_DIR);
|
|
142
|
+
let entries;
|
|
143
|
+
try {
|
|
144
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
145
|
+
} catch {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let deleted = 0;
|
|
150
|
+
const cutoff = Date.now() - ttlHours * 3_600_000;
|
|
151
|
+
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
154
|
+
const filePath = path.join(dir, entry.name);
|
|
155
|
+
try {
|
|
156
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
157
|
+
const session = JSON.parse(raw);
|
|
158
|
+
const lastActive = session.last_active ? new Date(session.last_active).getTime() : 0;
|
|
159
|
+
if (lastActive < cutoff) {
|
|
160
|
+
await fs.unlink(filePath);
|
|
161
|
+
deleted++;
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// Corrupt file — remove it
|
|
165
|
+
await fs.unlink(filePath).catch(() => {});
|
|
166
|
+
deleted++;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return deleted;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
loadSession,
|
|
175
|
+
saveSession,
|
|
176
|
+
appendTurn,
|
|
177
|
+
buildContextualInput,
|
|
178
|
+
cleanExpiredSessions,
|
|
179
|
+
sanitizeSessionId
|
|
180
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { randomUUID } = require('node:crypto');
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { openRuntimeDb, insertWorkerRun } = require('../runtime-store');
|
|
6
|
+
|
|
7
|
+
const INBOX_DIR = (projectDir, squadSlug) =>
|
|
8
|
+
path.join(projectDir, '.aioson', 'squads', squadSlug, 'inbox');
|
|
9
|
+
|
|
10
|
+
async function callSquad({ projectDir, from, to, worker, payload, conversationId, depth = 0 }) {
|
|
11
|
+
// 1. Cascade guard
|
|
12
|
+
if (depth > 5) return { ok: false, error: 'cascade_guard' };
|
|
13
|
+
|
|
14
|
+
conversationId = conversationId || randomUUID();
|
|
15
|
+
|
|
16
|
+
// 2. Resolver porta do squad destino no SQLite (manter DB aberto para log)
|
|
17
|
+
const handle = await openRuntimeDb(projectDir);
|
|
18
|
+
const db = handle?.db;
|
|
19
|
+
const port = db
|
|
20
|
+
?.prepare("SELECT port FROM squad_daemons WHERE squad_slug = ? AND status = 'running'")
|
|
21
|
+
.get(to)?.port;
|
|
22
|
+
|
|
23
|
+
let result;
|
|
24
|
+
|
|
25
|
+
// 3. Tentar chamada direta
|
|
26
|
+
if (port) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`http://127.0.0.1:${port}/webhook/${worker}`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify({ ...payload, _inter_squad: { from, conversationId, depth: depth + 1 } }),
|
|
32
|
+
signal: AbortSignal.timeout(10000)
|
|
33
|
+
});
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
result = { ok: res.ok, result: json, conversationId };
|
|
36
|
+
} catch {
|
|
37
|
+
// cai para inbox
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 4. Enfileirar na inbox do squad destino
|
|
42
|
+
if (!result) {
|
|
43
|
+
const inboxDir = INBOX_DIR(projectDir, to);
|
|
44
|
+
await fs.mkdir(inboxDir, { recursive: true });
|
|
45
|
+
const id = randomUUID();
|
|
46
|
+
await fs.writeFile(
|
|
47
|
+
path.join(inboxDir, `${id}.json`),
|
|
48
|
+
JSON.stringify({ id, from, to, worker, payload, conversationId, depth, created_at: new Date().toISOString() })
|
|
49
|
+
);
|
|
50
|
+
result = { ok: false, error: 'offline_queued', conversationId };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 5. Gravar chamada emitida no runtime store (trigger_type = 'inter-squad')
|
|
54
|
+
if (db) {
|
|
55
|
+
try {
|
|
56
|
+
insertWorkerRun(db, {
|
|
57
|
+
squadSlug: from,
|
|
58
|
+
workerSlug: worker,
|
|
59
|
+
triggerType: 'inter-squad',
|
|
60
|
+
inputJson: JSON.stringify({ to, payload, conversationId }),
|
|
61
|
+
outputJson: result.ok ? JSON.stringify(result.result) : null,
|
|
62
|
+
status: result.ok ? 'completed' : 'failed',
|
|
63
|
+
errorMessage: result.ok ? null : result.error,
|
|
64
|
+
durationMs: 0,
|
|
65
|
+
attempt: 1
|
|
66
|
+
});
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
db.close();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { callSquad, INBOX_DIR };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const SQUADS_DIR = path.join('.aioson', 'squads');
|
|
7
|
+
|
|
8
|
+
// Events that trigger an automatic refresh of the recovery context
|
|
9
|
+
const REFRESH_EVENTS = new Set(['task_completed', 'decision_made', 'handoff']);
|
|
10
|
+
|
|
11
|
+
// Approximate token count (chars / 4)
|
|
12
|
+
function estimateTokens(str) {
|
|
13
|
+
return Math.ceil(str.length / 4);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Read squad manifest (best-effort, returns {} on failure).
|
|
18
|
+
*/
|
|
19
|
+
async function readManifest(projectDir, squadSlug) {
|
|
20
|
+
const p = path.join(projectDir, SQUADS_DIR, squadSlug, 'squad.manifest.json');
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(await fs.readFile(p, 'utf8'));
|
|
23
|
+
} catch {
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read recent events from session-log.json (last N entries).
|
|
30
|
+
*/
|
|
31
|
+
async function readRecentEvents(projectDir, squadSlug, limit = 10) {
|
|
32
|
+
const p = path.join(projectDir, SQUADS_DIR, squadSlug, 'session-log.json');
|
|
33
|
+
try {
|
|
34
|
+
const raw = JSON.parse(await fs.readFile(p, 'utf8'));
|
|
35
|
+
const entries = Array.isArray(raw) ? raw : (raw.entries || []);
|
|
36
|
+
return entries.slice(-limit);
|
|
37
|
+
} catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read recent tasks from tasks.json (last 5, enriched).
|
|
44
|
+
*/
|
|
45
|
+
async function readRecentTasks(projectDir, squadSlug, limit = 5) {
|
|
46
|
+
const p = path.join(projectDir, SQUADS_DIR, squadSlug, 'tasks.json');
|
|
47
|
+
try {
|
|
48
|
+
const raw = JSON.parse(await fs.readFile(p, 'utf8'));
|
|
49
|
+
const tasks = Array.isArray(raw) ? raw : (raw.tasks || []);
|
|
50
|
+
return tasks.slice(-limit);
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read context-monitor.json snapshot for an agent.
|
|
58
|
+
*/
|
|
59
|
+
async function readContextSnapshot(projectDir, squadSlug, agentSlug) {
|
|
60
|
+
const p = path.join(projectDir, SQUADS_DIR, squadSlug, 'context-monitor.json');
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(await fs.readFile(p, 'utf8'));
|
|
63
|
+
if (agentSlug && data.agents && data.agents[agentSlug]) {
|
|
64
|
+
return data.agents[agentSlug];
|
|
65
|
+
}
|
|
66
|
+
return data;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build the markdown content for recovery-context.md.
|
|
74
|
+
* Target < 2000 tokens.
|
|
75
|
+
*/
|
|
76
|
+
function buildRecoveryMarkdown(squadSlug, agentSlug, manifest, tasks, events, ctxSnapshot) {
|
|
77
|
+
const lines = [];
|
|
78
|
+
|
|
79
|
+
lines.push(`# Recovery Context — ${squadSlug} / ${agentSlug}`);
|
|
80
|
+
lines.push(`> Generated: ${new Date().toISOString()}`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
|
|
83
|
+
// Squad goal
|
|
84
|
+
if (manifest.goal) {
|
|
85
|
+
lines.push('## Squad Goal');
|
|
86
|
+
lines.push(manifest.goal);
|
|
87
|
+
lines.push('');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Agent role (from executors array)
|
|
91
|
+
const executor = (manifest.executors || []).find(e => e.slug === agentSlug);
|
|
92
|
+
if (executor) {
|
|
93
|
+
lines.push('## Your Role');
|
|
94
|
+
lines.push(`**${executor.title || agentSlug}**: ${executor.role || ''}`);
|
|
95
|
+
lines.push('');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Recent tasks
|
|
99
|
+
if (tasks.length > 0) {
|
|
100
|
+
lines.push('## Recent Tasks');
|
|
101
|
+
for (const t of tasks) {
|
|
102
|
+
const status = t.status || 'unknown';
|
|
103
|
+
const title = t.title || t.slug || t.id || '(untitled)';
|
|
104
|
+
lines.push(`- [${status}] ${title}`);
|
|
105
|
+
if (t.output && typeof t.output === 'string') {
|
|
106
|
+
// Truncate long outputs
|
|
107
|
+
const out = t.output.length > 200 ? t.output.slice(0, 200) + '…' : t.output;
|
|
108
|
+
lines.push(` Output: ${out}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
lines.push('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Recent events
|
|
115
|
+
if (events.length > 0) {
|
|
116
|
+
lines.push('## Recent Events');
|
|
117
|
+
for (const ev of events) {
|
|
118
|
+
const ts = ev.created_at || ev.timestamp || '';
|
|
119
|
+
const type = ev.event_type || ev.type || 'event';
|
|
120
|
+
const msg = ev.message || ev.summary || '';
|
|
121
|
+
lines.push(`- [${ts}] ${type}: ${msg}`);
|
|
122
|
+
}
|
|
123
|
+
lines.push('');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Context snapshot
|
|
127
|
+
if (ctxSnapshot) {
|
|
128
|
+
lines.push('## Context Window at Last Compact');
|
|
129
|
+
const used = ctxSnapshot.totalUsed || 0;
|
|
130
|
+
const win = ctxSnapshot.windowSize || 0;
|
|
131
|
+
const pct = win > 0 ? Math.round((used / win) * 100) : 0;
|
|
132
|
+
lines.push(`Used: ${used.toLocaleString()} / ${win.toLocaleString()} tokens (${pct}%)`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
lines.push('---');
|
|
137
|
+
lines.push('*Inject this file at the top of your next session to restore context after a compact.*');
|
|
138
|
+
|
|
139
|
+
const content = lines.join('\n');
|
|
140
|
+
|
|
141
|
+
// Enforce token limit: if over budget, trim events section
|
|
142
|
+
if (estimateTokens(content) > 2000) {
|
|
143
|
+
// Rebuild with fewer events
|
|
144
|
+
return buildRecoveryMarkdown(squadSlug, agentSlug, manifest, tasks, events.slice(-3), ctxSnapshot);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return content;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate and write recovery-context.md for an agent.
|
|
152
|
+
* @param {string} projectDir
|
|
153
|
+
* @param {string} squadSlug
|
|
154
|
+
* @param {string} agentSlug
|
|
155
|
+
* @returns {{ ok: boolean, path: string, tokens: number }}
|
|
156
|
+
*/
|
|
157
|
+
async function generateRecovery(projectDir, squadSlug, agentSlug) {
|
|
158
|
+
const [manifest, tasks, events, ctxSnapshot] = await Promise.all([
|
|
159
|
+
readManifest(projectDir, squadSlug),
|
|
160
|
+
readRecentTasks(projectDir, squadSlug),
|
|
161
|
+
readRecentEvents(projectDir, squadSlug),
|
|
162
|
+
readContextSnapshot(projectDir, squadSlug, agentSlug)
|
|
163
|
+
]);
|
|
164
|
+
|
|
165
|
+
const content = buildRecoveryMarkdown(squadSlug, agentSlug, manifest, tasks, events, ctxSnapshot);
|
|
166
|
+
const tokens = estimateTokens(content);
|
|
167
|
+
|
|
168
|
+
const outDir = path.join(projectDir, SQUADS_DIR, squadSlug);
|
|
169
|
+
const outPath = path.join(outDir, `recovery-context.md`);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
173
|
+
await fs.writeFile(outPath, content, 'utf8');
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return { ok: false, error: err.message, path: outPath, tokens };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { ok: true, path: outPath, tokens, squadSlug, agentSlug };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Read the current recovery-context.md for an agent (returns null if missing).
|
|
183
|
+
*/
|
|
184
|
+
async function readRecovery(projectDir, squadSlug) {
|
|
185
|
+
const p = path.join(projectDir, SQUADS_DIR, squadSlug, 'recovery-context.md');
|
|
186
|
+
try {
|
|
187
|
+
return await fs.readFile(p, 'utf8');
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if a runtime event should trigger a recovery refresh.
|
|
195
|
+
* @param {string} eventType
|
|
196
|
+
*/
|
|
197
|
+
function shouldRefreshOnEvent(eventType) {
|
|
198
|
+
return REFRESH_EVENTS.has(eventType);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = { generateRecovery, readRecovery, shouldRefreshOnEvent, REFRESH_EVENTS };
|