@tomflow/proflow-platform-cli 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/deployment/adapter.d.ts +7 -7
- package/dist/deployment/descriptor.d.ts +1 -1
- package/dist/deployment/descriptor.js +1 -1
- package/dist/src/cli.js +262 -152
- package/dist/src/errors.d.ts +1 -1
- package/dist/src/errors.js +3 -0
- package/dist/src/install/package-manager.d.ts +1 -0
- package/dist/src/install/package-manager.js +3 -3
- package/dist/src/lifecycle/thin.js +10 -0
- package/dist/src/persistence/git-isolation.d.ts +4 -0
- package/dist/src/persistence/git-isolation.js +52 -0
- package/dist/src/registry/npm-registry.d.ts +2 -0
- package/dist/src/registry/npm-registry.js +6 -2
- package/dist/src/setup/interaction.d.ts +1 -1
- package/dist/src/setup/interaction.js +29 -21
- package/package.json +1 -1
- package/proflow.module.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @tomflow/proflow-platform-cli
|
|
2
2
|
|
|
3
|
+
## 0.1.41
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden Deployment Closeout: fail closed on partial installs and package-manager conflicts, normalize Git workspace isolation, make Dev Tunnel status fast and resumable without fabricating login failures, and align Browser Extension setup/conformance behavior.
|
|
8
|
+
|
|
9
|
+
## 0.1.40
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Ignore governed non-Module tool packages during Registry module discovery so Fresh install remains fixed at 23 active modules.
|
|
14
|
+
|
|
3
15
|
## 0.1.39
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -6,7 +6,7 @@ export declare const behaviorAdapter: {
|
|
|
6
6
|
readonly ok: true;
|
|
7
7
|
readonly status: "SUCCEEDED";
|
|
8
8
|
readonly moduleRef: "platform-cli";
|
|
9
|
-
readonly moduleVersion: "0.1.
|
|
9
|
+
readonly moduleVersion: "0.1.40";
|
|
10
10
|
};
|
|
11
11
|
observedEffects: never[];
|
|
12
12
|
}>;
|
|
@@ -16,7 +16,7 @@ export declare const behaviorAdapter: {
|
|
|
16
16
|
readonly ok: true;
|
|
17
17
|
readonly status: "SUCCEEDED";
|
|
18
18
|
readonly moduleRef: "platform-cli";
|
|
19
|
-
readonly moduleVersion: "0.1.
|
|
19
|
+
readonly moduleVersion: "0.1.40";
|
|
20
20
|
};
|
|
21
21
|
observedEffects: never[];
|
|
22
22
|
}>;
|
|
@@ -26,7 +26,7 @@ export declare const behaviorAdapter: {
|
|
|
26
26
|
ok: true;
|
|
27
27
|
status: "SUCCEEDED";
|
|
28
28
|
moduleRef: "platform-cli";
|
|
29
|
-
moduleVersion: "0.1.
|
|
29
|
+
moduleVersion: "0.1.40";
|
|
30
30
|
data: {
|
|
31
31
|
readonly setupStatus: "READY";
|
|
32
32
|
readonly runtimeStatus: "NOT_APPLICABLE";
|
|
@@ -40,7 +40,7 @@ export declare const behaviorAdapter: {
|
|
|
40
40
|
readonly ok: true;
|
|
41
41
|
readonly status: "SUCCEEDED";
|
|
42
42
|
readonly moduleRef: "platform-cli";
|
|
43
|
-
readonly moduleVersion: "0.1.
|
|
43
|
+
readonly moduleVersion: "0.1.40";
|
|
44
44
|
};
|
|
45
45
|
observedEffects: never[];
|
|
46
46
|
}>;
|
|
@@ -50,7 +50,7 @@ export declare const behaviorAdapter: {
|
|
|
50
50
|
ok: true;
|
|
51
51
|
status: "SUCCEEDED";
|
|
52
52
|
moduleRef: "platform-cli";
|
|
53
|
-
moduleVersion: "0.1.
|
|
53
|
+
moduleVersion: "0.1.40";
|
|
54
54
|
data: {
|
|
55
55
|
docs: string;
|
|
56
56
|
};
|
|
@@ -63,7 +63,7 @@ export declare const behaviorAdapter: {
|
|
|
63
63
|
readonly ok: true;
|
|
64
64
|
readonly status: "SUCCEEDED";
|
|
65
65
|
readonly moduleRef: "platform-cli";
|
|
66
|
-
readonly moduleVersion: "0.1.
|
|
66
|
+
readonly moduleVersion: "0.1.40";
|
|
67
67
|
};
|
|
68
68
|
observedEffects: never[];
|
|
69
69
|
}>;
|
|
@@ -73,7 +73,7 @@ export declare const behaviorAdapter: {
|
|
|
73
73
|
readonly ok: true;
|
|
74
74
|
readonly status: "SUCCEEDED";
|
|
75
75
|
readonly moduleRef: "platform-cli";
|
|
76
|
-
readonly moduleVersion: "0.1.
|
|
76
|
+
readonly moduleVersion: "0.1.40";
|
|
77
77
|
};
|
|
78
78
|
observedEffects: never[];
|
|
79
79
|
}>;
|
|
@@ -3,7 +3,7 @@ export declare const descriptor: {
|
|
|
3
3
|
readonly contractVersion: "1.0.0";
|
|
4
4
|
readonly moduleRef: "platform-cli";
|
|
5
5
|
readonly packageName: "@tomflow/proflow-platform-cli";
|
|
6
|
-
readonly moduleVersion: "0.1.
|
|
6
|
+
readonly moduleVersion: "0.1.40";
|
|
7
7
|
readonly kind: "cli";
|
|
8
8
|
readonly templateVersion: "1.0.0";
|
|
9
9
|
readonly platformCompatibility: ">=1.0.0 <2.0.0";
|
|
@@ -3,7 +3,7 @@ export const descriptor = {
|
|
|
3
3
|
contractVersion: "1.0.0",
|
|
4
4
|
moduleRef: "platform-cli",
|
|
5
5
|
packageName: "@tomflow/proflow-platform-cli",
|
|
6
|
-
moduleVersion: "0.1.
|
|
6
|
+
moduleVersion: "0.1.40",
|
|
7
7
|
kind: "cli",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/cli.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile, realpath, stat } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema,
|
|
4
|
+
import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
|
|
5
5
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
6
|
+
import { FROZEN_DEPLOYMENT_INSTALL_ORDER } from "./deployment-order.js";
|
|
6
7
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
7
8
|
import { InstalledModuleCatalog } from "./discovery/installed.js";
|
|
8
9
|
import { PlatformError } from "./errors.js";
|
|
9
|
-
import { observeWorkspaceInstalledVersion, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
|
|
10
|
+
import { observeWorkspaceInstalledVersion, preflightWorkspacePackageManager, removeWorkspacePackages, syncWorkspacePackages, } from "./install/package-manager.js";
|
|
10
11
|
import { cleanOwnedPnpmPolicy, observeMinimumReleaseAgeExclude, recordPnpmPolicyOwnership, } from "./install/pnpm-policy.js";
|
|
11
12
|
import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, startModulesThin, stopModulesThin, uninstallModulesThin, } from "./lifecycle/index.js";
|
|
13
|
+
import { ensureWorkspaceStateIsGitIgnored } from "./persistence/git-isolation.js";
|
|
12
14
|
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
13
15
|
import { reportProgress } from "./progress.js";
|
|
14
16
|
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
@@ -23,6 +25,25 @@ const COMMANDS = [
|
|
|
23
25
|
"start",
|
|
24
26
|
"stop",
|
|
25
27
|
];
|
|
28
|
+
function editDistance(left, right) {
|
|
29
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
30
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
31
|
+
const current = [leftIndex];
|
|
32
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
33
|
+
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) +
|
|
34
|
+
(left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
35
|
+
}
|
|
36
|
+
previous.splice(0, previous.length, ...current);
|
|
37
|
+
}
|
|
38
|
+
return previous[right.length] ?? Number.POSITIVE_INFINITY;
|
|
39
|
+
}
|
|
40
|
+
function commandSuggestion(raw) {
|
|
41
|
+
const ranked = COMMANDS.map((command) => ({
|
|
42
|
+
command,
|
|
43
|
+
distance: editDistance(raw.toLowerCase(), command),
|
|
44
|
+
})).sort((left, right) => left.distance - right.distance);
|
|
45
|
+
return ranked[0] && ranked[0].distance <= 2 ? ranked[0].command : undefined;
|
|
46
|
+
}
|
|
26
47
|
function parseArgs(argv) {
|
|
27
48
|
let workspace, moduleRef;
|
|
28
49
|
let special;
|
|
@@ -68,8 +89,12 @@ function parseArgs(argv) {
|
|
|
68
89
|
if (positional.length !== 1)
|
|
69
90
|
throw new PlatformError("INVALID_REQUEST", "commands accept no positional arguments");
|
|
70
91
|
const raw = positional[0] ?? "";
|
|
71
|
-
if (!COMMANDS.includes(raw))
|
|
72
|
-
|
|
92
|
+
if (!COMMANDS.includes(raw)) {
|
|
93
|
+
const suggestion = commandSuggestion(raw);
|
|
94
|
+
throw new PlatformError("INVALID_REQUEST", suggestion
|
|
95
|
+
? `未知命令 ${raw}。你是否想运行 platform ${suggestion}?`
|
|
96
|
+
: `未知命令 ${raw}。运行 platform --help 查看可用命令。`);
|
|
97
|
+
}
|
|
73
98
|
const command = raw;
|
|
74
99
|
if (moduleRef !== undefined && command !== "setup" && command !== "docs")
|
|
75
100
|
throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup or docs");
|
|
@@ -132,17 +157,24 @@ function batchStatus(result) {
|
|
|
132
157
|
return "ACTION_REQUIRED";
|
|
133
158
|
return "FAILED";
|
|
134
159
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
160
|
+
function missingFrozenModules(modules) {
|
|
161
|
+
const observed = new Set(modules.map((module) => module.moduleRef));
|
|
162
|
+
const hasCurrentPlatformSurface = FROZEN_DEPLOYMENT_INSTALL_ORDER.some((moduleRef) => observed.has(moduleRef));
|
|
163
|
+
if (!hasCurrentPlatformSurface)
|
|
164
|
+
return [];
|
|
165
|
+
return FROZEN_DEPLOYMENT_INSTALL_ORDER.filter((moduleRef) => !observed.has(moduleRef));
|
|
166
|
+
}
|
|
167
|
+
function assertInstalledModules(modules) {
|
|
168
|
+
if (modules.length === 0)
|
|
169
|
+
throw new PlatformError("PLATFORM_NOT_INSTALLED", "ProFlow 尚未安装。请先运行 platform install。");
|
|
170
|
+
const missing = missingFrozenModules(modules);
|
|
171
|
+
if (missing.length > 0)
|
|
172
|
+
throw new PlatformError("PLATFORM_INSTALL_INCOMPLETE", `ProFlow 安装不完整,缺少 ${missing.length} 个核心模块。请重新运行 platform install。`);
|
|
173
|
+
}
|
|
174
|
+
async function collectModuleStatuses(catalog, modules, root) {
|
|
143
175
|
const observed = await observeStatuses(catalog, modules, root);
|
|
144
176
|
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
145
|
-
|
|
177
|
+
return observed.map((item) => {
|
|
146
178
|
if (item.result.status !== "SUCCEEDED")
|
|
147
179
|
throw new PlatformError("COMMAND_FAILED", `module ${item.moduleRef} status observation did not return SUCCEEDED`);
|
|
148
180
|
const parsed = moduleStatusObservationSchema.safeParse(item.result.data);
|
|
@@ -164,13 +196,54 @@ async function handleStatus(root, runtime) {
|
|
|
164
196
|
: { externalAvailabilityClaim: item.externalAvailabilityClaim }),
|
|
165
197
|
};
|
|
166
198
|
});
|
|
199
|
+
}
|
|
200
|
+
async function handleStatus(root, runtime) {
|
|
201
|
+
reportProgress(runtime.onProgress, {
|
|
202
|
+
command: "status",
|
|
203
|
+
phase: "status",
|
|
204
|
+
status: "STARTED",
|
|
205
|
+
message: "正在读取模块状态",
|
|
206
|
+
});
|
|
207
|
+
const { catalog, modules } = await buildContext(root);
|
|
208
|
+
if (modules.length === 0) {
|
|
209
|
+
reportProgress(runtime.onProgress, {
|
|
210
|
+
command: "status",
|
|
211
|
+
phase: "status",
|
|
212
|
+
status: "SUCCEEDED",
|
|
213
|
+
message: "ProFlow 尚未安装",
|
|
214
|
+
});
|
|
215
|
+
return outcome("status", "SUCCEEDED", root, {
|
|
216
|
+
installed: false,
|
|
217
|
+
installState: "NOT_INSTALLED",
|
|
218
|
+
modules: [],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
const missing = missingFrozenModules(modules);
|
|
222
|
+
if (missing.length > 0) {
|
|
223
|
+
reportProgress(runtime.onProgress, {
|
|
224
|
+
command: "status",
|
|
225
|
+
phase: "status",
|
|
226
|
+
status: "SUCCEEDED",
|
|
227
|
+
message: `ProFlow 安装不完整,缺少 ${missing.length} 个核心模块`,
|
|
228
|
+
});
|
|
229
|
+
return outcome("status", "SUCCEEDED", root, {
|
|
230
|
+
installed: true,
|
|
231
|
+
installState: "INCOMPLETE",
|
|
232
|
+
missingModules: missing,
|
|
233
|
+
modules: [],
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
const output = await collectModuleStatuses(catalog, modules, root);
|
|
167
237
|
reportProgress(runtime.onProgress, {
|
|
168
238
|
command: "status",
|
|
169
239
|
phase: "status",
|
|
170
240
|
status: "SUCCEEDED",
|
|
171
241
|
message: `已读取 ${output.length} 个模块状态`,
|
|
172
242
|
});
|
|
173
|
-
return outcome("status", "SUCCEEDED", root, {
|
|
243
|
+
return outcome("status", "SUCCEEDED", root, {
|
|
244
|
+
installed: true,
|
|
245
|
+
modules: output,
|
|
246
|
+
});
|
|
174
247
|
}
|
|
175
248
|
async function handleDocs(root, parsed, runtime) {
|
|
176
249
|
reportProgress(runtime.onProgress, {
|
|
@@ -268,6 +341,7 @@ async function handleInstall(root, runtime) {
|
|
|
268
341
|
status: "STARTED",
|
|
269
342
|
message: "正在校验 Workspace",
|
|
270
343
|
});
|
|
344
|
+
const gitIsolation = await ensureWorkspaceStateIsGitIgnored(root);
|
|
271
345
|
const metadata = await ensureWorkspaceMetadata(root);
|
|
272
346
|
reportProgress(runtime.onProgress, {
|
|
273
347
|
command: "install",
|
|
@@ -298,6 +372,7 @@ async function handleInstall(root, runtime) {
|
|
|
298
372
|
command: "install",
|
|
299
373
|
phase: "registry",
|
|
300
374
|
kind: "detail",
|
|
375
|
+
retention: "REPLACE",
|
|
301
376
|
status: "SUCCEEDED",
|
|
302
377
|
current,
|
|
303
378
|
total,
|
|
@@ -352,6 +427,7 @@ async function handleInstall(root, runtime) {
|
|
|
352
427
|
await recordPnpmPolicyOwnership(root, pnpmPolicyBefore);
|
|
353
428
|
await validateInstalledPackageSet(root, discovered.candidates, previousManaged);
|
|
354
429
|
const { catalog, modules } = await buildContext(root);
|
|
430
|
+
assertInstalledModules(modules);
|
|
355
431
|
reportProgress(runtime.onProgress, {
|
|
356
432
|
command: "install",
|
|
357
433
|
phase: "validation",
|
|
@@ -368,11 +444,12 @@ async function handleInstall(root, runtime) {
|
|
|
368
444
|
version: item.moduleVersion,
|
|
369
445
|
})),
|
|
370
446
|
workspace: metadata,
|
|
447
|
+
gitIsolation,
|
|
371
448
|
modules: moduleInstall,
|
|
372
449
|
next: "platform status",
|
|
373
450
|
});
|
|
374
451
|
}
|
|
375
|
-
const usefulPackageOutput = /(?:
|
|
452
|
+
const usefulPackageOutput = /(?:warn|warning|error|ERR_)/i;
|
|
376
453
|
function reportPackageManagerOutput(runtime, command, output) {
|
|
377
454
|
if (!usefulPackageOutput.test(output.line))
|
|
378
455
|
return;
|
|
@@ -415,6 +492,8 @@ async function workspaceProFlowDependencies(root) {
|
|
|
415
492
|
}
|
|
416
493
|
async function handleUninstall(root, runtime) {
|
|
417
494
|
const packageNames = await workspaceProFlowDependencies(root);
|
|
495
|
+
if (packageNames.length > 0)
|
|
496
|
+
await preflightWorkspacePackageManager(root, runtime.executableAvailable);
|
|
418
497
|
const { catalog, modules } = await buildContext(root);
|
|
419
498
|
const moduleUninstall = await uninstallModulesThin(catalog, modules, root, runtime.onProgress);
|
|
420
499
|
if (!moduleUninstall.completed)
|
|
@@ -450,6 +529,7 @@ async function handleSetup(root, parsed, runtime) {
|
|
|
450
529
|
message: "正在分析模块配置",
|
|
451
530
|
});
|
|
452
531
|
const { catalog, modules } = await buildContext(root);
|
|
532
|
+
assertInstalledModules(modules);
|
|
453
533
|
const interaction = runtime.setupInteraction;
|
|
454
534
|
const inputByModule = new Map();
|
|
455
535
|
const approvedSteps = new Set();
|
|
@@ -475,24 +555,17 @@ async function handleSetup(root, parsed, runtime) {
|
|
|
475
555
|
const action = actionable?.result.actionRequired;
|
|
476
556
|
if (!actionable || !action)
|
|
477
557
|
break;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (typeof item === "string")
|
|
485
|
-
return [item];
|
|
486
|
-
if (isRecord(item) && typeof item.id === "string")
|
|
487
|
-
return [item.id];
|
|
488
|
-
return [];
|
|
489
|
-
});
|
|
490
|
-
}
|
|
558
|
+
const actionData = isRecord(actionable.result.data)
|
|
559
|
+
? actionable.result.data
|
|
560
|
+
: undefined;
|
|
561
|
+
const options = Array.isArray(actionData?.candidates)
|
|
562
|
+
? actionData.candidates.filter((item) => typeof item === "string")
|
|
563
|
+
: undefined;
|
|
491
564
|
const collected = await interaction.collect({
|
|
492
565
|
moduleRef: actionable.moduleRef,
|
|
493
566
|
action: action.action,
|
|
494
567
|
description: action.description,
|
|
495
|
-
...(
|
|
568
|
+
...(options?.length ? { options } : {}),
|
|
496
569
|
});
|
|
497
570
|
if (!collected)
|
|
498
571
|
break;
|
|
@@ -516,8 +589,10 @@ async function handleSetup(root, parsed, runtime) {
|
|
|
516
589
|
status: result.completed ? "SUCCEEDED" : "ACTION_REQUIRED",
|
|
517
590
|
message: result.completed ? "模块配置已就绪" : "配置清单已生成",
|
|
518
591
|
});
|
|
592
|
+
const moduleStatuses = await collectModuleStatuses(catalog, modules, root);
|
|
519
593
|
return outcome("setup", batchStatus(result), root, {
|
|
520
594
|
...result,
|
|
595
|
+
moduleStatuses,
|
|
521
596
|
...(parsed.moduleRef === undefined
|
|
522
597
|
? {}
|
|
523
598
|
: { targetModuleRef: parsed.moduleRef }),
|
|
@@ -525,6 +600,7 @@ async function handleSetup(root, parsed, runtime) {
|
|
|
525
600
|
}
|
|
526
601
|
async function handleStart(root, runtime) {
|
|
527
602
|
const { catalog, modules } = await buildContext(root);
|
|
603
|
+
assertInstalledModules(modules);
|
|
528
604
|
const result = await startModulesThin(catalog, modules, root, runtime.onProgress);
|
|
529
605
|
return outcome("start", batchStatus(result), root, result);
|
|
530
606
|
}
|
|
@@ -628,87 +704,112 @@ function humanTheme(options = {}) {
|
|
|
628
704
|
failure: (value) => paint("red", value),
|
|
629
705
|
};
|
|
630
706
|
}
|
|
707
|
+
function journeyState(entries) {
|
|
708
|
+
if (entries.length === 0 ||
|
|
709
|
+
entries.some((item) => item.setupStatus === "FAILED" || item.runtimeStatus === "FAILED"))
|
|
710
|
+
return "FAILED";
|
|
711
|
+
if (entries.some((item) => item.setupStatus === "ACTION_REQUIRED"))
|
|
712
|
+
return "ACTION_REQUIRED";
|
|
713
|
+
if (entries.some((item) => item.setupStatus === "BLOCKED"))
|
|
714
|
+
return "BLOCKED";
|
|
715
|
+
return entries.every((item) => item.setupStatus === "READY")
|
|
716
|
+
? "READY"
|
|
717
|
+
: "BLOCKED";
|
|
718
|
+
}
|
|
719
|
+
function setupJourney(modules) {
|
|
720
|
+
const byRef = new Map(modules.map((item) => [String(item.moduleRef), item]));
|
|
721
|
+
return [
|
|
722
|
+
{ label: "浏览器扩展", refs: ["execution-browser-extension"] },
|
|
723
|
+
{ label: "远程连接", refs: ["dev-tunnel"] },
|
|
724
|
+
{
|
|
725
|
+
label: "模型服务与 FAST / THINK",
|
|
726
|
+
refs: ["model-provider-api", "model-runtime"],
|
|
727
|
+
},
|
|
728
|
+
].map(({ label, refs }) => {
|
|
729
|
+
const entries = refs.flatMap((ref) => {
|
|
730
|
+
const item = byRef.get(ref);
|
|
731
|
+
return item ? [item] : [];
|
|
732
|
+
});
|
|
733
|
+
const issues = entries.flatMap((item) => Array.isArray(item.issues) ? item.issues.filter(isRecord) : []);
|
|
734
|
+
return {
|
|
735
|
+
label,
|
|
736
|
+
state: journeyState(entries),
|
|
737
|
+
modules: entries,
|
|
738
|
+
...(issues[0] ? { issue: issues[0] } : {}),
|
|
739
|
+
};
|
|
740
|
+
});
|
|
741
|
+
}
|
|
631
742
|
function renderStatus(data, theme) {
|
|
632
743
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
633
|
-
return "
|
|
634
|
-
const setupLabels = {
|
|
635
|
-
READY: "已就绪",
|
|
636
|
-
ACTION_REQUIRED: "需要操作",
|
|
637
|
-
BLOCKED: "等待依赖",
|
|
638
|
-
FAILED: "失败",
|
|
639
|
-
};
|
|
640
|
-
const runtimeLabels = {
|
|
641
|
-
RUNNING: "运行中",
|
|
642
|
-
STOPPED: "已停止",
|
|
643
|
-
FAILED: "运行失败",
|
|
644
|
-
NOT_APPLICABLE: "无独立进程",
|
|
645
|
-
};
|
|
744
|
+
return "无法读取状态。";
|
|
646
745
|
const modules = data.modules.filter(isRecord);
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
return
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
lines.push(
|
|
746
|
+
if (data.installState === "INCOMPLETE") {
|
|
747
|
+
const missing = Array.isArray(data.missingModules)
|
|
748
|
+
? data.missingModules.filter((item) => typeof item === "string")
|
|
749
|
+
: [];
|
|
750
|
+
return [
|
|
751
|
+
theme.title("ProFlow 状态"),
|
|
752
|
+
"",
|
|
753
|
+
theme.warning("ProFlow 安装不完整。"),
|
|
754
|
+
...(missing.length > 0 ? [`缺少 ${missing.length} 个核心模块。`] : []),
|
|
755
|
+
`下一步:${theme.command("platform install")}`,
|
|
756
|
+
"",
|
|
757
|
+
"PLATFORM_READY=NO",
|
|
758
|
+
].join("\n");
|
|
759
|
+
}
|
|
760
|
+
if (data.installed === false || modules.length === 0)
|
|
761
|
+
return [
|
|
762
|
+
theme.title("ProFlow 状态"),
|
|
763
|
+
"",
|
|
764
|
+
theme.warning("ProFlow 尚未安装。"),
|
|
765
|
+
`下一步:${theme.command("platform install")}`,
|
|
766
|
+
"",
|
|
767
|
+
"PLATFORM_READY=NO",
|
|
768
|
+
].join("\n");
|
|
769
|
+
const journey = setupJourney(modules);
|
|
770
|
+
const completed = journey.filter((step) => step.state === "READY").length;
|
|
771
|
+
const current = journey.find((step) => step.state !== "READY");
|
|
772
|
+
const running = modules.filter((item) => item.runtimeStatus === "RUNNING");
|
|
773
|
+
const platformReady = modules.every((item) => item.setupStatus === "READY" && item.runtimeStatus !== "FAILED");
|
|
774
|
+
const internalProblem = modules.find((item) => item.setupStatus !== "READY" || item.runtimeStatus === "FAILED");
|
|
775
|
+
const lines = [
|
|
776
|
+
theme.title("ProFlow 状态"),
|
|
777
|
+
"",
|
|
778
|
+
`${theme.section("配置进度")} ${completed}/3`,
|
|
779
|
+
...journey.map((step, index) => {
|
|
780
|
+
const marker = step.state === "READY"
|
|
781
|
+
? theme.success("✓")
|
|
782
|
+
: step === current
|
|
783
|
+
? step.state === "FAILED"
|
|
784
|
+
? theme.failure("✕")
|
|
785
|
+
: theme.warning("→")
|
|
786
|
+
: theme.muted("○");
|
|
787
|
+
const label = step.state === "READY"
|
|
788
|
+
? "已完成"
|
|
789
|
+
: step === current
|
|
790
|
+
? step.state === "FAILED"
|
|
791
|
+
? "验证失败"
|
|
792
|
+
: "当前步骤"
|
|
793
|
+
: "后续步骤";
|
|
794
|
+
return `${marker} ${index + 1}/3 ${step.label} · ${label}`;
|
|
795
|
+
}),
|
|
796
|
+
];
|
|
797
|
+
if (current) {
|
|
798
|
+
lines.push("", theme.section(`当前处理:${current.label}`));
|
|
799
|
+
if (current.issue?.message)
|
|
800
|
+
lines.push(`原因:${String(current.issue.message)}`);
|
|
801
|
+
lines.push(`下一步:${theme.command("platform setup")}`);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
lines.push("", theme.success("三个核心配置步骤均已完成。"));
|
|
805
|
+
if (internalProblem) {
|
|
806
|
+
const issue = Array.isArray(internalProblem.issues)
|
|
807
|
+
? internalProblem.issues.filter(isRecord)[0]
|
|
808
|
+
: undefined;
|
|
809
|
+
lines.push(theme.warning("仍有内部能力尚未闭环。"), ...(issue?.message ? [`原因:${String(issue.message)}`] : []), `下一步:${theme.command("platform setup")}`);
|
|
702
810
|
}
|
|
703
|
-
|
|
704
|
-
};
|
|
705
|
-
compact("运行中", running, "●");
|
|
706
|
-
compact("已就绪", healthy, "○");
|
|
707
|
-
const ready = healthy.length + running.length;
|
|
708
|
-
const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED").length;
|
|
709
|
-
const failed = failedModules.length;
|
|
710
|
-
const blocked = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED").length;
|
|
711
|
-
lines.push(theme.section("汇总"), `${theme.success(`${ready} 配置已完成`)} · ${theme.warning(`${action} 根阻塞`)} · ${theme.info(`${blocked} 下游等待`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`} · ${running.length} 个真实进程运行中`, `PLATFORM_READY=${action === 0 && blocked === 0 && failed === 0 ? "YES" : "NO"}`);
|
|
811
|
+
}
|
|
812
|
+
lines.push("", `${running.length} 个真实服务进程运行中`, `PLATFORM_READY=${platformReady ? "YES" : "NO"}`);
|
|
712
813
|
return lines.join("\n");
|
|
713
814
|
}
|
|
714
815
|
function renderTerminalMarkdown(source, theme) {
|
|
@@ -742,15 +843,19 @@ function renderDocs(data, theme) {
|
|
|
742
843
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
743
844
|
return "未发现模块文档。";
|
|
744
845
|
if (data.indexOnly === true) {
|
|
745
|
-
const modules = data.modules.filter(isRecord);
|
|
746
846
|
return [
|
|
747
847
|
theme.title("ProFlow 帮助"),
|
|
748
848
|
"",
|
|
749
|
-
"
|
|
750
|
-
` ${theme.command("platform docs --module <模块名>")}`,
|
|
849
|
+
"ProFlow 会自动完成内部模块的安装、依赖准备和验证。",
|
|
751
850
|
"",
|
|
752
|
-
theme.section(
|
|
753
|
-
|
|
851
|
+
theme.section("推荐流程"),
|
|
852
|
+
` ${theme.command("platform install")}`,
|
|
853
|
+
` ${theme.command("platform setup")}`,
|
|
854
|
+
` ${theme.command("platform start")}`,
|
|
855
|
+
"",
|
|
856
|
+
theme.section("当前需要帮助?"),
|
|
857
|
+
` 运行 ${theme.command("platform status")} 查看唯一的下一步。`,
|
|
858
|
+
` 配置未完成时运行 ${theme.command("platform setup")} 继续向导。`,
|
|
754
859
|
].join("\n");
|
|
755
860
|
}
|
|
756
861
|
const lines = [theme.title("ProFlow 模块文档")];
|
|
@@ -794,7 +899,10 @@ function renderSetup(data, theme) {
|
|
|
794
899
|
else
|
|
795
900
|
needsAction += 1;
|
|
796
901
|
lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`);
|
|
797
|
-
const
|
|
902
|
+
const planData = isRecord(raw.result.data) && Array.isArray(raw.result.data.steps)
|
|
903
|
+
? { steps: raw.result.data.steps }
|
|
904
|
+
: raw.result.data;
|
|
905
|
+
const plan = moduleSetupPlanDataSchema.safeParse(planData);
|
|
798
906
|
if (plan.success) {
|
|
799
907
|
const currentStep = plan.data.steps.find((step) => step.state === "BLOCKED") ??
|
|
800
908
|
plan.data.steps.find((step) => step.state === "TODO");
|
|
@@ -841,19 +949,38 @@ function renderSetup(data, theme) {
|
|
|
841
949
|
const dependencyBlockers = Array.isArray(data.blockers)
|
|
842
950
|
? data.blockers.filter(isRecord)
|
|
843
951
|
: [];
|
|
844
|
-
for (const item of dependencyBlockers) {
|
|
952
|
+
for (const item of dependencyBlockers.slice(0, 1)) {
|
|
845
953
|
const moduleRef = String(item.moduleRef ?? "unknown");
|
|
846
954
|
if (renderedModuleRefs.has(moduleRef))
|
|
847
955
|
continue;
|
|
848
956
|
blocked += 1;
|
|
849
|
-
lines.push(`${theme.info("◇")} ${theme.section(moduleRef)}`, ` 原因:${typeof item.reason === "string" ? item.reason : "等待上游 Module 就绪"}`, ` 下一步:${theme.command(
|
|
957
|
+
lines.push(`${theme.info("◇")} ${theme.section(setupModuleLabel(moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "等待上游 Module 就绪"}`, ` 下一步:${theme.command("platform setup")}`, "");
|
|
850
958
|
}
|
|
851
959
|
const targetModuleRef = typeof data.targetModuleRef === "string" ? data.targetModuleRef : undefined;
|
|
852
960
|
if (needsAction === 0 && blocked === 0)
|
|
853
961
|
lines.push(targetModuleRef
|
|
854
962
|
? `${setupModuleLabel(targetModuleRef)}配置已就绪。`
|
|
855
963
|
: "全部模块均已就绪。");
|
|
856
|
-
|
|
964
|
+
const snapshot = Array.isArray(data.moduleStatuses)
|
|
965
|
+
? data.moduleStatuses.filter(isRecord)
|
|
966
|
+
: [];
|
|
967
|
+
if (snapshot.length > 0) {
|
|
968
|
+
const journey = setupJourney(snapshot);
|
|
969
|
+
const completed = journey.filter((step) => step.state === "READY").length;
|
|
970
|
+
const current = journey.find((step) => step.state !== "READY");
|
|
971
|
+
lines.push(theme.section("配置进度"), `${completed}/3 个核心步骤已完成`, ...(current
|
|
972
|
+
? [
|
|
973
|
+
`当前处理:${current.label}`,
|
|
974
|
+
...(current.issue?.message
|
|
975
|
+
? [`原因:${String(current.issue.message)}`]
|
|
976
|
+
: []),
|
|
977
|
+
`下一步:${theme.command("platform setup")}`,
|
|
978
|
+
]
|
|
979
|
+
: [theme.success("全部核心配置已完成。")]));
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
|
|
983
|
+
}
|
|
857
984
|
return lines.join("\n").trimEnd();
|
|
858
985
|
}
|
|
859
986
|
function renderHelp(theme, command) {
|
|
@@ -871,12 +998,12 @@ function renderHelp(theme, command) {
|
|
|
871
998
|
description: "查看模块配置与运行状态",
|
|
872
999
|
},
|
|
873
1000
|
setup: {
|
|
874
|
-
usage: "platform setup [--workspace <路径>]
|
|
875
|
-
description: "
|
|
1001
|
+
usage: "platform setup [--workspace <路径>]",
|
|
1002
|
+
description: "按顺序完成当前唯一配置步骤",
|
|
876
1003
|
},
|
|
877
1004
|
docs: {
|
|
878
|
-
usage: "platform docs [--workspace <路径>]
|
|
879
|
-
description: "
|
|
1005
|
+
usage: "platform docs [--workspace <路径>]",
|
|
1006
|
+
description: "查看当前使用帮助",
|
|
880
1007
|
},
|
|
881
1008
|
start: {
|
|
882
1009
|
usage: "platform start [--workspace <路径>]",
|
|
@@ -912,12 +1039,11 @@ function renderHelp(theme, command) {
|
|
|
912
1039
|
"",
|
|
913
1040
|
theme.section("公共参数"),
|
|
914
1041
|
" --workspace <路径> 指定 ProFlow Workspace",
|
|
915
|
-
" setup --module <模块名> 只查看指定模块的配置步骤",
|
|
916
1042
|
" -h, --help 显示帮助",
|
|
917
1043
|
" -v, --version 显示版本",
|
|
918
1044
|
"",
|
|
919
1045
|
theme.section("推荐流程"),
|
|
920
|
-
` ${theme.command("install →
|
|
1046
|
+
` ${theme.command("install → setup → start")}`,
|
|
921
1047
|
"",
|
|
922
1048
|
theme.section("配置入口"),
|
|
923
1049
|
` ${theme.command("platform setup")}`,
|
|
@@ -951,38 +1077,21 @@ export function renderHumanResult(result, options = {}) {
|
|
|
951
1077
|
["dev-tunnel", 1],
|
|
952
1078
|
["model-provider-api", 2],
|
|
953
1079
|
]);
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
icon: "◆",
|
|
960
|
-
tone: theme.warning,
|
|
961
|
-
},
|
|
962
|
-
{ status: "BLOCKED", title: "下游等待", icon: "◇", tone: theme.info },
|
|
963
|
-
];
|
|
964
|
-
const lines = [theme.failure("平台未启动:存在未就绪模块"), ""];
|
|
965
|
-
for (const group of groups) {
|
|
966
|
-
const entries = blockers
|
|
967
|
-
.filter((item) => item.setupStatus === group.status)
|
|
1080
|
+
const lines = [theme.failure("平台未启动:配置尚未完成"), ""];
|
|
1081
|
+
let current;
|
|
1082
|
+
for (const status of ["FAILED", "ACTION_REQUIRED", "BLOCKED"]) {
|
|
1083
|
+
current = blockers
|
|
1084
|
+
.filter((item) => item.setupStatus === status)
|
|
968
1085
|
.sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
|
|
969
|
-
(productOrder.get(String(b.moduleRef)) ?? 100));
|
|
970
|
-
if (
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
lines.push(` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
|
|
977
|
-
}
|
|
978
|
-
lines.push("");
|
|
1086
|
+
(productOrder.get(String(b.moduleRef)) ?? 100))[0];
|
|
1087
|
+
if (current)
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
if (current) {
|
|
1091
|
+
const moduleRef = String(current.moduleRef);
|
|
1092
|
+
lines.push(`${current.setupStatus === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`, `原因:${typeof current.reason === "string" ? current.reason : "当前配置尚未完成"}`, "");
|
|
979
1093
|
}
|
|
980
|
-
|
|
981
|
-
? result.data.results.filter((item) => isRecord(item) &&
|
|
982
|
-
isRecord(item.result) &&
|
|
983
|
-
item.result.status === "SUCCEEDED").length - blockers.length
|
|
984
|
-
: 0;
|
|
985
|
-
lines.push(theme.section("检查汇总"), `${Math.max(0, readyCount)} 配置已完成 · ${blockers.filter((item) => item.setupStatus === "ACTION_REQUIRED").length} 根阻塞 · ${blockers.filter((item) => item.setupStatus === "BLOCKED").length} 下游等待 · 0 个真实进程启动`, "PLATFORM_READY=NO", `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
|
|
1094
|
+
lines.push("PLATFORM_READY=NO", `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
|
|
986
1095
|
return lines.join("\n");
|
|
987
1096
|
}
|
|
988
1097
|
}
|
|
@@ -992,7 +1101,8 @@ export function renderHumanResult(result, options = {}) {
|
|
|
992
1101
|
`${theme.failure("│")} ${theme.section(result.error.code)}`,
|
|
993
1102
|
`${theme.failure("│")} ${result.error.message}`,
|
|
994
1103
|
theme.failure("╰─ 请根据提示修复后重试"),
|
|
995
|
-
...(result.error.category === "USAGE"
|
|
1104
|
+
...(result.error.category === "USAGE" &&
|
|
1105
|
+
!result.error.message.includes("你是否想运行")
|
|
996
1106
|
? ["", renderHelp(theme, result.error.helpCommand)]
|
|
997
1107
|
: []),
|
|
998
1108
|
].join("\n");
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "WORKSPACE_NOT_FOUND", "WORKSPACE_INSTANCE_INVALID", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "UNINSTALL_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "COMMAND_FAILED"];
|
|
1
|
+
export declare const platformErrorCodes: readonly ["INVALID_REQUEST", "DESCRIPTOR_INVALID", "DUPLICATE_IDENTITY", "DEPENDENCY_UNRESOLVED", "DEPENDENCY_INCOMPATIBLE", "DEPENDENCY_CYCLE", "WORKSPACE_NOT_FOUND", "WORKSPACE_INSTANCE_INVALID", "WORKSPACE_STATE_NOT_ISOLATED", "PACKAGE_MANAGER_UNSUPPORTED", "PACKAGE_MANAGER_CONFLICT", "PACKAGE_MANAGER_UNAVAILABLE", "UNINSTALL_FAILED", "REGISTRY_UNAVAILABLE", "REGISTRY_AUTH_REQUIRED", "REGISTRY_RESPONSE_INVALID", "PACKAGE_NOT_FOUND", "PACKAGE_NOT_PROFLOW", "PLATFORM_NOT_INSTALLED", "PLATFORM_INSTALL_INCOMPLETE", "COMMAND_FAILED"];
|
|
2
2
|
export type PlatformErrorCode = (typeof platformErrorCodes)[number];
|
|
3
3
|
export declare class PlatformError extends Error {
|
|
4
4
|
readonly code: PlatformErrorCode;
|
package/dist/src/errors.js
CHANGED
|
@@ -7,6 +7,7 @@ export const platformErrorCodes = [
|
|
|
7
7
|
"DEPENDENCY_CYCLE",
|
|
8
8
|
"WORKSPACE_NOT_FOUND",
|
|
9
9
|
"WORKSPACE_INSTANCE_INVALID",
|
|
10
|
+
"WORKSPACE_STATE_NOT_ISOLATED",
|
|
10
11
|
"PACKAGE_MANAGER_UNSUPPORTED",
|
|
11
12
|
"PACKAGE_MANAGER_CONFLICT",
|
|
12
13
|
"PACKAGE_MANAGER_UNAVAILABLE",
|
|
@@ -16,6 +17,8 @@ export const platformErrorCodes = [
|
|
|
16
17
|
"REGISTRY_RESPONSE_INVALID",
|
|
17
18
|
"PACKAGE_NOT_FOUND",
|
|
18
19
|
"PACKAGE_NOT_PROFLOW",
|
|
20
|
+
"PLATFORM_NOT_INSTALLED",
|
|
21
|
+
"PLATFORM_INSTALL_INCOMPLETE",
|
|
19
22
|
"COMMAND_FAILED",
|
|
20
23
|
];
|
|
21
24
|
export class PlatformError extends Error {
|
|
@@ -38,3 +38,4 @@ export declare function removeWorkspacePackages(options: {
|
|
|
38
38
|
onOutput?: (line: PackageCommandOutput) => void;
|
|
39
39
|
}): Promise<WorkspacePackageMutationResult>;
|
|
40
40
|
export declare function observeWorkspaceInstalledVersion(workspaceRoot: string, packageName: string): Promise<string | undefined>;
|
|
41
|
+
export declare function preflightWorkspacePackageManager(workspaceRoot: string, executableAvailable?: (command: string) => boolean): Promise<WorkspacePackageManagerSelection>;
|
|
@@ -157,7 +157,7 @@ export async function syncWorkspacePackages(options) {
|
|
|
157
157
|
}
|
|
158
158
|
await ensureWorkspaceManifest(options.workspaceRoot);
|
|
159
159
|
const runner = options.runner ?? systemPackageCommandRunner();
|
|
160
|
-
const manager = await
|
|
160
|
+
const manager = await preflightWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
|
|
161
161
|
const targets = [...options.packages].sort((a, b) => a.packageName.localeCompare(b.packageName));
|
|
162
162
|
const specs = targets.map((item) => `${item.packageName}@${item.version}`);
|
|
163
163
|
await writeManagedPackageSet(options.workspaceRoot, targets);
|
|
@@ -212,7 +212,7 @@ export async function removeWorkspacePackages(options) {
|
|
|
212
212
|
return { packageManager: "npm", packages: [] };
|
|
213
213
|
}
|
|
214
214
|
const runner = options.runner ?? systemPackageCommandRunner();
|
|
215
|
-
const manager = await
|
|
215
|
+
const manager = await preflightWorkspacePackageManager(options.workspaceRoot, options.executableAvailable ?? findExecutable);
|
|
216
216
|
const args = await batchPackageManagerArgs(runner, manager, options.workspaceRoot, "remove", packageNames);
|
|
217
217
|
try {
|
|
218
218
|
await runner.run(manager.name, args, options.workspaceRoot, options.onOutput);
|
|
@@ -253,7 +253,7 @@ async function ensureWorkspaceManifest(workspaceRoot) {
|
|
|
253
253
|
}
|
|
254
254
|
await atomicWrite(path, `${JSON.stringify({ private: true }, null, 2)}\n`);
|
|
255
255
|
}
|
|
256
|
-
async function
|
|
256
|
+
export async function preflightWorkspacePackageManager(workspaceRoot, executableAvailable = findExecutable) {
|
|
257
257
|
const manager = await readWorkspacePackageManagerSelection(workspaceRoot);
|
|
258
258
|
if (!executableAvailable(manager.name)) {
|
|
259
259
|
throw new PlatformError("PACKAGE_MANAGER_UNAVAILABLE", `workspace package manager ${manager.name} is not available on PATH`);
|
|
@@ -87,6 +87,8 @@ async function runOrdered(catalog, modules, workspaceRoot, command, reverse, rep
|
|
|
87
87
|
reportProgress(reporter, {
|
|
88
88
|
command,
|
|
89
89
|
phase: command,
|
|
90
|
+
kind: "detail",
|
|
91
|
+
retention: "REPLACE",
|
|
90
92
|
current: index + 1,
|
|
91
93
|
total: modulesInOrder.length,
|
|
92
94
|
moduleRef: module.moduleRef,
|
|
@@ -100,6 +102,8 @@ async function runOrdered(catalog, modules, workspaceRoot, command, reverse, rep
|
|
|
100
102
|
reportProgress(reporter, {
|
|
101
103
|
command,
|
|
102
104
|
phase: command,
|
|
105
|
+
kind: "detail",
|
|
106
|
+
retention: "REPLACE",
|
|
103
107
|
current: index + 1,
|
|
104
108
|
total: modulesInOrder.length,
|
|
105
109
|
moduleRef: module.moduleRef,
|
|
@@ -222,6 +226,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
222
226
|
reportProgress(reporter, {
|
|
223
227
|
command: "setup",
|
|
224
228
|
phase: "setup",
|
|
229
|
+
kind: "detail",
|
|
230
|
+
retention: "REPLACE",
|
|
225
231
|
current: index + 1,
|
|
226
232
|
total: modulesInOrder.length,
|
|
227
233
|
moduleRef: module.moduleRef,
|
|
@@ -240,6 +246,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
240
246
|
reportProgress(reporter, {
|
|
241
247
|
command: "setup",
|
|
242
248
|
phase: "setup",
|
|
249
|
+
kind: "detail",
|
|
250
|
+
retention: "REPLACE",
|
|
243
251
|
current: index + 1,
|
|
244
252
|
total: modulesInOrder.length,
|
|
245
253
|
moduleRef: module.moduleRef,
|
|
@@ -254,6 +262,8 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
254
262
|
reportProgress(reporter, {
|
|
255
263
|
command: "setup",
|
|
256
264
|
phase: "setup",
|
|
265
|
+
kind: "detail",
|
|
266
|
+
retention: "REPLACE",
|
|
257
267
|
current: index + 1,
|
|
258
268
|
total: modulesInOrder.length,
|
|
259
269
|
moduleRef: module.moduleRef,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { PlatformError } from "../errors.js";
|
|
6
|
+
const execute = promisify(execFile);
|
|
7
|
+
async function git(root, args) {
|
|
8
|
+
try {
|
|
9
|
+
const result = await execute("git", ["-C", root, ...args], {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
});
|
|
12
|
+
return result.stdout.trim();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function ensureWorkspaceStateIsGitIgnored(workspaceRoot) {
|
|
19
|
+
const topLevel = await git(workspaceRoot, ["rev-parse", "--show-toplevel"]);
|
|
20
|
+
const gitDirectoryValue = await git(workspaceRoot, [
|
|
21
|
+
"rev-parse",
|
|
22
|
+
"--absolute-git-dir",
|
|
23
|
+
]);
|
|
24
|
+
if (!topLevel || !gitDirectoryValue)
|
|
25
|
+
return { gitRepository: false, changed: false };
|
|
26
|
+
if (await git(workspaceRoot, ["check-ignore", ".proflow/workspace.json"]))
|
|
27
|
+
return { gitRepository: true, changed: false };
|
|
28
|
+
const [canonicalTopLevel, canonicalWorkspaceRoot] = await Promise.all([
|
|
29
|
+
realpath(topLevel).catch(() => topLevel),
|
|
30
|
+
realpath(workspaceRoot).catch(() => workspaceRoot),
|
|
31
|
+
]);
|
|
32
|
+
const excludePath = resolve(gitDirectoryValue, "info", "exclude");
|
|
33
|
+
const workspaceRelative = relative(canonicalTopLevel, canonicalWorkspaceRoot).replaceAll("\\", "/");
|
|
34
|
+
const pattern = `/${workspaceRelative ? `${workspaceRelative}/` : ""}.proflow/`;
|
|
35
|
+
let existing = "";
|
|
36
|
+
try {
|
|
37
|
+
existing = await readFile(excludePath, "utf8");
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// A new repository may not have an info/exclude file yet.
|
|
41
|
+
}
|
|
42
|
+
if (!existing.split(/\r?\n/).includes(pattern)) {
|
|
43
|
+
await mkdir(dirname(excludePath), { recursive: true });
|
|
44
|
+
const prefix = existing.length === 0 || existing.endsWith("\n")
|
|
45
|
+
? existing
|
|
46
|
+
: `${existing}\n`;
|
|
47
|
+
await writeFile(excludePath, `${prefix}# ProFlow managed workspace state\n${pattern}\n`, "utf8");
|
|
48
|
+
}
|
|
49
|
+
if (!(await git(workspaceRoot, ["check-ignore", ".proflow/workspace.json"])))
|
|
50
|
+
throw new PlatformError("WORKSPACE_STATE_NOT_ISOLATED", "无法确保 .proflow 工作区状态被 Git 忽略;安装已安全停止。");
|
|
51
|
+
return { gitRepository: true, changed: true };
|
|
52
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const PRO_FLOW_SCOPE = "@tomflow";
|
|
2
2
|
export declare const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
|
|
3
3
|
export declare const RETIRED_PRO_FLOW_PACKAGES: Set<string>;
|
|
4
|
+
export declare const PRO_FLOW_TOOL_PACKAGES: Set<string>;
|
|
4
5
|
export interface NpmCommandResult {
|
|
5
6
|
stdout: string;
|
|
6
7
|
stderr: string;
|
|
@@ -28,6 +29,7 @@ export interface RegistryDiscoveryResult {
|
|
|
28
29
|
candidates: RegistryModuleCandidate[];
|
|
29
30
|
rejected: RegistryRejectedPackage[];
|
|
30
31
|
retired: string[];
|
|
32
|
+
tools: string[];
|
|
31
33
|
}
|
|
32
34
|
export declare function systemNpmRunner(): NpmCommandRunner;
|
|
33
35
|
export declare function resolveScopeRegistry(workspaceRoot: string, runner?: NpmCommandRunner): Promise<string>;
|
|
@@ -9,6 +9,9 @@ export const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
|
|
|
9
9
|
export const RETIRED_PRO_FLOW_PACKAGES = new Set([
|
|
10
10
|
"@tomflow/proflow-chatgpt-carrier",
|
|
11
11
|
]);
|
|
12
|
+
export const PRO_FLOW_TOOL_PACKAGES = new Set([
|
|
13
|
+
"@tomflow/proflow-devtunnel-cli",
|
|
14
|
+
]);
|
|
12
15
|
export function systemNpmRunner() {
|
|
13
16
|
return {
|
|
14
17
|
async run(args, cwd, timeoutMs) {
|
|
@@ -46,7 +49,8 @@ export async function discoverRegistryModules(options) {
|
|
|
46
49
|
? await searchPackageNames(options.workspaceRoot, registry, runner)
|
|
47
50
|
: [validateRequestedPackageName(options.packageName)];
|
|
48
51
|
const retired = names.filter((name) => RETIRED_PRO_FLOW_PACKAGES.has(name));
|
|
49
|
-
const
|
|
52
|
+
const tools = names.filter((name) => PRO_FLOW_TOOL_PACKAGES.has(name));
|
|
53
|
+
const activeNames = names.filter((name) => !RETIRED_PRO_FLOW_PACKAGES.has(name) && !PRO_FLOW_TOOL_PACKAGES.has(name));
|
|
50
54
|
options.onSearchComplete?.(activeNames.length);
|
|
51
55
|
const candidates = [];
|
|
52
56
|
const rejected = [];
|
|
@@ -75,7 +79,7 @@ export async function discoverRegistryModules(options) {
|
|
|
75
79
|
await Promise.all(Array.from({ length: Math.min(4, activeNames.length) }, () => worker()));
|
|
76
80
|
candidates.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
77
81
|
rejected.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
78
|
-
return { registry, candidates, rejected, retired };
|
|
82
|
+
return { registry, candidates, rejected, retired, tools };
|
|
79
83
|
}
|
|
80
84
|
async function searchPackageNames(workspaceRoot, registry, runner) {
|
|
81
85
|
const result = await runner.run([
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { cancel, confirm, intro, isCancel, outro, password, select, text, } from "@clack/prompts";
|
|
2
2
|
const stepPrompts = {
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"model-
|
|
6
|
-
"model-runtime": "下一步从真实模型列表选择 FAST 与 THINK。按 Enter 继续。",
|
|
3
|
+
"dev-tunnel": "下一步将检查远程连接;如需登录,会打开 GitHub 授权。是否继续?",
|
|
4
|
+
"model-provider-api": "下一步配置 OpenAI-compatible 模型服务地址。是否继续?",
|
|
5
|
+
"model-runtime": "下一步验证模型能力并配置 FAST 与 THINK。是否继续?",
|
|
7
6
|
};
|
|
8
7
|
function cancelled(value) {
|
|
9
8
|
if (!isCancel(value))
|
|
@@ -71,29 +70,26 @@ async function providerCredential() {
|
|
|
71
70
|
return undefined;
|
|
72
71
|
return { providerCredential: String(value) };
|
|
73
72
|
}
|
|
74
|
-
async function
|
|
73
|
+
async function modelRole(role, modelIds) {
|
|
75
74
|
const options = modelIds.map((id) => ({ value: id, label: id }));
|
|
76
|
-
const
|
|
77
|
-
message:
|
|
78
|
-
|
|
75
|
+
const selected = await select({
|
|
76
|
+
message: role === "fast"
|
|
77
|
+
? "选择 FAST 模型(快速、低延迟)"
|
|
78
|
+
: "选择 THINK 模型(复杂分析与推理)",
|
|
79
79
|
options,
|
|
80
|
+
showInstructions: false,
|
|
80
81
|
});
|
|
81
|
-
if (cancelled(
|
|
82
|
-
return undefined;
|
|
83
|
-
const reasonModel = await autocomplete({
|
|
84
|
-
message: "选择 THINK 模型(复杂分析与推理)",
|
|
85
|
-
placeholder: "输入关键字搜索模型",
|
|
86
|
-
options,
|
|
87
|
-
});
|
|
88
|
-
if (cancelled(reasonModel))
|
|
82
|
+
if (cancelled(selected))
|
|
89
83
|
return undefined;
|
|
90
84
|
const approved = await confirm({
|
|
91
|
-
message:
|
|
85
|
+
message: `确认将 ${String(selected)} 用作 ${role === "fast" ? "FAST" : "THINK"} 模型?`,
|
|
92
86
|
initialValue: true,
|
|
93
87
|
});
|
|
94
88
|
if (cancelled(approved) || approved !== true)
|
|
95
89
|
return undefined;
|
|
96
|
-
return
|
|
90
|
+
return role === "fast"
|
|
91
|
+
? { fastModel: String(selected) }
|
|
92
|
+
: { reasonModel: String(selected) };
|
|
97
93
|
}
|
|
98
94
|
export function createClackSetupInteraction() {
|
|
99
95
|
return {
|
|
@@ -111,14 +107,26 @@ export function createClackSetupInteraction() {
|
|
|
111
107
|
return !cancelled(approved) && approved === true;
|
|
112
108
|
},
|
|
113
109
|
async collect(request) {
|
|
110
|
+
if (request.action === "confirm-browser-developer-mode") {
|
|
111
|
+
const approved = await confirm({
|
|
112
|
+
message: "步骤 1/2:已经在 Chrome 中开启“开发者模式”了吗?",
|
|
113
|
+
initialValue: true,
|
|
114
|
+
});
|
|
115
|
+
if (cancelled(approved) || approved !== true)
|
|
116
|
+
return undefined;
|
|
117
|
+
return { developerModeConfirmed: true };
|
|
118
|
+
}
|
|
114
119
|
if (request.action === "provide-provider-endpoint") {
|
|
115
120
|
return providerEndpoint();
|
|
116
121
|
}
|
|
117
122
|
if (request.action === "provide-provider-credential") {
|
|
118
123
|
return providerCredential();
|
|
119
124
|
}
|
|
120
|
-
if (request.action
|
|
121
|
-
return
|
|
125
|
+
if (request.action === "select-fast-model" && request.options?.length) {
|
|
126
|
+
return modelRole("fast", request.options);
|
|
127
|
+
}
|
|
128
|
+
if (request.action === "select-reason-model" && request.options?.length) {
|
|
129
|
+
return modelRole("reason", request.options);
|
|
122
130
|
}
|
|
123
131
|
return undefined;
|
|
124
132
|
},
|
package/package.json
CHANGED
package/proflow.module.json
CHANGED