@jaimevalasek/aioson 1.17.3 → 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 +1 -1
- 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 +4 -0
- 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 +33 -1
- package/template/.aioson/agents/architect.md +33 -1
- package/template/.aioson/agents/briefing.md +23 -0
- 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 +11 -0
- package/template/.aioson/agents/sheldon.md +21 -1
- package/template/.aioson/agents/tester.md +114 -1
- 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'),
|
|
@@ -351,6 +351,10 @@ async function runSystemPublish({ args, options, logger, t }) {
|
|
|
351
351
|
|
|
352
352
|
logger.log('Creating ZIP package...');
|
|
353
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
|
+
}
|
|
354
358
|
const zipBase64 = zipBuffer.toString('base64');
|
|
355
359
|
const zipKb = (zipBuffer.length / 1024).toFixed(1);
|
|
356
360
|
logger.log(`ZIP: ${zipKb} KB (${fileCount} files)`);
|
|
@@ -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.',
|
|
@@ -217,7 +217,7 @@ module.exports = {
|
|
|
217
217
|
help_runtime_emit:
|
|
218
218
|
'aioson runtime:emit [path] --agent=<nome> [--type=<evento>] [--summary=<texto>] [--title=<texto>] [--refs=<arquivo[,arquivo2]>] [--plan-step=<id>] [--meta=<json>] [--json] [--locale=pt-BR]',
|
|
219
219
|
help_live_start:
|
|
220
|
-
'aioson live:start [path] --tool=codex|claude|gemini|opencode --agent=<nome> [--tool-bin=<binario>] [--tool-args=<args>] [--title=<texto>] [--goal=<texto>] [--plan=<arquivo>] [--session=<chave>] [--message=<texto>] [--attach] [--no-launch] [--tmux] [--json] [--locale=pt-BR]',
|
|
220
|
+
'aioson live:start [path] --tool=codex|claude|gemini|opencode --agent=<nome> [--tool-bin=<binario>] [--permission-mode=default|yolo] [--tool-args=<args>] [--title=<texto>] [--goal=<texto>] [--plan=<arquivo>] [--session=<chave>] [--message=<texto>] [--attach] [--no-launch] [--tmux] [--json] [--locale=pt-BR]',
|
|
221
221
|
help_live_status:
|
|
222
222
|
'aioson live:status [path] [--agent=<nome>] [--limit=8] [--watch=2] [--format=compact|tmux-bar] [--json] [--locale=pt-BR]',
|
|
223
223
|
help_live_handoff:
|
|
@@ -1116,10 +1116,11 @@ module.exports = {
|
|
|
1116
1116
|
plan_not_found: 'Arquivo de plano nao encontrado: {plan}',
|
|
1117
1117
|
no_active_session: 'Nenhuma sessao live ativa encontrada para {agent}.',
|
|
1118
1118
|
session_not_active: 'A sessao live de {agent} nao esta ativa.',
|
|
1119
|
-
json_requires_no_launch: '--json requer --no-launch para live:start porque o lancamento em primeiro plano e interativo.',
|
|
1120
|
-
tool_binary_not_found: 'Binario da ferramenta nao encontrado no PATH: {binary}',
|
|
1121
|
-
tool_mismatch: 'A sessao ativa usa a ferramenta "{existing}" mas --tool={requested} foi informado. Encerre a sessao primeiro ou use a mesma ferramenta.',
|
|
1122
|
-
|
|
1119
|
+
json_requires_no_launch: '--json requer --no-launch para live:start porque o lancamento em primeiro plano e interativo.',
|
|
1120
|
+
tool_binary_not_found: 'Binario da ferramenta nao encontrado no PATH: {binary}',
|
|
1121
|
+
tool_mismatch: 'A sessao ativa usa a ferramenta "{existing}" mas --tool={requested} foi informado. Encerre a sessao primeiro ou use a mesma ferramenta.',
|
|
1122
|
+
tool_mismatch_auto_closed: 'A sessao live anterior usava "{existing}" e foi fechada automaticamente. Iniciando nova sessao com "{requested}".',
|
|
1123
|
+
micro_task_already_open: 'Uma micro-tarefa live ja esta aberta para {agent}. Emita task_completed antes de task_started novamente.',
|
|
1123
1124
|
handoff_same_agent: 'live:handoff requer valores diferentes para --agent e --to.',
|
|
1124
1125
|
handoff_agent_mismatch: 'Nenhuma sessao live ativa encontrada para {agent}.',
|
|
1125
1126
|
watch_json_conflict: '--watch nao pode ser combinado com --json.',
|
package/src/lib/dev-resume.js
CHANGED
|
@@ -121,6 +121,10 @@ async function buildDevResumeData(projectPath) {
|
|
|
121
121
|
? lastHandoff.artifact_uris
|
|
122
122
|
: [];
|
|
123
123
|
|
|
124
|
+
const decisionRationale = Array.isArray(lastHandoff && lastHandoff.decision_rationale)
|
|
125
|
+
? lastHandoff.decision_rationale
|
|
126
|
+
: [];
|
|
127
|
+
|
|
124
128
|
return {
|
|
125
129
|
feature_slug: featureSlug,
|
|
126
130
|
classification,
|
|
@@ -128,7 +132,8 @@ async function buildDevResumeData(projectPath) {
|
|
|
128
132
|
artifacts_consumed: artifactsConsumed,
|
|
129
133
|
code_map_paths: extractCodeMapPaths(dossierRaw),
|
|
130
134
|
sheldon_plan: sheldonPlan,
|
|
131
|
-
next_step: devStateNext || deriveNextStepFromPlan(planRaw)
|
|
135
|
+
next_step: devStateNext || deriveNextStepFromPlan(planRaw),
|
|
136
|
+
decision_rationale: decisionRationale.length > 0 ? decisionRationale : undefined
|
|
132
137
|
};
|
|
133
138
|
}
|
|
134
139
|
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
// "continue last conversation" is achieved by passing the right resume flag
|
|
6
6
|
// at spawn time — AIOSON never has to track an internal session ID.
|
|
7
7
|
//
|
|
8
|
-
// Used by:
|
|
9
|
-
// - `aioson live:start --resume[=last|<id>]` to map to the correct argv
|
|
10
|
-
// - `aioson
|
|
11
|
-
//
|
|
8
|
+
// Used by:
|
|
9
|
+
// - `aioson live:start --resume[=last|<id>]` to map to the correct argv
|
|
10
|
+
// - `aioson live:start --permission-mode=yolo` to map to the correct argv
|
|
11
|
+
// - `aioson tool:capabilities` to expose this map as JSON to UI clients
|
|
12
|
+
// (e.g. AIOSON Play) so they don't duplicate the lookup.
|
|
12
13
|
//
|
|
13
14
|
// Keep entries minimal and source-of-truth here. Adding a new CLI = one entry.
|
|
14
15
|
const TOOL_CAPS = {
|
|
@@ -17,42 +18,50 @@ const TOOL_CAPS = {
|
|
|
17
18
|
binary: 'claude',
|
|
18
19
|
supports_resume: true,
|
|
19
20
|
resume_last: ['--continue'],
|
|
20
|
-
supports_session_id: true,
|
|
21
|
-
resume_session_id: ['--resume', '<id>'],
|
|
22
|
-
supports_session_picker: true,
|
|
23
|
-
session_picker: ['--resume'],
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
supports_session_id: true,
|
|
22
|
+
resume_session_id: ['--resume', '<id>'],
|
|
23
|
+
supports_session_picker: true,
|
|
24
|
+
session_picker: ['--resume'],
|
|
25
|
+
supports_yolo: true,
|
|
26
|
+
yolo_args: ['--dangerously-skip-permissions'],
|
|
27
|
+
},
|
|
28
|
+
codex: {
|
|
26
29
|
install_command: 'npm install -g @openai/codex',
|
|
27
30
|
binary: 'codex',
|
|
28
31
|
supports_resume: true,
|
|
29
32
|
resume_last: ['resume', '--last'],
|
|
30
|
-
supports_session_id: true,
|
|
31
|
-
resume_session_id: ['resume', '<id>'],
|
|
32
|
-
supports_session_picker: true,
|
|
33
|
-
session_picker: ['resume'],
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
supports_session_id: true,
|
|
34
|
+
resume_session_id: ['resume', '<id>'],
|
|
35
|
+
supports_session_picker: true,
|
|
36
|
+
session_picker: ['resume'],
|
|
37
|
+
supports_yolo: true,
|
|
38
|
+
yolo_args: ['--dangerously-bypass-approvals-and-sandbox'],
|
|
39
|
+
},
|
|
40
|
+
opencode: {
|
|
36
41
|
install_command: 'npm install -g opencode-ai',
|
|
37
42
|
binary: 'opencode',
|
|
38
43
|
supports_resume: true,
|
|
39
44
|
resume_last: ['--continue'],
|
|
40
|
-
supports_session_id: true,
|
|
41
|
-
resume_session_id: ['--session', '<id>'],
|
|
42
|
-
supports_session_picker: false,
|
|
43
|
-
session_picker: null,
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
supports_session_id: true,
|
|
46
|
+
resume_session_id: ['--session', '<id>'],
|
|
47
|
+
supports_session_picker: false,
|
|
48
|
+
session_picker: null,
|
|
49
|
+
supports_yolo: false,
|
|
50
|
+
yolo_args: null,
|
|
51
|
+
},
|
|
52
|
+
gemini: {
|
|
46
53
|
install_command: 'npm install -g @google/gemini-cli',
|
|
47
54
|
binary: 'gemini',
|
|
48
55
|
supports_resume: false,
|
|
49
56
|
resume_last: null,
|
|
50
|
-
supports_session_id: false,
|
|
51
|
-
resume_session_id: null,
|
|
52
|
-
supports_session_picker: false,
|
|
53
|
-
session_picker: null,
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
supports_session_id: false,
|
|
58
|
+
resume_session_id: null,
|
|
59
|
+
supports_session_picker: false,
|
|
60
|
+
session_picker: null,
|
|
61
|
+
supports_yolo: false,
|
|
62
|
+
yolo_args: null,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
56
65
|
|
|
57
66
|
function getToolCapabilities(tool) {
|
|
58
67
|
const key = String(tool || '').trim().toLowerCase();
|
|
@@ -71,7 +80,7 @@ function listSupportedTools() {
|
|
|
71
80
|
// - '' / undefined / null / false → no resume
|
|
72
81
|
// - any other string → treat as session id
|
|
73
82
|
// Returns [] when the tool doesn't support resume or resumeOpt is falsy.
|
|
74
|
-
function resolveResumeArgs(tool, resumeOpt) {
|
|
83
|
+
function resolveResumeArgs(tool, resumeOpt) {
|
|
75
84
|
if (resumeOpt === undefined || resumeOpt === null || resumeOpt === '' || resumeOpt === false) {
|
|
76
85
|
return [];
|
|
77
86
|
}
|
|
@@ -92,11 +101,29 @@ function resolveResumeArgs(tool, resumeOpt) {
|
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
return Array.isArray(caps.resume_last) ? [...caps.resume_last] : [];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolvePermissionModeArgs(tool, permissionMode) {
|
|
107
|
+
const mode = String(permissionMode || '').trim().toLowerCase();
|
|
108
|
+
if (!mode || mode === 'default') return [];
|
|
109
|
+
if (mode !== 'yolo') {
|
|
110
|
+
throw new Error(`permission_mode_unknown:${permissionMode}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const caps = getToolCapabilities(tool);
|
|
114
|
+
if (!caps) {
|
|
115
|
+
throw new Error(`tool_unknown:${tool}`);
|
|
116
|
+
}
|
|
117
|
+
if (!caps.supports_yolo || !Array.isArray(caps.yolo_args)) {
|
|
118
|
+
throw new Error(`permission_mode_unsupported:${tool}:yolo`);
|
|
119
|
+
}
|
|
120
|
+
return [...caps.yolo_args];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
TOOL_CAPS,
|
|
125
|
+
getToolCapabilities,
|
|
126
|
+
listSupportedTools,
|
|
127
|
+
resolveResumeArgs,
|
|
128
|
+
resolvePermissionModeArgs,
|
|
129
|
+
};
|