@jaimevalasek/aioson 1.17.2 → 1.18.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 +13 -0
- package/README.md +85 -51
- package/docs/en/3-recipes/full-feature-with-sheldon.md +1 -1
- package/docs/en/5-reference/cli-reference.md +4 -4
- package/docs/en/5-reference/qa-browser.md +2 -2
- package/docs/en/README.md +1 -1
- package/docs/en/deyvin-subtask-scout/how-to-use.md +2 -2
- package/docs/en/deyvin-subtask-scout/sub-task-scout.md +3 -3
- package/docs/en/deyvin-subtask-scout/troubleshooting.md +1 -1
- package/docs/pt/3-receitas/publicar-no-aioson-com.md +17 -0
- package/docs/pt/5-referencia/comandos-cli.md +2 -2
- package/docs/pt/5-referencia/inteligencia-adaptativa.md +3 -3
- package/docs/pt/5-referencia/skills.md +1 -1
- package/docs/pt/5-referencia/web3.md +3 -3
- package/docs/pt/README.md +1 -1
- package/docs/pt/_arquivo/README.md +1 -1
- package/docs/pt/_arquivo/cenarios.md +31 -31
- package/docs/pt/_arquivo/design-hybrid-forge.md +5 -5
- package/docs/pt/_arquivo/guia-engineer.md +1 -1
- package/docs/pt/_arquivo/profiler-system.md +1 -1
- package/docs/pt/_arquivo/site-forge.md +16 -16
- package/docs/pt/_arquivo/squad-genome.md +2 -2
- package/docs/pt/agentes.md +37 -37
- package/docs/pt/deyvin-subtask-scout/como-usar.md +2 -2
- package/docs/pt/deyvin-subtask-scout/sub-task-scout.md +1 -1
- package/docs/pt/deyvin-subtask-scout/troubleshooting.md +1 -1
- package/docs/pt/living-memory/README.md +1 -1
- package/docs/pt/living-memory/memoria-viva.md +2 -2
- package/docs/pt/living-memory/reflexao-in-harness.md +1 -1
- package/docs/pt/living-memory/troubleshooting.md +6 -6
- package/package.json +4 -2
- package/src/commands/gate-approve.js +56 -1
- package/src/commands/live.js +81 -54
- package/src/commands/op-capture.js +27 -2
- package/src/commands/op-list.js +33 -1
- package/src/commands/store-system.js +104 -12
- package/src/commands/tool-capabilities.js +14 -10
- package/src/commands/workflow-heal.js +47 -1
- package/src/i18n/messages/en.js +6 -5
- package/src/i18n/messages/pt-BR.js +6 -5
- package/src/lib/dev-resume.js +6 -1
- package/src/lib/tool-capabilities.js +64 -37
- package/src/operator-memory/decision.js +11 -4
- package/src/operator-memory/proposal.js +11 -7
- package/src/session-handoff.js +52 -1
- package/template/.aioson/agents/analyst.md +34 -2
- package/template/.aioson/agents/architect.md +33 -1
- package/template/.aioson/agents/briefing.md +26 -1
- package/template/.aioson/agents/copywriter.md +1 -1
- package/template/.aioson/agents/dev.md +2 -2
- package/template/.aioson/agents/deyvin.md +12 -12
- package/template/.aioson/agents/neo.md +74 -74
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/pentester.md +66 -14
- package/template/.aioson/agents/pm.md +18 -1
- package/template/.aioson/agents/product.md +12 -1
- package/template/.aioson/agents/qa.md +3 -3
- package/template/.aioson/agents/sheldon.md +24 -4
- package/template/.aioson/agents/tester.md +115 -2
- package/template/.aioson/docs/briefing/briefing-craft.md +16 -0
- package/template/.aioson/docs/deyvin/runtime-handoffs.md +1 -1
- package/template/.aioson/docs/handoff-persistence.md +7 -7
- package/template/.aioson/docs/pentester/browser-dast-playbook.md +398 -0
- package/template/.aioson/rules/agent-structural-contract.md +139 -0
- package/template/.aioson/skills/process/decision-presentation/SKILL.md +2 -2
package/src/commands/live.js
CHANGED
|
@@ -19,7 +19,7 @@ const {
|
|
|
19
19
|
const { ensureDir, exists } = require('../utils');
|
|
20
20
|
const { SUPPORTED_PROMPT_TOOLS } = require('../prompt-tool');
|
|
21
21
|
const { isTmuxAvailable, launchTmuxSession, buildSessionName, hasSession, attachSession } = require('../lib/tmux-launcher');
|
|
22
|
-
const { resolveResumeArgs } = require('../lib/tool-capabilities');
|
|
22
|
+
const { resolvePermissionModeArgs, resolveResumeArgs } = require('../lib/tool-capabilities');
|
|
23
23
|
|
|
24
24
|
const LIVE_EVENTS_LIMIT = 10;
|
|
25
25
|
const LIVE_MESSAGE_LIMIT = 500;
|
|
@@ -116,14 +116,16 @@ function parseJsonOption(value) {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// Combine `--resume` (mapped per-tool via TOOL_CAPS) with user-provided `--tool-args`.
|
|
120
|
-
// Resume args go FIRST so that codex `resume --last` (subcommand) lands at argv[1].
|
|
121
|
-
function buildLaunchArgs(options, tool) {
|
|
122
|
-
const resumeOpt = options.resume !== undefined ? options.resume : options.Resume;
|
|
123
|
-
const resumeArgs = resolveResumeArgs(tool, resumeOpt);
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
// Combine `--resume` (mapped per-tool via TOOL_CAPS) with user-provided `--tool-args`.
|
|
120
|
+
// Resume args go FIRST so that codex `resume --last` (subcommand) lands at argv[1].
|
|
121
|
+
function buildLaunchArgs(options, tool) {
|
|
122
|
+
const resumeOpt = options.resume !== undefined ? options.resume : options.Resume;
|
|
123
|
+
const resumeArgs = resolveResumeArgs(tool, resumeOpt);
|
|
124
|
+
const permissionMode = options['permission-mode'] || options.permissionMode;
|
|
125
|
+
const permissionArgs = resolvePermissionModeArgs(tool, permissionMode);
|
|
126
|
+
const userArgs = parseToolArgs(options['tool-args'] || options.toolArgs);
|
|
127
|
+
return [...resumeArgs, ...permissionArgs, ...userArgs];
|
|
128
|
+
}
|
|
127
129
|
|
|
128
130
|
function parseToolArgs(value) {
|
|
129
131
|
if (value === undefined || value === null || value === '') return [];
|
|
@@ -234,7 +236,7 @@ async function resolveExecutablePath(command) {
|
|
|
234
236
|
.filter(Boolean);
|
|
235
237
|
|
|
236
238
|
const extensions = process.platform === 'win32'
|
|
237
|
-
? Array.from(new Set([
|
|
239
|
+
? Array.from(new Set([...String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').map((entry) => entry.toLowerCase())]))
|
|
238
240
|
: [''];
|
|
239
241
|
|
|
240
242
|
for (const dir of pathEntries) {
|
|
@@ -1235,57 +1237,80 @@ async function runLiveStart({ args, options = {}, logger, t }) {
|
|
|
1235
1237
|
};
|
|
1236
1238
|
}
|
|
1237
1239
|
} else {
|
|
1238
|
-
// Non-tmux reuse logic
|
|
1240
|
+
// Non-tmux reuse logic
|
|
1239
1241
|
const existingTool = state.tool_session || null;
|
|
1240
1242
|
if (existingTool && existingTool !== tool) {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
attachChild = spawn(binaryPath, buildLaunchArgs(options, tool), {
|
|
1250
|
-
cwd: targetDir,
|
|
1251
|
-
env: process.env,
|
|
1252
|
-
stdio: 'inherit'
|
|
1243
|
+
// Auto-close stale session when tool changed (same pattern as tmux recovery above)
|
|
1244
|
+
updateRun(db, {
|
|
1245
|
+
runKey: existing.run.run_key,
|
|
1246
|
+
status: 'completed',
|
|
1247
|
+
summary: `Auto-closed: tool changed from ${existingTool} to ${tool}`,
|
|
1248
|
+
eventType: 'session_closed',
|
|
1249
|
+
phase: 'live',
|
|
1250
|
+
message: `Tool mismatch — auto-closed previous ${existingTool} session`
|
|
1253
1251
|
});
|
|
1254
|
-
state.child_pid = attachChild.pid || null;
|
|
1255
1252
|
if (existing.task?.task_key) {
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1253
|
+
updateTask(db, {
|
|
1254
|
+
taskKey: existing.task.task_key,
|
|
1255
|
+
status: 'completed',
|
|
1256
|
+
goal: `Auto-closed after tool change to ${tool}`
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
await clearAgentSession(runtimeDir, agentName);
|
|
1260
|
+
if (!options.json) {
|
|
1261
|
+
logger.log(t('live.tool_mismatch_auto_closed', { existing: existingTool, requested: tool }) ||
|
|
1262
|
+
`Previous session (${existingTool}) auto-closed — starting new with ${tool}`);
|
|
1263
|
+
}
|
|
1264
|
+
// Fall through to create a new session below
|
|
1265
|
+
} else {
|
|
1266
|
+
// Tools match (or no previous tool) — reuse existing session
|
|
1267
|
+
const attach = Boolean(options.attach);
|
|
1268
|
+
let attachChild = null;
|
|
1269
|
+
let attachResult = null;
|
|
1270
|
+
|
|
1271
|
+
if (attach && !noLaunch) {
|
|
1272
|
+
attachChild = spawn(binaryPath, buildLaunchArgs(options, tool), {
|
|
1273
|
+
cwd: targetDir,
|
|
1274
|
+
env: process.env,
|
|
1275
|
+
stdio: 'inherit',
|
|
1276
|
+
shell: process.platform === 'win32'
|
|
1277
|
+
});
|
|
1278
|
+
state.child_pid = attachChild.pid || null;
|
|
1279
|
+
if (existing.task?.task_key) {
|
|
1280
|
+
const taskMeta = parseTaskMeta(existing.task);
|
|
1281
|
+
taskMeta.child_pid = state.child_pid;
|
|
1282
|
+
updateTask(db, { taskKey: existing.task.task_key, metaJson: taskMeta });
|
|
1283
|
+
}
|
|
1259
1284
|
}
|
|
1260
|
-
}
|
|
1261
1285
|
|
|
1262
|
-
|
|
1286
|
+
await writeLiveState(runtimeDir, existing.sessionKey, state);
|
|
1263
1287
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1288
|
+
if (!options.json) {
|
|
1289
|
+
logger.log(t('live.session_already_active', { agent: agentName, session: existing.sessionKey, runKey: existing.run.run_key, dbPath }));
|
|
1290
|
+
}
|
|
1267
1291
|
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1292
|
+
if (attachChild) {
|
|
1293
|
+
attachResult = await waitForChild(attachChild);
|
|
1294
|
+
}
|
|
1271
1295
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1296
|
+
return {
|
|
1297
|
+
ok: true,
|
|
1298
|
+
targetDir,
|
|
1299
|
+
dbPath,
|
|
1300
|
+
agent: existing.agentName,
|
|
1301
|
+
tool: state.tool_session || tool,
|
|
1302
|
+
taskKey: existing.task?.task_key || existing.sessionRef?.taskKey || null,
|
|
1303
|
+
runKey: existing.run.run_key,
|
|
1304
|
+
sessionKey: existing.sessionKey,
|
|
1305
|
+
pid: state.child_pid || null,
|
|
1306
|
+
processState: detectProcessState(state.child_pid),
|
|
1307
|
+
reused: true,
|
|
1308
|
+
open: true,
|
|
1309
|
+
attached: attach,
|
|
1310
|
+
childExitCode: attachResult?.code ?? null,
|
|
1311
|
+
childSignal: attachResult?.signal ?? null
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1289
1314
|
}
|
|
1290
1315
|
}
|
|
1291
1316
|
|
|
@@ -1356,7 +1381,8 @@ async function runLiveStart({ args, options = {}, logger, t }) {
|
|
|
1356
1381
|
child = spawn(binaryPath, buildLaunchArgs(options, tool), {
|
|
1357
1382
|
cwd: targetDir,
|
|
1358
1383
|
env: process.env,
|
|
1359
|
-
stdio: 'inherit'
|
|
1384
|
+
stdio: 'inherit',
|
|
1385
|
+
shell: process.platform === 'win32'
|
|
1360
1386
|
});
|
|
1361
1387
|
taskMeta.child_pid = child.pid || null;
|
|
1362
1388
|
updateTask(db, {
|
|
@@ -1368,7 +1394,8 @@ async function runLiveStart({ args, options = {}, logger, t }) {
|
|
|
1368
1394
|
child = spawn(binaryPath, buildLaunchArgs(options, tool), {
|
|
1369
1395
|
cwd: targetDir,
|
|
1370
1396
|
env: process.env,
|
|
1371
|
-
stdio: 'inherit'
|
|
1397
|
+
stdio: 'inherit',
|
|
1398
|
+
shell: process.platform === 'win32'
|
|
1372
1399
|
});
|
|
1373
1400
|
taskMeta.child_pid = child.pid || null;
|
|
1374
1401
|
updateTask(db, {
|
|
@@ -27,6 +27,8 @@ const { captureSignal, readProposal, VALID_SIGNAL_TYPES } = require('../operator
|
|
|
27
27
|
const { promoteProposal } = require('../operator-memory/decision');
|
|
28
28
|
const { emitDossierEvent } = require('../lib/dossier-telemetry');
|
|
29
29
|
|
|
30
|
+
const CONFIRMATIONS_JSONL = '.aioson/runtime/session-confirmations.jsonl';
|
|
31
|
+
|
|
30
32
|
const PROMOTION_THRESHOLD = 2;
|
|
31
33
|
|
|
32
34
|
function existsCheckFactory(identity) {
|
|
@@ -55,6 +57,8 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
|
|
|
55
57
|
const quote = options.quote;
|
|
56
58
|
const proposal = options.proposal;
|
|
57
59
|
const sourceAgent = options['source-agent'] || options.sourceAgent || 'unknown';
|
|
60
|
+
const featureSlug = options.feature ? String(options.feature) : null;
|
|
61
|
+
const sessionId = options['session-id'] || options.sessionId || null;
|
|
58
62
|
|
|
59
63
|
if (!signal || !proposal) {
|
|
60
64
|
const err = `op:capture — required: --signal=<type> --proposal=<paraphrase>. Got signal=${signal}, proposal=${proposal ? 'present' : 'missing'}.`;
|
|
@@ -95,7 +99,9 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
|
|
|
95
99
|
signal_type: signal,
|
|
96
100
|
quote,
|
|
97
101
|
proposal,
|
|
98
|
-
source_agent: sourceAgent
|
|
102
|
+
source_agent: sourceAgent,
|
|
103
|
+
feature_slug: featureSlug,
|
|
104
|
+
session_id: sessionId
|
|
99
105
|
});
|
|
100
106
|
} catch (err) {
|
|
101
107
|
const errMsg = `op:capture failed: ${err.message}`;
|
|
@@ -104,6 +110,24 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
|
|
|
104
110
|
return { ok: false, exitCode: 1, error: errMsg };
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
// M2: append confirmation signals to session accumulator for decision_rationale
|
|
114
|
+
if (signal === 'confirmation') {
|
|
115
|
+
try {
|
|
116
|
+
const accPath = path.join(targetDir, CONFIRMATIONS_JSONL);
|
|
117
|
+
const accDir = path.dirname(accPath);
|
|
118
|
+
fs.mkdirSync(accDir, { recursive: true });
|
|
119
|
+
const entry = JSON.stringify({
|
|
120
|
+
agent: sourceAgent,
|
|
121
|
+
decision: proposal,
|
|
122
|
+
quote: quote || null,
|
|
123
|
+
timestamp: new Date().toISOString()
|
|
124
|
+
});
|
|
125
|
+
fs.appendFileSync(accPath, entry + '\n', 'utf8');
|
|
126
|
+
} catch {
|
|
127
|
+
// best-effort — never block op:capture
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
107
131
|
const count = result.proposal.detected_count;
|
|
108
132
|
|
|
109
133
|
if (count >= PROMOTION_THRESHOLD) {
|
|
@@ -142,5 +166,6 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
|
|
|
142
166
|
|
|
143
167
|
module.exports = {
|
|
144
168
|
runOpCapture,
|
|
145
|
-
PROMOTION_THRESHOLD
|
|
169
|
+
PROMOTION_THRESHOLD,
|
|
170
|
+
CONFIRMATIONS_JSONL
|
|
146
171
|
};
|
package/src/commands/op-list.js
CHANGED
|
@@ -21,13 +21,15 @@ const { readDecision } = require('../operator-memory/decision');
|
|
|
21
21
|
|
|
22
22
|
async function runOpList({ args = [], options = {}, logger }) {
|
|
23
23
|
if (options.help === true || args.includes('--help') || args.includes('-h')) {
|
|
24
|
-
if (logger) logger.log('op:list [--proposals] [--include-archived] [--format=table|json] — list active decisions.');
|
|
24
|
+
if (logger) logger.log('op:list [--proposals] [--include-archived] [--feature=<slug>] [--agent=<name>] [--format=table|json] — list active decisions.');
|
|
25
25
|
return { ok: true };
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const format = options.format || 'table';
|
|
29
29
|
const showProposals = Boolean(options.proposals);
|
|
30
30
|
const includeArchived = Boolean(options['include-archived']);
|
|
31
|
+
const filterFeature = options.feature ? String(options.feature) : null;
|
|
32
|
+
const filterAgent = options.agent ? String(options.agent) : null;
|
|
31
33
|
|
|
32
34
|
const resolved = resolveIdentity();
|
|
33
35
|
ensureStorageTree(resolved.identity);
|
|
@@ -65,9 +67,39 @@ async function runOpList({ args = [], options = {}, logger }) {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
// M3: apply --feature and --agent filters (BR-AO-07: AND-composable)
|
|
71
|
+
if (filterFeature) {
|
|
72
|
+
items = items.filter((item) => item.feature_slug === filterFeature);
|
|
73
|
+
}
|
|
74
|
+
if (filterAgent) {
|
|
75
|
+
items = items.filter((item) => item.source_agent === filterAgent);
|
|
76
|
+
}
|
|
77
|
+
|
|
68
78
|
if (format === 'json' || options.json) {
|
|
79
|
+
// BR-AO-09: structured JSON output when --feature is used
|
|
80
|
+
if (filterFeature) {
|
|
81
|
+
const decisions = items.map((item) => ({
|
|
82
|
+
agent: item.source_agent || 'unknown',
|
|
83
|
+
signal: item.signal_type || null,
|
|
84
|
+
quote: Array.isArray(item.quotes) ? (item.quotes[item.quotes.length - 1] || null) : null,
|
|
85
|
+
proposal: item.proposal || item.body || item.title || null,
|
|
86
|
+
timestamp: item.last_reinforced || item.last_detected || null,
|
|
87
|
+
session_id: item.session_id || null
|
|
88
|
+
}));
|
|
89
|
+
const result = {
|
|
90
|
+
ok: true,
|
|
91
|
+
feature: filterFeature,
|
|
92
|
+
decisions,
|
|
93
|
+
total: decisions.length
|
|
94
|
+
};
|
|
95
|
+
if (options.json) return result;
|
|
96
|
+
if (logger) logger.log(JSON.stringify(result, null, 2));
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
69
100
|
const result = {
|
|
70
101
|
ok: true,
|
|
102
|
+
feature: null,
|
|
71
103
|
identity: resolved.identity,
|
|
72
104
|
identity_source: resolved.source,
|
|
73
105
|
tier: showProposals ? 'proposals' : (includeArchived ? 'active+archive' : 'active'),
|
|
@@ -6,6 +6,33 @@ const { exists, ensureDir } = require('../utils');
|
|
|
6
6
|
const { readConfig } = require('./config');
|
|
7
7
|
const { readWorkspace, findProjectRoot } = require('./workspace');
|
|
8
8
|
|
|
9
|
+
let _terser = null;
|
|
10
|
+
function getTerser() {
|
|
11
|
+
if (!_terser) _terser = require('terser');
|
|
12
|
+
return _terser;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function createZipBuffer(files) {
|
|
16
|
+
const archiver = require('archiver');
|
|
17
|
+
const { PassThrough } = require('stream');
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const chunks = [];
|
|
20
|
+
const stream = new PassThrough();
|
|
21
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
22
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
23
|
+
stream.on('error', reject);
|
|
24
|
+
|
|
25
|
+
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
26
|
+
archive.on('error', reject);
|
|
27
|
+
archive.pipe(stream);
|
|
28
|
+
|
|
29
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
30
|
+
archive.append(content, { name: relPath });
|
|
31
|
+
}
|
|
32
|
+
archive.finalize();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
9
36
|
const DEFAULT_BASE_URL = 'https://aioson.com';
|
|
10
37
|
const SYSTEM_PACKAGES_DIR = '.aioson/system-packages';
|
|
11
38
|
const BACKUPS_DIR = '.aioson/.backups';
|
|
@@ -25,6 +52,17 @@ const SYSTEM_ALLOWED_EXTS = new Set([
|
|
|
25
52
|
'.gitignore',
|
|
26
53
|
]);
|
|
27
54
|
|
|
55
|
+
const SYSTEM_BUILD_ALLOWED_EXTS = new Set([
|
|
56
|
+
'.js', '.jsx', '.mjs', '.cjs',
|
|
57
|
+
'.json', '.jsonc',
|
|
58
|
+
'.css',
|
|
59
|
+
'.html',
|
|
60
|
+
'.svg', '.ico',
|
|
61
|
+
'.sql',
|
|
62
|
+
'.yaml', '.yml',
|
|
63
|
+
'.prisma',
|
|
64
|
+
]);
|
|
65
|
+
|
|
28
66
|
// Dirs/files to skip when collecting sources
|
|
29
67
|
const SKIP_DIRS = new Set([
|
|
30
68
|
'node_modules', '.git', 'dist', 'build', '.turbo', '.next',
|
|
@@ -33,13 +71,21 @@ const SKIP_DIRS = new Set([
|
|
|
33
71
|
'.aioson', '.claude', '.gemini', '.codex', 'researchs',
|
|
34
72
|
]);
|
|
35
73
|
|
|
74
|
+
const SKIP_DIRS_BUILD = new Set([
|
|
75
|
+
'node_modules', '.git', '.turbo', '.next',
|
|
76
|
+
'.cache', 'coverage', '.nyc_output',
|
|
77
|
+
'src', 'dashboard/src',
|
|
78
|
+
'.aioson', '.claude', '.gemini', '.codex', 'researchs',
|
|
79
|
+
]);
|
|
80
|
+
|
|
36
81
|
const SKIP_FILES = new Set([
|
|
37
82
|
'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
|
|
38
83
|
'bun.lockb',
|
|
39
84
|
]);
|
|
40
85
|
|
|
41
|
-
const MAX_FILE_BYTES = 512 * 1024;
|
|
42
|
-
const
|
|
86
|
+
const MAX_FILE_BYTES = 512 * 1024; // 512 KB per file (source)
|
|
87
|
+
const MAX_FILE_BYTES_BUILD = 2 * 1024 * 1024; // 2 MB per file (compiled bundles)
|
|
88
|
+
const MAX_PACKAGE_BYTES = 20 * 1024 * 1024; // 20 MB total
|
|
43
89
|
|
|
44
90
|
/**
|
|
45
91
|
* Parseia lista de emails autorizados a partir de:
|
|
@@ -114,15 +160,18 @@ async function storeGet(url, token) {
|
|
|
114
160
|
* Collect all eligible source files under `dir`.
|
|
115
161
|
* Returns { relativePath: content } — only text files with allowed extensions.
|
|
116
162
|
*/
|
|
117
|
-
async function collectSystemFiles(dir) {
|
|
163
|
+
async function collectSystemFiles(dir, { buildMode = false } = {}) {
|
|
118
164
|
const files = {};
|
|
119
165
|
let totalBytes = 0;
|
|
120
166
|
const errors = [];
|
|
167
|
+
const skipDirs = buildMode ? SKIP_DIRS_BUILD : SKIP_DIRS;
|
|
168
|
+
const allowedExts = buildMode ? SYSTEM_BUILD_ALLOWED_EXTS : SYSTEM_ALLOWED_EXTS;
|
|
121
169
|
|
|
122
170
|
async function walk(current, rel) {
|
|
123
171
|
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
124
172
|
for (const entry of entries) {
|
|
125
|
-
if (
|
|
173
|
+
if (skipDirs.has(entry.name)) continue;
|
|
174
|
+
if (rel && skipDirs.has(`${rel}/${entry.name}`)) continue;
|
|
126
175
|
if (SKIP_FILES.has(entry.name)) continue;
|
|
127
176
|
|
|
128
177
|
const fullPath = path.join(current, entry.name);
|
|
@@ -137,12 +186,12 @@ async function collectSystemFiles(dir) {
|
|
|
137
186
|
? `.${entry.name.split('.').pop().toLowerCase()}`
|
|
138
187
|
: '';
|
|
139
188
|
|
|
140
|
-
|
|
141
|
-
if (!SYSTEM_ALLOWED_EXTS.has(ext) && ext !== '') continue;
|
|
189
|
+
if (!allowedExts.has(ext) && ext !== '') continue;
|
|
142
190
|
|
|
143
191
|
try {
|
|
144
192
|
const stat = await fs.stat(fullPath);
|
|
145
|
-
|
|
193
|
+
const maxBytes = buildMode ? MAX_FILE_BYTES_BUILD : MAX_FILE_BYTES;
|
|
194
|
+
if (stat.size > maxBytes) {
|
|
146
195
|
errors.push(`File too large (skipped): "${relPath}" (${(stat.size / 1024).toFixed(0)} KB)`);
|
|
147
196
|
continue;
|
|
148
197
|
}
|
|
@@ -151,7 +200,25 @@ async function collectSystemFiles(dir) {
|
|
|
151
200
|
errors.push(`Package exceeds ${MAX_PACKAGE_BYTES / 1024 / 1024} MB limit — stop collecting.`);
|
|
152
201
|
return;
|
|
153
202
|
}
|
|
154
|
-
|
|
203
|
+
let content = await fs.readFile(fullPath, 'utf8');
|
|
204
|
+
|
|
205
|
+
if (buildMode && (ext === '.js' || ext === '.mjs' || ext === '.cjs')) {
|
|
206
|
+
try {
|
|
207
|
+
const terser = getTerser();
|
|
208
|
+
const result = await terser.minify(content, {
|
|
209
|
+
compress: { passes: 2, drop_console: false },
|
|
210
|
+
mangle: {
|
|
211
|
+
toplevel: true,
|
|
212
|
+
properties: { regex: /^_/ },
|
|
213
|
+
},
|
|
214
|
+
format: { comments: false },
|
|
215
|
+
});
|
|
216
|
+
if (result.code) content = result.code;
|
|
217
|
+
} catch {
|
|
218
|
+
// terser failed on this file — keep original compiled JS
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
155
222
|
files[relPath] = content;
|
|
156
223
|
} catch {
|
|
157
224
|
// binary or unreadable — skip silently
|
|
@@ -231,13 +298,27 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
231
298
|
const config = await readConfig();
|
|
232
299
|
const token = requireToken(config, t);
|
|
233
300
|
const dir = path.resolve(process.cwd(), args[0] || '.');
|
|
301
|
+
const buildMode = Boolean(options.build);
|
|
234
302
|
|
|
235
303
|
logger.log(t('system.publish_reading_manifest'));
|
|
236
304
|
const manifest = await readSystemJson(dir, t);
|
|
237
305
|
logger.log(t('system.package_manifest_ok', { slug: manifest.slug, version: manifest.version, name: manifest.name }));
|
|
238
306
|
|
|
239
|
-
|
|
240
|
-
|
|
307
|
+
if (buildMode) {
|
|
308
|
+
const buildCmd = manifest.build_command || 'npm run build';
|
|
309
|
+
logger.log(`Building: ${buildCmd}`);
|
|
310
|
+
const { execSync } = require('child_process');
|
|
311
|
+
try {
|
|
312
|
+
execSync(buildCmd, { cwd: dir, stdio: 'inherit', timeout: 300_000 });
|
|
313
|
+
} catch (e) {
|
|
314
|
+
throw new Error(`Build failed: ${e.message}`);
|
|
315
|
+
}
|
|
316
|
+
logger.log('Build complete. Collecting compiled output (source excluded)...');
|
|
317
|
+
} else {
|
|
318
|
+
logger.log(t('system.package_collecting_files'));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const { files, totalBytes, errors } = await collectSystemFiles(dir, { buildMode });
|
|
241
322
|
|
|
242
323
|
if (errors.length > 0) {
|
|
243
324
|
for (const e of errors) logger.log(` [WARN] ${e}`);
|
|
@@ -268,13 +349,24 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
268
349
|
return { ok: true, dryRun: true, manifest, fileCount, totalBytes, visibility, authorizedEmails };
|
|
269
350
|
}
|
|
270
351
|
|
|
352
|
+
logger.log('Creating ZIP package...');
|
|
353
|
+
const zipBuffer = await createZipBuffer(files);
|
|
354
|
+
const MAX_ZIP_BYTES = 2 * 1024 * 1024;
|
|
355
|
+
if (zipBuffer.length > MAX_ZIP_BYTES) {
|
|
356
|
+
throw new Error(`ZIP exceeds 2 MB limit (${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB). Reduce the number of files or bundle size.`);
|
|
357
|
+
}
|
|
358
|
+
const zipBase64 = zipBuffer.toString('base64');
|
|
359
|
+
const zipKb = (zipBuffer.length / 1024).toFixed(1);
|
|
360
|
+
logger.log(`ZIP: ${zipKb} KB (${fileCount} files)`);
|
|
361
|
+
|
|
271
362
|
logger.log(t('system.publish_sending'));
|
|
272
363
|
const baseUrl = resolveBaseUrl(config);
|
|
273
364
|
const response = await storePost(`${baseUrl}/api/store/systems/publish`, {
|
|
274
365
|
kind: 'aioson.store.system',
|
|
275
366
|
slug: manifest.slug,
|
|
276
367
|
version: manifest.version,
|
|
277
|
-
|
|
368
|
+
zipBase64,
|
|
369
|
+
files: buildMode ? undefined : files,
|
|
278
370
|
manifest,
|
|
279
371
|
visibility,
|
|
280
372
|
paid,
|
|
@@ -283,7 +375,7 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
283
375
|
}, token);
|
|
284
376
|
|
|
285
377
|
logger.log(t('system.publish_done', { slug: manifest.slug, url: `${baseUrl}/store/systems/${manifest.slug}` }));
|
|
286
|
-
logger.log(t('system.publish_summary', { files: fileCount, kb:
|
|
378
|
+
logger.log(t('system.publish_summary', { files: fileCount, kb: zipKb }));
|
|
287
379
|
return { ok: true, manifest, fileCount, totalBytes, visibility, paid, response };
|
|
288
380
|
}
|
|
289
381
|
|
|
@@ -6,9 +6,9 @@ const { TOOL_CAPS, getToolCapabilities, listSupportedTools } = require('../lib/t
|
|
|
6
6
|
// so external clients (AIOSON Play, IDE extensions) can drive UI without
|
|
7
7
|
// hard-coding their own copy of this lookup.
|
|
8
8
|
//
|
|
9
|
-
// Usage:
|
|
10
|
-
// aioson tool:capabilities --json
|
|
11
|
-
// aioson tool:capabilities --tool=claude --json
|
|
9
|
+
// Usage:
|
|
10
|
+
// aioson tool:capabilities --json
|
|
11
|
+
// aioson tool:capabilities --tool=claude --json
|
|
12
12
|
async function runToolCapabilities({ args: _args, options = {}, logger, t: _t }) {
|
|
13
13
|
const tool = options.tool ? String(options.tool).trim() : null;
|
|
14
14
|
|
|
@@ -22,8 +22,8 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
|
|
|
22
22
|
payload = { tool: tool.toLowerCase(), capabilities: caps };
|
|
23
23
|
} else {
|
|
24
24
|
payload = {
|
|
25
|
-
tools: TOOL_CAPS,
|
|
26
|
-
schema_version:
|
|
25
|
+
tools: TOOL_CAPS,
|
|
26
|
+
schema_version: 2,
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -37,8 +37,8 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
|
|
|
37
37
|
const caps = payload.capabilities;
|
|
38
38
|
logger.log(`Tool: ${payload.tool}`);
|
|
39
39
|
logger.log(` binary: ${caps.binary}`);
|
|
40
|
-
logger.log(` install_command: ${caps.install_command}`);
|
|
41
|
-
logger.log(` supports_resume: ${caps.supports_resume}`);
|
|
40
|
+
logger.log(` install_command: ${caps.install_command}`);
|
|
41
|
+
logger.log(` supports_resume: ${caps.supports_resume}`);
|
|
42
42
|
if (caps.supports_resume) {
|
|
43
43
|
logger.log(` resume_last: ${(caps.resume_last || []).join(' ')}`);
|
|
44
44
|
logger.log(` supports_session_id: ${caps.supports_session_id}`);
|
|
@@ -48,9 +48,13 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
|
|
|
48
48
|
logger.log(` supports_session_picker: ${caps.supports_session_picker}`);
|
|
49
49
|
if (caps.supports_session_picker) {
|
|
50
50
|
logger.log(` session_picker: ${(caps.session_picker || []).join(' ')}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
logger.log(` supports_yolo: ${caps.supports_yolo}`);
|
|
54
|
+
if (caps.supports_yolo) {
|
|
55
|
+
logger.log(` yolo_args: ${(caps.yolo_args || []).join(' ')}`);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
54
58
|
logger.log(`Supported tools: ${listSupportedTools().join(', ')}`);
|
|
55
59
|
logger.log(`Run with --tool=<name> for details, or --json for the full map.`);
|
|
56
60
|
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const path = require('node:path');
|
|
15
|
+
const fs = require('node:fs/promises');
|
|
15
16
|
const {
|
|
16
17
|
loadOrCreateState,
|
|
17
18
|
activateStage,
|
|
@@ -26,6 +27,28 @@ const {
|
|
|
26
27
|
incrementRetryCount,
|
|
27
28
|
buildHealingActivation
|
|
28
29
|
} = require('../self-healing');
|
|
30
|
+
const { CHECKPOINTS_DIR } = require('./gate-approve');
|
|
31
|
+
|
|
32
|
+
const GATE_ORDER = ['D', 'C', 'B', 'A'];
|
|
33
|
+
|
|
34
|
+
async function readLatestCheckpoint(targetDir, slug) {
|
|
35
|
+
const dir = path.join(targetDir, CHECKPOINTS_DIR);
|
|
36
|
+
try {
|
|
37
|
+
const files = await fs.readdir(dir);
|
|
38
|
+
const matching = files
|
|
39
|
+
.filter((f) => f.endsWith(`-${slug}.json`))
|
|
40
|
+
.sort((a, b) => {
|
|
41
|
+
const gateA = a.replace('gate-', '').charAt(0);
|
|
42
|
+
const gateB = b.replace('gate-', '').charAt(0);
|
|
43
|
+
return GATE_ORDER.indexOf(gateA) - GATE_ORDER.indexOf(gateB);
|
|
44
|
+
});
|
|
45
|
+
if (matching.length === 0) return null;
|
|
46
|
+
const raw = await fs.readFile(path.join(dir, matching[0]), 'utf8');
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
29
52
|
|
|
30
53
|
async function runWorkflowHeal({ args, options, logger, t }) {
|
|
31
54
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
@@ -58,6 +81,10 @@ async function runWorkflowHeal({ args, options, logger, t }) {
|
|
|
58
81
|
return { ok: false, reason: 'stage_not_in_sequence' };
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
// M1 checkpoint-at-gate: read latest checkpoint for recovery context (EC-AO-02: graceful fallback)
|
|
85
|
+
const featureSlug = state.featureSlug || null;
|
|
86
|
+
const checkpoint = featureSlug ? await readLatestCheckpoint(targetDir, featureSlug) : null;
|
|
87
|
+
|
|
61
88
|
// Build healing activation
|
|
62
89
|
let activation;
|
|
63
90
|
try {
|
|
@@ -74,6 +101,24 @@ async function runWorkflowHeal({ args, options, logger, t }) {
|
|
|
74
101
|
return { ok: false, reason: 'activation_failed', error: err.message };
|
|
75
102
|
}
|
|
76
103
|
|
|
104
|
+
if (checkpoint) {
|
|
105
|
+
const sanitize = (s) => String(s || '').replace(/[\r\n]+/g, ' ').slice(0, 200);
|
|
106
|
+
const files = Array.isArray(checkpoint.prerequisites_snapshot)
|
|
107
|
+
? checkpoint.prerequisites_snapshot.map((s) => sanitize(s && s.file)).join(', ') || 'none'
|
|
108
|
+
: 'none';
|
|
109
|
+
const cpBlock = [
|
|
110
|
+
'',
|
|
111
|
+
'## Checkpoint Recovery Context (auto-injected by AIOSON motor)',
|
|
112
|
+
'',
|
|
113
|
+
`> Last approved gate: **${sanitize(checkpoint.gate)}** (${sanitize(checkpoint.timestamp)})`,
|
|
114
|
+
`> Approved by: ${sanitize(checkpoint.agent)}`,
|
|
115
|
+
`> Artifacts at gate time: ${files}`,
|
|
116
|
+
''
|
|
117
|
+
].join('\n');
|
|
118
|
+
activation.prompt = activation.prompt + cpBlock;
|
|
119
|
+
activation.checkpoint = checkpoint;
|
|
120
|
+
}
|
|
121
|
+
|
|
77
122
|
// Increment retry counter
|
|
78
123
|
const newCount = await incrementRetryCount(targetDir, stage, activation.prompt.substring(0, 200));
|
|
79
124
|
|
|
@@ -127,10 +172,11 @@ async function runWorkflowHeal({ args, options, logger, t }) {
|
|
|
127
172
|
retryCount: newCount,
|
|
128
173
|
maxRetries: 3,
|
|
129
174
|
runtime,
|
|
175
|
+
checkpoint: checkpoint || null,
|
|
130
176
|
agent: activation.agent,
|
|
131
177
|
instructionPath: activation.instructionPath,
|
|
132
178
|
prompt: activation.prompt
|
|
133
179
|
};
|
|
134
180
|
}
|
|
135
181
|
|
|
136
|
-
module.exports = { runWorkflowHeal };
|
|
182
|
+
module.exports = { runWorkflowHeal, readLatestCheckpoint };
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -219,7 +219,7 @@ module.exports = {
|
|
|
219
219
|
help_runtime_emit:
|
|
220
220
|
'aioson runtime:emit [path] --agent=<name> [--type=<event>] [--summary=<text>] [--title=<text>] [--refs=<file[,file2]>] [--plan-step=<id>] [--meta=<json>] [--json] [--locale=en]',
|
|
221
221
|
help_live_start:
|
|
222
|
-
'aioson live:start [path] --tool=codex|claude|gemini|opencode --agent=<name> [--tool-bin=<binary>] [--tool-args=<args>] [--title=<text>] [--goal=<text>] [--plan=<file>] [--session=<key>] [--message=<text>] [--attach] [--no-launch] [--tmux] [--json] [--locale=en]',
|
|
222
|
+
'aioson live:start [path] --tool=codex|claude|gemini|opencode --agent=<name> [--tool-bin=<binary>] [--permission-mode=default|yolo] [--tool-args=<args>] [--title=<text>] [--goal=<text>] [--plan=<file>] [--session=<key>] [--message=<text>] [--attach] [--no-launch] [--tmux] [--json] [--locale=en]',
|
|
223
223
|
help_live_status:
|
|
224
224
|
'aioson live:status [path] [--agent=<name>] [--limit=8] [--watch=2] [--format=compact|tmux-bar] [--json] [--locale=en]',
|
|
225
225
|
help_live_handoff:
|
|
@@ -1090,10 +1090,11 @@ module.exports = {
|
|
|
1090
1090
|
plan_not_found: 'Plan file not found: {plan}',
|
|
1091
1091
|
no_active_session: 'No active live session found for {agent}.',
|
|
1092
1092
|
session_not_active: 'Live session for {agent} is not active.',
|
|
1093
|
-
json_requires_no_launch: '--json requires --no-launch for live:start because foreground launch is interactive.',
|
|
1094
|
-
tool_binary_not_found: 'Tool binary not found in PATH: {binary}',
|
|
1095
|
-
tool_mismatch: 'Active session uses tool "{existing}" but --tool={requested} was given. Close the session first or use the same tool.',
|
|
1096
|
-
|
|
1093
|
+
json_requires_no_launch: '--json requires --no-launch for live:start because foreground launch is interactive.',
|
|
1094
|
+
tool_binary_not_found: 'Tool binary not found in PATH: {binary}',
|
|
1095
|
+
tool_mismatch: 'Active session uses tool "{existing}" but --tool={requested} was given. Close the session first or use the same tool.',
|
|
1096
|
+
tool_mismatch_auto_closed: 'Previous live session used "{existing}" and was auto-closed. Starting a new session with "{requested}".',
|
|
1097
|
+
micro_task_already_open: 'A live micro-task is already open for {agent}. Emit task_completed before task_started again.',
|
|
1097
1098
|
handoff_same_agent: 'live:handoff requires different --agent and --to values.',
|
|
1098
1099
|
handoff_agent_mismatch: 'No active live session found for {agent}.',
|
|
1099
1100
|
watch_json_conflict: '--watch cannot be combined with --json.',
|