@jaimevalasek/aioson 1.36.0 → 1.37.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 +42 -0
- package/docs/en/1-understand/ecosystem-map.md +1 -1
- package/docs/en/1-understand/glossary.md +1 -1
- package/docs/en/2-start/first-project.md +1 -1
- package/docs/en/2-start/initial-decisions.md +4 -2
- package/docs/en/3-recipes/from-idea-to-prd-via-briefing.md +8 -1
- package/docs/en/3-recipes/full-feature-with-sheldon.md +3 -1
- package/docs/en/4-agents/README.md +6 -3
- package/docs/en/4-agents/briefing-refiner.md +146 -0
- package/docs/en/5-reference/README.md +1 -0
- package/docs/en/5-reference/autopilot-handoff.md +286 -0
- package/docs/en/5-reference/cli-reference.md +6 -0
- package/docs/pt/1-entender/glossario.md +1 -1
- package/docs/pt/1-entender/mapa-do-ecossistema.md +1 -1
- package/docs/pt/2-comecar/decisoes-iniciais.md +4 -2
- package/docs/pt/2-comecar/primeiro-projeto.md +1 -1
- package/docs/pt/3-receitas/da-ideia-ao-prd-via-briefing.md +8 -1
- package/docs/pt/3-receitas/feature-completa-com-sheldon.md +3 -1
- package/docs/pt/4-agentes/README.md +13 -11
- package/docs/pt/4-agentes/briefing-refiner.md +64 -40
- package/docs/pt/4-agentes/briefing.md +6 -1
- package/docs/pt/4-agentes/dev.md +19 -1
- package/docs/pt/4-agentes/deyvin.md +4 -0
- package/docs/pt/4-agentes/discover.md +4 -0
- package/docs/pt/4-agentes/neo.md +4 -0
- package/docs/pt/4-agentes/orache.md +6 -0
- package/docs/pt/4-agentes/orchestrator.md +12 -0
- package/docs/pt/4-agentes/pentester.md +6 -0
- package/docs/pt/4-agentes/product.md +19 -1
- package/docs/pt/4-agentes/qa.md +10 -2
- package/docs/pt/4-agentes/setup.md +3 -1
- package/docs/pt/4-agentes/sheldon.md +12 -0
- package/docs/pt/4-agentes/tester.md +6 -0
- package/docs/pt/4-agentes/ux-ui.md +2 -1
- package/docs/pt/5-referencia/README.md +1 -1
- package/docs/pt/5-referencia/agent-chain-continuity.md +1 -1
- package/docs/pt/5-referencia/autopilot-handoff.md +191 -74
- package/docs/pt/5-referencia/comandos-cli.md +16 -7
- package/docs/pt/5-referencia/skills.md +2 -0
- package/docs/pt/agentes.md +3 -1
- package/package.json +1 -1
- package/src/agent-execution/adapters/base.js +15 -0
- package/src/agent-execution/adapters/claude.js +3 -0
- package/src/agent-execution/adapters/codex.js +3 -0
- package/src/agent-execution/adapters/opencode.js +3 -0
- package/src/agent-execution/capabilities.js +9 -0
- package/src/agent-execution/dispatcher.js +72 -0
- package/src/agent-execution/executable-resolver.js +7 -0
- package/src/agent-execution/manifest.js +62 -0
- package/src/agent-execution/model-catalog.js +80 -0
- package/src/agent-execution/model-resolver.js +132 -0
- package/src/agent-execution/reports.js +9 -0
- package/src/agent-execution/schema.js +48 -0
- package/src/agent-execution/telemetry-bridge.js +15 -0
- package/src/agents.js +1 -1
- package/src/artifact-kinds.js +2 -1
- package/src/autopilot-signal.js +71 -0
- package/src/cli.js +31 -5
- package/src/commands/agent-execution.js +36 -0
- package/src/commands/agents.js +18 -2
- package/src/commands/briefing.js +337 -1
- package/src/commands/feature-close.js +136 -43
- package/src/commands/live.js +47 -11
- package/src/commands/op-capture.js +33 -3
- package/src/commands/op-reinforce.js +10 -22
- package/src/commands/update.js +5 -1
- package/src/commands/verification-plan.js +56 -12
- package/src/commands/verify-artifact.js +64 -1
- package/src/commands/workflow-execute.js +168 -36
- package/src/commands/workflow-next.js +60 -16
- package/src/doctor.js +4 -2
- package/src/harness/criteria-runner.js +4 -1
- package/src/i18n/messages/en.js +2 -1
- package/src/i18n/messages/es.js +2 -1
- package/src/i18n/messages/fr.js +2 -1
- package/src/i18n/messages/pt-BR.js +2 -1
- package/src/lib/briefing-refiner/apply-feedback.js +18 -4
- package/src/lib/briefing-refiner/feedback-schema.js +73 -4
- package/src/lib/briefing-refiner/refinement-report.js +11 -0
- package/src/lib/briefing-refiner/review-html.js +388 -68
- package/src/operator-memory/decision.js +41 -0
- package/src/parser.js +6 -0
- package/src/runtime-store.js +167 -8
- package/template/.aioson/agents/briefing-refiner.md +87 -47
- package/template/.aioson/agents/briefing.md +4 -0
- package/template/.aioson/agents/dev.md +9 -2
- package/template/.aioson/agents/deyvin.md +4 -0
- package/template/.aioson/agents/discover.md +4 -0
- package/template/.aioson/agents/neo.md +4 -0
- package/template/.aioson/agents/orache.md +4 -0
- package/template/.aioson/agents/orchestrator.md +16 -0
- package/template/.aioson/agents/pentester.md +4 -0
- package/template/.aioson/agents/product.md +26 -1
- package/template/.aioson/agents/qa.md +5 -1
- package/template/.aioson/agents/sheldon.md +9 -1
- package/template/.aioson/agents/tester.md +4 -0
- package/template/.aioson/agents/ux-ui.md +1 -1
- package/template/.aioson/docs/agent-help.md +126 -0
- package/template/.aioson/docs/autopilot-handoff.md +32 -16
- package/template/.aioson/docs/dev/phase-loop.md +9 -7
- package/template/.aioson/docs/play/llm-data-and-bindings.md +70 -7
- package/template/.aioson/schemas/agent-execution.schema.json +28 -0
- package/template/.aioson/skills/design/interface-design/SKILL.md +17 -0
- package/template/AGENTS.md +36 -36
- package/template/CLAUDE.md +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const crypto = require('node:crypto');
|
|
6
|
+
const { AGENTS, validateManifest } = require('./schema');
|
|
7
|
+
const { capabilities } = require('./capabilities');
|
|
8
|
+
const { loadModelCatalog } = require('./model-catalog');
|
|
9
|
+
const { resolveModel, validateReasoningEffort } = require('./model-resolver');
|
|
10
|
+
|
|
11
|
+
function manifestPath(projectDir, feature) { return path.join(projectDir, '.aioson', 'context', `agent-execution-${feature}.json`); }
|
|
12
|
+
function defaults(feature, host = 'codex') {
|
|
13
|
+
const agents = {};
|
|
14
|
+
for (const id of AGENTS) agents[id] = { enabled: true, host, mode: 'external', model: 'configured-default', writable_roots: [], fallbacks: [], report: `.aioson/context/reports/${feature}/{run_id}/${id}.json` };
|
|
15
|
+
return { version: 1, feature, host, generated_at: new Date().toISOString(), agents, capacity_policy: { strategy: 'pause', max_attempts: 1, backoff_ms: 0 }, cycle_limits: { dev_qa: 3, tester: 3, pentester: 3 }, reporting: { format: 'json', markdown: true } };
|
|
16
|
+
}
|
|
17
|
+
function stable(value) { if (Array.isArray(value)) return value.map(stable); if (value && typeof value === 'object') return Object.fromEntries(Object.keys(value).sort().map(k => [k, stable(value[k])])); return value; }
|
|
18
|
+
function digest(value) { return crypto.createHash('sha256').update(JSON.stringify(stable(value))).digest('hex'); }
|
|
19
|
+
function mergeAdditive(existing, base) {
|
|
20
|
+
if (Array.isArray(existing)) return existing;
|
|
21
|
+
if (!existing || typeof existing !== 'object') return existing === undefined ? base : existing;
|
|
22
|
+
const out = { ...base };
|
|
23
|
+
for (const [key, value] of Object.entries(existing)) out[key] = value && typeof value === 'object' && !Array.isArray(value) ? mergeAdditive(value, base && base[key]) : value;
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
async function initManifest(projectDir, feature, host, { overwrite = false } = {}) {
|
|
27
|
+
const file = manifestPath(projectDir, feature); const base = defaults(feature, host);
|
|
28
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
29
|
+
let value = base;
|
|
30
|
+
try { const old = JSON.parse(await fs.readFile(file, 'utf8')); if (!overwrite) value = mergeAdditive(old, base); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
31
|
+
await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
32
|
+
return { path: file, manifest: value, digest: digest(value) };
|
|
33
|
+
}
|
|
34
|
+
async function loadManifest(projectDir, feature) {
|
|
35
|
+
const file = manifestPath(projectDir, feature);
|
|
36
|
+
try { const value = JSON.parse(await fs.readFile(file, 'utf8')); const validation = validateManifest(value, feature); return { exists: true, path: file, manifest: value, digest: digest(value), ...validation }; }
|
|
37
|
+
catch (error) { if (error.code === 'ENOENT') return { exists: false, legacy: true, path: file, ok: true }; return { exists: true, path: file, ok: false, errors: [{ path: '$', message: error.message }] }; }
|
|
38
|
+
}
|
|
39
|
+
function resolveAgent(manifest, agent, overrides = {}) { const entry = manifest.agents[agent]; return { ...entry, host: overrides.host || entry.host || manifest.host, model: overrides.model || entry.model, source: 'manifest' }; }
|
|
40
|
+
async function resolveExecutionEntry(entry, { catalogLoader = loadModelCatalog } = {}) {
|
|
41
|
+
const cap = capabilities(entry.host);
|
|
42
|
+
if (entry.reasoning_effort && !cap.reasoning_effort) return { ok: false, reason: 'unsupported_reasoning_effort', host: entry.host, model_requested: entry.model, candidates: [] };
|
|
43
|
+
const catalog = cap.model_catalog ? await catalogLoader(entry.host) : { available: false, reason: 'unsupported_model_catalog', models: [] };
|
|
44
|
+
const model = resolveModel(entry.model, catalog);
|
|
45
|
+
if (!model.ok) return { ...entry, ...model, model_requested: entry.model };
|
|
46
|
+
const effort = validateReasoningEffort(model, entry.reasoning_effort);
|
|
47
|
+
if (!effort.ok) return { ...entry, ok: false, reason: effort.reason, supported: effort.supported, candidates: [], model_requested: entry.model, model_resolved: model.resolved };
|
|
48
|
+
return {
|
|
49
|
+
...entry,
|
|
50
|
+
ok: true,
|
|
51
|
+
model_requested: entry.model,
|
|
52
|
+
model: model.resolved,
|
|
53
|
+
model_resolved: model.resolved,
|
|
54
|
+
model_resolution_strategy: model.strategy,
|
|
55
|
+
catalog_source: model.catalog_source,
|
|
56
|
+
catalog_fetched_at: model.catalog_fetched_at,
|
|
57
|
+
reasoning_effort: effort.reasoning_effort,
|
|
58
|
+
reasoning_effort_verification: effort.verification
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function resolveAgentExecution(manifest, agent, overrides = {}, options = {}) { return resolveExecutionEntry(resolveAgent(manifest, agent, overrides), options); }
|
|
62
|
+
module.exports = { defaults, digest, initManifest, loadManifest, manifestPath, mergeAdditive, resolveAgent, resolveAgentExecution, resolveExecutionEntry };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { REASONING_EFFORTS } = require('./schema');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_MAX_MODELS = 1000;
|
|
10
|
+
const SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/;
|
|
11
|
+
|
|
12
|
+
function unavailable(reason) {
|
|
13
|
+
return { available: false, reason, source: null, fetched_at: null, client_version: null, models: [] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function codexHome(env, home) {
|
|
17
|
+
const configured = typeof env.CODEX_HOME === 'string' ? env.CODEX_HOME.trim() : '';
|
|
18
|
+
return configured || path.join(home, '.codex');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeEfforts(levels) {
|
|
22
|
+
if (!Array.isArray(levels)) return [];
|
|
23
|
+
const supported = new Set(REASONING_EFFORTS);
|
|
24
|
+
return [...new Set(levels.map(level => typeof level === 'string' ? level : level?.effort)
|
|
25
|
+
.filter(effort => typeof effort === 'string' && supported.has(effort)))];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sanitizeModels(models) {
|
|
29
|
+
if (!Array.isArray(models)) return [];
|
|
30
|
+
const bySlug = new Map();
|
|
31
|
+
for (const model of models) {
|
|
32
|
+
const slug = typeof model?.slug === 'string' ? model.slug.trim() : '';
|
|
33
|
+
if (!SAFE_MODEL_ID.test(slug) || bySlug.has(slug)) continue;
|
|
34
|
+
const display = typeof model.display_name === 'string' && model.display_name.trim()
|
|
35
|
+
? model.display_name.trim().slice(0, 200)
|
|
36
|
+
: slug;
|
|
37
|
+
bySlug.set(slug, {
|
|
38
|
+
slug,
|
|
39
|
+
display_name: display,
|
|
40
|
+
supported_efforts: normalizeEfforts(model.supported_reasoning_levels)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return [...bySlug.values()];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function loadModelCatalog(host, options = {}) {
|
|
47
|
+
if (host !== 'codex') return unavailable('unsupported_model_catalog');
|
|
48
|
+
const env = options.env || process.env;
|
|
49
|
+
const home = options.home || os.homedir();
|
|
50
|
+
const file = path.join(codexHome(env, home), 'models_cache.json');
|
|
51
|
+
const maxBytes = Math.max(1, Number(options.maxBytes) || DEFAULT_MAX_BYTES);
|
|
52
|
+
const maxModels = Math.max(1, Number(options.maxModels) || DEFAULT_MAX_MODELS);
|
|
53
|
+
let stat;
|
|
54
|
+
try {
|
|
55
|
+
stat = await fs.stat(file);
|
|
56
|
+
} catch {
|
|
57
|
+
return unavailable('catalog_unavailable');
|
|
58
|
+
}
|
|
59
|
+
if (!stat.isFile()) return unavailable('catalog_unavailable');
|
|
60
|
+
if (stat.size > maxBytes) return unavailable('catalog_too_large');
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
64
|
+
} catch {
|
|
65
|
+
return unavailable('catalog_invalid');
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(parsed?.models) && parsed.models.length > maxModels) return unavailable('catalog_too_many_models');
|
|
68
|
+
const models = sanitizeModels(parsed?.models);
|
|
69
|
+
if (!models.length) return unavailable('catalog_incompatible');
|
|
70
|
+
return {
|
|
71
|
+
available: true,
|
|
72
|
+
reason: null,
|
|
73
|
+
source: 'codex_local_cache',
|
|
74
|
+
fetched_at: typeof parsed.fetched_at === 'string' ? parsed.fetched_at.slice(0, 100) : null,
|
|
75
|
+
client_version: typeof parsed.client_version === 'string' ? parsed.client_version.slice(0, 50) : null,
|
|
76
|
+
models
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { DEFAULT_MAX_BYTES, DEFAULT_MAX_MODELS, loadModelCatalog, sanitizeModels };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { MAX_MODEL_NAME_LENGTH, REASONING_EFFORTS } = require('./schema');
|
|
4
|
+
|
|
5
|
+
const LITERAL_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/;
|
|
6
|
+
const GENERIC_ALIASES = new Set(['gpt', 'model', 'openai', 'codex']);
|
|
7
|
+
|
|
8
|
+
function tokens(value) {
|
|
9
|
+
return String(value || '').normalize('NFKD').replace(/[\u0300-\u036f]/g, '')
|
|
10
|
+
.toLowerCase().match(/[a-z]+|\d+/g) || [];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeModelName(value) { return tokens(value).join('-'); }
|
|
14
|
+
function numericTokens(value) { return tokens(value).filter(token => /^\d+$/.test(token)); }
|
|
15
|
+
function alphaTokens(value) { return tokens(value).filter(token => /^[a-z]+$/.test(token)); }
|
|
16
|
+
|
|
17
|
+
function distance(left, right) {
|
|
18
|
+
const a = String(left); const b = String(right);
|
|
19
|
+
if (a.length > MAX_MODEL_NAME_LENGTH || b.length > MAX_MODEL_NAME_LENGTH) return Number.POSITIVE_INFINITY;
|
|
20
|
+
const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
21
|
+
for (let i = 0; i <= a.length; i++) matrix[i][0] = i;
|
|
22
|
+
for (let j = 0; j <= b.length; j++) matrix[0][j] = j;
|
|
23
|
+
for (let i = 1; i <= a.length; i++) {
|
|
24
|
+
for (let j = 1; j <= b.length; j++) {
|
|
25
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
26
|
+
matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
|
|
27
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
28
|
+
matrix[i][j] = Math.min(matrix[i][j], matrix[i - 2][j - 2] + cost);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return matrix[a.length][b.length];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function candidates(models) {
|
|
36
|
+
return (Array.isArray(models) ? models : []).filter(model => model && typeof model.slug === 'string')
|
|
37
|
+
.map(model => ({
|
|
38
|
+
slug: model.slug,
|
|
39
|
+
display_name: typeof model.display_name === 'string' ? model.display_name : model.slug,
|
|
40
|
+
supported_efforts: Array.isArray(model.supported_efforts) ? model.supported_efforts : []
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function uniqueBySlug(models) { return [...new Map(models.map(model => [model.slug, model])).values()]; }
|
|
45
|
+
function diagnosticSlugs(models) { return uniqueBySlug(models).map(model => model.slug).sort().slice(0, 5); }
|
|
46
|
+
|
|
47
|
+
function success(requested, model, strategy, catalog) {
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
requested,
|
|
51
|
+
resolved: model.slug,
|
|
52
|
+
strategy,
|
|
53
|
+
catalog_source: catalog?.source || null,
|
|
54
|
+
catalog_fetched_at: catalog?.fetched_at || null,
|
|
55
|
+
supported_efforts: model.supported_efforts || []
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function failure(requested, reason, models = [], catalog = null) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
requested,
|
|
63
|
+
resolved: null,
|
|
64
|
+
reason,
|
|
65
|
+
candidates: diagnosticSlugs(models),
|
|
66
|
+
catalog_source: catalog?.source || null,
|
|
67
|
+
catalog_fetched_at: catalog?.fetched_at || null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveModel(value, catalog) {
|
|
72
|
+
const requested = typeof value === 'string' ? value.trim() : '';
|
|
73
|
+
if (!requested) return failure(requested, 'invalid_model');
|
|
74
|
+
if (requested.length > MAX_MODEL_NAME_LENGTH) return failure(requested.slice(0, MAX_MODEL_NAME_LENGTH), 'invalid_model');
|
|
75
|
+
if (requested === 'configured-default') {
|
|
76
|
+
return success(requested, { slug: requested, supported_efforts: [] }, 'configured_default', catalog);
|
|
77
|
+
}
|
|
78
|
+
if (!catalog?.available) {
|
|
79
|
+
if (!LITERAL_MODEL_ID.test(requested)) return failure(requested, catalog?.reason || 'catalog_unavailable');
|
|
80
|
+
return success(requested, { slug: requested, supported_efforts: [] }, 'unverified_literal', null);
|
|
81
|
+
}
|
|
82
|
+
const models = candidates(catalog.models);
|
|
83
|
+
const exact = models.find(model => model.slug === requested);
|
|
84
|
+
if (exact) return success(requested, exact, 'exact_slug', catalog);
|
|
85
|
+
|
|
86
|
+
const requestedKey = normalizeModelName(requested);
|
|
87
|
+
const normalized = uniqueBySlug(models.filter(model => normalizeModelName(model.slug) === requestedKey
|
|
88
|
+
|| normalizeModelName(model.display_name) === requestedKey));
|
|
89
|
+
if (normalized.length === 1) return success(requested, normalized[0], 'normalized_name', catalog);
|
|
90
|
+
if (normalized.length > 1) return failure(requested, 'ambiguous_model', normalized, catalog);
|
|
91
|
+
|
|
92
|
+
const requestedTokens = tokens(requested);
|
|
93
|
+
const isGeneric = requestedTokens.length === 1 && GENERIC_ALIASES.has(requestedTokens[0]);
|
|
94
|
+
const isUsableAlias = requestedKey.length >= 4 && !isGeneric && requestedTokens.some(token => /^[a-z]+$/.test(token));
|
|
95
|
+
if (isUsableAlias) {
|
|
96
|
+
const alias = uniqueBySlug(models.filter(model => {
|
|
97
|
+
const slugTokens = tokens(model.slug); const displayTokens = tokens(model.display_name);
|
|
98
|
+
const suffix = list => list.length >= requestedTokens.length
|
|
99
|
+
&& requestedTokens.every((token, index) => token === list[list.length - requestedTokens.length + index]);
|
|
100
|
+
return suffix(slugTokens) || suffix(displayTokens);
|
|
101
|
+
}));
|
|
102
|
+
if (alias.length === 1) return success(requested, alias[0], 'unique_alias', catalog);
|
|
103
|
+
if (alias.length > 1) return failure(requested, 'ambiguous_model', alias, catalog);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const requestedNumbers = numericTokens(requested);
|
|
107
|
+
const requestedAlpha = alphaTokens(requested);
|
|
108
|
+
const eligible = models.filter(model => {
|
|
109
|
+
const modelNumbers = numericTokens(model.slug);
|
|
110
|
+
if (requestedNumbers.length && requestedNumbers.join('.') !== modelNumbers.join('.')) return false;
|
|
111
|
+
const modelAlpha = alphaTokens(model.slug);
|
|
112
|
+
return !requestedAlpha.length || requestedAlpha[0] === modelAlpha[0];
|
|
113
|
+
});
|
|
114
|
+
const ranked = eligible.map(model => ({ model, score: distance(requestedKey, normalizeModelName(model.slug)) }))
|
|
115
|
+
.sort((a, b) => a.score - b.score || a.model.slug.localeCompare(b.model.slug));
|
|
116
|
+
const maxDistance = Math.min(3, Math.max(1, Math.floor(requestedKey.length * 0.1)));
|
|
117
|
+
if (!ranked.length || ranked[0].score > maxDistance) return failure(requested, 'model_not_found', [], catalog);
|
|
118
|
+
const best = ranked.filter(item => item.score === ranked[0].score);
|
|
119
|
+
if (best.length !== 1) return failure(requested, 'ambiguous_model', best.map(item => item.model), catalog);
|
|
120
|
+
return success(requested, best[0].model, 'fuzzy_unique', catalog);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function validateReasoningEffort(resolution, effort) {
|
|
124
|
+
if (effort === undefined || effort === null || effort === '') return { ok: true, reasoning_effort: null, verification: 'inherited' };
|
|
125
|
+
if (!REASONING_EFFORTS.includes(effort)) return { ok: false, reason: 'invalid_reasoning_effort', supported: REASONING_EFFORTS };
|
|
126
|
+
if (!resolution?.ok) return { ok: false, reason: resolution?.reason || 'model_resolution_failed', supported: [] };
|
|
127
|
+
const supported = Array.isArray(resolution.supported_efforts) ? resolution.supported_efforts : [];
|
|
128
|
+
if (supported.length && !supported.includes(effort)) return { ok: false, reason: 'unsupported_reasoning_effort', supported };
|
|
129
|
+
return { ok: true, reasoning_effort: effort, verification: supported.length ? 'catalog' : 'unverified' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { distance, normalizeModelName, resolveModel, validateReasoningEffort };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('node:fs/promises');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const VERDICTS = ['PASS', 'FAIL', 'BLOCKED'];
|
|
5
|
+
const IDENTITY_FIELDS = ['feature','run_id','attempt_id','agent','host','model_resolved','manifest_digest'];
|
|
6
|
+
function validateReport(r, expected = null) { const errors=[]; const req=['version','feature','run_id','attempt_id','agent','host','model_requested','model_resolved','manifest_digest','writable_roots','started_at','finished_at','verdict','findings','evidence'];if(expected?.model_resolution_strategy!==undefined)req.push('model_resolution_strategy'); for(const k of req) if(r?.[k]===undefined) errors.push({path:`$.${k}`,message:'is required'}); if(r && !VERDICTS.includes(r.verdict)) errors.push({path:'$.verdict',message:'must be PASS, FAIL, or BLOCKED'}); if(r && !Array.isArray(r.findings)) errors.push({path:'$.findings',message:'must be an array'});if(r&&!Array.isArray(r.writable_roots))errors.push({path:'$.writable_roots',message:'must be an array'}); if(expected) for(const key of IDENTITY_FIELDS) if(r?.[key]!==expected[key]) errors.push({path:`$.${key}`,message:'does not match the registered attempt'});for(const key of ['model_requested','model_resolution_strategy'])if(expected?.[key]!==undefined&&r?.[key]!==expected[key])errors.push({path:`$.${key}`,message:'does not match the registered attempt'});if(expected&&expected.reasoning_effort&&r?.reasoning_effort!==expected.reasoning_effort)errors.push({path:'$.reasoning_effort',message:'does not match the registered attempt'});if(expected&&JSON.stringify(r?.writable_roots)!==JSON.stringify(expected.writable_roots))errors.push({path:'$.writable_roots',message:'does not match the registered attempt'}); if(expected && !['ready','running'].includes(expected.status)) errors.push({path:'$.attempt_id',message:'attempt is not eligible to report'}); return {ok:errors.length===0,errors}; }
|
|
7
|
+
function safeReportPath(projectDir, relative) { const root=path.resolve(projectDir,'.aioson','context','reports'); const target=path.resolve(projectDir,relative); if(target!==root && !target.startsWith(root+path.sep)) throw new Error('report path escapes report root'); return target; }
|
|
8
|
+
async function writeReport(projectDir, relative, report, expected) { const check=validateReport(report,expected); if(!check.ok){const e=new Error('invalid report');e.errors=check.errors;throw e;} const file=safeReportPath(projectDir,relative); await fs.mkdir(path.dirname(file),{recursive:true}); const handle=await fs.open(file,'wx'); try{await handle.writeFile(`${JSON.stringify(report,null,2)}\n`,'utf8')}finally{await handle.close()} return file; }
|
|
9
|
+
module.exports={validateReport,safeReportPath,writeReport};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const HOSTS = ['claude', 'codex', 'opencode'];
|
|
4
|
+
const MODES = ['fresh-session', 'subagent', 'external', 'current-session'];
|
|
5
|
+
const AGENTS = ['dev', 'qa', 'tester', 'pentester', 'validator'];
|
|
6
|
+
const REASONING_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
7
|
+
const MAX_MODEL_NAME_LENGTH = 200;
|
|
8
|
+
const ROOT_KEYS = ['version','feature','host','generated_at','agents','capacity_policy','cycle_limits','reporting'];
|
|
9
|
+
const AGENT_KEYS = ['enabled','host','mode','model','reasoning_effort','writable_roots','fallbacks','report'];
|
|
10
|
+
const SECRET_KEY = /token|secret|password|authorization|api[_-]?key/i;
|
|
11
|
+
|
|
12
|
+
function validateManifest(value, expectedFeature) {
|
|
13
|
+
const errors = [];
|
|
14
|
+
const add = (path, message) => errors.push({ path, message });
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return { ok: false, errors: [{ path: '$', message: 'must be an object' }] };
|
|
16
|
+
const scanSecrets=(node,p='$')=>{if(!node||typeof node!=='object')return;for(const [key,child] of Object.entries(node)){if(SECRET_KEY.test(key))add(`${p}.${key}`,'secret fields are forbidden; use environment configuration');scanSecrets(child,`${p}.${key}`)}};scanSecrets(value);
|
|
17
|
+
for (const key of Object.keys(value)) if (!ROOT_KEYS.includes(key)) add(`$.${key}`, SECRET_KEY.test(key) ? 'secret fields are forbidden; use environment configuration' : 'unknown field');
|
|
18
|
+
if (value.version !== 1) add('$.version', 'must equal 1');
|
|
19
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value.feature || '')) add('$.feature', 'must be a kebab-case slug');
|
|
20
|
+
if (expectedFeature && value.feature !== expectedFeature) add('$.feature', `must equal ${expectedFeature}`);
|
|
21
|
+
if (!HOSTS.includes(value.host)) add('$.host', `must be one of ${HOSTS.join(', ')}`);
|
|
22
|
+
if (!value.agents || typeof value.agents !== 'object') add('$.agents', 'must be an object');
|
|
23
|
+
else for(const key of Object.keys(value.agents)) if(!AGENTS.includes(key)) add(`$.agents.${key}`,'unknown agent');
|
|
24
|
+
for (const id of AGENTS) {
|
|
25
|
+
const agent = value.agents && value.agents[id];
|
|
26
|
+
if (!agent || typeof agent !== 'object') { add(`$.agents.${id}`, 'is required'); continue; }
|
|
27
|
+
for (const key of Object.keys(agent)) if (!AGENT_KEYS.includes(key)) add(`$.agents.${id}.${key}`, SECRET_KEY.test(key) ? 'secret fields are forbidden; use environment configuration' : 'unknown field');
|
|
28
|
+
if (typeof agent.enabled !== 'boolean') add(`$.agents.${id}.enabled`, 'must be boolean');
|
|
29
|
+
if (!HOSTS.includes(agent.host)) add(`$.agents.${id}.host`, `must be one of ${HOSTS.join(', ')}`);
|
|
30
|
+
if (!MODES.includes(agent.mode)) add(`$.agents.${id}.mode`, `must be one of ${MODES.join(', ')}`);
|
|
31
|
+
if (typeof agent.model !== 'string' || !agent.model.trim()) add(`$.agents.${id}.model`, 'must be a non-empty model id');
|
|
32
|
+
else if (agent.model.length > MAX_MODEL_NAME_LENGTH) add(`$.agents.${id}.model`, `must be at most ${MAX_MODEL_NAME_LENGTH} characters`);
|
|
33
|
+
if (agent.reasoning_effort !== undefined && !REASONING_EFFORTS.includes(agent.reasoning_effort)) add(`$.agents.${id}.reasoning_effort`, `must be one of ${REASONING_EFFORTS.join(', ')}`);
|
|
34
|
+
if(!Array.isArray(agent.writable_roots))add(`$.agents.${id}.writable_roots`,'must be an array');else agent.writable_roots.forEach((root,index)=>{if(typeof root!=='string'||!root.trim())add(`$.agents.${id}.writable_roots[${index}]`,'must be a non-empty path')});
|
|
35
|
+
if (!Array.isArray(agent.fallbacks)) add(`$.agents.${id}.fallbacks`, 'must be an array');
|
|
36
|
+
else agent.fallbacks.forEach((fallback,index)=>{const p=`$.agents.${id}.fallbacks[${index}]`;if(!fallback||typeof fallback!=='object'||Array.isArray(fallback)){add(p,'must be {host, model}');return}for(const key of Object.keys(fallback))if(!['host','model'].includes(key))add(`${p}.${key}`,'unknown field');if(!HOSTS.includes(fallback.host))add(`${p}.host`,`must be one of ${HOSTS.join(', ')}`);if(typeof fallback.model!=='string'||!fallback.model.trim())add(`${p}.model`,'must be a non-empty model id');else if(fallback.model.length>MAX_MODEL_NAME_LENGTH)add(`${p}.model`,`must be at most ${MAX_MODEL_NAME_LENGTH} characters`)});
|
|
37
|
+
if (typeof agent.report !== 'string' || !agent.report.includes('{run_id}')) add(`$.agents.${id}.report`, 'must include {run_id}');
|
|
38
|
+
}
|
|
39
|
+
const limits = value.cycle_limits;
|
|
40
|
+
if (!limits || typeof limits !== 'object') add('$.cycle_limits', 'is required');
|
|
41
|
+
else for (const [key, number] of Object.entries(limits)) if (!Number.isInteger(number) || number < 0) add(`$.cycle_limits.${key}`, 'must be a non-negative integer');
|
|
42
|
+
if (!value.capacity_policy || !['pause', 'retry', 'wait', 'fallback'].includes(value.capacity_policy.strategy)) add('$.capacity_policy.strategy', 'must be pause, retry, wait, or fallback');
|
|
43
|
+
else { for(const key of Object.keys(value.capacity_policy)) if(!['strategy','max_attempts','backoff_ms','allow_cross_host'].includes(key)) add(`$.capacity_policy.${key}`,'unknown field'); if(!Number.isInteger(value.capacity_policy.max_attempts)||value.capacity_policy.max_attempts<1)add('$.capacity_policy.max_attempts','must be a positive integer'); if(!Number.isInteger(value.capacity_policy.backoff_ms)||value.capacity_policy.backoff_ms<0)add('$.capacity_policy.backoff_ms','must be a non-negative integer'); }
|
|
44
|
+
if(!value.reporting||typeof value.reporting!=='object')add('$.reporting','is required');else for(const key of Object.keys(value.reporting))if(!['format','markdown'].includes(key))add(`$.reporting.${key}`,'unknown field');
|
|
45
|
+
return { ok: errors.length === 0, errors };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { AGENTS, HOSTS, MAX_MODEL_NAME_LENGTH, MODES, REASONING_EFFORTS, validateManifest };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const crypto=require('node:crypto');
|
|
3
|
+
const {openRuntimeDb,createExecutionRun,attachExecutionProcess,transitionExecutionRun,appendExecutionEvent,attachExecutionReport,pruneExecutionTelemetry}=require('../runtime-store');
|
|
4
|
+
const DEFAULTS={maxQueueBytes:256*1024,maxEventBytes:16*1024,maxPersistedBytes:1024*1024,maxPendingBytes:64*1024,flushMs:100};
|
|
5
|
+
function redact(value){return String(value||'').replace(/(authorization\s*[:=]\s*(?:bearer\s+)?|api[_-]?key\s*[:=]\s*|token\s*[:=]\s*|password\s*[:=]\s*)[^\s,;]+/gi,'$1[REDACTED]').replace(/\b(sk-[A-Za-z0-9_-]{8,})\b/g,'[REDACTED]');}
|
|
6
|
+
function fingerprint(pid,startedAt){return crypto.createHash('sha256').update(`${pid}:${startedAt}`).digest('hex').slice(0,24);}
|
|
7
|
+
async function createTelemetryBridge(projectDir,correlation,options={}){
|
|
8
|
+
const cfg={...DEFAULTS,...options};const opened=await openRuntimeDb(projectDir);const db=opened.db;pruneExecutionTelemetry(db,{retentionDays:options.retentionDays||14,batch:100});const run=createExecutionRun(db,correlation);let queue=[],queued=0,persisted=0,dropped=0,closed=false,timer=null;const pendingByStream=new Map();
|
|
9
|
+
const flush=()=>{if(!queue.length&&!dropped)return;const items=queue;queue=[];queued=0;for(const item of items){appendExecutionEvent(db,run.telemetry_run_id,item);persisted+=item.bytes;}if(dropped){appendExecutionEvent(db,run.telemetry_run_id,{type:'output_truncated',safe_summary:`${dropped} output bytes dropped`,bytes:Math.min(dropped,cfg.maxEventBytes)});db.prepare('UPDATE agent_execution_runs SET truncated_bytes=truncated_bytes+?,dropped_events=dropped_events+1 WHERE telemetry_run_id=?').run(dropped,run.telemetry_run_id);dropped=0;}};
|
|
10
|
+
const schedule=()=>{if(!timer)timer=setTimeout(()=>{timer=null;try{flush();}catch{}},cfg.flushMs)};
|
|
11
|
+
const enqueue=(stream,text)=>{let safe=redact(text);let bytes=Buffer.byteLength(safe);if(bytes>cfg.maxEventBytes){dropped+=bytes-cfg.maxEventBytes;safe=Buffer.from(safe).subarray(0,cfg.maxEventBytes).toString();bytes=Buffer.byteLength(safe)}if(persisted+queued+bytes>cfg.maxPersistedBytes||queued+bytes>cfg.maxQueueBytes){dropped+=bytes;return}queue.push({type:'output',stream,safe_summary:safe,bytes});queued+=bytes};
|
|
12
|
+
const output=(stream,chunk)=>{if(closed)return;let pending=(pendingByStream.get(stream)||'')+String(chunk||'');if(Buffer.byteLength(pending)>cfg.maxPendingBytes){dropped+=Buffer.byteLength(pending);pendingByStream.set(stream,'');return schedule()}let newline;while((newline=pending.indexOf('\n'))>=0){enqueue(stream,pending.slice(0,newline+1));pending=pending.slice(newline+1)}pendingByStream.set(stream,pending);schedule();};
|
|
13
|
+
return {run,output,onSpawn(pid,startedAt=new Date().toISOString()){return attachExecutionProcess(db,correlation,{pid,fingerprint:fingerprint(pid,startedAt)})},event(type,summary,payload){appendExecutionEvent(db,correlation,{type,safe_summary:redact(summary),payload})},transition(next,reason){flush();return transitionExecutionRun(db,correlation,next,reason)},report(report,path,digest){flush();return attachExecutionReport(db,correlation,{...report,path,digest})},close(){if(closed)return;closed=true;if(timer)clearTimeout(timer);for(const [stream,pending] of pendingByStream)if(pending)enqueue(stream,pending);flush();db.close();}};
|
|
14
|
+
}
|
|
15
|
+
module.exports={createTelemetryBridge,redact,fingerprint};
|
package/src/agents.js
CHANGED
|
@@ -67,7 +67,7 @@ function buildAgentPrompt(agent, tool, options = {}) {
|
|
|
67
67
|
'',
|
|
68
68
|
`**Language boundary:** Agent instructions are canonical in English. All user-facing communication must be in ${interactionLanguage}.`,
|
|
69
69
|
'',
|
|
70
|
-
`**Scope boundary:** You operate exclusively as ${agent.command}. Do not perform work that belongs to another agent. When your work is complete, output only the handoff — which agent is next and why. Do not continue into that agent\'s territory.${autoHandoff ? ' Exception: autopilot handoff is active for this stage — follow `.aioson/docs/autopilot-handoff.md` and auto-invoke the next agent\'s skill when no stop condition applies. The chain
|
|
70
|
+
`**Scope boundary:** You operate exclusively as ${agent.command}. Do not perform work that belongs to another agent. When your work is complete, output only the handoff — which agent is next and why. Do not continue into that agent\'s territory.${autoHandoff ? ' Exception: autopilot handoff is active for this stage — follow `.aioson/docs/autopilot-handoff.md` and auto-invoke the next agent\'s skill when no stop condition applies. The chain runs the whole feature: the spec authority (`@sheldon`/`@orchestrator`) seeds the agentic scheme and crosses into `@dev` via the `dev-state.md` cold-start packet once its own gates/decisions are settled, and the post-dev review cycle (`@dev` → `@qa` → `@tester`/`@pentester` when their triggers fire → `@validator`) continues automatically. It stops only for a genuine human decision or a stop condition, and NEVER auto-runs `feature:close`/publish — those require explicit human approval.' : ''}`,
|
|
71
71
|
].join('\n');
|
|
72
72
|
|
|
73
73
|
if (safeTool === 'claude') {
|
package/src/artifact-kinds.js
CHANGED
|
@@ -29,7 +29,8 @@ const AGENT_ARTIFACT_KIND = {
|
|
|
29
29
|
'design-hybrid-forge': { kind: 'hybrid-skill', needs: 'slug' },
|
|
30
30
|
copywriter: { kind: 'copy', needs: 'slug' },
|
|
31
31
|
orache: { kind: 'orache-report', needs: 'file' },
|
|
32
|
-
'site-forge': { kind: 'site', needs: 'dir', opts: { noBuild: true } }
|
|
32
|
+
'site-forge': { kind: 'site', needs: 'dir', opts: { noBuild: true } },
|
|
33
|
+
'briefing-refiner': { kind: 'review', needs: 'slug' }
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const NEEDS_FLAG = { slug: '--slug=<slug>', file: '--file=<path>', dir: '--dir=<dir>' };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Autopilot activation signal — the single source of truth for "is autopilot
|
|
5
|
+
* on?" outside the agent markdown files.
|
|
6
|
+
*
|
|
7
|
+
* Contract (docs/autopilot-handoff.md "Activation"), highest precedence first:
|
|
8
|
+
* - A scheme for the CURRENT feature with `agentic_policy.enabled: false`
|
|
9
|
+
* (the `--step` disarm) → OFF, even over `auto_handoff: true` — an explicit
|
|
10
|
+
* per-feature choice always wins over the project default.
|
|
11
|
+
* - `auto_handoff: true` in project.context.md frontmatter → ON (project default).
|
|
12
|
+
* - `auto_handoff: false` → OFF, even if a seeded scheme is lying around
|
|
13
|
+
* (step-by-step is the standing choice; a leftover scheme must not override it).
|
|
14
|
+
* - flag absent → ON only when `.aioson/context/workflow-execute.json` exists
|
|
15
|
+
* with `agentic_policy.enabled: true` (the per-feature "Autopilot" choice,
|
|
16
|
+
* which deliberately does NOT persist the frontmatter flag). When a slug is
|
|
17
|
+
* provided, the scheme counts only if it was seeded for THAT feature — a
|
|
18
|
+
* scheme left by a different/closed feature does not.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('node:fs/promises');
|
|
22
|
+
const path = require('node:path');
|
|
23
|
+
const { validateProjectContextFile } = require('./context');
|
|
24
|
+
|
|
25
|
+
const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
|
|
26
|
+
|
|
27
|
+
async function readSeededScheme(targetDir) {
|
|
28
|
+
try {
|
|
29
|
+
const raw = await fs.readFile(path.join(targetDir, EXECUTION_STATE_RELATIVE_PATH), 'utf8');
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function resolveAutopilotSignal(targetDir, { slug = null, projectContext = null } = {}) {
|
|
37
|
+
let flag;
|
|
38
|
+
try {
|
|
39
|
+
const context = projectContext || await validateProjectContextFile(targetDir);
|
|
40
|
+
flag = context && context.data && Object.prototype.hasOwnProperty.call(context.data, 'auto_handoff')
|
|
41
|
+
? context.data.auto_handoff === true
|
|
42
|
+
: null;
|
|
43
|
+
} catch {
|
|
44
|
+
flag = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const scheme = await readSeededScheme(targetDir);
|
|
48
|
+
const slugMatches = Boolean(
|
|
49
|
+
scheme && (!slug || !scheme.feature || String(scheme.feature) === String(slug))
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Explicit per-feature disarm (--step) beats the project-wide flag.
|
|
53
|
+
if (
|
|
54
|
+
scheme && scheme.agentic_policy && scheme.agentic_policy.enabled === false &&
|
|
55
|
+
slugMatches
|
|
56
|
+
) {
|
|
57
|
+
return { enabled: false, source: 'scheme_disarmed' };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (flag === true) return { enabled: true, source: 'frontmatter' };
|
|
61
|
+
if (flag === false) return { enabled: false, source: 'frontmatter_off' };
|
|
62
|
+
|
|
63
|
+
const schemeEnabled = Boolean(scheme && scheme.agentic_policy && scheme.agentic_policy.enabled === true);
|
|
64
|
+
if (!schemeEnabled) return { enabled: false, source: null };
|
|
65
|
+
if (!slugMatches) return { enabled: false, source: 'scheme_other_feature' };
|
|
66
|
+
return { enabled: true, source: 'seeded_scheme' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
resolveAutopilotSignal
|
|
71
|
+
};
|
package/src/cli.js
CHANGED
|
@@ -188,7 +188,8 @@ const { runPreflight } = require('./commands/preflight');
|
|
|
188
188
|
const { runClassify } = require('./commands/classify');
|
|
189
189
|
const { runPrototypeCheck } = require('./commands/prototype-check');
|
|
190
190
|
const { runVerifyImplementation } = require('./commands/verify-implementation');
|
|
191
|
-
const { runVerificationPlan } = require('./commands/verification-plan');
|
|
191
|
+
const { runVerificationPlan } = require('./commands/verification-plan');
|
|
192
|
+
const { runAgentExecution } = require('./commands/agent-execution');
|
|
192
193
|
const { runSizing } = require('./commands/sizing');
|
|
193
194
|
const { runDetectTestRunner } = require('./commands/detect-test-runner');
|
|
194
195
|
const { runPulseUpdate } = require('./commands/pulse-update');
|
|
@@ -237,7 +238,7 @@ const { runGenomePublish, runGenomeInstallStore, runGenomeInstall, runGenomeList
|
|
|
237
238
|
const { runSkillPublish, runSkillInstallStore, runSkillListRemote } = require('./commands/store-skill');
|
|
238
239
|
const { runSquadPublish, runSquadInstall, runSquadGrant, runSquadList } = require('./commands/store-squad');
|
|
239
240
|
const { runSystemPackage, runSystemPublish, runSystemList, runSystemInstall } = require('./commands/store-system');
|
|
240
|
-
const { runBriefingApprove, runBriefingUnapprove } = require('./commands/briefing');
|
|
241
|
+
const { runBriefingApprove, runBriefingUnapprove, runBriefingReview, runBriefingApplyFeedback } = require('./commands/briefing');
|
|
241
242
|
const { runCompressAgents } = require('./commands/compress-agents');
|
|
242
243
|
|
|
243
244
|
const JSON_SUPPORTED_COMMANDS = new Set([
|
|
@@ -354,6 +355,10 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
354
355
|
'audit-code',
|
|
355
356
|
'verify:artifact',
|
|
356
357
|
'verify-artifact',
|
|
358
|
+
'briefing:review',
|
|
359
|
+
'briefing-review',
|
|
360
|
+
'briefing:apply-feedback',
|
|
361
|
+
'briefing-apply-feedback',
|
|
357
362
|
'review:feature',
|
|
358
363
|
'review-feature',
|
|
359
364
|
'config',
|
|
@@ -669,7 +674,21 @@ const JSON_SUPPORTED_COMMANDS = new Set([
|
|
|
669
674
|
'verify:implementation',
|
|
670
675
|
'verify-implementation',
|
|
671
676
|
'verification:plan',
|
|
672
|
-
'verification-plan',
|
|
677
|
+
'verification-plan',
|
|
678
|
+
'agent:execution:init',
|
|
679
|
+
'agent-execution-init',
|
|
680
|
+
'agent:execution:validate',
|
|
681
|
+
'agent-execution-validate',
|
|
682
|
+
'agent:execution:show',
|
|
683
|
+
'agent-execution-show',
|
|
684
|
+
'agent:execution:dispatch',
|
|
685
|
+
'agent-execution-dispatch',
|
|
686
|
+
'agent:execution:resume',
|
|
687
|
+
'agent-execution-resume',
|
|
688
|
+
'agent:execution:status',
|
|
689
|
+
'agent-execution-status',
|
|
690
|
+
'agent:execution:events',
|
|
691
|
+
'agent-execution-events',
|
|
673
692
|
'sizing',
|
|
674
693
|
'detect:test-runner',
|
|
675
694
|
'detect-test-runner',
|
|
@@ -1622,8 +1641,11 @@ async function main() {
|
|
|
1622
1641
|
result = await runPreflight({ args, options, logger: commandLogger });
|
|
1623
1642
|
} else if (command === 'classify') {
|
|
1624
1643
|
result = await runClassify({ args, options, logger: commandLogger });
|
|
1625
|
-
} else if (command === 'verification:plan' || command === 'verification-plan') {
|
|
1626
|
-
result = await runVerificationPlan({ args, options, logger: commandLogger });
|
|
1644
|
+
} else if (command === 'verification:plan' || command === 'verification-plan') {
|
|
1645
|
+
result = await runVerificationPlan({ args, options, logger: commandLogger });
|
|
1646
|
+
} else if (command.startsWith('agent:execution:') || command.startsWith('agent-execution-')) {
|
|
1647
|
+
const sub = command.replace(/^agent:execution:|^agent-execution-/, '');
|
|
1648
|
+
result = await runAgentExecution({ args, options: { ...options, sub }, logger: commandLogger });
|
|
1627
1649
|
} else if (command === 'prototype:check' || command === 'prototype-check') {
|
|
1628
1650
|
result = await runPrototypeCheck({ args, options, logger: commandLogger });
|
|
1629
1651
|
} else if (command === 'verify:implementation' || command === 'verify-implementation') {
|
|
@@ -1770,6 +1792,10 @@ async function main() {
|
|
|
1770
1792
|
result = await runBriefingApprove({ args, options, logger: commandLogger });
|
|
1771
1793
|
} else if (command === 'briefing:unapprove' || command === 'briefing-unapprove') {
|
|
1772
1794
|
result = await runBriefingUnapprove({ args, options, logger: commandLogger });
|
|
1795
|
+
} else if (command === 'briefing:review' || command === 'briefing-review') {
|
|
1796
|
+
result = await runBriefingReview({ args, options, logger: commandLogger });
|
|
1797
|
+
} else if (command === 'briefing:apply-feedback' || command === 'briefing-apply-feedback') {
|
|
1798
|
+
result = await runBriefingApplyFeedback({ args, options, logger: commandLogger });
|
|
1773
1799
|
} else {
|
|
1774
1800
|
const message = t('cli.unknown_command', { command });
|
|
1775
1801
|
if (jsonMode) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const path=require('node:path');
|
|
3
|
+
const {initManifest,loadManifest,resolveAgentExecution}=require('../agent-execution/manifest');
|
|
4
|
+
const {dispatch,readState}=require('../agent-execution/dispatcher');
|
|
5
|
+
const {openRuntimeDb,getExecutionSnapshot,listExecutionEvents}=require('../runtime-store');
|
|
6
|
+
async function resolveAllAgents(manifest,catalogLoader){
|
|
7
|
+
const entries=await Promise.all(Object.keys(manifest.agents).map(async id=>[id,await resolveAgentExecution(manifest,id,{}, {catalogLoader})]));
|
|
8
|
+
return Object.fromEntries(entries);
|
|
9
|
+
}
|
|
10
|
+
function resolutionErrors(resolutions){
|
|
11
|
+
return Object.entries(resolutions).filter(([,value])=>!value.ok).map(([id,value])=>({path:`$.agents.${id}.model`,message:value.reason,candidates:value.candidates||[],supported:value.supported||[]}));
|
|
12
|
+
}
|
|
13
|
+
async function runAgentExecution({args,options={},logger,catalogLoader}){
|
|
14
|
+
const projectDir=path.resolve(process.cwd(),args[0]||'.'); const sub=options.sub||'show'; const feature=String(options.feature||options.slug||'').trim();
|
|
15
|
+
if(!feature)return {ok:false,reason:'feature_required',message:'Use --feature=<slug>'};
|
|
16
|
+
if(sub==='init'){const created=await initManifest(projectDir,feature,String(options.host||'codex')); const result={ok:true,feature,path:path.relative(projectDir,created.path),digest:created.digest,manifest:created.manifest,availability:'validated_at_dispatch'}; if(!options.json)logger.log(`Agent execution manifest: ${result.path}\nValidate: aioson agent:execution:validate . --feature=${feature}`); return result;}
|
|
17
|
+
const loaded=await loadManifest(projectDir,feature);
|
|
18
|
+
if(sub==='validate'){
|
|
19
|
+
let resolutions={};let errors=loaded.errors||[];
|
|
20
|
+
if(loaded.exists&&loaded.ok){resolutions=await resolveAllAgents(loaded.manifest,catalogLoader);errors=resolutionErrors(resolutions)}
|
|
21
|
+
const result={ok:Boolean(loaded.exists&&loaded.ok&&!errors.length),feature,path:path.relative(projectDir,loaded.path),legacy:!loaded.exists,digest:loaded.digest||null,errors,resolutions,availability:'validated_at_dispatch'};
|
|
22
|
+
if(!options.json)(result.ok?logger.log(`Valid: ${result.path} (models resolved against the current runtime catalog).`):logger.error(JSON.stringify(result.errors,null,2)));return result;
|
|
23
|
+
}
|
|
24
|
+
if(sub==='show'){
|
|
25
|
+
if(!loaded.exists)return {ok:true,legacy:true,feature,path:path.relative(projectDir,loaded.path),message:'Manifest absent; legacy configured-default remains active.'};
|
|
26
|
+
if(!loaded.ok)return {ok:false,feature,errors:loaded.errors};
|
|
27
|
+
const agents=await resolveAllAgents(loaded.manifest,catalogLoader);const errors=resolutionErrors(agents);
|
|
28
|
+
return {ok:!errors.length,feature,path:path.relative(projectDir,loaded.path),digest:loaded.digest,agents,errors,availability:'validated_at_dispatch'};
|
|
29
|
+
}
|
|
30
|
+
if(sub==='dispatch')return dispatch({projectDir,feature,agent:options.agent||'dev',runId:options['run-id'],promptPath:options.prompt,catalogLoader});
|
|
31
|
+
if(sub==='resume'){const state=await readState(projectDir,feature);if(!state)return {ok:false,reason:'state_missing'};return dispatch({projectDir,feature,agent:options.agent||state.attempts.at(-1)?.agent||'dev',runId:state.run_id,promptPath:options.prompt,catalogLoader});}
|
|
32
|
+
if(sub==='status'){const {db}=await openRuntimeDb(projectDir);try{return{ok:true,schema_version:1,feature,runs:getExecutionSnapshot(db,{feature,agent:options.agent,state:options.state,limit:options.limit})}}finally{db.close()}}
|
|
33
|
+
if(sub==='events'){const state=await readState(projectDir,feature);const run=String(options.run||state?.attempts?.at(-1)?.telemetry_run_id||'');if(!run)return{ok:false,reason:'telemetry_run_required'};const {db}=await openRuntimeDb(projectDir);try{const owned=getExecutionSnapshot(db,{feature,limit:200}).some(item=>item.telemetry_run_id===run);if(!owned)return{ok:false,reason:'telemetry_run_not_found'};return{ok:true,schema_version:1,trust:'sanitized_untrusted_output',feature,telemetry_run_id:run,...listExecutionEvents(db,run,{after:options.after,limit:options.limit})}}finally{db.close()}}
|
|
34
|
+
return {ok:false,reason:'invalid_subcommand',valid:['init','validate','show','dispatch','resume','status','events']};
|
|
35
|
+
}
|
|
36
|
+
module.exports={runAgentExecution};
|
package/src/commands/agents.js
CHANGED
|
@@ -12,7 +12,8 @@ const {
|
|
|
12
12
|
const { normalizeInteractionLanguage } = require('../locales');
|
|
13
13
|
const { validateProjectContextFile, getInteractionLanguage } = require('../context');
|
|
14
14
|
const { exists } = require('../utils');
|
|
15
|
-
const { loadOrCreateState, runWorkflowNext } = require('./workflow-next');
|
|
15
|
+
const { AUTOPILOT_HANDOFF_STAGES, loadOrCreateState, runWorkflowNext } = require('./workflow-next');
|
|
16
|
+
const { resolveAutopilotSignal } = require('../autopilot-signal');
|
|
16
17
|
const {
|
|
17
18
|
bootstrapDirectAgentPrompt,
|
|
18
19
|
classifyDirectAgentRuntime
|
|
@@ -278,12 +279,27 @@ async function runAgentPrompt({ args, options, logger, t }) {
|
|
|
278
279
|
manifest,
|
|
279
280
|
requestedMode: promptAgent.id === 'scope-check' && getScopeCheckModeOption(options) ? null : options.mode || null
|
|
280
281
|
});
|
|
282
|
+
// Direct handoffs carry the autopilot exception too — without it the
|
|
283
|
+
// injected scope boundary instructs a manual stop and overrides the agent
|
|
284
|
+
// .md's autopilot section even when the flag/scheme says to chain.
|
|
285
|
+
let autoHandoff = false;
|
|
286
|
+
if (AUTOPILOT_HANDOFF_STAGES.has(promptAgent.id)) {
|
|
287
|
+
try {
|
|
288
|
+
const signal = await resolveAutopilotSignal(targetDir, {
|
|
289
|
+
slug: options.feature ? String(options.feature).trim() : null
|
|
290
|
+
});
|
|
291
|
+
autoHandoff = signal.enabled;
|
|
292
|
+
} catch {
|
|
293
|
+
autoHandoff = false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
281
296
|
prompt = buildAgentPrompt(promptAgent, tool, {
|
|
282
297
|
instructionPath,
|
|
283
298
|
interactionLanguage: locale,
|
|
284
299
|
autonomyMode: effectiveMode,
|
|
285
300
|
capabilitySummary: buildAgentCapabilitySummary(manifest, tool),
|
|
286
|
-
activationContext
|
|
301
|
+
activationContext,
|
|
302
|
+
autoHandoff
|
|
287
303
|
});
|
|
288
304
|
const runtimeClass = classifyDirectAgentRuntime(promptAgent.id);
|
|
289
305
|
const handoffLabel = runtimeClass.source === 'squad_session'
|