abelworkflow 1.1.2 → 1.1.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 CHANGED
@@ -63,7 +63,7 @@ node .\bin\abelworkflow.mjs install --agents-dir "$HOME\.agents"
63
63
 
64
64
  - Claude 配置默认写入 `bypassPermissions` 全权限模式(YOLO),第三方 API 仅使用 API Key 认证,不添加任何 MCP 权限。已有 permissions、deny、hooks 和 timeout 保持不变。
65
65
  - Codex 只更新 AbelWorkflow 管理的认证字段,保留未知字段、其他 Provider 和用户 token。
66
- - Pi 0.80.0 及以上版本按无会话启动时实际选中的当前有效 Provider 配置 API,并将其 API Key 保存在 `~/.pi/agent/auth.json`;`models.json` 只保留模型定义。旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
66
+ - Pi 0.80.0 及以上版本按无会话启动时实际选中的当前有效 Provider 配置 API;首次使用且 Pi 明确没有可用模型时,引导创建 `gpt` 自定义 Provider。API Key 保存在 `~/.pi/agent/auth.json`,`models.json` 只保留模型定义;旧 models-only key 会按先写 auth、后删除旧 key 的顺序迁移。
67
67
  - Grok 默认模型统一为 `grok-4.20-non-reasoning`。
68
68
  - Context7 使用显式 CommonJS 入口 `context7-api.cjs`。
69
69
  - dev-browser 发布运行时使用 Node ESM 编译产物,入口为 `node dist/scripts/start.js`,不依赖 Bun 或运行时 `npx tsx`。
@@ -11,6 +11,10 @@ import {
11
11
  import { defaultPaths, maskSecret, pathToLabel } from "../paths.mjs";
12
12
 
13
13
  const minimumPiVersion = [0, 80, 0];
14
+ const piBootstrapProviderId = "gpt";
15
+ const piBootstrapApi = "openai-completions";
16
+ const piBootstrapBaseUrl = "https://api.openai.com/v1";
17
+ const piBootstrapModelId = "gpt-5.5";
14
18
  const piRpcRequestId = "abelworkflow-provider";
15
19
  const piRpcArgs = [
16
20
  "--mode", "rpc",
@@ -68,15 +72,28 @@ function assertSupportedPiVersion(value) {
68
72
  return version;
69
73
  }
70
74
 
75
+ function getPiProcessInvocation(args, {
76
+ platform = process.platform,
77
+ comspec = process.env.ComSpec || process.env.COMSPEC || "cmd.exe"
78
+ } = {}) {
79
+ return platform === "win32"
80
+ ? { command: comspec, args: ["/d", "/c", "pi", ...args] }
81
+ : { command: "pi", args };
82
+ }
83
+
71
84
  function detectPiVersion() {
72
- const result = spawnSync("pi", ["--version"], {
85
+ const invocation = getPiProcessInvocation(["--version"]);
86
+ const result = spawnSync(invocation.command, invocation.args, {
73
87
  encoding: "utf8",
74
- shell: process.platform === "win32",
75
88
  stdio: ["ignore", "pipe", "pipe"]
76
89
  });
77
90
  return result.status === 0 ? `${result.stdout || ""} ${result.stderr || ""}`.trim() : undefined;
78
91
  }
79
92
 
93
+ function isPiUnknownModel(model) {
94
+ return model?.provider === "unknown" && model?.id === "unknown" && model?.api === "unknown";
95
+ }
96
+
80
97
  function parsePiRpcEffectiveModel(value) {
81
98
  for (const line of String(value || "").split(/\r?\n/u)) {
82
99
  let payload;
@@ -85,27 +102,26 @@ function parsePiRpcEffectiveModel(value) {
85
102
  } catch {
86
103
  continue;
87
104
  }
88
- const model = payload?.type === "response"
89
- && payload.command === "get_state"
90
- && payload.success === true
91
- ? payload.data?.model
92
- : undefined;
93
- const provider = typeof model?.provider === "string" ? model.provider.trim() : "";
94
- const id = typeof model?.id === "string" ? model.id.trim() : "";
95
- if (!provider || !id) continue;
96
- return {
97
- provider,
98
- id,
99
- api: typeof model.api === "string" ? model.api.trim() : "",
100
- baseUrl: typeof model.baseUrl === "string" ? model.baseUrl.trim() : ""
105
+ if (payload?.type !== "response"
106
+ || payload.command !== "get_state"
107
+ || payload.success !== true) continue;
108
+ const model = payload.data?.model;
109
+ if (model === null) return null;
110
+ const effectiveModel = {
111
+ provider: typeof model?.provider === "string" ? model.provider.trim() : "",
112
+ id: typeof model?.id === "string" ? model.id.trim() : "",
113
+ api: typeof model?.api === "string" ? model.api.trim() : "",
114
+ baseUrl: typeof model?.baseUrl === "string" ? model.baseUrl.trim() : ""
101
115
  };
116
+ if (isPiUnknownModel(effectiveModel)) return null;
117
+ if (effectiveModel.provider && effectiveModel.id) return effectiveModel;
102
118
  }
103
119
  }
104
120
 
105
121
  function runPiRpcCommand(command, args, {
106
122
  input = "",
107
123
  maxBuffer = 1024 * 1024,
108
- shell = process.platform === "win32",
124
+ platform = process.platform,
109
125
  start = spawn,
110
126
  timeout = 20000
111
127
  } = {}) {
@@ -129,9 +145,11 @@ function runPiRpcCommand(command, args, {
129
145
  try {
130
146
  const env = { ...process.env };
131
147
  delete env.NODE_TEST_CONTEXT;
132
- child = start(command, args, {
148
+ const invocation = command === "pi"
149
+ ? getPiProcessInvocation(args, { platform })
150
+ : { command, args };
151
+ child = start(invocation.command, invocation.args, {
133
152
  env,
134
- shell,
135
153
  stdio: ["pipe", "pipe", "ignore"],
136
154
  windowsHide: true
137
155
  });
@@ -148,7 +166,7 @@ function runPiRpcCommand(command, args, {
148
166
  stdout += chunk;
149
167
  if (Buffer.byteLength(stdout, "utf8") > maxBuffer) {
150
168
  finish(null);
151
- } else if (parsePiRpcEffectiveModel(stdout)) {
169
+ } else if (parsePiRpcEffectiveModel(stdout) !== undefined) {
152
170
  finish(0);
153
171
  }
154
172
  });
@@ -166,7 +184,7 @@ async function detectPiEffectiveModel(run = runPiRpcCommand) {
166
184
  encoding: "utf8",
167
185
  input: `${JSON.stringify({ id: piRpcRequestId, type: "get_state" })}\n`,
168
186
  maxBuffer: 1024 * 1024,
169
- shell: process.platform === "win32",
187
+ platform: process.platform,
170
188
  timeout: 20000
171
189
  });
172
190
  } catch {
@@ -281,6 +299,37 @@ function resolveExistingPiApiConfig(modelsConfig = {}, settings = {}, auth = {},
281
299
  };
282
300
  }
283
301
 
302
+ function resolvePiApiTarget(modelsConfig = {}, settings = {}, auth = {}, effectiveModel) {
303
+ const hasNoEffectiveModel = effectiveModel === null || isPiUnknownModel(effectiveModel);
304
+ const configuration = resolveExistingPiApiConfig(
305
+ modelsConfig,
306
+ settings,
307
+ auth,
308
+ hasNoEffectiveModel ? undefined : effectiveModel
309
+ );
310
+ if (configuration.providerId || !hasNoEffectiveModel) {
311
+ return { bootstrap: false, configuration };
312
+ }
313
+
314
+ const provider = modelsConfig.providers?.[piBootstrapProviderId];
315
+ const models = Array.isArray(provider?.models) ? provider.models.filter((model) => model?.id) : [];
316
+ const credential = auth[piBootstrapProviderId];
317
+ const apiKey = credential?.type === "api_key" && typeof credential.key === "string"
318
+ ? credential.key
319
+ : typeof provider?.apiKey === "string" ? provider.apiKey : "";
320
+ return {
321
+ bootstrap: true,
322
+ configuration: {
323
+ providerId: piBootstrapProviderId,
324
+ baseUrl: typeof provider?.baseUrl === "string" ? provider.baseUrl : piBootstrapBaseUrl,
325
+ api: typeof provider?.api === "string" ? provider.api : piBootstrapApi,
326
+ apiKey,
327
+ modelIds: models.map((model) => model.id),
328
+ defaultModel: models[0]?.id || piBootstrapModelId
329
+ }
330
+ };
331
+ }
332
+
284
333
  function assertConfigurablePiProvider(modelsConfig = {}, configuration = {}) {
285
334
  const { providerId, defaultModel, api } = configuration;
286
335
  const provider = modelsConfig.providers?.[providerId];
@@ -419,11 +468,14 @@ async function persistPiConfiguration(paths, configuration, operations = {}) {
419
468
  }
420
469
 
421
470
  async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = async () => {}, promptApi, runtime = {}) {
471
+ const log = runtime.log ?? p.log;
472
+ const spinner = runtime.spinner ?? p.spinner;
473
+ const text = runtime.text ?? p.text;
422
474
  const piVersion = await (runtime.getPiVersion ?? detectPiVersion)();
423
475
  try {
424
476
  assertSupportedPiVersion(piVersion);
425
477
  } catch (error) {
426
- (runtime.log ?? p.log).warn(error.message || String(error));
478
+ log.warn(error.message || String(error));
427
479
  return;
428
480
  }
429
481
  const {
@@ -437,7 +489,7 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
437
489
  models: modelsConfig,
438
490
  settings
439
491
  } = await readExistingPiConfiguration(paths);
440
- const detectionSpinner = p.spinner();
492
+ const detectionSpinner = spinner();
441
493
  detectionSpinner.start("正在识别 Pi 当前有效模型");
442
494
  let effectiveModel;
443
495
  try {
@@ -447,16 +499,21 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
447
499
  ? `已识别 ${effectiveModel.provider}/${effectiveModel.id}`
448
500
  : "未识别到 Pi 当前有效模型");
449
501
  }
450
- const existing = resolveExistingPiApiConfig(modelsConfig, settings, auth, effectiveModel);
502
+ const target = resolvePiApiTarget(modelsConfig, settings, auth, effectiveModel);
503
+ const existing = target.configuration;
451
504
  const providerId = requirePiProviderId(existing.providerId);
452
- assertConfigurablePiProvider(modelsConfig, existing);
505
+ if (target.bootstrap) {
506
+ log.info(`Pi 尚未配置可用模型,将创建自定义 Provider ${providerId}。`);
507
+ } else {
508
+ assertConfigurablePiProvider(modelsConfig, existing);
509
+ }
453
510
  if (effectiveModel
454
511
  && (settings.defaultProvider !== existing.providerId || settings.defaultModel !== existing.defaultModel)) {
455
- p.log.warn(`Pi 保存的默认模型 ${settings.defaultProvider || "未知"}/${settings.defaultModel || "未知"} 与当前有效模型 ${existing.providerId}/${existing.defaultModel} 不同;将配置当前有效模型。`);
512
+ log.warn(`Pi 保存的默认模型 ${settings.defaultProvider || "未知"}/${settings.defaultModel || "未知"} 与当前有效模型 ${existing.providerId}/${existing.defaultModel} 不同;将配置当前有效模型。`);
456
513
  }
457
514
  const providerLabel = `Pi ${providerId}`;
458
515
 
459
- const baseUrlInput = await p.text({
516
+ const baseUrlInput = await text({
460
517
  message: `${providerLabel} Base URL`,
461
518
  initialValue: existing.baseUrl,
462
519
  validate: required()
@@ -480,7 +537,7 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
480
537
  existingValue: existing.apiKey
481
538
  });
482
539
 
483
- const modelIdsText = await p.text({
540
+ const modelIdsText = await text({
484
541
  message: `${providerLabel} 模型 ID(多个用逗号分隔)`,
485
542
  initialValue: (existing.modelIds.length
486
543
  ? existing.modelIds
@@ -490,7 +547,7 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
490
547
  assertNotCancelled(modelIdsText);
491
548
  const modelIds = parsePiModelIds(modelIdsText);
492
549
 
493
- const defaultModel = await p.text({
550
+ const defaultModel = await text({
494
551
  message: "Pi 默认模型",
495
552
  initialValue: modelIds.includes(existing.defaultModel) ? existing.defaultModel : modelIds[0],
496
553
  validate: (value) => modelIds.includes(String(value || "").trim()) ? undefined : "默认模型必须在模型 ID 列表中"
@@ -509,10 +566,10 @@ async function configurePiApi(paths = defaultPaths, ensurePiResourcesLinked = as
509
566
  await ensurePiResourcesLinked(paths);
510
567
  await persistPiConfiguration(paths, configuration);
511
568
 
512
- p.log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
513
- p.log.step(`已更新 ${pathToLabel(paths.piSettingsPath, paths.homeDir)} (默认模型: ${finalDefaultModel})`);
514
- p.log.step(`已更新 ${pathToLabel(paths.piAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
515
- p.log.step(`已链接 Pi 扩展到 ${pathToLabel(join(paths.piAgentDir, "extensions"), paths.homeDir)}`);
569
+ log.step(`已更新 ${pathToLabel(paths.piModelsPath, paths.homeDir)} (${providerId}, ${baseUrl})`);
570
+ log.step(`已更新 ${pathToLabel(paths.piSettingsPath, paths.homeDir)} (默认模型: ${finalDefaultModel})`);
571
+ log.step(`已更新 ${pathToLabel(paths.piAuthPath, paths.homeDir)} (${maskSecret(finalApiKey)})`);
572
+ log.step(`已链接 Pi 扩展到 ${pathToLabel(join(paths.piAgentDir, "extensions"), paths.homeDir)}`);
516
573
  }
517
574
 
518
575
  export {
@@ -525,6 +582,7 @@ export {
525
582
  configurePiApi,
526
583
  detectPiEffectiveModel,
527
584
  getPiApiPromptOptions,
585
+ getPiProcessInvocation,
528
586
  inferPiApiFromBaseUrl,
529
587
  normalizeOpenAiBaseUrl,
530
588
  parsePiRpcEffectiveModel,
@@ -532,6 +590,7 @@ export {
532
590
  persistPiConfiguration,
533
591
  readExistingPiConfiguration,
534
592
  resolveExistingPiApiConfig,
593
+ resolvePiApiTarget,
535
594
  runPiRpcCommand,
536
595
  updatePiAuthFile
537
596
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "abelworkflow",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Install AbelWorkflow into ~/.agents and create Claude/Codex symlinks.",
5
5
  "type": "module",
6
6
  "scripts": {