abelworkflow 1.2.1 → 1.2.3
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/README.md +13 -3
- package/lib/config/toml.mjs +286 -2
- package/lib/installer/state.mjs +6 -0
- package/lib/paths.mjs +1 -0
- package/lib/providers/claude.mjs +69 -4
- package/lib/providers/codex.mjs +81 -3
- package/lib/providers/pi.mjs +13 -8
- package/lib/templates/codex/agents/explorer.toml +1 -1
- package/lib/templates/codex/agents/worker.toml +1 -1
- package/lib/templates/codex/config-base.toml +77 -0
- package/lib/templates/workflow/commands/abel-design.md +161 -23
- package/lib/templates/workflow/commands/abel-diagnose.md +56 -30
- package/lib/templates/workflow/commands/abel-implement.md +150 -30
- package/lib/templates/workflow/commands/abel-init.md +1 -1
- package/package.json +1 -3
- package/extensions/k2think-compat.ts +0 -40
- package/skills/prompt-enhancer/SKILL.md +0 -24
- package/skills/prompt-enhancer/TEMPLATE.md +0 -87
package/README.md
CHANGED
|
@@ -68,14 +68,24 @@ python -m pip install -r skills/grok-search/requirements.txt
|
|
|
68
68
|
|
|
69
69
|
## 配置行为
|
|
70
70
|
|
|
71
|
-
- Claude
|
|
72
|
-
-
|
|
71
|
+
- Claude API 配置会合并个人默认模板(`$schema`、`attribution`、`hooks`、`alwaysThinkingEnabled`、`language` 与全局权限白名单),删除已弃用的 `includeCoAuthoredBy`;白名单使用当前任务工具 `TaskCreate`、`TaskGet`、`TaskUpdate`、`TaskList`,缺失项补入默认模板但保留已有权限数组。
|
|
72
|
+
- Claude API 使用 `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` 统一关闭非必要流量并保留 `API_TIMEOUT_MS`,清理重叠的 `DISABLE_TELEMETRY` 与 `DISABLE_ERROR_REPORTING`。
|
|
73
|
+
- Claude API 管理 `ANTHROPIC_BASE_URL`、`ANTHROPIC_API_KEY`,并将所选模型同步到 `ANTHROPIC_MODEL`、Opus/Sonnet/Haiku 默认模型与 `CLAUDE_CODE_SUBAGENT_MODEL`;冲突的 `ANTHROPIC_AUTH_TOKEN` 会被删除,其他用户配置保持不变。
|
|
74
|
+
- Codex 配置以 Codex 最新稳定版为基线,不保留旧版本兼容配置;配置会合并个人默认模板,使用 `approval_policy = "never"` 与 `sandbox_mode = "danger-full-access"` 赋予 Agent 完全权限,同时管理 AbelWorkflow Provider 路由、`OPENAI_API_KEY` 与 `auth_mode`,清理冲突的 `preferred_auth_method`、`temp_env_key` 及目标 Provider 旧路由字段,保留其他认证字段。
|
|
73
75
|
- Pi 配置更新 `abelworkflow` Provider、对应凭据及全局 `defaultProvider`、`defaultModel`,保留其他 Provider 与无关设置。
|
|
74
|
-
-
|
|
76
|
+
- 所有 Provider 在配置结构或路径无法安全合并时直接失败。
|
|
75
77
|
- Skill 密钥写入 `~/.agents/skills/<skill>/.env`,属于用户私密配置。
|
|
76
78
|
- `.skill-lock.json` 完全属于用户:安装器不读取、不修改、不备份、不删除,也不会将其放入发布包。
|
|
77
79
|
- 自定义 CA 应通过标准证书环境变量显式配置,不关闭 TLS 校验。
|
|
78
80
|
|
|
81
|
+
### Codex WebSocket 中转
|
|
82
|
+
|
|
83
|
+
AbelWorkflow 为目标 Provider 设置 `wire_api = "responses"` 与 `supports_websockets = true`,使 Codex 最新稳定版优先使用 Responses API WebSocket。旧版 `[features]` 配置 `responses_websockets` 与 `responses_websockets_v2` 会被清理,不作为兼容开关保留。
|
|
84
|
+
|
|
85
|
+
Codex 根据 Provider Base URL 推导 WebSocket 地址。例如,`https://relay.example/v1` 对应 `wss://relay.example/v1/responses`;使用 HTTP Base URL 时则对应 `ws://`。中转必须在该路径支持 WebSocket Upgrade,并实现 Codex 使用的 Responses API WebSocket 协议、认证头和事件格式;只支持 HTTP/SSE Responses API 不满足此要求。
|
|
86
|
+
|
|
87
|
+
`supports_websockets = true` 是 Provider 能力声明,AbelWorkflow 不会在写入配置时探测中转能力。如果握手或协议不兼容,请修复中转并查看 Codex 日志;是否回退 HTTP 由当前 Codex 运行时决定,AbelWorkflow 只保证默认优先尝试 WebSocket,不保证 WebSocket-only。
|
|
88
|
+
|
|
79
89
|
## 部署映射
|
|
80
90
|
|
|
81
91
|
| 托管内容 | Claude Code | Codex | Pi |
|
package/lib/config/toml.mjs
CHANGED
|
@@ -237,10 +237,206 @@ function findAssignment(assignments, field, section) {
|
|
|
237
237
|
return assignments.find((entry) => entry.section === section && entry.path.length === 1 && entry.path[0] === field);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
+
function assignmentPath(entry) {
|
|
241
|
+
return entry.section ? [...entry.section.path, ...entry.path] : entry.path;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function pathStartsWith(path, prefix) {
|
|
245
|
+
return prefix.length <= path.length && prefix.every((part, index) => path[index] === part);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function skipTomlTrivia(text, index) {
|
|
249
|
+
while (index < text.length) {
|
|
250
|
+
if (/\s/u.test(text[index])) {
|
|
251
|
+
index += 1;
|
|
252
|
+
} else if (text[index] === "#") {
|
|
253
|
+
const newline = text.indexOf("\n", index + 1);
|
|
254
|
+
index = newline === -1 ? text.length : newline + 1;
|
|
255
|
+
} else {
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return index;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function scanInlineTomlValue(text, index) {
|
|
263
|
+
const state = { arrayDepth: 0, inlineDepth: 0, mode: "normal" };
|
|
264
|
+
for (; index < text.length;) {
|
|
265
|
+
if (state.mode === "multiline-basic") {
|
|
266
|
+
if (text[index] === "\\") index += 2;
|
|
267
|
+
else if (text.startsWith('"""', index)) index = closeMultilineString(text, state, index, '"');
|
|
268
|
+
else index += 1;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (state.mode === "multiline-literal") {
|
|
272
|
+
if (text.startsWith("'''", index)) index = closeMultilineString(text, state, index, "'");
|
|
273
|
+
else index += 1;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (state.mode === "basic") {
|
|
277
|
+
if (text[index] === "\\") index += 2;
|
|
278
|
+
else if (text[index] === '"') {
|
|
279
|
+
state.mode = "normal";
|
|
280
|
+
index += 1;
|
|
281
|
+
} else index += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (state.mode === "literal") {
|
|
285
|
+
if (text[index] === "'") state.mode = "normal";
|
|
286
|
+
index += 1;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (text[index] === "#") {
|
|
291
|
+
const newline = text.indexOf("\n", index + 1);
|
|
292
|
+
index = newline === -1 ? text.length : newline + 1;
|
|
293
|
+
} else if (text.startsWith('"""', index)) {
|
|
294
|
+
state.mode = "multiline-basic";
|
|
295
|
+
index += 3;
|
|
296
|
+
} else if (text.startsWith("'''", index)) {
|
|
297
|
+
state.mode = "multiline-literal";
|
|
298
|
+
index += 3;
|
|
299
|
+
} else if (text[index] === '"') {
|
|
300
|
+
state.mode = "basic";
|
|
301
|
+
index += 1;
|
|
302
|
+
} else if (text[index] === "'") {
|
|
303
|
+
state.mode = "literal";
|
|
304
|
+
index += 1;
|
|
305
|
+
} else if (text[index] === "[") {
|
|
306
|
+
state.arrayDepth += 1;
|
|
307
|
+
index += 1;
|
|
308
|
+
} else if (text[index] === "]" && state.arrayDepth) {
|
|
309
|
+
state.arrayDepth -= 1;
|
|
310
|
+
index += 1;
|
|
311
|
+
} else if (text[index] === "{") {
|
|
312
|
+
state.inlineDepth += 1;
|
|
313
|
+
index += 1;
|
|
314
|
+
} else if (text[index] === "}" && state.inlineDepth) {
|
|
315
|
+
state.inlineDepth -= 1;
|
|
316
|
+
index += 1;
|
|
317
|
+
} else if (!state.arrayDepth && !state.inlineDepth && (text[index] === "," || text[index] === "}")) {
|
|
318
|
+
break;
|
|
319
|
+
} else {
|
|
320
|
+
index += 1;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return index;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function parseInlineTomlTable(text, open, path, tables = []) {
|
|
327
|
+
if (text[open] !== "{") return null;
|
|
328
|
+
const table = { close: -1, entries: [], open, path };
|
|
329
|
+
tables.push(table);
|
|
330
|
+
let index = skipTomlTrivia(text, open + 1);
|
|
331
|
+
|
|
332
|
+
while (index < text.length && text[index] !== "}") {
|
|
333
|
+
const start = index;
|
|
334
|
+
const key = parseKeyPath(text, index);
|
|
335
|
+
if (!key) return null;
|
|
336
|
+
index = skipWhitespace(text, key.index);
|
|
337
|
+
if (text[index] !== "=") return null;
|
|
338
|
+
const valueStart = skipTomlTrivia(text, index + 1);
|
|
339
|
+
const entryPath = [...path, ...key.path];
|
|
340
|
+
const nested = text[valueStart] === "{"
|
|
341
|
+
? parseInlineTomlTable(text, valueStart, entryPath, tables)
|
|
342
|
+
: null;
|
|
343
|
+
index = nested ? nested.close + 1 : scanInlineTomlValue(text, valueStart);
|
|
344
|
+
let valueEnd = index;
|
|
345
|
+
while (valueEnd > valueStart && /[ \t\r\n]/u.test(text[valueEnd - 1])) valueEnd -= 1;
|
|
346
|
+
index = skipTomlTrivia(text, index);
|
|
347
|
+
const comma = text[index] === "," ? index : -1;
|
|
348
|
+
table.entries.push({ comma, path: entryPath, start, valueEnd, valueStart });
|
|
349
|
+
if (comma === -1) break;
|
|
350
|
+
index = skipTomlTrivia(text, comma + 1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (text[index] !== "}") return null;
|
|
354
|
+
table.close = index;
|
|
355
|
+
return { close: index, table, tables };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function findInlineTomlTable(document, content, targetPath) {
|
|
359
|
+
const candidates = document.assignments
|
|
360
|
+
.filter((entry) => pathStartsWith(targetPath, assignmentPath(entry)))
|
|
361
|
+
.filter((entry) => content[entry.valueStart] === "{")
|
|
362
|
+
.sort((left, right) => assignmentPath(right).length - assignmentPath(left).length);
|
|
363
|
+
|
|
364
|
+
for (const entry of candidates) {
|
|
365
|
+
const syntax = parseInlineTomlTable(content, entry.valueStart, assignmentPath(entry));
|
|
366
|
+
const table = syntax?.tables
|
|
367
|
+
.filter((candidate) => pathStartsWith(targetPath, candidate.path))
|
|
368
|
+
.sort((left, right) => right.path.length - left.path.length)[0];
|
|
369
|
+
if (table) return { entry, syntax, table };
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function updateInlineTomlTable(content, inline, targetPath, values) {
|
|
375
|
+
const remaining = new Map(Object.entries(values));
|
|
376
|
+
const edits = [];
|
|
377
|
+
for (const entry of inline.table.entries) {
|
|
378
|
+
if (!pathStartsWith(entry.path, targetPath) || entry.path.length !== targetPath.length + 1) continue;
|
|
379
|
+
const field = entry.path.at(-1);
|
|
380
|
+
if (!remaining.has(field)) continue;
|
|
381
|
+
edits.push({ start: entry.valueStart, end: entry.valueEnd, text: formatValue(remaining.get(field)) });
|
|
382
|
+
remaining.delete(field);
|
|
383
|
+
}
|
|
384
|
+
if (remaining.size) {
|
|
385
|
+
let insertAt = inline.table.close;
|
|
386
|
+
while (insertAt > inline.table.open + 1 && /\s/u.test(content[insertAt - 1])) insertAt -= 1;
|
|
387
|
+
const trailingComma = content[insertAt - 1] === ",";
|
|
388
|
+
const prefix = inline.table.entries.length ? trailingComma ? " " : ", " : "";
|
|
389
|
+
const pathPrefix = targetPath.slice(inline.table.path.length);
|
|
390
|
+
const fields = [...remaining].map(([field, value]) => (
|
|
391
|
+
`${formatDottedTomlPath([...pathPrefix, field])} = ${formatValue(value)}`
|
|
392
|
+
));
|
|
393
|
+
edits.push({ start: insertAt, end: insertAt, text: `${prefix}${fields.join(", ")}` });
|
|
394
|
+
}
|
|
395
|
+
return applyEdits(content, edits);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function removeInlineTomlTableField(content, inline, targetPath) {
|
|
399
|
+
const entryIndex = inline.table.entries.findIndex((entry) => samePath(entry.path, targetPath));
|
|
400
|
+
if (entryIndex === -1) return content;
|
|
401
|
+
const entry = inline.table.entries[entryIndex];
|
|
402
|
+
const previous = inline.table.entries[entryIndex - 1];
|
|
403
|
+
const next = inline.table.entries[entryIndex + 1];
|
|
404
|
+
if (next) return `${content.slice(0, entry.start)}${content.slice(next.start)}`;
|
|
405
|
+
const start = previous?.comma === undefined || previous.comma === -1 ? entry.start : previous.comma;
|
|
406
|
+
return `${content.slice(0, start)}${content.slice(inline.table.close)}`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function formatDottedTomlPath(path) {
|
|
410
|
+
return path.map(formatTomlKeySegment).join(".");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function insertTomlAssignments(content, section, pathPrefix, values) {
|
|
414
|
+
const lineEnding = detectLineEnding(content);
|
|
415
|
+
const lines = Object.entries(values).map(([field, value]) => (
|
|
416
|
+
`${formatDottedTomlPath([...pathPrefix, field])} = ${formatValue(value)}`
|
|
417
|
+
));
|
|
418
|
+
if (!lines.length) return content;
|
|
419
|
+
|
|
420
|
+
if (!section) {
|
|
421
|
+
const document = parseDocument(content);
|
|
422
|
+
const top = content.slice(0, document.topLevelEnd).trimEnd();
|
|
423
|
+
const rest = content.slice(document.topLevelEnd);
|
|
424
|
+
const nextTop = top ? `${top}${lineEnding}${lines.join(lineEnding)}` : lines.join(lineEnding);
|
|
425
|
+
return `${nextTop}${rest ? `${lineEnding}${lineEnding}${rest}` : lineEnding}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const leading = section.end && content[section.end - 1] !== "\n" ? lineEnding : "";
|
|
429
|
+
return `${content.slice(0, section.end)}${leading}${lines.join(lineEnding)}${lineEnding}${content.slice(section.end)}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
240
432
|
function formatValue(value) {
|
|
241
433
|
if (typeof value === "string") return JSON.stringify(value);
|
|
242
434
|
if (typeof value === "boolean") return value ? "true" : "false";
|
|
243
|
-
|
|
435
|
+
if (Number.isSafeInteger(value)) return String(value);
|
|
436
|
+
if (typeof value === "bigint"
|
|
437
|
+
&& value >= BigInt(Number.MIN_SAFE_INTEGER)
|
|
438
|
+
&& value <= BigInt(Number.MAX_SAFE_INTEGER)) return String(value);
|
|
439
|
+
throw new TypeError("Managed TOML values must be strings, booleans, or safe integers");
|
|
244
440
|
}
|
|
245
441
|
|
|
246
442
|
function formatTomlKeySegment(value) {
|
|
@@ -248,6 +444,39 @@ function formatTomlKeySegment(value) {
|
|
|
248
444
|
return /^[A-Za-z0-9_-]+$/u.test(segment) ? segment : JSON.stringify(segment);
|
|
249
445
|
}
|
|
250
446
|
|
|
447
|
+
function extractTopLevelTomlEntries(content) {
|
|
448
|
+
const document = parseDocument(content);
|
|
449
|
+
return document.assignments
|
|
450
|
+
.filter(({ path, section }) => section === null && path.length === 1)
|
|
451
|
+
.map((entry) => ({
|
|
452
|
+
field: entry.path[0],
|
|
453
|
+
raw: content.slice(entry.start, entry.end)
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function mergeMissingTopLevelTomlEntries(content, entries) {
|
|
458
|
+
if (!entries.length) return content;
|
|
459
|
+
const lineEnding = detectLineEnding(content);
|
|
460
|
+
const document = parseDocument(content);
|
|
461
|
+
const top = content.slice(0, document.topLevelEnd);
|
|
462
|
+
const rest = content.slice(document.topLevelEnd);
|
|
463
|
+
const existingFields = new Set(
|
|
464
|
+
document.assignments
|
|
465
|
+
.filter(({ path, section }) => section === null && path.length === 1)
|
|
466
|
+
.map(({ path }) => path[0])
|
|
467
|
+
);
|
|
468
|
+
const missing = entries.filter(({ field }) => !existingFields.has(field));
|
|
469
|
+
if (!missing.length) return content;
|
|
470
|
+
|
|
471
|
+
let nextTop = top.trimEnd();
|
|
472
|
+
for (const entry of missing) {
|
|
473
|
+
nextTop = nextTop ? `${nextTop}${lineEnding}${entry.raw}` : entry.raw;
|
|
474
|
+
}
|
|
475
|
+
nextTop = nextTop.trimEnd();
|
|
476
|
+
if (rest && nextTop) nextTop += `${lineEnding}${lineEnding}`;
|
|
477
|
+
return `${nextTop}${rest}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
251
480
|
function updateTopLevelTomlField(content, field, value) {
|
|
252
481
|
if (value === null) return removeTopLevelTomlField(content, field);
|
|
253
482
|
const document = parseDocument(content);
|
|
@@ -273,9 +502,20 @@ function removeTopLevelTomlField(content, field) {
|
|
|
273
502
|
|
|
274
503
|
function removeTomlSectionField(content, sectionName, field) {
|
|
275
504
|
const document = parseDocument(content);
|
|
505
|
+
const sectionPath = requestedPath(sectionName);
|
|
276
506
|
const section = findSection(document, sectionName);
|
|
277
507
|
const entry = section && findAssignment(document.assignments, field, section);
|
|
278
|
-
|
|
508
|
+
if (entry) return `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}`;
|
|
509
|
+
if (!sectionPath) return content;
|
|
510
|
+
|
|
511
|
+
const fieldPath = [...sectionPath, field];
|
|
512
|
+
const dottedEntry = document.assignments.find((candidate) => samePath(assignmentPath(candidate), fieldPath));
|
|
513
|
+
if (dottedEntry) {
|
|
514
|
+
return `${content.slice(0, dottedEntry.start)}${content.slice(dottedEntry.lineEnd)}`;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const inline = findInlineTomlTable(document, content, sectionPath);
|
|
518
|
+
return inline ? removeInlineTomlTableField(content, inline, fieldPath) : content;
|
|
279
519
|
}
|
|
280
520
|
|
|
281
521
|
function applyEdits(content, edits) {
|
|
@@ -289,8 +529,50 @@ function applyEdits(content, edits) {
|
|
|
289
529
|
function updateTomlSectionFields(content, sectionName, values) {
|
|
290
530
|
const lineEnding = detectLineEnding(content);
|
|
291
531
|
const document = parseDocument(content);
|
|
532
|
+
const sectionPath = requestedPath(sectionName);
|
|
292
533
|
const section = findSection(document, sectionName);
|
|
293
534
|
const entries = Object.entries(values).filter(([, value]) => value !== undefined && value !== null && value !== "");
|
|
535
|
+
if (!section && sectionPath) {
|
|
536
|
+
const inline = findInlineTomlTable(document, content, sectionPath);
|
|
537
|
+
if (inline) return updateInlineTomlTable(content, inline, sectionPath, Object.fromEntries(entries));
|
|
538
|
+
|
|
539
|
+
const descendantEntries = document.assignments.filter((entry) => (
|
|
540
|
+
assignmentPath(entry).length > sectionPath.length
|
|
541
|
+
&& pathStartsWith(assignmentPath(entry), sectionPath)
|
|
542
|
+
));
|
|
543
|
+
if (descendantEntries.length) {
|
|
544
|
+
const directEntries = descendantEntries.filter((entry) => (
|
|
545
|
+
assignmentPath(entry).length === sectionPath.length + 1
|
|
546
|
+
));
|
|
547
|
+
const remaining = new Map(entries);
|
|
548
|
+
const edits = [];
|
|
549
|
+
for (const entry of directEntries) {
|
|
550
|
+
const field = assignmentPath(entry).at(-1);
|
|
551
|
+
if (!remaining.has(field)) continue;
|
|
552
|
+
edits.push({
|
|
553
|
+
start: entry.valueStart,
|
|
554
|
+
end: entry.valueEnd,
|
|
555
|
+
text: formatValue(remaining.get(field))
|
|
556
|
+
});
|
|
557
|
+
remaining.delete(field);
|
|
558
|
+
}
|
|
559
|
+
let nextContent = applyEdits(content, edits);
|
|
560
|
+
if (remaining.size) {
|
|
561
|
+
const candidate = descendantEntries[0].section;
|
|
562
|
+
const container = candidate
|
|
563
|
+
&& candidate.path.length < sectionPath.length
|
|
564
|
+
&& pathStartsWith(sectionPath, candidate.path)
|
|
565
|
+
? candidate
|
|
566
|
+
: null;
|
|
567
|
+
const prefix = container ? sectionPath.slice(container.path.length) : sectionPath;
|
|
568
|
+
const nextDocument = parseDocument(nextContent);
|
|
569
|
+
const nextContainer = container && findSection(nextDocument, formatDottedTomlPath(container.path));
|
|
570
|
+
nextContent = insertTomlAssignments(nextContent, nextContainer, prefix, Object.fromEntries(remaining));
|
|
571
|
+
}
|
|
572
|
+
return nextContent;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
294
576
|
if (!section) {
|
|
295
577
|
const lines = [`[${sectionName}]`, ...entries.map(([field, value]) => `${field} = ${formatValue(value)}`)];
|
|
296
578
|
const block = lines.join(lineEnding);
|
|
@@ -324,7 +606,9 @@ function updateTomlSectionFields(content, sectionName, values) {
|
|
|
324
606
|
|
|
325
607
|
export {
|
|
326
608
|
detectLineEnding,
|
|
609
|
+
extractTopLevelTomlEntries,
|
|
327
610
|
formatTomlKeySegment,
|
|
611
|
+
mergeMissingTopLevelTomlEntries,
|
|
328
612
|
parseToml,
|
|
329
613
|
removeTomlSectionField,
|
|
330
614
|
removeTopLevelTomlField,
|
package/lib/installer/state.mjs
CHANGED
|
@@ -14,6 +14,11 @@ function isMetadataV2(previousMetadata) {
|
|
|
14
14
|
return previousMetadata?.schemaVersion === 2;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function isPublishedLegacyMetadata(previousMetadata) {
|
|
18
|
+
return previousMetadata?.package === "abelworkflow"
|
|
19
|
+
&& (previousMetadata.schemaVersion === undefined || previousMetadata.schemaVersion === 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
function objectField(value) {
|
|
18
23
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
19
24
|
}
|
|
@@ -36,6 +41,7 @@ async function readInstallMetadata(paths) {
|
|
|
36
41
|
throw new TypeError(`Install metadata in ${metadataPathFor(paths)} must contain a JSON object`);
|
|
37
42
|
}
|
|
38
43
|
if (Object.keys(metadata).length > 0 && !isMetadataV2(metadata)) {
|
|
44
|
+
if (isPublishedLegacyMetadata(metadata)) return {};
|
|
39
45
|
throw new Error(`Unsupported install metadata schema in ${metadataPathFor(paths)}`);
|
|
40
46
|
}
|
|
41
47
|
return metadata;
|
package/lib/paths.mjs
CHANGED
|
@@ -27,6 +27,7 @@ function createPaths({
|
|
|
27
27
|
claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
|
|
28
28
|
codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
|
|
29
29
|
codexAuthPath: join(absoluteHomeDir, ".codex", "auth.json"),
|
|
30
|
+
codexTemplateConfigPath: join(codexTemplateRoot, "config-base.toml"),
|
|
30
31
|
codexTemplateAgentsPath: join(codexTemplateRoot, "agents"),
|
|
31
32
|
piAgentDir,
|
|
32
33
|
piAuthPath: join(piAgentDir, "auth.json"),
|
package/lib/providers/claude.mjs
CHANGED
|
@@ -7,13 +7,55 @@ import {
|
|
|
7
7
|
import { maskSecret, pathToLabel } from "../paths.mjs";
|
|
8
8
|
import { normalizeHttpBaseUrl } from "./url.mjs";
|
|
9
9
|
|
|
10
|
+
const claudeModelEnvKeys = [
|
|
11
|
+
"ANTHROPIC_MODEL",
|
|
12
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
13
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
14
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
15
|
+
"CLAUDE_CODE_SUBAGENT_MODEL"
|
|
16
|
+
];
|
|
17
|
+
const defaultClaudeSettings = {
|
|
18
|
+
$schema: "https://json.schemastore.org/claude-code-settings.json",
|
|
19
|
+
attribution: {
|
|
20
|
+
commit: "",
|
|
21
|
+
pr: "",
|
|
22
|
+
sessionUrl: false
|
|
23
|
+
},
|
|
24
|
+
env: {
|
|
25
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
|
|
26
|
+
API_TIMEOUT_MS: "1000000"
|
|
27
|
+
},
|
|
28
|
+
permissions: {
|
|
29
|
+
allow: [
|
|
30
|
+
"Bash",
|
|
31
|
+
"Skill",
|
|
32
|
+
"Read",
|
|
33
|
+
"Agent",
|
|
34
|
+
"Write",
|
|
35
|
+
"Edit",
|
|
36
|
+
"Glob",
|
|
37
|
+
"Grep",
|
|
38
|
+
"WebFetch",
|
|
39
|
+
"WebSearch",
|
|
40
|
+
"NotebookEdit",
|
|
41
|
+
"TaskCreate",
|
|
42
|
+
"TaskGet",
|
|
43
|
+
"TaskUpdate",
|
|
44
|
+
"TaskList"
|
|
45
|
+
],
|
|
46
|
+
deny: []
|
|
47
|
+
},
|
|
48
|
+
hooks: {},
|
|
49
|
+
alwaysThinkingEnabled: true,
|
|
50
|
+
language: "Chinese"
|
|
51
|
+
};
|
|
52
|
+
|
|
10
53
|
function getExistingClaudeApiConfig(settings) {
|
|
11
|
-
assertPlainObject(settings, "Claude settings");
|
|
12
54
|
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
13
55
|
return {
|
|
14
56
|
baseUrl: env.ANTHROPIC_BASE_URL || "https://api.anthropic.com",
|
|
15
57
|
key: env.ANTHROPIC_API_KEY || "",
|
|
16
|
-
model:
|
|
58
|
+
model: claudeModelEnvKeys.map((field) => env[field]).find((value) => typeof value === "string" && value) || ""
|
|
17
59
|
};
|
|
18
60
|
}
|
|
19
61
|
|
|
@@ -24,16 +66,39 @@ function buildClaudeApiSettings(settings, {
|
|
|
24
66
|
}) {
|
|
25
67
|
assertPlainObject(settings, "Claude settings");
|
|
26
68
|
const env = settings.env === undefined ? {} : assertPlainObject(settings.env, "Claude settings env");
|
|
69
|
+
const permissions = settings.permissions === undefined
|
|
70
|
+
? {}
|
|
71
|
+
: assertPlainObject(settings.permissions, "Claude settings permissions");
|
|
72
|
+
const attribution = settings.attribution === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: assertPlainObject(settings.attribution, "Claude settings attribution");
|
|
27
75
|
const nextSettings = {
|
|
76
|
+
...defaultClaudeSettings,
|
|
28
77
|
...settings,
|
|
78
|
+
attribution: {
|
|
79
|
+
...defaultClaudeSettings.attribution,
|
|
80
|
+
...attribution
|
|
81
|
+
},
|
|
29
82
|
env: {
|
|
83
|
+
...defaultClaudeSettings.env,
|
|
30
84
|
...env,
|
|
31
85
|
ANTHROPIC_BASE_URL: normalizeHttpBaseUrl(baseUrl),
|
|
32
|
-
ANTHROPIC_API_KEY: key
|
|
33
|
-
|
|
86
|
+
ANTHROPIC_API_KEY: key
|
|
87
|
+
},
|
|
88
|
+
permissions: {
|
|
89
|
+
...defaultClaudeSettings.permissions,
|
|
90
|
+
...permissions,
|
|
91
|
+
allow: Array.isArray(permissions.allow) ? permissions.allow : [...defaultClaudeSettings.permissions.allow],
|
|
92
|
+
deny: Array.isArray(permissions.deny) ? permissions.deny : [...defaultClaudeSettings.permissions.deny]
|
|
34
93
|
}
|
|
35
94
|
};
|
|
95
|
+
delete nextSettings.includeCoAuthoredBy;
|
|
36
96
|
delete nextSettings.env.ANTHROPIC_AUTH_TOKEN;
|
|
97
|
+
delete nextSettings.env.DISABLE_TELEMETRY;
|
|
98
|
+
delete nextSettings.env.DISABLE_ERROR_REPORTING;
|
|
99
|
+
for (const field of claudeModelEnvKeys) {
|
|
100
|
+
nextSettings.env[field] = String(model).trim();
|
|
101
|
+
}
|
|
37
102
|
return nextSettings;
|
|
38
103
|
}
|
|
39
104
|
|
package/lib/providers/codex.mjs
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
writeText
|
|
11
11
|
} from "../config/store.mjs";
|
|
12
12
|
import {
|
|
13
|
+
extractTopLevelTomlEntries,
|
|
13
14
|
formatTomlKeySegment,
|
|
15
|
+
mergeMissingTopLevelTomlEntries,
|
|
14
16
|
parseToml,
|
|
15
17
|
removeTomlSectionField,
|
|
16
18
|
removeTopLevelTomlField,
|
|
@@ -23,6 +25,21 @@ import { normalizeOpenAiBaseUrl } from "./url.mjs";
|
|
|
23
25
|
|
|
24
26
|
const CODEX_ENV_KEY = "OPENAI_API_KEY";
|
|
25
27
|
const CODEX_PROVIDER_ID = "abelworkflow";
|
|
28
|
+
const publishedCodexDeveloperInstructionHashes = new Set([
|
|
29
|
+
"9a70e45a6f4a201f617b657207f32b61901ecc70071dfb4f9d905cc23a6f35a7",
|
|
30
|
+
"81aa8aaf309ed374dbc2d8ae55ca793ad39a4befc75aba6cac32736da368e2fd"
|
|
31
|
+
]);
|
|
32
|
+
const obsoleteCodexTopLevelFields = [
|
|
33
|
+
"disable_response_storage",
|
|
34
|
+
"network_access",
|
|
35
|
+
"supports_websockets",
|
|
36
|
+
"requires_openai_auth"
|
|
37
|
+
];
|
|
38
|
+
const obsoleteCodexFeatureFields = [
|
|
39
|
+
"js_repl",
|
|
40
|
+
"responses_websockets",
|
|
41
|
+
"responses_websockets_v2"
|
|
42
|
+
];
|
|
26
43
|
|
|
27
44
|
function assertTomlTable(value, name) {
|
|
28
45
|
if (value === undefined) return {};
|
|
@@ -36,6 +53,59 @@ function assertTomlTable(value, name) {
|
|
|
36
53
|
return value;
|
|
37
54
|
}
|
|
38
55
|
|
|
56
|
+
function mergeCodexTemplateDefaults(content, templateContent) {
|
|
57
|
+
const template = parseToml(templateContent);
|
|
58
|
+
const current = parseToml(content);
|
|
59
|
+
const templateEntries = extractTopLevelTomlEntries(templateContent);
|
|
60
|
+
let nextContent = mergeMissingTopLevelTomlEntries(content, templateEntries);
|
|
61
|
+
const currentInstructions = typeof current.developer_instructions === "string"
|
|
62
|
+
? current.developer_instructions
|
|
63
|
+
: "";
|
|
64
|
+
|
|
65
|
+
if (publishedCodexDeveloperInstructionHashes.has(hashBytes(currentInstructions))) {
|
|
66
|
+
const legacyDefaults = {
|
|
67
|
+
approvals_reviewer: "guardian_subagent",
|
|
68
|
+
approval_policy: "on-request",
|
|
69
|
+
sandbox_mode: "workspace-write",
|
|
70
|
+
model: "gpt-5.5"
|
|
71
|
+
};
|
|
72
|
+
const replacementFields = new Set(["developer_instructions"]);
|
|
73
|
+
for (const [field, value] of Object.entries(legacyDefaults)) {
|
|
74
|
+
if (current[field] === value) replacementFields.add(field);
|
|
75
|
+
}
|
|
76
|
+
for (const field of replacementFields) {
|
|
77
|
+
nextContent = removeTopLevelTomlField(nextContent, field);
|
|
78
|
+
}
|
|
79
|
+
nextContent = mergeMissingTopLevelTomlEntries(
|
|
80
|
+
nextContent,
|
|
81
|
+
templateEntries.filter(({ field }) => replacementFields.has(field))
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const field of obsoleteCodexTopLevelFields) {
|
|
86
|
+
nextContent = removeTopLevelTomlField(nextContent, field);
|
|
87
|
+
}
|
|
88
|
+
for (const field of obsoleteCodexFeatureFields) {
|
|
89
|
+
nextContent = removeTomlSectionField(nextContent, "features", field);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const sectionName of ["agents", "features"]) {
|
|
93
|
+
const templateSection = assertTomlTable(template[sectionName], `template ${sectionName}`);
|
|
94
|
+
const currentSection = assertTomlTable(current[sectionName], sectionName);
|
|
95
|
+
const missingValues = Object.fromEntries(
|
|
96
|
+
Object.entries(templateSection).filter(([field]) => !Object.hasOwn(currentSection, field))
|
|
97
|
+
);
|
|
98
|
+
if (Object.keys(missingValues).length) {
|
|
99
|
+
nextContent = updateTomlSectionFields(nextContent, sectionName, missingValues);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return nextContent;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function loadBundledCodexConfigTemplate(paths) {
|
|
106
|
+
return readFile(paths.codexTemplateConfigPath, "utf8");
|
|
107
|
+
}
|
|
108
|
+
|
|
39
109
|
async function readCodexAgentTarget(path) {
|
|
40
110
|
try {
|
|
41
111
|
const targetStat = await lstat(path);
|
|
@@ -189,6 +259,7 @@ async function getExistingCodexApiConfig(paths) {
|
|
|
189
259
|
}
|
|
190
260
|
|
|
191
261
|
async function persistCodexConfiguration(paths, intent, operations = {}) {
|
|
262
|
+
const templateContent = await loadBundledCodexConfigTemplate(paths);
|
|
192
263
|
return withFileTransaction([
|
|
193
264
|
{ path: paths.codexConfigPath, safeRoot: paths.homeDir },
|
|
194
265
|
{ path: paths.codexAuthPath, safeRoot: paths.homeDir, sensitive: true }
|
|
@@ -198,6 +269,7 @@ async function persistCodexConfiguration(paths, intent, operations = {}) {
|
|
|
198
269
|
assertPlainObject(currentAuth, "Codex auth");
|
|
199
270
|
const current = resolveExistingCodexApiConfig(currentContent, currentAuth);
|
|
200
271
|
const content = buildCodexConfigContent(currentContent, {
|
|
272
|
+
templateContent,
|
|
201
273
|
providerName: current.providerName,
|
|
202
274
|
baseUrl: intent.baseUrl
|
|
203
275
|
});
|
|
@@ -270,6 +342,7 @@ async function configureCodexApi(paths, promptApi, ownership = {}, runtime = {})
|
|
|
270
342
|
}
|
|
271
343
|
|
|
272
344
|
function buildCodexConfigContent(currentContent, {
|
|
345
|
+
templateContent = "",
|
|
273
346
|
providerName,
|
|
274
347
|
baseUrl
|
|
275
348
|
}) {
|
|
@@ -280,17 +353,21 @@ function buildCodexConfigContent(currentContent, {
|
|
|
280
353
|
`model_providers.${CODEX_PROVIDER_ID}`
|
|
281
354
|
);
|
|
282
355
|
const providerSection = `model_providers.${formatTomlKeySegment(CODEX_PROVIDER_ID)}`;
|
|
283
|
-
let content =
|
|
356
|
+
let content = currentContent.trim()
|
|
357
|
+
? mergeCodexTemplateDefaults(currentContent, templateContent)
|
|
358
|
+
: templateContent;
|
|
359
|
+
content = updateTopLevelTomlField(content, "model_provider", CODEX_PROVIDER_ID);
|
|
284
360
|
content = removeTopLevelTomlField(content, "preferred_auth_method");
|
|
285
361
|
content = removeTopLevelTomlField(content, "temp_env_key");
|
|
286
|
-
for (const field of ["env_key", "temp_env_key"
|
|
362
|
+
for (const field of ["env_key", "temp_env_key"]) {
|
|
287
363
|
content = removeTomlSectionField(content, providerSection, field);
|
|
288
364
|
}
|
|
289
365
|
content = updateTomlSectionFields(content, providerSection, {
|
|
290
366
|
name: providerName || CODEX_PROVIDER_ID,
|
|
291
367
|
base_url: normalizeOpenAiBaseUrl(baseUrl),
|
|
292
368
|
wire_api: "responses",
|
|
293
|
-
requires_openai_auth: true
|
|
369
|
+
requires_openai_auth: true,
|
|
370
|
+
supports_websockets: true
|
|
294
371
|
});
|
|
295
372
|
parseToml(content);
|
|
296
373
|
return content;
|
|
@@ -315,6 +392,7 @@ export {
|
|
|
315
392
|
configureCodexApi,
|
|
316
393
|
deployBundledCodexAgents,
|
|
317
394
|
mergeCodexAuthData,
|
|
395
|
+
mergeCodexTemplateDefaults,
|
|
318
396
|
persistCodexConfiguration,
|
|
319
397
|
resolveExistingCodexApiConfig
|
|
320
398
|
};
|
package/lib/providers/pi.mjs
CHANGED
|
@@ -84,17 +84,18 @@ function buildPiModelsConfig(modelsConfig = {}, { baseUrl, api, apiKey, modelIds
|
|
|
84
84
|
const provider = {
|
|
85
85
|
baseUrl: normalizeOpenAiBaseUrl(baseUrl),
|
|
86
86
|
api,
|
|
87
|
-
...(responses ? {
|
|
88
|
-
|
|
89
|
-
compat: { supportsDeveloperRole: false }
|
|
90
|
-
} : {}),
|
|
87
|
+
...(responses ? { apiKey } : {}),
|
|
88
|
+
compat: { supportsDeveloperRole: false },
|
|
91
89
|
models: modelIds.map((modelId) => {
|
|
92
90
|
const name = existingModels.get(modelId)?.name;
|
|
93
|
-
const model = {
|
|
91
|
+
const model = {
|
|
92
|
+
id: modelId,
|
|
93
|
+
name: typeof name === "string" && name ? name : modelId,
|
|
94
|
+
reasoning: true,
|
|
95
|
+
input: ["text", "image"]
|
|
96
|
+
};
|
|
94
97
|
return responses ? {
|
|
95
98
|
...model,
|
|
96
|
-
reasoning: true,
|
|
97
|
-
input: ["text", "image"],
|
|
98
99
|
thinkingLevelMap: {
|
|
99
100
|
off: null,
|
|
100
101
|
minimal: "low",
|
|
@@ -106,7 +107,11 @@ function buildPiModelsConfig(modelsConfig = {}, { baseUrl, api, apiKey, modelIds
|
|
|
106
107
|
},
|
|
107
108
|
contextWindow: 262144,
|
|
108
109
|
maxTokens: 131072
|
|
109
|
-
} :
|
|
110
|
+
} : {
|
|
111
|
+
...model,
|
|
112
|
+
contextWindow: 262144,
|
|
113
|
+
maxTokens: 64000
|
|
114
|
+
};
|
|
110
115
|
})
|
|
111
116
|
};
|
|
112
117
|
|
|
@@ -11,7 +11,7 @@ Do NOT dispatch for review after implementation — use reviewer instead.
|
|
|
11
11
|
Do NOT dispatch if the affected files are already known and confirmed.
|
|
12
12
|
"""
|
|
13
13
|
nickname_candidates = ["Atlas", "Trace", "Scout"]
|
|
14
|
-
model = "gpt-5.6-
|
|
14
|
+
model = "gpt-5.6-luna"
|
|
15
15
|
model_reasoning_effort = "high"
|
|
16
16
|
sandbox_mode = "read-only"
|
|
17
17
|
|