abelworkflow 0.2.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import "../lib/cli.mjs";
2
+ import { main } from "../lib/cli.mjs";
3
+
4
+ main().catch((error) => {
5
+ console.error(error instanceof Error ? error.message : String(error));
6
+ process.exit(1);
7
+ });
package/lib/cli.mjs CHANGED
@@ -12,10 +12,14 @@ const home = homedir();
12
12
  const defaultAgentsDir = join(home, ".agents");
13
13
  const installMetadataName = ".abelworkflow-install.json";
14
14
  const claudeSettingsPath = join(home, ".claude", "settings.json");
15
- const claudeVscodeConfigPath = join(home, ".claude", "config.json");
16
15
  const claudeMetaConfigPath = join(home, ".claude.json");
17
16
  const codexConfigPath = join(home, ".codex", "config.toml");
18
17
  const codexAuthPath = join(home, ".codex", "auth.json");
18
+ const codexTemplateRoot = join(packageRoot, "lib", "templates", "codex");
19
+ const codexTemplateConfigPath = join(codexTemplateRoot, "config-base.toml");
20
+ const codexTemplateAgentsPath = join(codexTemplateRoot, "agents");
21
+ const installBackupStamp = Date.now();
22
+ const createdBackupPaths = new Set();
19
23
  const claudeModelEnvKeys = [
20
24
  "ANTHROPIC_MODEL",
21
25
  "ANTHROPIC_DEFAULT_OPUS_MODEL",
@@ -222,17 +226,38 @@ async function pathTargetExists(path) {
222
226
  }
223
227
  }
224
228
 
229
+ async function createBackupPath(targetPath) {
230
+ let index = 0;
231
+ while (true) {
232
+ const backupPath = `${targetPath}.bak.${installBackupStamp}${index ? `-${index}` : ""}`;
233
+ if (!(await pathExists(backupPath))) {
234
+ return backupPath;
235
+ }
236
+ index += 1;
237
+ }
238
+ }
239
+
240
+ async function backupExistingPath(targetPath) {
241
+ if (createdBackupPaths.has(targetPath) || !(await pathExists(targetPath))) {
242
+ return null;
243
+ }
244
+
245
+ const backupPath = await createBackupPath(targetPath);
246
+ await cp(targetPath, backupPath, { recursive: true, force: false });
247
+ createdBackupPaths.add(targetPath);
248
+ console.log(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
249
+ return backupPath;
250
+ }
251
+
225
252
  async function backupIfNeeded(targetPath, force) {
226
253
  if (!(await pathExists(targetPath))) {
227
254
  return null;
228
255
  }
229
- if (!force) {
230
- const backupPath = `${targetPath}.bak.${Date.now()}`;
231
- await rename(targetPath, backupPath);
232
- return backupPath;
233
- }
234
- await rm(targetPath, { recursive: true, force: true });
235
- return null;
256
+
257
+ const backupPath = await createBackupPath(targetPath);
258
+ await rename(targetPath, backupPath);
259
+ console.log(`已备份已有配置: ${pathToLabel(targetPath)} -> ${pathToLabel(backupPath)}`);
260
+ return backupPath;
236
261
  }
237
262
 
238
263
  async function syncManagedFiles(agentsDir) {
@@ -298,8 +323,10 @@ async function replaceManagedEntry(source, target, entry) {
298
323
 
299
324
  const sourceStat = await lstat(source);
300
325
  if (sourceStat.isDirectory()) {
326
+ await backupExistingPath(target);
301
327
  await rm(target, { recursive: true, force: true });
302
328
  } else if (await pathExists(target)) {
329
+ await backupExistingPath(target);
303
330
  const targetStat = await lstat(target);
304
331
  if (targetStat.isDirectory()) {
305
332
  await rm(target, { recursive: true, force: true });
@@ -850,6 +877,11 @@ async function writeJsonFileSafe(path, data) {
850
877
  await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
851
878
  }
852
879
 
880
+ async function writeJsonFileWithBackup(path, data) {
881
+ await backupExistingPath(path);
882
+ await writeJsonFileSafe(path, data);
883
+ }
884
+
853
885
  function parseDotenv(content) {
854
886
  const values = {};
855
887
  for (const rawLine of content.split(/\r?\n/u)) {
@@ -912,6 +944,7 @@ async function updateDotenvFile(path, updates) {
912
944
  current[key] = String(value);
913
945
  }
914
946
  }
947
+ await backupExistingPath(path);
915
948
  await mkdir(dirname(path), { recursive: true });
916
949
  await writeFile(path, renderDotenv(current), "utf8");
917
950
  }
@@ -1276,54 +1309,90 @@ async function configureClaudeApi() {
1276
1309
  nextSettings.env[field] = model;
1277
1310
  }
1278
1311
 
1279
- await writeJsonFileSafe(claudeSettingsPath, nextSettings);
1280
-
1281
- const vscodeConfig = await readJsonFileSafe(claudeVscodeConfigPath, {});
1282
- vscodeConfig.primaryApiKey = "abelworkflow";
1283
- await writeJsonFileSafe(claudeVscodeConfigPath, vscodeConfig);
1312
+ await writeJsonFileWithBackup(claudeSettingsPath, nextSettings);
1284
1313
 
1285
1314
  const metaConfig = await readJsonFileSafe(claudeMetaConfigPath, {});
1286
1315
  metaConfig.hasCompletedOnboarding = true;
1287
1316
  ensureApprovedClaudeApiKey(metaConfig, key);
1288
- await writeJsonFileSafe(claudeMetaConfigPath, metaConfig);
1317
+ await writeJsonFileWithBackup(claudeMetaConfigPath, metaConfig);
1289
1318
 
1290
1319
  console.log(`已更新 ${pathToLabel(claudeSettingsPath)} (${authType}, ${baseUrl}, ${maskSecret(key)})`);
1291
1320
  }
1292
1321
 
1293
1322
  function updateTopLevelTomlField(content, field, value) {
1294
1323
  const lineEnding = detectLineEnding(content);
1295
- const firstSectionMatch = content.match(/^\[/mu);
1296
- const topLevelEnd = firstSectionMatch?.index ?? content.length;
1297
- let topLevel = content.slice(0, topLevelEnd);
1298
- const rest = content.slice(topLevelEnd);
1299
- const fieldRegex = new RegExp(`^(#\\s*)?${escapeRegExp(field)}\\s*=\\s*["'][^"']*["'][ \\t]*(?:#.*)?\\r?$`, "mu");
1300
-
1301
1324
  if (value === null) {
1302
- topLevel = collapseBlankLines(topLevel.replace(fieldRegex, ""), lineEnding);
1303
- } else {
1304
- const nextLine = `${field} = ${JSON.stringify(value)}`;
1305
- if (fieldRegex.test(topLevel)) {
1306
- topLevel = topLevel.replace(fieldRegex, nextLine);
1307
- } else {
1308
- topLevel = topLevel.trimEnd()
1309
- ? `${topLevel.trimEnd()}${lineEnding}${nextLine}${lineEnding}`
1310
- : `${nextLine}${lineEnding}`;
1325
+ return removeTopLevelTomlField(content, field);
1326
+ }
1327
+
1328
+ const { topLevel, rest } = splitTopLevelTomlContent(content);
1329
+ const entry = extractTopLevelTomlEntries(content).find((item) => item.field === field);
1330
+ const nextLine = `${field} = ${JSON.stringify(value)}`;
1331
+ let nextTopLevel;
1332
+
1333
+ if (entry) {
1334
+ const start = topLevel.indexOf(entry.raw);
1335
+ if (start === -1) {
1336
+ return content;
1311
1337
  }
1338
+ nextTopLevel = `${topLevel.slice(0, start)}${nextLine}${topLevel.slice(start + entry.raw.length)}`;
1339
+ } else {
1340
+ nextTopLevel = topLevel.trimEnd()
1341
+ ? `${topLevel.trimEnd()}${lineEnding}${nextLine}${lineEnding}`
1342
+ : `${nextLine}${lineEnding}`;
1343
+ }
1344
+
1345
+ nextTopLevel = nextTopLevel.trimEnd();
1346
+
1347
+ if (rest && nextTopLevel) {
1348
+ nextTopLevel = `${nextTopLevel}${lineEnding}${lineEnding}`;
1349
+ }
1350
+
1351
+ return `${nextTopLevel}${rest}`;
1352
+ }
1353
+
1354
+ function removeTopLevelTomlField(content, field) {
1355
+ const lineEnding = detectLineEnding(content);
1356
+ const { topLevel, rest } = splitTopLevelTomlContent(content);
1357
+ const entry = extractTopLevelTomlEntries(content).find((item) => item.field === field);
1358
+ if (!entry) {
1359
+ return content;
1360
+ }
1361
+
1362
+ const start = topLevel.indexOf(entry.raw);
1363
+ if (start === -1) {
1364
+ return content;
1312
1365
  }
1313
1366
 
1314
- topLevel = topLevel.trimEnd();
1367
+ let nextTopLevel = `${topLevel.slice(0, start)}${topLevel.slice(start + entry.raw.length)}`;
1368
+ nextTopLevel = collapseBlankLines(nextTopLevel, lineEnding).trimEnd();
1315
1369
 
1316
- if (rest && topLevel) {
1317
- topLevel = `${topLevel}${lineEnding}${lineEnding}`;
1370
+ if (rest && nextTopLevel) {
1371
+ nextTopLevel = `${nextTopLevel}${lineEnding}${lineEnding}`;
1318
1372
  }
1319
1373
 
1320
- return `${topLevel}${rest}`;
1374
+ return `${nextTopLevel}${rest}`;
1321
1375
  }
1322
1376
 
1323
1377
  function removeTomlSection(content, sectionName) {
1324
1378
  const lineEnding = detectLineEnding(content);
1325
- const sectionRegex = new RegExp(`(?:\\r?\\n)?\\[${escapeRegExp(sectionName)}\\][\\s\\S]*?(?=\\r?\\n\\[|$)`, "gu");
1326
- return collapseBlankLines(content.replace(sectionRegex, ""), lineEnding).trimEnd();
1379
+ const sectionHeaderRegex = new RegExp(
1380
+ `(?:^|\\r?\\n)\\[${escapeRegExp(sectionName)}\\](?:[ \\t]+#.*)?[ \\t]*\\r?$`,
1381
+ "mu"
1382
+ );
1383
+ const headerMatch = sectionHeaderRegex.exec(content);
1384
+ if (!headerMatch) {
1385
+ return content;
1386
+ }
1387
+
1388
+ const sectionStart = headerMatch[0].startsWith("\n") || headerMatch[0].startsWith("\r\n")
1389
+ ? headerMatch.index + headerMatch[0].indexOf("[")
1390
+ : headerMatch.index;
1391
+ const headerLineEnd = content.indexOf(lineEnding, sectionStart);
1392
+ const bodyStart = headerLineEnd === -1 ? content.length : headerLineEnd + lineEnding.length;
1393
+ const nextSectionStart = findTomlSectionStart(content, bodyStart);
1394
+ const sectionEnd = nextSectionStart === -1 ? content.length : nextSectionStart;
1395
+ return collapseBlankLines(`${content.slice(0, sectionStart)}${content.slice(sectionEnd)}`, lineEnding).trimEnd();
1327
1396
  }
1328
1397
 
1329
1398
  function buildTomlSection(sectionName, values, lineEnding = "\n") {
@@ -1343,18 +1412,235 @@ function buildTomlSection(sectionName, values, lineEnding = "\n") {
1343
1412
  return `${lines.join(lineEnding)}${lineEnding}`;
1344
1413
  }
1345
1414
 
1346
- function readTopLevelTomlString(content, field) {
1347
- let inSection = false;
1348
- for (const line of content.split(/\r?\n/u)) {
1415
+ function formatTomlValue(value) {
1416
+ if (typeof value === "string") {
1417
+ return JSON.stringify(value);
1418
+ }
1419
+ if (typeof value === "boolean") {
1420
+ return value ? "true" : "false";
1421
+ }
1422
+ return String(value);
1423
+ }
1424
+
1425
+ function getTomlLines(content) {
1426
+ if (!content) {
1427
+ return [];
1428
+ }
1429
+
1430
+ const chunks = content.match(/[^\r\n]*(?:\r?\n|$)/gu) || [];
1431
+ const lines = [];
1432
+ let offset = 0;
1433
+
1434
+ for (const chunk of chunks) {
1435
+ if (!chunk && offset >= content.length) {
1436
+ break;
1437
+ }
1438
+
1439
+ const lineEndingMatch = chunk.match(/\r?\n$/u);
1440
+ const lineEnding = lineEndingMatch ? lineEndingMatch[0] : "";
1441
+ lines.push({
1442
+ line: lineEnding ? chunk.slice(0, -lineEnding.length) : chunk,
1443
+ start: offset
1444
+ });
1445
+ offset += chunk.length;
1446
+
1447
+ if (!lineEnding && offset >= content.length) {
1448
+ break;
1449
+ }
1450
+ }
1451
+
1452
+ return lines;
1453
+ }
1454
+
1455
+ function findTomlSectionStart(content, fromIndex = 0) {
1456
+ const scopedContent = content.slice(fromIndex);
1457
+ let multilineDelimiter = "";
1458
+
1459
+ for (const { line, start } of getTomlLines(scopedContent)) {
1460
+ const trimmed = line.trim();
1461
+
1462
+ if (multilineDelimiter) {
1463
+ if (line.includes(multilineDelimiter)) {
1464
+ multilineDelimiter = "";
1465
+ }
1466
+ continue;
1467
+ }
1468
+
1469
+ if (!trimmed || trimmed.startsWith("#")) {
1470
+ continue;
1471
+ }
1472
+
1473
+ if (/^\[[^\]]+\]\s*(?:#.*)?$/u.test(trimmed)) {
1474
+ return fromIndex + start;
1475
+ }
1476
+
1477
+ const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*)$/u);
1478
+ if (!match) {
1479
+ continue;
1480
+ }
1481
+
1482
+ const delimiter = getTomlMultilineStringDelimiter(match[2].trimStart());
1483
+ if (delimiter && match[2].indexOf(delimiter, delimiter.length) === -1) {
1484
+ multilineDelimiter = delimiter;
1485
+ }
1486
+ }
1487
+
1488
+ return -1;
1489
+ }
1490
+
1491
+ function splitTopLevelTomlContent(content) {
1492
+ const topLevelEnd = findTomlSectionStart(content);
1493
+ return {
1494
+ topLevel: topLevelEnd === -1 ? content : content.slice(0, topLevelEnd),
1495
+ rest: topLevelEnd === -1 ? "" : content.slice(topLevelEnd)
1496
+ };
1497
+ }
1498
+
1499
+ function getTomlMultilineStringDelimiter(value) {
1500
+ if (value.startsWith(`"""`)) {
1501
+ return `"""`;
1502
+ }
1503
+ if (value.startsWith(`'''`)) {
1504
+ return `'''`;
1505
+ }
1506
+ return "";
1507
+ }
1508
+
1509
+ function extractTopLevelTomlEntries(content) {
1510
+ const { topLevel } = splitTopLevelTomlContent(content);
1511
+ const lineEnding = detectLineEnding(topLevel);
1512
+ const lines = topLevel.split(/\r?\n/u);
1513
+ const entries = [];
1514
+
1515
+ for (let index = 0; index < lines.length; index += 1) {
1516
+ const line = lines[index];
1349
1517
  const trimmed = line.trim();
1350
1518
  if (!trimmed || trimmed.startsWith("#")) {
1351
1519
  continue;
1352
1520
  }
1353
- if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
1354
- inSection = true;
1521
+
1522
+ const match = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*)$/u);
1523
+ if (!match) {
1524
+ continue;
1525
+ }
1526
+
1527
+ const rawLines = [line];
1528
+ const delimiter = getTomlMultilineStringDelimiter(match[2].trimStart());
1529
+ if (delimiter && match[2].indexOf(delimiter, delimiter.length) === -1) {
1530
+ for (index += 1; index < lines.length; index += 1) {
1531
+ rawLines.push(lines[index]);
1532
+ if (lines[index].includes(delimiter)) {
1533
+ break;
1534
+ }
1535
+ }
1536
+ }
1537
+
1538
+ entries.push({
1539
+ field: match[1],
1540
+ raw: rawLines.join(lineEnding)
1541
+ });
1542
+ }
1543
+
1544
+ return entries;
1545
+ }
1546
+
1547
+ function mergeMissingTopLevelTomlEntries(content, entries) {
1548
+ if (!entries.length) {
1549
+ return content;
1550
+ }
1551
+
1552
+ const lineEnding = detectLineEnding(content);
1553
+ const { topLevel, rest } = splitTopLevelTomlContent(content);
1554
+ const existingFields = new Set(extractTopLevelTomlEntries(content).map(({ field }) => field));
1555
+ const missingEntries = entries.filter(({ field }) => !existingFields.has(field));
1556
+ if (!missingEntries.length) {
1557
+ return content;
1558
+ }
1559
+
1560
+ let nextTopLevel = topLevel.trimEnd();
1561
+ for (const entry of missingEntries) {
1562
+ nextTopLevel = nextTopLevel
1563
+ ? `${nextTopLevel}${lineEnding}${entry.raw}`
1564
+ : entry.raw;
1565
+ }
1566
+
1567
+ nextTopLevel = nextTopLevel.trimEnd();
1568
+ if (rest && nextTopLevel) {
1569
+ nextTopLevel = `${nextTopLevel}${lineEnding}${lineEnding}`;
1570
+ }
1571
+ return `${nextTopLevel}${rest}`;
1572
+ }
1573
+
1574
+ function updateTomlBodyFields(body, values, lineEnding = "\n") {
1575
+ const normalizedBody = body.replace(/\r?\n$/u, "");
1576
+ const lines = normalizedBody ? normalizedBody.split(/\r?\n/u) : [];
1577
+ const remaining = new Map(
1578
+ Object.entries(values).filter(([, value]) => value !== undefined && value !== null && value !== "")
1579
+ );
1580
+ const managedFields = new Set(remaining.keys());
1581
+ const seenFields = new Set();
1582
+ const nextLines = [];
1583
+
1584
+ for (const rawLine of lines) {
1585
+ const match = rawLine.match(/^(\s*)([A-Za-z0-9_]+)\s*=\s*.*$/u);
1586
+ if (!match || !managedFields.has(match[2])) {
1587
+ nextLines.push(rawLine);
1588
+ continue;
1589
+ }
1590
+
1591
+ if (seenFields.has(match[2])) {
1355
1592
  continue;
1356
1593
  }
1357
- if (inSection) {
1594
+
1595
+ nextLines.push(`${match[1]}${match[2]} = ${formatTomlValue(remaining.get(match[2]))}`);
1596
+ seenFields.add(match[2]);
1597
+ remaining.delete(match[2]);
1598
+ }
1599
+
1600
+ for (const [field, value] of remaining) {
1601
+ nextLines.push(`${field} = ${formatTomlValue(value)}`);
1602
+ }
1603
+
1604
+ return nextLines.join(lineEnding);
1605
+ }
1606
+
1607
+ function updateTomlSectionFields(content, sectionName, values) {
1608
+ const lineEnding = detectLineEnding(content);
1609
+ const sectionHeaderRegex = new RegExp(
1610
+ `^\\[${escapeRegExp(sectionName)}\\](?:[ \\t]+#.*)?[ \\t]*\\r?$`,
1611
+ "mu"
1612
+ );
1613
+ const headerMatch = sectionHeaderRegex.exec(content);
1614
+
1615
+ if (!headerMatch) {
1616
+ const nextSection = buildTomlSection(sectionName, values, lineEnding).trimEnd();
1617
+ return content.trimEnd()
1618
+ ? `${content.trimEnd()}${lineEnding}${lineEnding}${nextSection}${lineEnding}`
1619
+ : `${nextSection}${lineEnding}`;
1620
+ }
1621
+
1622
+ const sectionStart = headerMatch.index;
1623
+ const headerLineEnd = content.indexOf(lineEnding, sectionStart);
1624
+ const bodyStart = headerLineEnd === -1 ? content.length : headerLineEnd + lineEnding.length;
1625
+ const headerLine = content.slice(sectionStart, headerLineEnd === -1 ? content.length : headerLineEnd);
1626
+ const nextSectionStart = findTomlSectionStart(content, bodyStart);
1627
+ const sectionEnd = nextSectionStart === -1 ? content.length : nextSectionStart;
1628
+ const prefix = content.slice(0, sectionStart);
1629
+ const body = content.slice(bodyStart, sectionEnd);
1630
+ const suffix = content.slice(sectionEnd);
1631
+ const nextBody = updateTomlBodyFields(body, values, lineEnding);
1632
+ const renderedSection = nextBody
1633
+ ? `${headerLine}${lineEnding}${nextBody}${lineEnding}`
1634
+ : `${headerLine}${lineEnding}`;
1635
+
1636
+ return `${prefix}${renderedSection}${suffix.replace(/^\r?\n/u, "")}`;
1637
+ }
1638
+
1639
+ function readTopLevelTomlString(content, field) {
1640
+ const { topLevel } = splitTopLevelTomlContent(content);
1641
+ for (const line of topLevel.split(/\r?\n/u)) {
1642
+ const trimmed = line.trim();
1643
+ if (!trimmed || trimmed.startsWith("#")) {
1358
1644
  continue;
1359
1645
  }
1360
1646
  const match = trimmed.match(new RegExp(`^${escapeRegExp(field)}\\s*=\\s*"([^"]+)"$`, "u"));
@@ -1366,78 +1652,242 @@ function readTopLevelTomlString(content, field) {
1366
1652
  }
1367
1653
 
1368
1654
  function parseTomlSection(content, sectionName) {
1369
- const match = content.match(new RegExp(`\\[${escapeRegExp(sectionName)}\\]\\r?\\n([\\s\\S]*?)(?=\\r?\\n\\[|$)`, "u"));
1370
- if (!match) {
1655
+ const lineEnding = detectLineEnding(content);
1656
+ const sectionHeaderRegex = new RegExp(
1657
+ `^\\[${escapeRegExp(sectionName)}\\](?:[ \\t]+#.*)?[ \\t]*\\r?$`,
1658
+ "mu"
1659
+ );
1660
+ const headerMatch = sectionHeaderRegex.exec(content);
1661
+ if (!headerMatch) {
1371
1662
  return {};
1372
1663
  }
1373
1664
 
1665
+ const sectionStart = headerMatch.index;
1666
+ const headerLineEnd = content.indexOf(lineEnding, sectionStart);
1667
+ const bodyStart = headerLineEnd === -1 ? content.length : headerLineEnd + lineEnding.length;
1668
+ const nextSectionStart = findTomlSectionStart(content, bodyStart);
1669
+ const body = content.slice(bodyStart, nextSectionStart === -1 ? content.length : nextSectionStart);
1670
+
1374
1671
  const values = {};
1375
- for (const rawLine of match[1].split(/\r?\n/u)) {
1672
+ for (const rawLine of body.split(/\r?\n/u)) {
1376
1673
  const line = rawLine.trim();
1377
1674
  if (!line || line.startsWith("#")) {
1378
1675
  continue;
1379
1676
  }
1380
- const stringMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*"([^"]*)"$/u);
1677
+ const stringMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*"([^"]*)"\s*(?:#.*)?$/u);
1381
1678
  if (stringMatch) {
1382
1679
  values[stringMatch[1]] = stringMatch[2];
1383
1680
  continue;
1384
1681
  }
1385
- const boolMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*(true|false)$/u);
1682
+ const boolMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*(true|false)\s*(?:#.*)?$/u);
1386
1683
  if (boolMatch) {
1387
1684
  values[boolMatch[1]] = boolMatch[2] === "true";
1685
+ continue;
1686
+ }
1687
+ const numberMatch = line.match(/^([A-Za-z0-9_]+)\s*=\s*(-?\d+(?:\.\d+)?)\s*(?:#.*)?$/u);
1688
+ if (numberMatch) {
1689
+ values[numberMatch[1]] = Number(numberMatch[2]);
1388
1690
  }
1389
1691
  }
1390
1692
  return values;
1391
1693
  }
1392
1694
 
1393
- async function getExistingCodexApiConfig() {
1394
- const content = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
1395
- const auth = await readJsonFileSafe(codexAuthPath, {});
1695
+ function stripCodexSubagentDefaults(content) {
1696
+ const lineEnding = detectLineEnding(content);
1697
+ let nextContent = removeTopLevelTomlField(content, "approvals_reviewer");
1698
+ nextContent = removeTopLevelTomlField(nextContent, "developer_instructions");
1699
+ nextContent = removeTomlSection(nextContent, "agents");
1700
+
1701
+ const featureValues = parseTomlSection(nextContent, "features");
1702
+ delete featureValues.multi_agent;
1703
+ delete featureValues.guardian_approval;
1704
+ nextContent = removeTomlSection(nextContent, "features");
1705
+
1706
+ if (Object.keys(featureValues).length) {
1707
+ const featuresSection = buildTomlSection("features", featureValues, lineEnding).trimEnd();
1708
+ nextContent = nextContent.trimEnd()
1709
+ ? `${nextContent.trimEnd()}${lineEnding}${lineEnding}${featuresSection}${lineEnding}`
1710
+ : `${featuresSection}${lineEnding}`;
1711
+ }
1712
+
1713
+ return nextContent;
1714
+ }
1715
+
1716
+ function mergeCodexTemplateDefaults(content, templateContent) {
1717
+ const defaultTopLevelFields = new Set([
1718
+ "personality",
1719
+ "disable_response_storage",
1720
+ "approvals_reviewer",
1721
+ "approval_policy",
1722
+ "sandbox_mode",
1723
+ "service_tier",
1724
+ "model",
1725
+ "model_reasoning_effort",
1726
+ "developer_instructions"
1727
+ ]);
1728
+ const templateEntries = extractTopLevelTomlEntries(templateContent)
1729
+ .filter(({ field }) => defaultTopLevelFields.has(field));
1730
+ let nextContent = mergeMissingTopLevelTomlEntries(content, templateEntries);
1731
+
1732
+ for (const sectionName of ["agents", "features"]) {
1733
+ const templateValues = parseTomlSection(templateContent, sectionName);
1734
+ const currentValues = parseTomlSection(nextContent, sectionName);
1735
+ const missingValues = Object.fromEntries(
1736
+ Object.entries(templateValues).filter(([field]) => !(field in currentValues))
1737
+ );
1738
+ if (Object.keys(missingValues).length) {
1739
+ nextContent = updateTomlSectionFields(nextContent, sectionName, missingValues);
1740
+ }
1741
+ }
1742
+
1743
+ return nextContent;
1744
+ }
1745
+
1746
+ async function loadBundledCodexConfigTemplate() {
1747
+ return readFile(codexTemplateConfigPath, "utf8");
1748
+ }
1749
+
1750
+ async function deployBundledCodexAgents() {
1751
+ const targetDir = join(home, ".codex", "agents");
1752
+ await mkdir(targetDir, { recursive: true });
1753
+ const entries = (await readdir(codexTemplateAgentsPath, { withFileTypes: true }))
1754
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".toml"))
1755
+ .map((entry) => entry.name)
1756
+ .sort((left, right) => left.localeCompare(right));
1757
+
1758
+ const deployed = [];
1759
+ for (const name of entries) {
1760
+ const source = join(codexTemplateAgentsPath, name);
1761
+ const target = join(targetDir, name);
1762
+ await backupExistingPath(target);
1763
+ await cp(source, target, { force: true });
1764
+ deployed.push(target);
1765
+ }
1766
+ return deployed;
1767
+ }
1768
+
1769
+ function getDefaultCodexEnvKey(providerId) {
1770
+ return `${providerId.toUpperCase().replace(/-/gu, "_")}_API_KEY`;
1771
+ }
1772
+
1773
+ function resolveExistingCodexApiConfig(content, auth = {}) {
1396
1774
  const providerId = readTopLevelTomlString(content, "model_provider") || "abelworkflow";
1397
1775
  const provider = parseTomlSection(content, `model_providers.${providerId}`);
1398
- const envKey = provider.temp_env_key || "OPENAI_API_KEY";
1776
+ const requiresOpenAiAuth = provider.requires_openai_auth !== false;
1777
+ const configuredEnvKey = provider.temp_env_key || "";
1778
+ const defaultEnvKey = requiresOpenAiAuth ? "OPENAI_API_KEY" : getDefaultCodexEnvKey(providerId);
1779
+ const envKey = configuredEnvKey || defaultEnvKey;
1780
+ const apiKeyCandidates = [
1781
+ configuredEnvKey,
1782
+ envKey,
1783
+ defaultEnvKey,
1784
+ "OPENAI_API_KEY"
1785
+ ].filter(Boolean);
1786
+ const apiKeyMatch = apiKeyCandidates.find((key) => typeof auth[key] === "string" && auth[key]);
1787
+ const apiKey = apiKeyMatch ? auth[apiKeyMatch] : "";
1788
+
1399
1789
  return {
1400
1790
  providerId,
1401
- providerName: provider.name || (providerId === "abelworkflow" ? "AbelWorkflow" : providerId),
1791
+ providerName: provider.name || providerId,
1402
1792
  baseUrl: provider.base_url || "https://api.openai.com/v1",
1403
1793
  envKey,
1404
- apiKey: auth[envKey] || ""
1794
+ legacyEnvKeys: configuredEnvKey && configuredEnvKey !== envKey ? [configuredEnvKey] : [],
1795
+ apiKey
1405
1796
  };
1406
1797
  }
1407
1798
 
1799
+ async function getExistingCodexApiConfig() {
1800
+ const content = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
1801
+ const auth = await readJsonFileSafe(codexAuthPath, {});
1802
+ return resolveExistingCodexApiConfig(content, auth);
1803
+ }
1804
+
1408
1805
  async function configureCodexApi() {
1409
1806
  const existing = await getExistingCodexApiConfig();
1410
1807
  const providerId = existing.providerId || "abelworkflow";
1411
- const providerName = existing.providerName || "AbelWorkflow";
1808
+ const providerName = existing.providerName || providerId;
1412
1809
  const baseUrl = await promptText("Codex Base URL", {
1413
1810
  defaultValue: existing.baseUrl
1414
1811
  });
1415
1812
  const apiKey = await promptSecret("Codex 第三方 API Key", {
1416
1813
  defaultValue: existing.apiKey || undefined
1417
1814
  });
1418
- const envKey = `${providerId.toUpperCase().replace(/-/gu, "_")}_API_KEY`;
1815
+ const shouldDeploySubagents = await promptConfirm("是否部署 Codex subagents 配置?", true);
1816
+ const envKey = existing.envKey || "OPENAI_API_KEY";
1817
+ const currentContent = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
1818
+ const templateContent = await loadBundledCodexConfigTemplate();
1819
+ const content = buildCodexConfigContent(currentContent, {
1820
+ templateContent,
1821
+ mergeMissingTemplateDefaults: true,
1822
+ includeSubagentDefaults: shouldDeploySubagents,
1823
+ providerId,
1824
+ providerName,
1825
+ baseUrl,
1826
+ envKey
1827
+ });
1419
1828
 
1420
- let content = await pathExists(codexConfigPath) ? await readFile(codexConfigPath, "utf8") : "";
1829
+ await backupExistingPath(codexConfigPath);
1830
+ await mkdir(dirname(codexConfigPath), { recursive: true });
1831
+ await writeFile(codexConfigPath, content, "utf8");
1832
+
1833
+ const auth = mergeCodexAuthData(await readJsonFileSafe(codexAuthPath, {}), envKey, apiKey, existing.legacyEnvKeys || []);
1834
+ await writeJsonFileWithBackup(codexAuthPath, auth);
1835
+
1836
+ console.log(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
1837
+ console.log(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(apiKey)})`);
1838
+ if (shouldDeploySubagents) {
1839
+ const deployed = await deployBundledCodexAgents();
1840
+ console.log(`已部署 ${deployed.length} 个 Codex subagents 到 ${pathToLabel(join(home, ".codex", "agents"))}`);
1841
+ } else {
1842
+ console.log("已跳过 Codex subagents 部署。");
1843
+ }
1844
+ }
1845
+
1846
+ function buildCodexConfigContent(currentContent, {
1847
+ templateContent = "",
1848
+ mergeMissingTemplateDefaults = false,
1849
+ includeSubagentDefaults = true,
1850
+ providerId,
1851
+ providerName,
1852
+ baseUrl,
1853
+ envKey
1854
+ }) {
1855
+ const effectiveTemplateContent = includeSubagentDefaults
1856
+ ? templateContent
1857
+ : stripCodexSubagentDefaults(templateContent);
1858
+ const hasCurrentContent = Boolean(currentContent.trim());
1859
+ let content = hasCurrentContent ? currentContent : effectiveTemplateContent;
1860
+ if (!includeSubagentDefaults && hasCurrentContent) {
1861
+ content = stripCodexSubagentDefaults(content);
1862
+ }
1863
+ if (hasCurrentContent && mergeMissingTemplateDefaults && effectiveTemplateContent.trim()) {
1864
+ content = mergeCodexTemplateDefaults(content, effectiveTemplateContent);
1865
+ }
1421
1866
  const lineEnding = detectLineEnding(content);
1867
+ if (includeSubagentDefaults && readTopLevelTomlString(content, "approvals_reviewer") === "guardian_subagent") {
1868
+ content = updateTopLevelTomlField(content, "approvals_reviewer", "reviewer");
1869
+ }
1422
1870
  content = updateTopLevelTomlField(content, "model_provider", providerId);
1423
- content = removeTomlSection(content, `model_providers.${providerId}`);
1424
- content = `${content.trimEnd() ? `${content.trimEnd()}${lineEnding}${lineEnding}` : ""}${buildTomlSection(`model_providers.${providerId}`, {
1871
+ content = updateTopLevelTomlField(content, "preferred_auth_method", "apikey");
1872
+ content = updateTomlSectionFields(content, `model_providers.${providerId}`, {
1425
1873
  name: providerName,
1426
1874
  base_url: baseUrl,
1427
1875
  wire_api: "responses",
1428
1876
  temp_env_key: envKey,
1429
1877
  requires_openai_auth: true
1430
- }, lineEnding)}`;
1431
-
1432
- await mkdir(dirname(codexConfigPath), { recursive: true });
1433
- await writeFile(codexConfigPath, `${content.trim()}${lineEnding}`, "utf8");
1434
-
1435
- const auth = await readJsonFileSafe(codexAuthPath, {});
1436
- auth[envKey] = apiKey;
1437
- await writeJsonFileSafe(codexAuthPath, auth);
1878
+ });
1879
+ return `${content.trim()}${lineEnding}`;
1880
+ }
1438
1881
 
1439
- console.log(`已更新 ${pathToLabel(codexConfigPath)} (${providerId}, ${baseUrl})`);
1440
- console.log(`已更新 ${pathToLabel(codexAuthPath)} (${maskSecret(apiKey)})`);
1882
+ function mergeCodexAuthData(auth, envKey, apiKey, legacyEnvKeys = []) {
1883
+ const nextAuth = auth && typeof auth === "object" ? { ...auth } : {};
1884
+ for (const key of legacyEnvKeys) {
1885
+ if (key && key !== envKey) {
1886
+ delete nextAuth[key];
1887
+ }
1888
+ }
1889
+ nextAuth[envKey] = apiKey;
1890
+ return nextAuth;
1441
1891
  }
1442
1892
 
1443
1893
  async function installCliTool(tool) {
@@ -1577,7 +2027,20 @@ async function main() {
1577
2027
  await runInteractiveMenu(options);
1578
2028
  }
1579
2029
 
1580
- main().catch((error) => {
1581
- console.error(error instanceof Error ? error.message : String(error));
1582
- process.exit(1);
1583
- });
2030
+ export {
2031
+ buildCodexConfigContent,
2032
+ main,
2033
+ mergeCodexAuthData,
2034
+ mergeClaudeSettingsWithDefaults,
2035
+ resolveExistingCodexApiConfig,
2036
+ updateTomlSectionFields
2037
+ };
2038
+
2039
+ const isDirectExecution = process.argv[1] ? resolve(process.argv[1]) === __filename : false;
2040
+
2041
+ if (isDirectExecution) {
2042
+ main().catch((error) => {
2043
+ console.error(error instanceof Error ? error.message : String(error));
2044
+ process.exit(1);
2045
+ });
2046
+ }
@@ -0,0 +1,56 @@
1
+ # .codex/agents/default.toml
2
+
3
+ name = "default"
4
+ description = """
5
+ Dispatch for synthesis, general fallback, and mixed-scope tasks that do not
6
+ cleanly fit explorer, planner, reviewer, or worker.
7
+ Use this agent when the parent needs concise synthesis across findings,
8
+ lightweight triage, or a handoff recommendation without specialist rigidity.
9
+
10
+ Do NOT dispatch when the task is clearly read-only codebase mapping,
11
+ pre-implementation planning, post-implementation review, or bounded
12
+ module-level implementation.
13
+ """
14
+ nickname_candidates = ["Relay", "Pivot", "Anchor"]
15
+ model = "gpt-5.4"
16
+ model_reasoning_effort = "high"
17
+ sandbox_mode = "workspace-write"
18
+
19
+ developer_instructions = """
20
+ You are the general-purpose fallback sub-agent. Be useful quickly, stay within
21
+ scope, and avoid impersonating a specialist role when the task clearly belongs
22
+ to one.
23
+
24
+ ## Priorities (in order)
25
+
26
+ 1. Role fit — identify whether the request truly belongs to default
27
+ 2. Synthesis — combine the relevant evidence into a direct, usable result
28
+ 3. Minimal action — do the smallest thing that resolves the request safely
29
+ 4. Clear handoff — if the task becomes specialist work, stop and say which
30
+ agent should take over and why
31
+
32
+ ## Rules
33
+
34
+ - Prefer direct answers, concise synthesis, and lightweight triage over broad
35
+ exploration.
36
+ - Do not spawn subagents unless the parent explicitly instructs you to.
37
+ - Do not redesign systems, expand scope, or perform speculative cleanup.
38
+ - Do not default to code changes. Only edit when the parent explicitly asks or
39
+ the task is so small and local that a worker handoff would be unnecessary.
40
+ - If the request is clearly specialist work, say so explicitly and constrain
41
+ your response to the highest-value support you can provide before handoff.
42
+ - If you edit files, keep the change minimal, verify what you can, and report
43
+ any limits clearly.
44
+
45
+ ## Output
46
+
47
+ Normal completion:
48
+ 1. Result: <answer, synthesis, or completed action>
49
+ 2. Evidence/Files: <relevant files, commands, or inputs used>
50
+ 3. Residual risk: <notes, or "none">
51
+
52
+ Specialist handoff:
53
+ 1. Recommended agent: <explorer|planner|reviewer|worker>
54
+ 2. Why: <why default should stop here>
55
+ 3. Next input: <what the parent should send that agent>
56
+ """
@@ -0,0 +1,63 @@
1
+ # .codex/agents/explorer.toml
2
+
3
+ name = "explorer"
4
+ description = """
5
+ Dispatch for read-only codebase mapping before any changes are made: tracing
6
+
7
+ execution paths, locating symbols, understanding data flow, and identifying
8
+ which files a planned change will affect.
9
+ Do NOT dispatch for review after implementation — use reviewer instead.
10
+
11
+ Do NOT dispatch if the affected files are already known and confirmed.
12
+ """
13
+ nickname_candidates = ["Atlas", "Trace", "Scout"]
14
+ model = "gpt-5.4"
15
+ model_reasoning_effort = "high"
16
+ sandbox_mode = "read-only"
17
+
18
+
19
+ developer_instructions = """
20
+ Your only job is to map. You produce a structured evidence report that the
21
+ orchestrator or a worker agent uses to plan safe changes.
22
+
23
+ ## Rules
24
+
25
+ - Do not edit any file, ever.
26
+ - Do not propose fixes or refactors unless the parent explicitly asks.
27
+
28
+ - Do not spawn subagents unless the parent explicitly instructs you to.
29
+ - Prefer targeted file reads and symbol searches over broad directory scans.
30
+ - Every claim must cite an exact file and line number. No approximations.
31
+
32
+ ## Output format
33
+
34
+ Use this exact structure:
35
+
36
+ ## Exploration Summary
37
+ Entry point: <file>:<line> — <symbol or request handler>
38
+ Goal: <what the orchestrator asked you to find>
39
+
40
+ ## Execution Path
41
+
42
+ 1. <file>:<line> — <what happens here>
43
+ 2. <file>:<line> — <what happens here>
44
+ (continue until the path reaches its terminal or the boundary of the question)
45
+
46
+ ## Key Symbols
47
+ - <SymbolName> — <file>:<line> — <role in one sentence>
48
+
49
+ ## Affected File List
50
+ Files that a change to the entry point is likely to touch:
51
+ - <path> — <reason>
52
+
53
+ ## Impact Boundary
54
+ <2–4 sentences: what is safely inside scope, what sits just outside it,
55
+ and any shared state or cross-module dependencies the worker must not break>
56
+
57
+ ## Open Questions
58
+ <anything ambiguous that the orchestrator should resolve before work starts;
59
+ omit this section if there are none>
60
+
61
+
62
+ Omit sections that are empty. Do not add commentary outside this structure.
63
+ """
@@ -0,0 +1,89 @@
1
+ name = "planner"
2
+
3
+ description = """
4
+ Dispatch for pre-implementation planning: understanding the request, defining
5
+ safe scope, sequencing work, identifying key files, surfacing risks, and
6
+ preparing an execution plan for a worker or the orchestrator.
7
+
8
+ Do NOT dispatch for codebase mapping when the main need is to trace execution
9
+ paths or locate symbols — use explorer instead.
10
+
11
+ Do NOT dispatch for simple, obvious changes where planning overhead would
12
+
13
+ exceed execution value.
14
+ Do NOT dispatch after implementation is complete — use reviewer instead.
15
+ """
16
+ nickname_candidates = ["Blueprint", "Compass", "Architect"]
17
+ model = "gpt-5.4"
18
+ model_reasoning_effort = "high"
19
+ sandbox_mode = "read-only"
20
+
21
+ developer_instructions = """
22
+ Plan like the engineer who will be responsible for getting the change shipped
23
+ safely, not like someone writing a design essay.
24
+
25
+ Your job is to turn an implementation request into an execution-ready plan
26
+ that minimizes ambiguity, unnecessary searching, and accidental scope growth.
27
+
28
+ ## Priorities (in order)
29
+
30
+ 1. Scope clarity — what is actually being changed and what is not
31
+ 2. Safe sequencing — what must happen first, and what depends on what
32
+ 3. File targeting — which files matter most for execution
33
+
34
+ 4. Risk visibility — what could break, regress, or expand scope
35
+ 5. Verification readiness — how the parent agent should confirm correctness
36
+
37
+ ## Rules
38
+
39
+ - Do not edit or write any code.
40
+ - Do not produce patches, replacement snippets, or speculative implementations.
41
+ - Do not spawn subagents unless the parent explicitly instructs you to.
42
+
43
+ - Prefer concrete execution guidance over abstract architectural commentary.
44
+
45
+ - Keep the plan constrained to the requested task; do not redesign adjacent systems unless required by the request.
46
+ - If the request cannot be completed safely without crossing module or ownership boundaries, say so explicitly.
47
+
48
+ ## Output format
49
+
50
+ Use this exact structure so the orchestrator can act on it directly:
51
+
52
+ ## Plan Summary
53
+ Goal: <what needs to be accomplished>
54
+
55
+ Scope: <what is in scope and what is explicitly out of scope>
56
+
57
+ ## Problem Analysis
58
+ <brief explanation of the underlying issue, requested behavior, or relevant constraint>
59
+
60
+ ## Implementation Steps
61
+ 1. <step> — <why this comes first or what it unlocks>
62
+ 2. <step> — <dependency, ordering, or expected result>
63
+ 3. <step> — <dependency, ordering, or expected result>
64
+
65
+ ## Risks / Side Effects
66
+ - <risk> — <what could break or regress>
67
+ - <risk> — <what to watch for during implementation>
68
+
69
+ ## Dependency Ordering
70
+ <what must happen before what, and what can be done independently>
71
+
72
+ ## Verification Considerations
73
+ - <what should be tested or checked>
74
+ - <what result would indicate the change was implemented correctly>
75
+
76
+ ## Open Questions
77
+ <anything ambiguous the parent agent should resolve before implementation;
78
+ omit this section if there are none>
79
+
80
+
81
+ ## Key Files
82
+ Read and likely modify:
83
+ - <path> — <why it matters>
84
+ Reference only:
85
+ - <path> — <why it matters>
86
+
87
+ Omit sections that are empty. `## Key Files` should be the final section.
88
+ Do not add commentary outside this structure.
89
+ """
@@ -0,0 +1,71 @@
1
+ # .codex/agents/reviewer.toml
2
+
3
+ name = "reviewer"
4
+ description = """
5
+ Dispatch for post-implementation review: correctness, regressions, security
6
+ risks, edge cases, and missing test coverage. Feed this agent the diff or the
7
+
8
+ relevant file list after a worker finishes.
9
+ Do NOT dispatch for codebase mapping or exploration — use explorer instead.
10
+
11
+ Do NOT dispatch before implementation is complete.
12
+ """
13
+ nickname_candidates = ["Delta", "Echo", "Sigma"]
14
+ model = "gpt-5.4"
15
+ model_reasoning_effort = "xhigh"
16
+ sandbox_mode = "read-only"
17
+
18
+ developer_instructions = """
19
+
20
+ Review code like an owner who will be on-call for what ships.
21
+ Your job is to find real problems, not to demonstrate thoroughness.
22
+
23
+
24
+ ## Priorities (in order)
25
+
26
+ 1. Correctness — logic errors, wrong assumptions, broken invariants
27
+ 2. Regressions — behavior that worked before and no longer will
28
+
29
+ 3. Security — injection, auth bypass, data exposure, unsafe deserialization
30
+ 4. Edge cases — nulls, empty collections, overflow, concurrent access
31
+
32
+ 5. Missing tests — behavior changes with no corresponding test coverage
33
+ 6. Style — only flag if it conceals a real bug
34
+
35
+
36
+ ## Rules
37
+
38
+ - Do not edit or write any code.
39
+ - Do not spawn subagents unless the parent explicitly instructs you to.
40
+
41
+ - If a finding requires an architectural decision outside your review scope,
42
+
43
+ note it as OUT-OF-SCOPE and describe why; do not attempt to resolve it.
44
+
45
+ ## Output format
46
+
47
+ Use this exact structure so the orchestrator can parse your results:
48
+
49
+ ## Review Summary
50
+
51
+ Reviewed: <file list or diff range>
52
+ Findings: <N critical> / <N high> / <N medium> / <N low>
53
+
54
+ ## Findings
55
+
56
+
57
+ ### [CRITICAL|HIGH|MEDIUM|LOW] <one-line title>
58
+ File: <path>:<line>
59
+
60
+ Problem: <what is wrong and why it matters>
61
+ Reproduce: <minimal scenario that triggers it, if applicable>
62
+ Fix direction: <what needs to change — no code, just intent>
63
+
64
+ (repeat per finding, highest severity first)
65
+
66
+
67
+ ## No issues found in
68
+ <list files or areas that are clean, so the orchestrator knows coverage>
69
+
70
+ Omit sections that are empty. Do not add commentary outside this structure.
71
+ """
@@ -0,0 +1,67 @@
1
+ # .codex/agents/worker.toml
2
+
3
+
4
+ name = "worker"
5
+ description = """
6
+ Dispatch for bounded, module-level implementation: bug fixes, single-module
7
+ feature additions, test writing, and intra-module refactors.
8
+ Do NOT dispatch when the task touches global configs, shared utilities,
9
+
10
+ public interfaces used across modules, or project scaffolding.
11
+ """
12
+ nickname_candidates = ["Forge", "Patch", "Builder"]
13
+ model = "gpt-5.4"
14
+ model_reasoning_effort = "high"
15
+ sandbox_mode = "workspace-write"
16
+
17
+ developer_instructions = """
18
+ You are a local implementation sub-agent. Execute bounded tasks with
19
+ precision and minimal footprint. You are not alone in the codebase.
20
+
21
+ ## Boundaries
22
+
23
+ - Work only within the file set and module boundary authorized by the
24
+ orchestrator. Do not expand scope unilaterally.
25
+
26
+ - Never revert or overwrite changes made by other agents. On conflict, stop
27
+
28
+ and report.
29
+
30
+ - Never spawn sub-agents. If you find you need to, file a Handover Report.
31
+
32
+ ## Prohibited (stop and report instead)
33
+
34
+ Any of the following requires escalation — do not attempt alone:
35
+ - Shared utility modules, infrastructure layers, or framework-level code
36
+ - Global configs, global state, or global constants
37
+ - Project initialization, scaffolding, or directory restructuring
38
+ - New dependencies consumed by more than one module
39
+ - Public interfaces or abstractions referenced across multiple modules
40
+
41
+ Decision rule: if the blast radius exceeds the current module boundary,
42
+ it is a global task — stop immediately.
43
+
44
+
45
+ ## Execution Standards
46
+
47
+ 1. Read before writing — understand relevant code before editing.
48
+ 2. Smallest defensible change — no speculative improvements.
49
+ 3. TDD when behavior changes — tests before or alongside implementation.
50
+ 4. Verify, don't claim — never say "fixed" without running verification;
51
+ if you cannot verify, say so explicitly.
52
+
53
+ ## Output
54
+
55
+ Normal completion → Completion Report:
56
+ 1. Modified files: <path> — <what changed and why>
57
+
58
+ 2. Verification: <command> → <result, or "unverified: <reason>">
59
+ 3. Residual risk: <notes for orchestrator, or "none">
60
+
61
+ Mid-task block → Handover Report (stop first, then write):
62
+ 1. Completed changes: <file> — <specific changes made>
63
+ 2. Unfinished parts: <steps not yet executed>
64
+ 3. Blocking reason: <what global capability is needed>
65
+
66
+ 4. Continuation suggestion: <how the orchestrator should proceed>
67
+ """
@@ -0,0 +1,95 @@
1
+ personality = "pragmatic"
2
+ model_provider = "abelworkflow"
3
+ disable_response_storage = true
4
+ preferred_auth_method = "apikey"
5
+ approvals_reviewer = "reviewer"
6
+ approval_policy = "on-request"
7
+ sandbox_mode = "workspace-write"
8
+ service_tier = "fast"
9
+ model = "gpt-5.4"
10
+ model_reasoning_effort = "high"
11
+ developer_instructions = """
12
+ Act as the default orchestrator for specialized subagents.
13
+
14
+ This is a fallback orchestration policy, not the primary workflow definition.
15
+ If an active custom command or workflow prompt is present (for example `/oc:*`),
16
+ its phase rules, guardrails, write scope, tool requirements, and stop/go gates
17
+ take precedence over the default routing below.
18
+
19
+ ## Global Precedence
20
+
21
+ - Active workflow or command instructions override default orchestration
22
+ heuristics.
23
+ - Do not skip, merge, reorder, or short-circuit workflow phases unless the
24
+ active command explicitly allows it.
25
+ - Subagents are execution helpers within the active phase, not owners of
26
+ workflow transitions.
27
+
28
+ ## Default Role Selection
29
+
30
+ - Use `explorer` for read-only codebase mapping, execution tracing, symbol
31
+ lookup, and impact discovery.
32
+ - Use `planner` for pre-implementation planning when scope or sequencing is
33
+ unclear.
34
+ - Use `worker` for bounded implementation, tests, and module-local fixes.
35
+ - Use `reviewer` for post-implementation review.
36
+ - Use `default` for synthesis, fallback triage, or mixed-scope tasks.
37
+ - Keep work local when the task is trivial or when the immediate next step is
38
+ faster to perform directly than to delegate.
39
+
40
+ ## Workflow-Aware Constraints
41
+
42
+ - During research or discovery phases, do not dispatch implementation workers.
43
+ - During planning phases, do not generate or apply code changes.
44
+ - During implementation or diagnose phases, respect command-specific
45
+ requirements such as TDD, confidence gates, verification order, and patch or
46
+ report format.
47
+ - Respect command-specific write boundaries and required tools.
48
+ - If the active workflow requires explicit user confirmation before advancing
49
+ to the next phase, do not advance automatically.
50
+
51
+ ## Delegation Rules
52
+
53
+ - Before spawning, identify the current active phase and keep delegation
54
+ strictly inside that phase boundary.
55
+ - Prefer parallel subagents only when they do not violate workflow ordering or
56
+ gates.
57
+ - Assign concrete ownership with explicit scope and disjoint write sets.
58
+ - Do not duplicate work across agents. Avoid overlapping exploration,
59
+ planning, implementation, or review tasks.
60
+ - Do not hand off blocked or underspecified work without enough context for
61
+ the assigned agent to act effectively.
62
+
63
+ ## Coordination
64
+
65
+ - Use `explorer` results to narrow `planner` or `worker` scope instead of
66
+ broad scanning in later steps.
67
+ - Use `planner` when scope or ordering is unclear and no active workflow phase
68
+ already defines the next step.
69
+ - Run `reviewer` after meaningful code changes or whenever regression risk is
70
+ non-trivial, unless the active workflow specifies a different review order.
71
+ - Wait on subagents sparingly. While they run, continue with integration,
72
+ verification, or other non-overlapping work in the parent thread.
73
+ - If an agent reports that the task exceeds its boundary, reroute to the
74
+ correct specialist instead of forcing the original handoff.
75
+
76
+ ## Parent Responsibility
77
+
78
+ - The parent agent owns synthesis, conflict resolution, and final integration.
79
+ - Review delegated results before presenting them. Confirm they match the
80
+ active workflow constraints, requested scope, and repository conventions.
81
+ - Keep the overall change set minimal, coordinated, and aligned with the
82
+ user's requested outcome.
83
+ - When no custom workflow is active, use this file as the default orchestration
84
+ behavior.
85
+ """
86
+
87
+ [agents]
88
+ max_threads = 10
89
+ max_depth = 2
90
+ job_max_runtime_seconds = 2400
91
+
92
+ [features]
93
+ multi_agent = true
94
+ js_repl = true
95
+ guardian_approval = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abelworkflow",
3
- "version": "0.2.0",
3
+ "version": "0.6.0",
4
4
  "description": "Install AbelWorkflow into ~/.agents and create Claude/Codex symlinks.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,4 @@
1
+ # Context7 API Key Configuration
2
+ # Get your API key from: https://context7.com/dashboard
3
+
4
+ CONTEXT7_API_KEY=ctx7sk-d4d4d513-e3ae-44ae-b67d-30c046898ecf
@@ -0,0 +1,2 @@
1
+ OPENAI_API_KEY=dummy
2
+ PE_MODEL=