@hybridlabor-api/aos 4.7.0 → 4.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.agents/agents.md +8 -3
  2. package/.agents/nodes.json +2 -1
  3. package/.agents/state.schema.json +24 -0
  4. package/.agents/vendor-manifest.json +6 -0
  5. package/.agents/workflows/startcycle-graph.md +2 -2
  6. package/.claude/agents/architect.md +5 -4
  7. package/.claude/agents/techlead.md +2 -0
  8. package/.claude/workflows/startcycle-dispatch.mjs +175 -17
  9. package/.opencode/agents/architect.md +5 -4
  10. package/.opencode/agents/techlead.md +2 -0
  11. package/THIRD_PARTY_NOTICES.md +9 -1
  12. package/bin/aos-doctor.mjs +386 -0
  13. package/bin/aos-store.mjs +171 -0
  14. package/docs/AUDIT_BRIEF_ECC_STORE.md +122 -0
  15. package/docs/PLAN_AOS_STORE_PHASE1.md +190 -0
  16. package/installer.js +68 -15
  17. package/lib/aos-archify-contract.mjs +277 -0
  18. package/lib/ecc-store-index.json +1 -0
  19. package/mcp_config.json +4 -0
  20. package/mcps/mcsc/packages/core/src/adapters/codex.js +160 -37
  21. package/package.json +7 -2
  22. package/scripts/build-ecc-store-index.mjs +147 -0
  23. package/skills/basic/startcycle-graph/SKILL.md +13 -0
  24. package/skills/global_config/agenttrail/bin/agenttrail.mjs +153 -42
  25. package/skills/global_config/agenttrail/public/index.html +5 -4
  26. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +8 -2
  27. package/skills/global_config/archify/examples/dataflow-product-analytics.html +15048 -0
  28. package/skills/global_config/archify/examples/lifecycle-agent-run.html +14983 -0
  29. package/skills/global_config/archify/examples/sequence-cache-miss-request.html +15063 -0
  30. package/skills/global_config/archify/examples/web-app-rendered.html +15012 -0
  31. package/skills/global_config/archify/examples/workflow-agent-tool-call-rendered.html +15054 -0
  32. package/skills/global_config/ask-tim/SKILL.md +10 -0
  33. package/skills/global_config/mcsc/SKILL.md +60 -0
  34. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +31 -6
  35. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +4 -2
  36. package/docs/handover/agenttrail-for-ao.md +0 -58
  37. package/docs/handover/deja-for-ao.md +0 -51
@@ -1,56 +1,179 @@
1
- import { execFile } from 'node:child_process';
2
- import { promisify } from 'node:util';
1
+ import { spawn } from 'node:child_process';
3
2
  import { emitTrail } from '../trail.js';
4
3
 
5
- const execFileAsync = promisify(execFile);
4
+ const MAX_OUTPUT = 1024 * 1024 * 50; // 50MB, mirrors the old execFile maxBuffer
6
5
 
7
6
  /**
8
7
  * Adapter für die Codex CLI.
9
8
  * Implementiert die DelegationRequest Schnittstelle.
10
- * @param {import('./types.js').DelegationRequest} req
9
+ *
10
+ * Uses `codex exec --json` (confirmed present: `codex exec --help` lists
11
+ * `--json Print events to stdout as JSONL`) instead of a blind exec, so a
12
+ * delegated Codex run gets the same mid-run tool-call visibility on
13
+ * agenttrail's board as the opencode adapter already has. The envelope shape
14
+ * below (`thread.started` / `turn.started` / `item.completed` / `turn.failed`,
15
+ * `item.completed` items carrying `{id, type, ...}`) was confirmed live
16
+ * against the installed CLI (codex-cli 0.154.0). The exact `item.type` string
17
+ * for a real tool call (e.g. shell exec, patch apply, the plan/todo tool)
18
+ * was NOT confirmed live — every probe run in this environment hit a 401
19
+ * auth error before Codex executed anything. `buildToolInput` below is
20
+ * therefore deliberately generic/defensive rather than keyed to guessed
21
+ * field names, and Codex's plan/todo tool is not mapped into a
22
+ * `PostToolUse` with `tool_name: 'update_plan'` here — doing that from
23
+ * unverified field names would silently ship a wrong mapping. Re-verify
24
+ * with authenticated Codex and tighten this once the real item shape for a
25
+ * tool call (and for its plan/todo tool specifically) is known.
26
+ *
27
+ * @param {import('./types.js').DelegationRequest} req
11
28
  * @returns {Promise<{ exit: number; output: string }>}
12
29
  */
13
30
  export async function delegate(req) {
31
+ const cwd = req.cwd || process.cwd();
14
32
  const trailEvent = {
15
33
  session_id: `mcsc-codex-${process.pid}-${Date.now()}`,
16
- cwd: req.cwd || process.cwd(),
34
+ cwd,
17
35
  agent:
18
36
  typeof req.label === 'string' && req.label.length > 0
19
37
  ? `codex:${req.label}`
20
38
  : 'codex',
21
39
  };
22
40
  emitTrail({ ...trailEvent, hook_event_name: 'SessionStart' });
23
- try {
24
- const args = ['exec'];
25
- if (req.model) args.push('-m', req.model);
26
- args.push(req.prompt);
27
-
28
- // Recursion Guard
29
- const env = { ...process.env, MCSC_CALLER: 'codex' };
30
-
31
- const run = execFileAsync(
32
- 'codex', args,
33
- {
34
- cwd: req.cwd,
35
- encoding: 'utf-8',
36
- maxBuffer: 1024 * 1024 * 50, // 50MB Limit
37
- env,
38
- signal: req.signal
39
- }
40
- );
41
- // An open stdin pipe makes the CLI wait for input forever.
42
- run.child.stdin.end();
43
- const { stdout, stderr } = await run;
44
-
45
- emitTrail({ ...trailEvent, hook_event_name: 'SessionEnd' });
46
- return { exit: 0, output: stdout || stderr };
47
- } catch (e) {
48
- emitTrail({ ...trailEvent, hook_event_name: 'SessionEnd' });
49
- if (e.name === 'AbortError') {
50
- emitTrail({ ...trailEvent, hook_event_name: 'SessionEnd' });
51
- return { exit: 1, output: 'Cancelled by orchestrator' };
41
+
42
+ const env = { ...process.env, MCSC_CALLER: 'codex' };
43
+ const args = ['exec', '--json', '--skip-git-repo-check'];
44
+ if (req.model) args.push('-m', req.model);
45
+ args.push(req.prompt);
46
+
47
+ const child = spawn('codex', args, {
48
+ cwd,
49
+ env,
50
+ stdio: ['ignore', 'pipe', 'pipe'],
51
+ });
52
+
53
+ return new Promise((resolve) => {
54
+ let lastAgentMessage = '';
55
+ let stdoutBuf = '';
56
+ let stderrBuf = '';
57
+ let stdoutBytes = 0;
58
+ let stderrBytes = 0;
59
+ let overflowed = false;
60
+ let aborted = false;
61
+ let settled = false;
62
+ let turnFailed = false;
63
+
64
+ const onAbort = () => {
65
+ aborted = true;
66
+ child.kill('SIGKILL');
67
+ };
68
+ if (req.signal) {
69
+ if (req.signal.aborted) onAbort();
70
+ else req.signal.addEventListener('abort', onAbort, { once: true });
52
71
  }
53
- emitTrail({ ...trailEvent, hook_event_name: 'SessionEnd' });
54
- return { exit: 1, output: (e.stderr || e.message || '').trim() };
55
- }
72
+
73
+ const finalize = (result) => {
74
+ if (settled) return;
75
+ settled = true;
76
+ if (req.signal) req.signal.removeEventListener('abort', onAbort);
77
+ emitTrail({ ...trailEvent, hook_event_name: 'SessionEnd' });
78
+ resolve(result);
79
+ };
80
+
81
+ // Defensive/generic: no confirmed field names for a real tool-call item
82
+ // (see the doc comment above), so this only forwards what's plausibly
83
+ // present rather than asserting a specific Codex item shape.
84
+ const buildToolInput = (item) => {
85
+ const toolInput = {};
86
+ if (!item || typeof item !== 'object') return toolInput;
87
+ const rawCommand = item.command || item.cmd;
88
+ if (typeof rawCommand === 'string' && rawCommand.length > 0) toolInput.command = rawCommand;
89
+ else if (Array.isArray(rawCommand)) toolInput.command = rawCommand.join(' ');
90
+ const rawPath = item.file_path || item.path;
91
+ if (typeof rawPath === 'string' && rawPath.length > 0) toolInput.file_path = rawPath;
92
+ return toolInput;
93
+ };
94
+
95
+ const handleLine = (line) => {
96
+ if (!line) return;
97
+ let evt;
98
+ try {
99
+ evt = JSON.parse(line);
100
+ } catch {
101
+ return; // Not valid JSON — ignore.
102
+ }
103
+ if (!evt || typeof evt !== 'object') return;
104
+
105
+ if (evt.type === 'turn.completed' && typeof evt.last_agent_message === 'string') {
106
+ lastAgentMessage = evt.last_agent_message;
107
+ return;
108
+ }
109
+ if (evt.type === 'turn.failed') {
110
+ turnFailed = true;
111
+ return;
112
+ }
113
+ if (evt.type === 'item.completed' && evt.item && typeof evt.item === 'object') {
114
+ const itemType = evt.item.type;
115
+ // Informational item types carry no tool call — skip them so the
116
+ // board doesn't show a fake "tool" for plain error/text output.
117
+ if (itemType === 'error' || itemType === 'reasoning' || itemType === 'agent_message') return;
118
+ emitTrail({
119
+ ...trailEvent,
120
+ hook_event_name: 'PostToolUse',
121
+ tool_name: String(itemType || 'codex_item')
122
+ .replace(/(^|_)([a-z])/g, (_, sep, c) => c.toUpperCase()),
123
+ tool_input: buildToolInput(evt.item),
124
+ });
125
+ }
126
+ };
127
+
128
+ child.stdout.setEncoding('utf8');
129
+ child.stdout.on('data', (chunk) => {
130
+ stdoutBytes += Buffer.byteLength(chunk);
131
+ if (stdoutBytes > MAX_OUTPUT) {
132
+ if (!overflowed) {
133
+ overflowed = true;
134
+ child.kill('SIGKILL');
135
+ }
136
+ return;
137
+ }
138
+ stdoutBuf += chunk;
139
+ let idx;
140
+ while ((idx = stdoutBuf.indexOf('\n')) !== -1) {
141
+ const line = stdoutBuf.slice(0, idx).trim();
142
+ stdoutBuf = stdoutBuf.slice(idx + 1);
143
+ handleLine(line);
144
+ }
145
+ });
146
+
147
+ child.stderr.setEncoding('utf8');
148
+ child.stderr.on('data', (chunk) => {
149
+ stderrBytes += Buffer.byteLength(chunk);
150
+ if (stderrBytes > MAX_OUTPUT) return; // cap stderr, never fail on it
151
+ stderrBuf += chunk;
152
+ });
153
+
154
+ child.on('error', (err) => {
155
+ finalize({ exit: 1, output: (stderrBuf || err.message || '').trim() });
156
+ });
157
+
158
+ child.on('close', (code) => {
159
+ const rest = stdoutBuf.trim();
160
+ if (rest) handleLine(rest); // flush a trailing line without newline
161
+ if (aborted) {
162
+ finalize({ exit: 1, output: 'Cancelled by orchestrator' });
163
+ return;
164
+ }
165
+ if (overflowed) {
166
+ finalize({ exit: 1, output: 'stdout maxBuffer length exceeded' });
167
+ return;
168
+ }
169
+ if (code === 0 && !turnFailed) {
170
+ finalize({ exit: 0, output: lastAgentMessage || stderrBuf });
171
+ return;
172
+ }
173
+ finalize({ exit: 1, output: (lastAgentMessage || stderrBuf || `codex exited with code ${code}`).trim() });
174
+ });
175
+
176
+ // An open stdin pipe makes the CLI wait for input forever.
177
+ child.stdin.end();
178
+ });
56
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/aos",
3
- "version": "4.7.0",
3
+ "version": "4.7.2",
4
4
  "description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
5
5
  "main": "installer.js",
6
6
  "engines": {
@@ -16,12 +16,16 @@
16
16
  "aos-archify": "skills/global_config/archify/bin/archify.mjs",
17
17
  "aos-dashboard": "bin/aos-dashboard.mjs",
18
18
  "aos-config": "bin/aos-config.mjs",
19
+ "aos-store": "bin/aos-store.mjs",
20
+ "aos-doctor": "bin/aos-doctor.mjs",
19
21
  "aos-uninstall": "bin/aos-uninstall.mjs"
20
22
  },
21
23
  "scripts": {
22
24
  "validate": "node scripts/validate-skills.mjs",
23
25
  "validate:strict": "node scripts/validate-skills.mjs --strict",
24
- "test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs"
26
+ "store:index": "node scripts/build-ecc-store-index.mjs",
27
+ "doctor": "node bin/aos-doctor.mjs",
28
+ "test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs && node tests/aos-store.test.mjs && node tests/aos-doctor.test.mjs"
25
29
  },
26
30
  "publishConfig": {
27
31
  "access": "public"
@@ -48,6 +52,7 @@
48
52
  ".codex-plugin/",
49
53
  ".opencode/",
50
54
  "docs/",
55
+ "!docs/handover/**",
51
56
  "assets/",
52
57
  "skills/",
53
58
  "mcps/",
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { execFileSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
6
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
7
+ import { tmpdir } from 'node:os';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const MANIFEST_PATH = join(ROOT, '.agents', 'vendor-manifest.json');
12
+ const OUTPUT_PATH = join(ROOT, 'lib', 'ecc-store-index.json');
13
+ const manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf8'));
14
+ const entry = manifest.vendors?.['ecc-store'];
15
+ if (!entry?.pinned_commit || !/^[a-f0-9]{40}$/.test(entry.pinned_commit)) {
16
+ throw new Error('Missing or invalid .agents/vendor-manifest.json ECC store pin');
17
+ }
18
+
19
+ const checkout = mkdtempSync(join(tmpdir(), 'aos-ecc-store-'));
20
+
21
+ function runGit(args) {
22
+ return execFileSync('git', args, { cwd: checkout, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
23
+ }
24
+
25
+ function walk(dir, output = []) {
26
+ if (!existsSync(dir)) return output;
27
+ for (const item of readdirSync(dir, { withFileTypes: true })) {
28
+ const fullPath = join(dir, item.name);
29
+ if (item.isDirectory()) walk(fullPath, output);
30
+ else if (item.isFile()) output.push(fullPath);
31
+ }
32
+ return output;
33
+ }
34
+
35
+ function frontmatterValue(text, key) {
36
+ const match = text.match(/^---\s*\n([\s\S]*?)\n---/);
37
+ if (!match) return '';
38
+ const lines = match[1].split(/\r?\n/);
39
+ const start = lines.findIndex((line) => new RegExp(`^${key}:`, 'i').test(line));
40
+ if (start < 0) return '';
41
+ const first = lines[start].slice(lines[start].indexOf(':') + 1).trim();
42
+ if (first && !/^[>|][-+]?$/.test(first)) return first.replace(/^['"]|['"]$/g, '');
43
+ const values = [];
44
+ for (let index = start + 1; index < lines.length; index += 1) {
45
+ const line = lines[index];
46
+ if (line && !/^\s/.test(line)) break;
47
+ values.push(line.trim());
48
+ }
49
+ return values.join(' ').trim();
50
+ }
51
+
52
+ function categoryFor(name) {
53
+ const value = name.toLowerCase();
54
+ if (/(media|video|motion|3d|blender|manim|creative|remotion|comfy)/.test(value)) return 'media-eventtech';
55
+ if (/(design|frontend|react|vue|angular|next|tailwind|ui-|uiux|brand)/.test(value) || /(^|-)ui($|-)/.test(value)) return 'design-ui-ux';
56
+ if (/(agent|pipeline|orchestr|autonom|benchmark|eval|security|skill|workflow)/.test(value)) return 'bdb-core';
57
+ if (/(hardware|pcb|circuit|electronic)/.test(value)) return 'engineering-hardware';
58
+ if (/(engineering|architecture|database|api|devops|docker|kubernetes|python|pytorch|typescript|rust|golang|java|spring|django|fastapi|react|test|build|resolver|reviewer|performance)/.test(value)) return 'engineering-method';
59
+ return 'library';
60
+ }
61
+
62
+ function compatibilityWarnings(text) {
63
+ const warnings = [];
64
+ if (/\bBash\b/.test(text)) warnings.push('mentions Bash');
65
+ if (/\bView\b/.test(text)) warnings.push('mentions View');
66
+ return warnings;
67
+ }
68
+
69
+ function localNames() {
70
+ const names = new Set();
71
+ for (const file of walk(join(ROOT, 'skills'))) {
72
+ if (basename(file) === 'SKILL.md') {
73
+ const text = readFileSync(file, 'utf8');
74
+ names.add((frontmatterValue(text, 'name') || basename(dirname(file))).trim().toLowerCase());
75
+ }
76
+ }
77
+ for (const root of [join(ROOT, '.claude', 'agents'), join(ROOT, '.agents', 'agents'), join(ROOT, 'agents')]) {
78
+ if (!existsSync(root)) continue;
79
+ for (const name of readdirSync(root)) names.add(name.replace(/\.md$/i, '').toLowerCase());
80
+ }
81
+ return names;
82
+ }
83
+
84
+ function hash(content) {
85
+ return createHash('sha256').update(content).digest('hex');
86
+ }
87
+
88
+ function itemFromFile(file, type) {
89
+ const content = readFileSync(file, 'utf8');
90
+ const name = type === 'skills'
91
+ ? (frontmatterValue(content, 'name') || basename(dirname(file))).trim()
92
+ : basename(file, '.md');
93
+ if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(`Invalid store name: ${name}`);
94
+ const description = (frontmatterValue(content, 'description') || type).replace(/\s+/g, ' ').trim();
95
+ const shortDescription = description.length > 32 ? `${description.slice(0, 31)}…` : description;
96
+ const base = {
97
+ description: shortDescription,
98
+ category: categoryFor(name),
99
+ tier: 'free',
100
+ origin: 'ecc',
101
+ requires_auth: false,
102
+ sha256: hash(content),
103
+ upstream_path: relative(checkout, file).split(sep).join('/'),
104
+ };
105
+ return { name, item: base, warnings: compatibilityWarnings(content) };
106
+ }
107
+
108
+ try {
109
+ runGit(['init', '--quiet']);
110
+ runGit(['remote', 'add', 'origin', entry.upstream]);
111
+ runGit(['fetch', '--quiet', '--depth=1', 'origin', entry.pinned_commit]);
112
+ runGit(['checkout', '--quiet', '--detach', 'FETCH_HEAD']);
113
+ const generatedAt = runGit(['show', '-s', '--format=%cI', 'FETCH_HEAD']);
114
+ const coreNames = localNames();
115
+ const skills = {};
116
+ const subagents = {};
117
+ let compatibilityWarningCount = 0;
118
+ const skillFiles = walk(join(checkout, 'skills')).filter((file) => basename(file) === 'SKILL.md');
119
+ const agentFiles = walk(join(checkout, 'agents')).filter((file) => file.endsWith('.md'));
120
+ for (const file of skillFiles) {
121
+ const { name, item, warnings } = itemFromFile(file, 'skills');
122
+ compatibilityWarningCount += warnings.length;
123
+ if (coreNames.has(name.toLowerCase())) continue;
124
+ skills[name] = item;
125
+ }
126
+ for (const file of agentFiles) {
127
+ const { name, item, warnings } = itemFromFile(file, 'subagents');
128
+ compatibilityWarningCount += warnings.length;
129
+ if (coreNames.has(name.toLowerCase())) continue;
130
+ subagents[name] = item;
131
+ }
132
+ const index = {
133
+ version: '1.0.0',
134
+ generated_at: generatedAt,
135
+ pinned_commit: entry.pinned_commit,
136
+ harness_support: ['claude', 'antigravity', 'codex', 'cursor', 'roo'],
137
+ compatibility_warning_count: compatibilityWarningCount,
138
+ skills: Object.fromEntries(Object.entries(skills).sort(([a], [b]) => a.localeCompare(b))),
139
+ subagents: Object.fromEntries(Object.entries(subagents).sort(([a], [b]) => a.localeCompare(b))),
140
+ };
141
+ const output = JSON.stringify(index) + '\n';
142
+ if (Buffer.byteLength(output) >= 100 * 1024) throw new Error(`Store index exceeds 100 KB: ${Buffer.byteLength(output)} bytes`);
143
+ writeFileSync(OUTPUT_PATH, output);
144
+ console.log(`Wrote ${OUTPUT_PATH} with ${Object.keys(skills).length} skills and ${Object.keys(subagents).length} subagents (${Buffer.byteLength(output)} bytes).`);
145
+ } finally {
146
+ rmSync(checkout, { recursive: true, force: true });
147
+ }
@@ -118,6 +118,19 @@ the status-mark rules from the `agenttrail` skill: `[~]` before starting,
118
118
  `[x]` when done, `[!]` when stuck, with `by: <agent>`, saving the plan file
119
119
  immediately.
120
120
 
121
+ ## Architecture diagram (Archify, native)
122
+
123
+ Architect authors `production_artifacts/00_architecture.json` (editable truth)
124
+ and delivers `production_artifacts/00_architecture.html` (read-only) via
125
+ `aos-archify validate` / `aos-archify deliver` with `--quality showcase --json`
126
+ at the Architect→TechLead boundary; only a passing showcase receipt (9/9
127
+ checks, 0 errors) sets `state.artifacts.architecture`, and a failed
128
+ validate/deliver preserves the previous HTML and escalates instead of
129
+ proceeding silently. The plan links the diagram with a
130
+ `url: production_artifacts/00_architecture.html` line so the live map opens it
131
+ from the card. Review the HTML in plan-canvas alongside the plan:
132
+ `aos-plan-canvas open production_artifacts/00_architecture.html`.
133
+
121
134
  ## Why this file is a thin router, not a spec
122
135
 
123
136
  The full contract — state schema, node/edge table, the Stop-hook