abelworkflow 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AbelWorkflow
2
2
 
3
- Codex、OpenCode、Claude Code 的 SkillsCommands 配置仓库。
3
+ Codex、OpenCode、Claude Code、Pi 的 SkillsCommands/Prompts 和扩展配置仓库。
4
4
 
5
5
  ## 目录结构
6
6
 
@@ -17,6 +17,7 @@ Codex、OpenCode、Claude Code 的 Skills 和 Commands 配置仓库。
17
17
  │ └── prompt-enhancer/ # 提示词优化器
18
18
  ├── commands/ # 命令目录
19
19
  │ └── oc/ # 工作流命令
20
+ ├── extensions/ # Pi 扩展
20
21
  ├── AGENTS.md # Agent 全局系统 prompts
21
22
  └── README.md
22
23
  ```
@@ -76,11 +77,12 @@ npx abelworkflow@latest
76
77
  > 说明:
77
78
  > - npm 发布包名必须使用小写,所以实际可执行命令是 `npx abelworkflow`。
78
79
  > - 交互式模式下,默认会打开初始化菜单,支持:
79
- > - 同步 `~/.agents` 并自动重建 `~/.claude` / `~/.codex` 链接
80
+ > - 同步 `~/.agents` 并自动重建 `~/.claude` / `~/.codex` / `~/.pi/agent` 链接
80
81
  > - 交互式填写 `grok-search`、`context7-auto-research`、`prompt-enhancer` 的 `.env`
81
- > - 一键安装或更新 `Claude Code`、`Codex`
82
+ > - 一键安装或更新 `Claude Code`、`Codex`、`Pi`
82
83
  > - 配置 `Claude Code` 的第三方 API 到 `~/.claude/settings.json`
83
84
  > - 配置 `Codex` 的第三方 API 到 `~/.codex/config.toml` 和 `~/.codex/auth.json`
85
+ > - 配置 `Pi` 的自定义 API 到 `~/.pi/agent/models.json` 中的 `gpt` provider,并设置 `~/.pi/agent/settings.json` 默认模型,同时携带 Pi 扩展
84
86
  > - 非交互场景请显式使用 `npx abelworkflow install`,不再保留旧的默认自动同步逻辑。
85
87
 
86
88
  ### 交互式初始化能力
@@ -97,12 +99,14 @@ npx abelworkflow --help
97
99
  其中完整初始化会按需引导你完成:
98
100
 
99
101
  1. 安装 AbelWorkflow 到 `~/.agents`
100
- 2. 自动链接到 `~/.claude/` 和 `~/.codex/`
102
+ 2. 自动链接到 `~/.claude/`、`~/.codex/` 和 `~/.pi/agent/`
101
103
  3. 可选安装 `Claude Code` CLI
102
104
  4. 可选配置 `Claude Code` 第三方 API
103
105
  5. 可选安装 `Codex` CLI
104
106
  6. 可选配置 `Codex` 第三方 API
105
- 7. 可选填写三个技能的环境变量
107
+ 7. 可选安装 `Pi` CLI
108
+ 8. 可选配置 Pi `gpt` provider 自定义 API,并链接 Pi 扩展
109
+ 9. 可选填写三个技能的环境变量
106
110
 
107
111
  ### 技能环境写入位置
108
112
 
@@ -174,11 +178,12 @@ node .\bin\abelworkflow.mjs install
174
178
 
175
179
  ### 映射关系(本仓库 → 配置目录)
176
180
 
177
- | 本仓库 | Claude Code | Codex | 说明 |
178
- |---|---|---|---|
179
- | `AGENTS.md` | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | 全局系统提示词/规则 |
180
- | `skills/<skill>/` | `~/.claude/skills/<skill>/` | `~/.codex/skills/<skill>/` | Skills(每个目录一个技能) |
181
- | `commands/abel-*.md` | `~/.claude/commands/abel-*.md` | `~/.codex/prompts/abel-*.md` | 扁平化部署,避免命名冲突 |
181
+ | 本仓库 | Claude Code | Codex | Pi | 说明 |
182
+ |---|---|---|---|---|
183
+ | `AGENTS.md` | `~/.claude/CLAUDE.md` | `~/.codex/AGENTS.md` | `~/.pi/agent/AGENTS.md` | 全局系统提示词/规则 |
184
+ | `skills/<skill>/` | `~/.claude/skills/<skill>/` | `~/.codex/skills/<skill>/` | `~/.pi/agent/skills/<skill>/` | Skills(每个目录一个技能) |
185
+ | `commands/abel-*.md` | `~/.claude/commands/abel-*.md` | `~/.codex/prompts/abel-*.md` | `~/.pi/agent/prompts/abel-*.md` | 扁平化部署,避免命名冲突 |
186
+ | `extensions/*.ts` | - | - | `~/.pi/agent/extensions/*.ts` | Pi 扩展 |
182
187
 
183
188
  ### 验证(可选)
184
189
 
@@ -0,0 +1,25 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+
3
+ export default function (pi: ExtensionAPI) {
4
+ pi.on("before_provider_request", (event, ctx) => {
5
+ const payload = event.payload as any;
6
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
7
+ if (ctx.model?.provider !== "gpt") return;
8
+ if (!("input" in payload)) return;
9
+
10
+ const input = Array.isArray(payload.input) ? payload.input : [];
11
+ const first = input[0];
12
+ const isPrompt = first?.role === "system" || first?.role === "developer";
13
+ const instructions =
14
+ (typeof payload.instructions === "string" && payload.instructions.trim()) ||
15
+ (isPrompt && typeof first.content === "string" && first.content.trim()) ||
16
+ ctx.getSystemPrompt().trim();
17
+
18
+ const nextPayload = { ...payload };
19
+ delete nextPayload.prompt_cache_key;
20
+ delete nextPayload.prompt_cache_retention;
21
+ delete nextPayload.max_output_tokens;
22
+
23
+ return { ...nextPayload, instructions, input: isPrompt ? input.slice(1) : input };
24
+ });
25
+ }
package/lib/cli/logic.mjs CHANGED
@@ -6,14 +6,38 @@ const interactiveMenuDescriptors = [
6
6
  { value: "grok-search", label: "配置 grok-search", hint: "技能", group: "skill" },
7
7
  { value: "context7", label: "配置 context7-auto-research", hint: "技能", group: "skill" },
8
8
  { value: "prompt-enhancer", label: "配置 prompt-enhancer", hint: "技能", group: "skill" },
9
- { value: "claude-install", label: "安装/更新 Claude Code", hint: "CLI", group: "cli" },
10
- { value: "claude-api", label: "配置 Claude API", hint: "CLI", group: "cli" },
11
- { value: "codex-install", label: "安装/更新 Codex", hint: "CLI", group: "cli" },
12
- { value: "codex-api", label: "配置 Codex API", hint: "CLI", group: "cli" },
9
+ { value: "pi-cli", label: "安装/配置 Pi", hint: "CLI", group: "cli" },
10
+ { value: "codex-cli", label: "安装/配置 Codex", hint: "CLI", group: "cli" },
11
+ { value: "claude-cli", label: "安装/配置 Claude Code", hint: "CLI", group: "cli" },
13
12
  { value: "exit", label: "退出", group: "exit" }
14
13
  ];
15
14
 
16
15
  const interactiveMenuDefaultValue = "full-init";
16
+ const cliToolMenuDescriptorMap = {
17
+ pi: [
18
+ { value: "pi-install", label: "安装/更新 Pi" },
19
+ { value: "pi-api", label: "配置 Pi API" },
20
+ { value: "back", label: "返回上一级" }
21
+ ],
22
+ codex: [
23
+ { value: "codex-install", label: "安装/更新 Codex" },
24
+ { value: "codex-api", label: "配置 Codex API" },
25
+ { value: "back", label: "返回上一级" }
26
+ ],
27
+ claude: [
28
+ { value: "claude-install", label: "安装/更新 Claude Code" },
29
+ { value: "claude-api", label: "配置 Claude Code API" },
30
+ { value: "back", label: "返回上一级" }
31
+ ]
32
+ };
33
+
34
+ function buildCliToolMenuDescriptors(tool) {
35
+ const descriptors = cliToolMenuDescriptorMap[tool];
36
+ if (!descriptors) {
37
+ throw new Error(`Unknown CLI tool: ${tool}`);
38
+ }
39
+ return descriptors.map((descriptor) => ({ ...descriptor }));
40
+ }
17
41
 
18
42
  class CancelledError extends Error {
19
43
  constructor(message = "用户取消") {
@@ -175,6 +199,7 @@ function getRunCommandSpawnOptions(platform = process.env.ABELWORKFLOW_TEST_PLAT
175
199
  export {
176
200
  assertInteractiveMenuSupported,
177
201
  assertNotCancelled,
202
+ buildCliToolMenuDescriptors,
178
203
  CancelledError,
179
204
  confirmOrCancel,
180
205
  getRunCommandSpawnOptions,
package/lib/cli.mjs CHANGED
@@ -9,6 +9,7 @@ import c from "picocolors";
9
9
  import {
10
10
  assertInteractiveMenuSupported,
11
11
  assertNotCancelled,
12
+ buildCliToolMenuDescriptors,
12
13
  CancelledError,
13
14
  confirmOrCancel,
14
15
  getRunCommandSpawnOptions,
@@ -33,6 +34,13 @@ const codexAuthPath = join(home, ".codex", "auth.json");
33
34
  const codexTemplateRoot = join(packageRoot, "lib", "templates", "codex");
34
35
  const codexTemplateConfigPath = join(codexTemplateRoot, "config-base.toml");
35
36
  const codexTemplateAgentsPath = join(codexTemplateRoot, "agents");
37
+ const piAgentDir = join(home, ".pi", "agent");
38
+ const piModelsPath = join(piAgentDir, "models.json");
39
+ const piSettingsPath = join(piAgentDir, "settings.json");
40
+ const piProviderId = "gpt";
41
+ const piDefaultApi = "openai-responses";
42
+ const piDefaultBaseUrl = "https://api.openai.com/v1";
43
+ const piDefaultModel = "gpt-5.5";
36
44
  const installBackupStamp = Date.now();
37
45
  const createdBackupPaths = new Set();
38
46
  const augmentContextEnginePermission = "mcp__augment-context-engine";
@@ -95,6 +103,7 @@ const managedEntries = [
95
103
  { target: "README.md" },
96
104
  { target: "commands", preserveExisting: true },
97
105
  { target: "skills", preserveExisting: true, filter: shouldCopySkillPath },
106
+ { target: "extensions", preserveExisting: true },
98
107
  { target: ".skill-lock.json" },
99
108
  { target: ".gitignore", sourceCandidates: [".gitignore", ".npmignore"] }
100
109
  ];
@@ -390,6 +399,39 @@ async function writeInstallMetadata(agentsDir, metadata) {
390
399
  await writeFile(join(agentsDir, installMetadataName), `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
391
400
  }
392
401
 
402
+ function linkedTargetsFromResults(results) {
403
+ return Object.fromEntries(
404
+ results
405
+ .filter((result) => result.sourcePath)
406
+ .map((result) => [
407
+ result.targetPath,
408
+ {
409
+ sourcePath: result.sourcePath,
410
+ kind: result.kind,
411
+ mode: result.mode
412
+ }
413
+ ])
414
+ );
415
+ }
416
+
417
+ function mergeLinkedTargets(previousLinkedTargets = {}, results = []) {
418
+ const nextLinkedTargets = { ...previousLinkedTargets };
419
+ for (const result of results) {
420
+ if (result.sourcePath) {
421
+ nextLinkedTargets[result.targetPath] = {
422
+ sourcePath: result.sourcePath,
423
+ kind: result.kind,
424
+ mode: result.mode
425
+ };
426
+ continue;
427
+ }
428
+ if (result.status === "removed") {
429
+ delete nextLinkedTargets[result.targetPath];
430
+ }
431
+ }
432
+ return nextLinkedTargets;
433
+ }
434
+
393
435
  async function renderManagedWorkflowFile(path, augmentContextEngine) {
394
436
  if (!(await pathIsFile(path))) {
395
437
  return;
@@ -666,6 +708,18 @@ async function getCommandNames(commandsDir) {
666
708
  return names.filter(Boolean);
667
709
  }
668
710
 
711
+ async function getPiExtensionNames(extensionsDir) {
712
+ if (!(await pathIsDirectory(extensionsDir))) {
713
+ return [];
714
+ }
715
+
716
+ const entries = await readdir(extensionsDir, { withFileTypes: true });
717
+ const names = await Promise.all(
718
+ entries.map(async (entry) => ((await isPiExtensionEntry(extensionsDir, entry)) ? entry.name : null))
719
+ );
720
+ return names.filter(Boolean);
721
+ }
722
+
669
723
  async function pathIsDirectory(path) {
670
724
  try {
671
725
  return (await stat(path)).isDirectory();
@@ -710,6 +764,23 @@ async function isMarkdownFileEntry(root, entry) {
710
764
  return pathIsFile(join(root, entry.name));
711
765
  }
712
766
 
767
+ async function isPiExtensionEntry(root, entry) {
768
+ const entryPath = join(root, entry.name);
769
+ if (entry.isDirectory()) {
770
+ return pathIsFile(join(entryPath, "index.ts"));
771
+ }
772
+ if (entry.isFile()) {
773
+ return entry.name.endsWith(".ts");
774
+ }
775
+ if (!entry.isSymbolicLink()) {
776
+ return false;
777
+ }
778
+ if (entry.name.endsWith(".ts") && await pathIsFile(entryPath)) {
779
+ return true;
780
+ }
781
+ return pathIsFile(join(entryPath, "index.ts"));
782
+ }
783
+
713
784
  async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames, previousLinkedTargets) {
714
785
  if (!(await pathExists(targetDir))) {
715
786
  return [];
@@ -844,6 +915,71 @@ async function linkCodex(agentsDir, previousLinkedTargets) {
844
915
  return results;
845
916
  }
846
917
 
918
+ async function linkPi(agentsDir, previousLinkedTargets) {
919
+ const results = [];
920
+ await mkdir(piAgentDir, { recursive: true });
921
+ await removeIfNotDirectory(join(piAgentDir, "skills"));
922
+ await removeIfNotDirectory(join(piAgentDir, "prompts"));
923
+ await removeIfNotDirectory(join(piAgentDir, "extensions"));
924
+ await mkdir(join(piAgentDir, "skills"), { recursive: true });
925
+ await mkdir(join(piAgentDir, "prompts"), { recursive: true });
926
+ await mkdir(join(piAgentDir, "extensions"), { recursive: true });
927
+
928
+ results.push(
929
+ await ensureManagedLink(
930
+ join(piAgentDir, "AGENTS.md"),
931
+ join(agentsDir, "AGENTS.md"),
932
+ "file",
933
+ previousLinkedTargets
934
+ )
935
+ );
936
+ results.push(...(await linkSkillDirectories(piAgentDir, agentsDir, previousLinkedTargets)));
937
+
938
+ const commandFiles = await getCommandNames(join(agentsDir, "commands"));
939
+ results.push(
940
+ ...(await pruneManagedTargets(
941
+ join(piAgentDir, "prompts"),
942
+ join(agentsDir, "commands"),
943
+ commandFiles,
944
+ previousLinkedTargets
945
+ ))
946
+ );
947
+ for (const fileName of commandFiles) {
948
+ results.push(
949
+ await ensureManagedLink(
950
+ join(piAgentDir, "prompts", fileName),
951
+ join(agentsDir, "commands", fileName),
952
+ "file",
953
+ previousLinkedTargets
954
+ )
955
+ );
956
+ }
957
+
958
+ const extensionNames = await getPiExtensionNames(join(agentsDir, "extensions"));
959
+ results.push(
960
+ ...(await pruneManagedTargets(
961
+ join(piAgentDir, "extensions"),
962
+ join(agentsDir, "extensions"),
963
+ extensionNames,
964
+ previousLinkedTargets
965
+ ))
966
+ );
967
+ for (const entryName of extensionNames) {
968
+ const sourcePath = join(agentsDir, "extensions", entryName);
969
+ const kind = await pathIsDirectory(sourcePath) ? "dir" : "file";
970
+ results.push(
971
+ await ensureManagedLink(
972
+ join(piAgentDir, "extensions", entryName),
973
+ sourcePath,
974
+ kind,
975
+ previousLinkedTargets
976
+ )
977
+ );
978
+ }
979
+
980
+ return results;
981
+ }
982
+
847
983
  async function installManagedWorkflow(options) {
848
984
  let previousMetadata = {};
849
985
  let managedChildren = {};
@@ -870,30 +1006,21 @@ async function installManagedWorkflow(options) {
870
1006
  }
871
1007
 
872
1008
  const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
873
- s.start("正在链接 Claude / Codex...");
1009
+ s.start("正在链接 Claude / Codex / Pi...");
874
1010
  let claudeResults;
875
1011
  let codexResults;
1012
+ let piResults;
876
1013
  try {
877
1014
  claudeResults = await linkClaude(options.agentsDir, previousLinkedTargets);
878
1015
  codexResults = await linkCodex(options.agentsDir, previousLinkedTargets);
1016
+ piResults = await linkPi(options.agentsDir, previousLinkedTargets);
879
1017
  } catch (e) {
880
1018
  s.cancel(c.red(`链接失败: ${e.message}`));
881
1019
  throw e;
882
1020
  }
883
1021
  s.stop("链接完成");
884
1022
 
885
- const linkedTargets = Object.fromEntries(
886
- [...claudeResults, ...codexResults]
887
- .filter((result) => result.sourcePath)
888
- .map((result) => [
889
- result.targetPath,
890
- {
891
- sourcePath: result.sourcePath,
892
- kind: result.kind,
893
- mode: result.mode
894
- }
895
- ])
896
- );
1023
+ const linkedTargets = linkedTargetsFromResults([...claudeResults, ...codexResults, ...piResults]);
897
1024
 
898
1025
  const managedClaudePermissions = await ensureClaudeSettingsForFeature(augmentContextEngine, previousMetadata);
899
1026
 
@@ -909,7 +1036,7 @@ async function installManagedWorkflow(options) {
909
1036
  linkedTargets
910
1037
  });
911
1038
 
912
- const resultLines = [...claudeResults, ...codexResults].map((result) => {
1039
+ const resultLines = [...claudeResults, ...codexResults, ...piResults].map((result) => {
913
1040
  const icon = result.status === "unchanged" ? c.gray("=")
914
1041
  : result.status === "removed" ? c.yellow("−")
915
1042
  : c.green("+");
@@ -943,6 +1070,188 @@ async function writeJsonFileWithBackup(path, data) {
943
1070
  await writeJsonFileSafe(path, data);
944
1071
  }
945
1072
 
1073
+ function stripJsonComments(content) {
1074
+ let output = "";
1075
+ let inString = false;
1076
+ let quote = "";
1077
+ let escaped = false;
1078
+ for (let i = 0; i < content.length; i += 1) {
1079
+ const char = content[i];
1080
+ const next = content[i + 1];
1081
+
1082
+ if (inString) {
1083
+ output += char;
1084
+ if (escaped) {
1085
+ escaped = false;
1086
+ } else if (char === "\\") {
1087
+ escaped = true;
1088
+ } else if (char === quote) {
1089
+ inString = false;
1090
+ quote = "";
1091
+ }
1092
+ continue;
1093
+ }
1094
+
1095
+ if (char === "\"" || char === "'") {
1096
+ inString = true;
1097
+ quote = char;
1098
+ output += char;
1099
+ continue;
1100
+ }
1101
+
1102
+ if (char === "/" && next === "/") {
1103
+ while (i < content.length && content[i] !== "\n") {
1104
+ i += 1;
1105
+ }
1106
+ if (i < content.length) {
1107
+ output += content[i];
1108
+ }
1109
+ continue;
1110
+ }
1111
+
1112
+ if (char === "/" && next === "*") {
1113
+ i += 2;
1114
+ while (i < content.length && !(content[i] === "*" && content[i + 1] === "/")) {
1115
+ output += content[i] === "\n" ? "\n" : "";
1116
+ i += 1;
1117
+ }
1118
+ i += 1;
1119
+ continue;
1120
+ }
1121
+
1122
+ output += char;
1123
+ }
1124
+ return stripJsonTrailingCommas(output);
1125
+ }
1126
+
1127
+ function stripJsonTrailingCommas(content) {
1128
+ let output = "";
1129
+ let inString = false;
1130
+ let quote = "";
1131
+ let escaped = false;
1132
+ for (let i = 0; i < content.length; i += 1) {
1133
+ const char = content[i];
1134
+
1135
+ if (inString) {
1136
+ output += char;
1137
+ if (escaped) {
1138
+ escaped = false;
1139
+ } else if (char === "\\") {
1140
+ escaped = true;
1141
+ } else if (char === quote) {
1142
+ inString = false;
1143
+ quote = "";
1144
+ }
1145
+ continue;
1146
+ }
1147
+
1148
+ if (char === "\"" || char === "'") {
1149
+ inString = true;
1150
+ quote = char;
1151
+ output += char;
1152
+ continue;
1153
+ }
1154
+
1155
+ if (char === ",") {
1156
+ let nextIndex = i + 1;
1157
+ while (nextIndex < content.length && /\s/u.test(content[nextIndex])) {
1158
+ nextIndex += 1;
1159
+ }
1160
+ if (content[nextIndex] === "}" || content[nextIndex] === "]") {
1161
+ continue;
1162
+ }
1163
+ }
1164
+
1165
+ output += char;
1166
+ }
1167
+ return output;
1168
+ }
1169
+
1170
+ async function readJsoncFileSafe(path, fallback = {}) {
1171
+ if (!(await pathExists(path))) {
1172
+ return fallback;
1173
+ }
1174
+
1175
+ try {
1176
+ return JSON.parse(stripJsonComments(await readFile(path, "utf8")));
1177
+ } catch {
1178
+ return fallback;
1179
+ }
1180
+ }
1181
+
1182
+ function parsePiModelIds(value) {
1183
+ return [...new Set(String(value || "")
1184
+ .split(/[\n,]+/u)
1185
+ .map((item) => item.trim())
1186
+ .filter(Boolean))];
1187
+ }
1188
+
1189
+ function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}) {
1190
+ const provider = modelsConfig.providers?.[piProviderId] && typeof modelsConfig.providers[piProviderId] === "object"
1191
+ ? modelsConfig.providers[piProviderId]
1192
+ : {};
1193
+ const models = Array.isArray(provider.models) ? provider.models.filter((model) => model?.id) : [];
1194
+ return {
1195
+ baseUrl: provider.baseUrl || piDefaultBaseUrl,
1196
+ api: provider.api || piDefaultApi,
1197
+ apiKey: provider.apiKey || "",
1198
+ modelIds: models.map((model) => model.id),
1199
+ defaultModel: settings.defaultProvider === piProviderId && settings.defaultModel
1200
+ ? settings.defaultModel
1201
+ : models[0]?.id || piDefaultModel
1202
+ };
1203
+ }
1204
+
1205
+ function buildPiModelConfig(modelId, existingModel = {}) {
1206
+ return {
1207
+ ...existingModel,
1208
+ id: modelId,
1209
+ name: existingModel.name || modelId,
1210
+ reasoning: existingModel.reasoning ?? true,
1211
+ input: Array.isArray(existingModel.input) ? existingModel.input : ["text", "image"],
1212
+ contextWindow: existingModel.contextWindow ?? 262144,
1213
+ maxTokens: existingModel.maxTokens ?? 64000
1214
+ };
1215
+ }
1216
+
1217
+ function buildPiModelsConfig(modelsConfig = {}, { baseUrl, api, apiKey, modelIds }) {
1218
+ const providers = modelsConfig.providers && typeof modelsConfig.providers === "object" ? modelsConfig.providers : {};
1219
+ const currentProvider = providers[piProviderId] && typeof providers[piProviderId] === "object" ? providers[piProviderId] : {};
1220
+ const existingModels = new Map(
1221
+ (Array.isArray(currentProvider.models) ? currentProvider.models : [])
1222
+ .filter((model) => model?.id)
1223
+ .map((model) => [model.id, model])
1224
+ );
1225
+
1226
+ return {
1227
+ ...modelsConfig,
1228
+ providers: {
1229
+ ...providers,
1230
+ [piProviderId]: {
1231
+ ...currentProvider,
1232
+ baseUrl,
1233
+ api,
1234
+ apiKey,
1235
+ compat: {
1236
+ ...(currentProvider.compat && typeof currentProvider.compat === "object" ? currentProvider.compat : {}),
1237
+ supportsDeveloperRole: false
1238
+ },
1239
+ models: modelIds.map((modelId) => buildPiModelConfig(modelId, existingModels.get(modelId)))
1240
+ }
1241
+ }
1242
+ };
1243
+ }
1244
+
1245
+ function buildPiSettingsConfig(settings = {}, defaultModel) {
1246
+ return {
1247
+ ...settings,
1248
+ defaultProvider: piProviderId,
1249
+ defaultModel,
1250
+ defaultThinkingLevel: settings.defaultThinkingLevel || "high",
1251
+ enableSkillCommands: settings.enableSkillCommands ?? true
1252
+ };
1253
+ }
1254
+
946
1255
  function parseDotenv(content) {
947
1256
  const values = {};
948
1257
  for (const rawLine of content.split(/\r?\n/u)) {
@@ -1010,10 +1319,33 @@ async function updateDotenvFile(path, updates) {
1010
1319
  await writeFile(path, renderDotenv(current), "utf8");
1011
1320
  }
1012
1321
 
1013
- function commandExists(command) {
1322
+ function getCommandPath(command) {
1014
1323
  const checker = isWindows() ? "where" : "which";
1015
- const result = spawnSync(checker, [command], { stdio: "ignore" });
1016
- return result.status === 0;
1324
+ const result = spawnSync(checker, [command], { encoding: "utf8" });
1325
+ if (result.status !== 0) {
1326
+ return undefined;
1327
+ }
1328
+ return result.stdout
1329
+ .split(/\r?\n/u)
1330
+ .map((line) => line.trim())
1331
+ .find(Boolean);
1332
+ }
1333
+
1334
+ function commandExists(command) {
1335
+ return Boolean(getCommandPath(command));
1336
+ }
1337
+
1338
+ function readCommandOutput(command, args) {
1339
+ const result = spawnSync(command, args, {
1340
+ encoding: "utf8",
1341
+ shell: isWindows(),
1342
+ stdio: ["ignore", "pipe", "ignore"]
1343
+ });
1344
+ if (result.status !== 0) {
1345
+ return undefined;
1346
+ }
1347
+ const outputValue = result.stdout.trim();
1348
+ return outputValue || undefined;
1017
1349
  }
1018
1350
 
1019
1351
  async function runCommand(command, args) {
@@ -1856,6 +2188,89 @@ async function getExistingCodexApiConfig() {
1856
2188
  return resolveExistingCodexApiConfig(content, auth);
1857
2189
  }
1858
2190
 
2191
+ async function ensurePiResourcesLinked(agentsDir) {
2192
+ const { previousMetadata, managedChildren } = await syncManagedFiles(agentsDir);
2193
+ const augmentContextEngine = resolveAugmentContextEngineFeature({}, previousMetadata);
2194
+ await renderManagedWorkflowFiles(agentsDir, augmentContextEngine);
2195
+
2196
+ const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
2197
+ const piResults = await linkPi(agentsDir, previousLinkedTargets);
2198
+ await writeInstallMetadata(agentsDir, {
2199
+ ...previousMetadata,
2200
+ package: previousMetadata.package || "abelworkflow",
2201
+ installedAt: previousMetadata.installedAt || new Date().toISOString(),
2202
+ features: {
2203
+ ...(previousMetadata.features && typeof previousMetadata.features === "object" ? previousMetadata.features : {}),
2204
+ augmentContextEngine
2205
+ },
2206
+ managedChildren,
2207
+ managedClaudePermissions: getPreviousManagedClaudePermissions(previousMetadata),
2208
+ linkedTargets: mergeLinkedTargets(previousLinkedTargets, piResults)
2209
+ });
2210
+ return piResults;
2211
+ }
2212
+
2213
+ async function configurePiApi(agentsDir) {
2214
+ const modelsConfig = await readJsoncFileSafe(piModelsPath, {});
2215
+ const settings = await readJsonFileSafe(piSettingsPath, {});
2216
+ const existing = resolveExistingPiApiConfig(modelsConfig, settings);
2217
+
2218
+ const baseUrl = await p.text({
2219
+ message: "Pi gpt Base URL",
2220
+ defaultValue: existing.baseUrl,
2221
+ validate: required()
2222
+ });
2223
+ assertNotCancelled(baseUrl);
2224
+
2225
+ const piApiOptions = ["openai-responses", "openai-completions"];
2226
+ const api = await selectOrCancel({
2227
+ message: "Pi gpt API 类型",
2228
+ options: [
2229
+ { value: "openai-responses", label: "OpenAI Responses API" },
2230
+ { value: "openai-completions", label: "OpenAI Chat Completions" }
2231
+ ],
2232
+ initialValue: piApiOptions.includes(existing.api) ? existing.api : piDefaultApi
2233
+ });
2234
+
2235
+ const apiKey = await p.password({
2236
+ message: "Pi gpt API Key(输入 - 清除)",
2237
+ mask: "*",
2238
+ defaultValue: existing.apiKey || undefined,
2239
+ validate: requiredUnlessExisting(existing.apiKey, "API Key 不能为空")
2240
+ });
2241
+ assertNotCancelled(apiKey);
2242
+ const finalApiKey = resolvePasswordValue(apiKey, existing.apiKey);
2243
+
2244
+ const modelIdsText = await p.text({
2245
+ message: "Pi gpt 模型 ID(多个用逗号分隔)",
2246
+ defaultValue: (existing.modelIds.length ? existing.modelIds : [existing.defaultModel]).join(","),
2247
+ validate: (value) => parsePiModelIds(value).length ? undefined : "至少需要一个模型 ID"
2248
+ });
2249
+ assertNotCancelled(modelIdsText);
2250
+ const modelIds = parsePiModelIds(modelIdsText);
2251
+
2252
+ const defaultModel = await p.text({
2253
+ message: "Pi 默认模型",
2254
+ defaultValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
2255
+ validate: (value) => modelIds.includes(String(value || "").trim()) ? undefined : "默认模型必须在模型 ID 列表中"
2256
+ });
2257
+ assertNotCancelled(defaultModel);
2258
+
2259
+ const finalDefaultModel = String(defaultModel).trim();
2260
+ await ensurePiResourcesLinked(agentsDir);
2261
+ await writeJsonFileWithBackup(piModelsPath, buildPiModelsConfig(modelsConfig, {
2262
+ baseUrl,
2263
+ api,
2264
+ apiKey: finalApiKey,
2265
+ modelIds
2266
+ }));
2267
+ await writeJsonFileWithBackup(piSettingsPath, buildPiSettingsConfig(settings, finalDefaultModel));
2268
+
2269
+ p.log.step(`已更新 ${pathToLabel(piModelsPath)} (${piProviderId}, ${baseUrl})`);
2270
+ p.log.step(`已更新 ${pathToLabel(piSettingsPath)} (默认模型: ${finalDefaultModel})`);
2271
+ p.log.step(`已链接 Pi 扩展到 ${pathToLabel(join(piAgentDir, "extensions"))}`);
2272
+ }
2273
+
1859
2274
  async function configureCodexApi() {
1860
2275
  const existing = await getExistingCodexApiConfig();
1861
2276
  const providerId = existing.providerId || "abelworkflow";
@@ -1939,7 +2354,8 @@ function buildCodexConfigContent(currentContent, {
1939
2354
  base_url: baseUrl,
1940
2355
  wire_api: "responses",
1941
2356
  temp_env_key: envKey,
1942
- requires_openai_auth: true
2357
+ requires_openai_auth: true,
2358
+ supports_websockets: true
1943
2359
  });
1944
2360
  return `${content.trim()}${lineEnding}`;
1945
2361
  }
@@ -1948,17 +2364,158 @@ function mergeCodexAuthData(auth, envKey, apiKey, legacyEnvKeys = []) {
1948
2364
  return { [envKey]: apiKey };
1949
2365
  }
1950
2366
 
2367
+ function normalizeInstallPath(value) {
2368
+ if (!value) {
2369
+ return "";
2370
+ }
2371
+ return value.replace(/\\/gu, "/").replace(/\/+$/u, "").toLowerCase();
2372
+ }
2373
+
2374
+ function installPathIsWithin(path, parentPath) {
2375
+ const normalizedPath = normalizeInstallPath(path);
2376
+ const normalizedParent = normalizeInstallPath(parentPath);
2377
+ return Boolean(normalizedPath && normalizedParent)
2378
+ && (normalizedPath === normalizedParent || normalizedPath.startsWith(`${normalizedParent}/`));
2379
+ }
2380
+
2381
+ function getNpmGlobalBinDirs(npmGlobalPrefix, platform = getPlatform()) {
2382
+ if (!npmGlobalPrefix) {
2383
+ return [];
2384
+ }
2385
+ return platform === "win32" ? [npmGlobalPrefix] : [join(npmGlobalPrefix, "bin")];
2386
+ }
2387
+
2388
+ function inferPackageManagerFromCommandPath(commandPath, {
2389
+ bunGlobalBinDir,
2390
+ npmGlobalPrefix,
2391
+ platform = getPlatform()
2392
+ } = {}) {
2393
+ if (installPathIsWithin(commandPath, bunGlobalBinDir)) {
2394
+ return "bun";
2395
+ }
2396
+ if (getNpmGlobalBinDirs(npmGlobalPrefix, platform)
2397
+ .some((binDir) => installPathIsWithin(commandPath, binDir))) {
2398
+ return "npm";
2399
+ }
2400
+ return null;
2401
+ }
2402
+
2403
+ function chooseCliInstallPackageManager({
2404
+ commandPath,
2405
+ availablePackageManagers = [],
2406
+ bunGlobalBinDir,
2407
+ npmGlobalPrefix,
2408
+ platform = getPlatform(),
2409
+ nodeAvailable = true,
2410
+ supportedPackageManagers = ["bun", "npm"]
2411
+ } = {}) {
2412
+ const supported = new Set(supportedPackageManagers);
2413
+ const available = new Set(availablePackageManagers.filter((packageManager) => {
2414
+ if (!supported.has(packageManager)) {
2415
+ return false;
2416
+ }
2417
+ return packageManager !== "bun" || nodeAvailable;
2418
+ }));
2419
+ const bunBlockedByMissingNode = !nodeAvailable
2420
+ && supported.has("bun")
2421
+ && availablePackageManagers.includes("bun");
2422
+ const existingPackageManager = inferPackageManagerFromCommandPath(commandPath, {
2423
+ bunGlobalBinDir,
2424
+ npmGlobalPrefix,
2425
+ platform
2426
+ });
2427
+
2428
+ if (existingPackageManager && available.has(existingPackageManager)) {
2429
+ return { packageManager: existingPackageManager, source: "existing" };
2430
+ }
2431
+ for (const packageManager of ["bun", "npm"]) {
2432
+ if (available.has(packageManager)) {
2433
+ return { packageManager, source: "available" };
2434
+ }
2435
+ }
2436
+ return {
2437
+ packageManager: null,
2438
+ source: bunBlockedByMissingNode ? "missing-node" : (availablePackageManagers.length > 0 ? "unsupported" : "missing")
2439
+ };
2440
+ }
2441
+
2442
+ function buildCliToolInstallCommand(packageManager, { packageName, skipScripts = false }) {
2443
+ if (packageManager === "npm") {
2444
+ return {
2445
+ command: "npm",
2446
+ args: [
2447
+ "install",
2448
+ "-g",
2449
+ ...(skipScripts ? ["--ignore-scripts"] : []),
2450
+ packageName,
2451
+ "--force"
2452
+ ]
2453
+ };
2454
+ }
2455
+ if (packageManager === "bun") {
2456
+ return {
2457
+ command: "bun",
2458
+ args: [
2459
+ "install",
2460
+ "-g",
2461
+ ...(skipScripts ? ["--ignore-scripts"] : []),
2462
+ packageName
2463
+ ]
2464
+ };
2465
+ }
2466
+ throw new Error(`Unsupported package manager: ${packageManager}`);
2467
+ }
2468
+
2469
+ function getPackageManagerInstallHelp(platform = getPlatform()) {
2470
+ const platformLabel = {
2471
+ darwin: "macOS",
2472
+ win32: "Windows",
2473
+ linux: "Linux"
2474
+ }[platform] || platform;
2475
+ return {
2476
+ platformLabel,
2477
+ mainlandUrl: "https://npmmirror.com/mirrors/node/",
2478
+ officialUrl: "https://nodejs.org/en/download/"
2479
+ };
2480
+ }
2481
+
2482
+ function getAvailablePackageManagers() {
2483
+ return ["bun", "npm"].filter((packageManager) => commandExists(packageManager));
2484
+ }
2485
+
2486
+ function getBunGlobalBinDir(availablePackageManagers = getAvailablePackageManagers()) {
2487
+ if (!availablePackageManagers.includes("bun")) {
2488
+ return undefined;
2489
+ }
2490
+ return readCommandOutput("bun", ["pm", "bin", "-g"]) || join(home, ".bun", "bin");
2491
+ }
2492
+
2493
+ function getNpmGlobalPrefix(availablePackageManagers = getAvailablePackageManagers()) {
2494
+ if (!availablePackageManagers.includes("npm")) {
2495
+ return undefined;
2496
+ }
2497
+ return readCommandOutput("npm", ["prefix", "-g"]);
2498
+ }
2499
+
1951
2500
  async function installCliTool(tool) {
1952
2501
  const toolConfig = {
1953
2502
  claude: {
1954
2503
  label: "Claude Code",
1955
2504
  command: "claude",
1956
- packageName: "@anthropic-ai/claude-code"
2505
+ packageName: "@anthropic-ai/claude-code",
2506
+ supportedPackageManagers: ["npm"],
2507
+ installRequirement: "Claude Code 安装需要执行 postinstall;Bun 默认会阻止该脚本,因此需使用 npm。"
1957
2508
  },
1958
2509
  codex: {
1959
2510
  label: "Codex",
1960
2511
  command: "codex",
1961
2512
  packageName: "@openai/codex"
2513
+ },
2514
+ pi: {
2515
+ label: "Pi",
2516
+ command: "pi",
2517
+ packageName: "@earendil-works/pi-coding-agent",
2518
+ skipScripts: true
1962
2519
  }
1963
2520
  }[tool];
1964
2521
 
@@ -1966,10 +2523,40 @@ async function installCliTool(tool) {
1966
2523
  throw new Error(`Unsupported tool: ${tool}`);
1967
2524
  }
1968
2525
 
1969
- const installed = commandExists(toolConfig.command);
2526
+ const commandPath = getCommandPath(toolConfig.command);
2527
+ const installed = Boolean(commandPath);
2528
+ const availablePackageManagers = getAvailablePackageManagers();
2529
+ const packageManagerChoice = chooseCliInstallPackageManager({
2530
+ commandPath,
2531
+ availablePackageManagers,
2532
+ bunGlobalBinDir: getBunGlobalBinDir(availablePackageManagers),
2533
+ npmGlobalPrefix: getNpmGlobalPrefix(availablePackageManagers),
2534
+ platform: getPlatform(),
2535
+ nodeAvailable: commandExists("node"),
2536
+ supportedPackageManagers: toolConfig.supportedPackageManagers
2537
+ });
2538
+
2539
+ if (!packageManagerChoice.packageManager) {
2540
+ const help = getPackageManagerInstallHelp();
2541
+ p.log.warn(`未检测到可用于安装 ${toolConfig.label} 的包管理器。`);
2542
+ if (packageManagerChoice.source === "missing-node") {
2543
+ p.log.message(`${toolConfig.label} 是 Node CLI;使用 Bun 安装前也需要先安装 Node.js。`);
2544
+ }
2545
+ if (toolConfig.installRequirement) {
2546
+ p.log.message(toolConfig.installRequirement);
2547
+ }
2548
+ p.log.message(`${help.platformLabel} 可先安装 Node.js/npm,再重新运行安装。`);
2549
+ p.log.message(`中国大陆镜像: ${help.mainlandUrl}`);
2550
+ p.log.message(`官方下载页: ${help.officialUrl}`);
2551
+ return;
2552
+ }
2553
+
1970
2554
  if (installed) {
2555
+ const managerMessage = packageManagerChoice.source === "existing"
2556
+ ? `检测到原安装方式为 ${packageManagerChoice.packageManager}`
2557
+ : `未识别原安装方式,将使用 ${packageManagerChoice.packageManager}`;
1971
2558
  const shouldUpdate = await confirmOrCancel({
1972
- message: `${toolConfig.label} 已检测到,是否继续执行 npm 强制安装/更新?`,
2559
+ message: `${toolConfig.label} 已检测到(${managerMessage}),是否继续安装/更新?`,
1973
2560
  initialValue: false
1974
2561
  });
1975
2562
  if (!shouldUpdate) {
@@ -1978,10 +2565,11 @@ async function installCliTool(tool) {
1978
2565
  }
1979
2566
  }
1980
2567
 
2568
+ const installCommand = buildCliToolInstallCommand(packageManagerChoice.packageManager, toolConfig);
1981
2569
  const s = p.spinner();
1982
- s.start(`正在安装 ${toolConfig.label}...`);
2570
+ s.start(`正在使用 ${packageManagerChoice.packageManager} 安装 ${toolConfig.label}...`);
1983
2571
  try {
1984
- await runCommand("npm", ["install", "-g", toolConfig.packageName, "--force"]);
2572
+ await runCommand(installCommand.command, installCommand.args);
1985
2573
  s.stop(`${toolConfig.label} 安装完成`);
1986
2574
  } catch (e) {
1987
2575
  s.cancel(c.red(`${toolConfig.label} 安装失败: ${e.message}`));
@@ -2013,6 +2601,12 @@ async function runFullInit(options) {
2013
2601
  if (await confirmOrCancel({ message: "是否配置 Codex 第三方 API?", initialValue: commandExists("codex") })) {
2014
2602
  await configureCodexApi();
2015
2603
  }
2604
+ if (await confirmOrCancel({ message: "是否安装或更新 Pi CLI?", initialValue: false })) {
2605
+ await installCliTool("pi");
2606
+ }
2607
+ if (await confirmOrCancel({ message: "是否配置 Pi gpt 自定义 API?", initialValue: commandExists("pi") })) {
2608
+ await configurePiApi(options.agentsDir);
2609
+ }
2016
2610
  if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
2017
2611
  await configureGrokSearchEnv(options.agentsDir);
2018
2612
  }
@@ -2030,6 +2624,59 @@ async function runInteractiveMenu(options) {
2030
2624
  p.intro(c.bold(c.bgCyan(c.black(" AbelWorkflow Setup "))));
2031
2625
  p.log.message(`工作流目录: ${c.cyan(pathToLabel(options.agentsDir))}`);
2032
2626
 
2627
+ const buildOption = (d) => {
2628
+ const opt = { value: d.value, label: d.label };
2629
+ if (d.hint) {
2630
+ opt.hint = d.hint;
2631
+ }
2632
+ return opt;
2633
+ };
2634
+ const cliToolMenus = {
2635
+ "pi-cli": {
2636
+ tool: "pi",
2637
+ title: "Pi",
2638
+ actions: {
2639
+ "pi-install": async () => installCliTool("pi"),
2640
+ "pi-api": async () => configurePiApi(options.agentsDir)
2641
+ }
2642
+ },
2643
+ "codex-cli": {
2644
+ tool: "codex",
2645
+ title: "Codex",
2646
+ actions: {
2647
+ "codex-install": async () => installCliTool("codex"),
2648
+ "codex-api": async () => configureCodexApi()
2649
+ }
2650
+ },
2651
+ "claude-cli": {
2652
+ tool: "claude",
2653
+ title: "Claude Code",
2654
+ actions: {
2655
+ "claude-install": async () => installCliTool("claude"),
2656
+ "claude-api": async () => configureClaudeApi()
2657
+ }
2658
+ }
2659
+ };
2660
+ const runCliToolMenu = async ({ tool, title, actions }) => {
2661
+ while (true) {
2662
+ const choice = await p.select({
2663
+ message: `请选择 ${title} 操作`,
2664
+ options: buildCliToolMenuDescriptors(tool).map(buildOption),
2665
+ initialValue: `${tool}-install`
2666
+ });
2667
+
2668
+ if (p.isCancel(choice) || choice === "back") {
2669
+ return;
2670
+ }
2671
+
2672
+ const action = actions[choice];
2673
+ if (!action) {
2674
+ p.log.warn(`未知 CLI 工具菜单选项: ${choice}`);
2675
+ continue;
2676
+ }
2677
+ await action();
2678
+ }
2679
+ };
2033
2680
  const menuActions = {
2034
2681
  "full-init": async () => runFullInit(options),
2035
2682
  install: async () => installManagedWorkflow({
@@ -2040,18 +2687,9 @@ async function runInteractiveMenu(options) {
2040
2687
  "grok-search": async () => configureGrokSearchEnv(options.agentsDir),
2041
2688
  context7: async () => configureContext7Env(options.agentsDir),
2042
2689
  "prompt-enhancer": async () => configurePromptEnhancerEnv(options.agentsDir),
2043
- "claude-install": async () => installCliTool("claude"),
2044
- "claude-api": async () => configureClaudeApi(),
2045
- "codex-install": async () => installCliTool("codex"),
2046
- "codex-api": async () => configureCodexApi()
2047
- };
2048
-
2049
- const buildOption = (d) => {
2050
- const opt = { value: d.value, label: d.label };
2051
- if (d.hint) {
2052
- opt.hint = d.hint;
2053
- }
2054
- return opt;
2690
+ "pi-cli": async () => runCliToolMenu(cliToolMenus["pi-cli"]),
2691
+ "codex-cli": async () => runCliToolMenu(cliToolMenus["codex-cli"]),
2692
+ "claude-cli": async () => runCliToolMenu(cliToolMenus["claude-cli"])
2055
2693
  };
2056
2694
 
2057
2695
  while (true) {
@@ -2162,11 +2800,17 @@ async function main() {
2162
2800
 
2163
2801
  export {
2164
2802
  applyClaudePermissionFeature,
2803
+ buildCliToolInstallCommand,
2165
2804
  buildDefaultClaudeSettings,
2166
2805
  buildCodexConfigContent,
2806
+ buildPiModelsConfig,
2807
+ buildPiSettingsConfig,
2808
+ chooseCliInstallPackageManager,
2809
+ getPackageManagerInstallHelp,
2167
2810
  getAugmentContextEnginePromptOptions,
2168
2811
  getRunCommandSpawnOptions,
2169
2812
  hasPromptEnhancerApiConfig,
2813
+ inferPackageManagerFromCommandPath,
2170
2814
  main,
2171
2815
  mergeCodexAuthData,
2172
2816
  mergeClaudeSettingsWithDefaults,
@@ -2174,5 +2818,8 @@ export {
2174
2818
  resolveAugmentContextEngineFeature,
2175
2819
  resolvePromptEnhancerMode,
2176
2820
  resolveExistingCodexApiConfig,
2821
+ parsePiModelIds,
2822
+ resolveExistingPiApiConfig,
2823
+ stripJsonComments,
2177
2824
  updateTomlSectionFields
2178
2825
  };
@@ -12,7 +12,7 @@ pre-implementation planning, post-implementation review, or bounded
12
12
  module-level implementation.
13
13
  """
14
14
  nickname_candidates = ["Relay", "Pivot", "Anchor"]
15
- model = "gpt-5.5"
15
+ model = "gpt-5.6-sol"
16
16
  model_reasoning_effort = "high"
17
17
  sandbox_mode = "workspace-write"
18
18
 
@@ -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.5"
14
+ model = "gpt-5.6-sol"
15
15
  model_reasoning_effort = "high"
16
16
  sandbox_mode = "read-only"
17
17
 
@@ -14,7 +14,7 @@ exceed execution value.
14
14
  Do NOT dispatch after implementation is complete — use reviewer instead.
15
15
  """
16
16
  nickname_candidates = ["Blueprint", "Compass", "Architect"]
17
- model = "gpt-5.5"
17
+ model = "gpt-5.6-sol"
18
18
  model_reasoning_effort = "high"
19
19
  sandbox_mode = "read-only"
20
20
 
@@ -11,7 +11,7 @@ Do NOT dispatch for codebase mapping or exploration — use explorer instead.
11
11
  Do NOT dispatch before implementation is complete.
12
12
  """
13
13
  nickname_candidates = ["Delta", "Echo", "Sigma"]
14
- model = "gpt-5.5"
14
+ model = "gpt-5.6-sol"
15
15
  model_reasoning_effort = "xhigh"
16
16
  sandbox_mode = "read-only"
17
17
 
@@ -10,7 +10,7 @@ Do NOT dispatch when the task touches global configs, shared utilities,
10
10
  public interfaces used across modules, or project scaffolding.
11
11
  """
12
12
  nickname_candidates = ["Forge", "Patch", "Builder"]
13
- model = "gpt-5.5"
13
+ model = "gpt-5.6-sol"
14
14
  model_reasoning_effort = "high"
15
15
  sandbox_mode = "workspace-write"
16
16
 
@@ -5,11 +5,9 @@ preferred_auth_method = "apikey"
5
5
  approvals_reviewer = "guardian_subagent"
6
6
  approval_policy = "on-request"
7
7
  sandbox_mode = "workspace-write"
8
- model = "gpt-5.5"
8
+ model = "gpt-5.6-sol"
9
9
  model_reasoning_effort = "high"
10
10
  network_access = true
11
- supports_websockets = true
12
- requires_openai_auth = true
13
11
  developer_instructions = """
14
12
  Act as the default orchestrator for specialized subagents.
15
13
 
@@ -95,5 +93,4 @@ job_max_runtime_seconds = 2400
95
93
  multi_agent = true
96
94
  js_repl = true
97
95
  guardian_approval = true
98
- responses_websockets_v2 = true
99
96
  shell_snapshot = true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abelworkflow",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Install AbelWorkflow into ~/.agents and create Claude/Codex symlinks.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -17,6 +17,7 @@
17
17
  "README.md",
18
18
  "commands",
19
19
  "skills",
20
+ "extensions",
20
21
  ".skill-lock.json",
21
22
  ".gitignore"
22
23
  ],