@tomflow/proflow-platform-cli 0.1.37 → 0.1.39
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.d.ts +2 -0
- package/dist/src/cli.js +177 -81
- package/dist/src/deployment-order.d.ts +1 -1
- package/dist/src/deployment-order.js +0 -1
- package/dist/src/install/package-manager.js +1 -1
- package/dist/src/lifecycle/dispatch.d.ts +1 -0
- package/dist/src/lifecycle/dispatch.js +8 -1
- package/dist/src/lifecycle/thin.d.ts +1 -1
- package/dist/src/lifecycle/thin.js +47 -51
- package/dist/src/registry/index.d.ts +1 -1
- package/dist/src/registry/index.js +1 -1
- package/dist/src/registry/npm-registry.d.ts +2 -0
- package/dist/src/registry/npm-registry.js +11 -6
- package/dist/src/setup/interaction.d.ts +14 -0
- package/dist/src/setup/interaction.js +126 -0
- package/package.json +4 -3
- 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.39
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add professional setup orchestration, Provider FAST/THINK input, recoverable managed Dev Tunnel provisioning, Browser installation guidance, and precise status diagnostics.
|
|
8
|
+
|
|
9
|
+
## 0.1.38
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Retire the obsolete Carrier package and Browser standalone management CLI, make Platform setup/status preserve owner and dependency semantics, and enforce read-only status observation across the final Real-3 deployment closeout.
|
|
14
|
+
|
|
3
15
|
## 0.1.37
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39";
|
|
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.39",
|
|
7
7
|
kind: "cli",
|
|
8
8
|
templateVersion: "1.0.0",
|
|
9
9
|
platformCompatibility: ">=1.0.0 <2.0.0",
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type PackageCommandRunner } from "./install/package-manager.ts";
|
|
3
3
|
import { type PlatformProgressReporter } from "./progress.ts";
|
|
4
4
|
import { type NpmCommandRunner } from "./registry/index.ts";
|
|
5
|
+
import { type SetupInteraction } from "./setup/interaction.ts";
|
|
5
6
|
declare const COMMANDS: readonly ["install", "uninstall", "status", "setup", "docs", "start", "stop"];
|
|
6
7
|
type Command = (typeof COMMANDS)[number];
|
|
7
8
|
export type CliStatus = "SUCCEEDED" | "ACTION_REQUIRED" | "BLOCKED" | "FAILED";
|
|
@@ -23,6 +24,7 @@ export interface CliRuntimeOptions {
|
|
|
23
24
|
packageRunner?: PackageCommandRunner;
|
|
24
25
|
executableAvailable?: (command: string) => boolean;
|
|
25
26
|
onProgress?: PlatformProgressReporter;
|
|
27
|
+
setupInteraction?: SetupInteraction;
|
|
26
28
|
}
|
|
27
29
|
export declare function runCli(argv: readonly string[], runtime?: CliRuntimeOptions): Promise<CliOutcome>;
|
|
28
30
|
export interface HumanRenderOptions {
|
package/dist/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, } from "@tomflow/proflow-module-contract";
|
|
4
|
+
import { moduleDocsDataSchema, moduleSetupPlanDataSchema, moduleStatusObservationSchema, readModuleSharedFacts, } from "@tomflow/proflow-module-contract";
|
|
5
5
|
import { descriptor as platformCliDescriptor } from "../deployment/descriptor.js";
|
|
6
6
|
import { AutoModuleCatalog, discoverModules } from "./discovery/discover.js";
|
|
7
7
|
import { InstalledModuleCatalog } from "./discovery/installed.js";
|
|
@@ -12,6 +12,7 @@ import { installModulesThin, observeDocs, observeStatuses, setupModulesThin, sta
|
|
|
12
12
|
import { ensureWorkspaceMetadata } from "./persistence/workspace-metadata.js";
|
|
13
13
|
import { reportProgress } from "./progress.js";
|
|
14
14
|
import { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, } from "./registry/index.js";
|
|
15
|
+
import { createClackSetupInteraction, } from "./setup/interaction.js";
|
|
15
16
|
import { createTerminalProgressReporter } from "./terminal.js";
|
|
16
17
|
const COMMANDS = [
|
|
17
18
|
"install",
|
|
@@ -70,8 +71,8 @@ function parseArgs(argv) {
|
|
|
70
71
|
if (!COMMANDS.includes(raw))
|
|
71
72
|
throw new PlatformError("INVALID_REQUEST", `unknown command ${raw}`);
|
|
72
73
|
const command = raw;
|
|
73
|
-
if (moduleRef !== undefined && command !== "setup")
|
|
74
|
-
throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup");
|
|
74
|
+
if (moduleRef !== undefined && command !== "setup" && command !== "docs")
|
|
75
|
+
throw new PlatformError("INVALID_REQUEST", "--module is only valid with setup or docs");
|
|
75
76
|
return {
|
|
76
77
|
command,
|
|
77
78
|
...(workspace === undefined ? {} : { workspace }),
|
|
@@ -158,6 +159,9 @@ async function handleStatus(root, runtime) {
|
|
|
158
159
|
...(parsed.data.issues === undefined
|
|
159
160
|
? {}
|
|
160
161
|
: { issues: parsed.data.issues }),
|
|
162
|
+
...(item.externalAvailabilityClaim === undefined
|
|
163
|
+
? {}
|
|
164
|
+
: { externalAvailabilityClaim: item.externalAvailabilityClaim }),
|
|
161
165
|
};
|
|
162
166
|
});
|
|
163
167
|
reportProgress(runtime.onProgress, {
|
|
@@ -168,7 +172,7 @@ async function handleStatus(root, runtime) {
|
|
|
168
172
|
});
|
|
169
173
|
return outcome("status", "SUCCEEDED", root, { modules: output });
|
|
170
174
|
}
|
|
171
|
-
async function handleDocs(root, runtime) {
|
|
175
|
+
async function handleDocs(root, parsed, runtime) {
|
|
172
176
|
reportProgress(runtime.onProgress, {
|
|
173
177
|
command: "docs",
|
|
174
178
|
phase: "docs",
|
|
@@ -176,7 +180,28 @@ async function handleDocs(root, runtime) {
|
|
|
176
180
|
message: "正在整理模块文档",
|
|
177
181
|
});
|
|
178
182
|
const { catalog, modules: resolvedModules } = await buildContext(root);
|
|
179
|
-
const
|
|
183
|
+
const selectedModules = parsed.moduleRef === undefined
|
|
184
|
+
? resolvedModules
|
|
185
|
+
: resolvedModules.filter((item) => item.moduleRef === parsed.moduleRef);
|
|
186
|
+
if (parsed.moduleRef !== undefined && selectedModules.length === 0)
|
|
187
|
+
throw new PlatformError("INVALID_REQUEST", `docs target module ${parsed.moduleRef} was not discovered`);
|
|
188
|
+
if (parsed.moduleRef === undefined) {
|
|
189
|
+
reportProgress(runtime.onProgress, {
|
|
190
|
+
command: "docs",
|
|
191
|
+
phase: "docs",
|
|
192
|
+
status: "SUCCEEDED",
|
|
193
|
+
message: `已整理 ${selectedModules.length} 个模块的文档索引`,
|
|
194
|
+
});
|
|
195
|
+
return outcome("docs", "SUCCEEDED", root, {
|
|
196
|
+
indexOnly: true,
|
|
197
|
+
modules: selectedModules.map((module) => ({
|
|
198
|
+
moduleRef: module.moduleRef,
|
|
199
|
+
version: module.moduleVersion,
|
|
200
|
+
})),
|
|
201
|
+
errors: [],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const docs = await observeDocs(catalog, selectedModules, root);
|
|
180
205
|
const byRef = new Map(resolvedModules.map((module) => [module.moduleRef, module]));
|
|
181
206
|
const modules = [];
|
|
182
207
|
const errors = [];
|
|
@@ -267,7 +292,7 @@ async function handleInstall(root, runtime) {
|
|
|
267
292
|
kind: "detail",
|
|
268
293
|
status: "STARTED",
|
|
269
294
|
total,
|
|
270
|
-
message: `Registry 返回 ${total}
|
|
295
|
+
message: `Registry 返回 ${total} 个候选模块,正在做安装前核验`,
|
|
271
296
|
}),
|
|
272
297
|
onPackageChecked: ({ current, total, packageName }) => reportProgress(runtime.onProgress, {
|
|
273
298
|
command: "install",
|
|
@@ -277,7 +302,7 @@ async function handleInstall(root, runtime) {
|
|
|
277
302
|
current,
|
|
278
303
|
total,
|
|
279
304
|
moduleRef: packageName.replace(PRO_FLOW_PACKAGE_PREFIX, ""),
|
|
280
|
-
message:
|
|
305
|
+
message: `安装前核验 ${packageName.replace(PRO_FLOW_PACKAGE_PREFIX, "")}`,
|
|
281
306
|
}),
|
|
282
307
|
...(runtime.registryRunner === undefined
|
|
283
308
|
? {}
|
|
@@ -332,7 +357,7 @@ async function handleInstall(root, runtime) {
|
|
|
332
357
|
phase: "validation",
|
|
333
358
|
kind: "phase",
|
|
334
359
|
status: "SUCCEEDED",
|
|
335
|
-
message:
|
|
360
|
+
message: `安装完成,${modules.length} 个模块已从本地安装物验证`,
|
|
336
361
|
});
|
|
337
362
|
const moduleInstall = await installModulesThin(catalog, modules, root, runtime.onProgress);
|
|
338
363
|
return outcome("install", batchStatus(moduleInstall), root, {
|
|
@@ -425,17 +450,78 @@ async function handleSetup(root, parsed, runtime) {
|
|
|
425
450
|
message: "正在分析模块配置",
|
|
426
451
|
});
|
|
427
452
|
const { catalog, modules } = await buildContext(root);
|
|
428
|
-
const
|
|
453
|
+
const interaction = runtime.setupInteraction;
|
|
454
|
+
const inputByModule = new Map();
|
|
455
|
+
const approvedSteps = new Set();
|
|
456
|
+
const confirmSetupStep = interaction?.confirmStep
|
|
457
|
+
? async (moduleRef) => {
|
|
458
|
+
if (approvedSteps.has(moduleRef))
|
|
459
|
+
return;
|
|
460
|
+
if (!(await interaction.confirmStep?.(moduleRef)))
|
|
461
|
+
throw new PlatformError("INVALID_REQUEST", "配置已取消");
|
|
462
|
+
approvedSteps.add(moduleRef);
|
|
463
|
+
}
|
|
464
|
+
: undefined;
|
|
465
|
+
if (interaction)
|
|
466
|
+
interaction.begin();
|
|
467
|
+
let target = parsed.moduleRef === undefined
|
|
429
468
|
? undefined
|
|
430
469
|
: { moduleRef: parsed.moduleRef };
|
|
431
|
-
|
|
470
|
+
let result = await setupModulesThin(catalog, modules, root, target, runtime.onProgress, confirmSetupStep);
|
|
471
|
+
for (let attempt = 0; interaction && !result.completed && attempt < 8; attempt += 1) {
|
|
472
|
+
const actionable = [...result.results]
|
|
473
|
+
.reverse()
|
|
474
|
+
.find((item) => item.result.status === "ACTION_REQUIRED");
|
|
475
|
+
const action = actionable?.result.actionRequired;
|
|
476
|
+
if (!actionable || !action)
|
|
477
|
+
break;
|
|
478
|
+
let modelIds;
|
|
479
|
+
if (actionable.moduleRef === "model-runtime") {
|
|
480
|
+
const facts = await readModuleSharedFacts({ workspaceRoot: root }, "model-provider-api");
|
|
481
|
+
const inventory = facts?.models;
|
|
482
|
+
if (Array.isArray(inventory))
|
|
483
|
+
modelIds = inventory.flatMap((item) => {
|
|
484
|
+
if (typeof item === "string")
|
|
485
|
+
return [item];
|
|
486
|
+
if (isRecord(item) && typeof item.id === "string")
|
|
487
|
+
return [item.id];
|
|
488
|
+
return [];
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
const collected = await interaction.collect({
|
|
492
|
+
moduleRef: actionable.moduleRef,
|
|
493
|
+
action: action.action,
|
|
494
|
+
description: action.description,
|
|
495
|
+
...(modelIds?.length ? { modelIds } : {}),
|
|
496
|
+
});
|
|
497
|
+
if (!collected)
|
|
498
|
+
break;
|
|
499
|
+
const previous = inputByModule.get(actionable.moduleRef) ?? {};
|
|
500
|
+
const input = { ...previous, ...collected };
|
|
501
|
+
inputByModule.set(actionable.moduleRef, input);
|
|
502
|
+
const targeted = await setupModulesThin(catalog, modules, root, { moduleRef: actionable.moduleRef, input }, runtime.onProgress, confirmSetupStep);
|
|
503
|
+
if (parsed.moduleRef !== undefined || !targeted.completed) {
|
|
504
|
+
result = targeted;
|
|
505
|
+
target = { moduleRef: actionable.moduleRef, input };
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
target = undefined;
|
|
509
|
+
result = await setupModulesThin(catalog, modules, root, undefined, runtime.onProgress, confirmSetupStep);
|
|
510
|
+
}
|
|
511
|
+
if (interaction && result.completed)
|
|
512
|
+
interaction.finish("配置已完成");
|
|
432
513
|
reportProgress(runtime.onProgress, {
|
|
433
514
|
command: "setup",
|
|
434
515
|
phase: "setup",
|
|
435
516
|
status: result.completed ? "SUCCEEDED" : "ACTION_REQUIRED",
|
|
436
517
|
message: result.completed ? "模块配置已就绪" : "配置清单已生成",
|
|
437
518
|
});
|
|
438
|
-
return outcome("setup", batchStatus(result), root,
|
|
519
|
+
return outcome("setup", batchStatus(result), root, {
|
|
520
|
+
...result,
|
|
521
|
+
...(parsed.moduleRef === undefined
|
|
522
|
+
? {}
|
|
523
|
+
: { targetModuleRef: parsed.moduleRef }),
|
|
524
|
+
});
|
|
439
525
|
}
|
|
440
526
|
async function handleStart(root, runtime) {
|
|
441
527
|
const { catalog, modules } = await buildContext(root);
|
|
@@ -482,7 +568,7 @@ export async function runCli(argv, runtime = {}) {
|
|
|
482
568
|
case "setup":
|
|
483
569
|
return await handleSetup(root, parsed, runtime);
|
|
484
570
|
case "docs":
|
|
485
|
-
return await handleDocs(root, runtime);
|
|
571
|
+
return await handleDocs(root, parsed, runtime);
|
|
486
572
|
case "start":
|
|
487
573
|
return await handleStart(root, runtime);
|
|
488
574
|
case "stop":
|
|
@@ -558,9 +644,17 @@ function renderStatus(data, theme) {
|
|
|
558
644
|
NOT_APPLICABLE: "无独立进程",
|
|
559
645
|
};
|
|
560
646
|
const modules = data.modules.filter(isRecord);
|
|
647
|
+
const productOrder = new Map([
|
|
648
|
+
["execution-browser-extension", 0],
|
|
649
|
+
["dev-tunnel", 1],
|
|
650
|
+
["model-provider-api", 2],
|
|
651
|
+
]);
|
|
652
|
+
const orderedProblems = (entries) => [...entries].sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
|
|
653
|
+
(productOrder.get(String(b.moduleRef)) ?? 100));
|
|
561
654
|
const failedModules = modules.filter((item) => item.setupStatus === "FAILED" || item.runtimeStatus === "FAILED");
|
|
562
|
-
const actionModules = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" &&
|
|
563
|
-
|
|
655
|
+
const actionModules = orderedProblems(modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" &&
|
|
656
|
+
item.runtimeStatus !== "FAILED"));
|
|
657
|
+
const blockedModules = orderedProblems(modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED"));
|
|
564
658
|
const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
|
|
565
659
|
const healthy = modules.filter((item) => item.setupStatus === "READY" &&
|
|
566
660
|
item.runtimeStatus !== "RUNNING" &&
|
|
@@ -574,8 +668,11 @@ function renderStatus(data, theme) {
|
|
|
574
668
|
const moduleRef = String(raw.moduleRef);
|
|
575
669
|
const setupStatus = String(raw.setupStatus);
|
|
576
670
|
const runtimeStatus = String(raw.runtimeStatus);
|
|
671
|
+
const runtimeLabel = raw.externalAvailabilityClaim === "AVAILABLE"
|
|
672
|
+
? "外部资源可用"
|
|
673
|
+
: (runtimeLabels[runtimeStatus] ?? "未知");
|
|
577
674
|
lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
|
|
578
|
-
lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${
|
|
675
|
+
lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabel}`);
|
|
579
676
|
const issues = Array.isArray(raw.issues)
|
|
580
677
|
? raw.issues.filter(isRecord)
|
|
581
678
|
: [];
|
|
@@ -584,7 +681,8 @@ function renderStatus(data, theme) {
|
|
|
584
681
|
if (Array.isArray(issue.relatedModuleRefs) &&
|
|
585
682
|
issue.relatedModuleRefs.length > 0)
|
|
586
683
|
lines.push(` 依赖:${issue.relatedModuleRefs.map(String).join("、")}`);
|
|
587
|
-
|
|
684
|
+
if (setupStatus !== "BLOCKED")
|
|
685
|
+
lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
|
|
588
686
|
}
|
|
589
687
|
lines.push("");
|
|
590
688
|
}
|
|
@@ -597,7 +695,9 @@ function renderStatus(data, theme) {
|
|
|
597
695
|
return;
|
|
598
696
|
lines.push(theme.section(title), "");
|
|
599
697
|
for (const item of entries) {
|
|
600
|
-
const runtime =
|
|
698
|
+
const runtime = item.externalAvailabilityClaim === "AVAILABLE"
|
|
699
|
+
? "外部资源可用"
|
|
700
|
+
: (runtimeLabels[String(item.runtimeStatus)] ?? "未知");
|
|
601
701
|
lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
|
|
602
702
|
}
|
|
603
703
|
lines.push("");
|
|
@@ -608,7 +708,7 @@ function renderStatus(data, theme) {
|
|
|
608
708
|
const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED").length;
|
|
609
709
|
const failed = failedModules.length;
|
|
610
710
|
const blocked = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED").length;
|
|
611
|
-
lines.push(theme.section("汇总"), `${theme.success(`${ready}
|
|
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"}`);
|
|
612
712
|
return lines.join("\n");
|
|
613
713
|
}
|
|
614
714
|
function renderTerminalMarkdown(source, theme) {
|
|
@@ -641,6 +741,18 @@ function renderTerminalMarkdown(source, theme) {
|
|
|
641
741
|
function renderDocs(data, theme) {
|
|
642
742
|
if (!isRecord(data) || !Array.isArray(data.modules))
|
|
643
743
|
return "未发现模块文档。";
|
|
744
|
+
if (data.indexOnly === true) {
|
|
745
|
+
const modules = data.modules.filter(isRecord);
|
|
746
|
+
return [
|
|
747
|
+
theme.title("ProFlow 帮助"),
|
|
748
|
+
"",
|
|
749
|
+
"先运行 platform status 查看当前状态;需要某项详细说明时使用:",
|
|
750
|
+
` ${theme.command("platform docs --module <模块名>")}`,
|
|
751
|
+
"",
|
|
752
|
+
theme.section(`可用文档(${modules.length})`),
|
|
753
|
+
...modules.map((item) => ` ${String(item.moduleRef).padEnd(34)} ${theme.muted(String(item.version ?? ""))}`),
|
|
754
|
+
].join("\n");
|
|
755
|
+
}
|
|
644
756
|
const lines = [theme.title("ProFlow 模块文档")];
|
|
645
757
|
for (const raw of data.modules)
|
|
646
758
|
if (isRecord(raw))
|
|
@@ -651,40 +763,13 @@ function renderDocs(data, theme) {
|
|
|
651
763
|
.map((item) => `- ${String(item.moduleRef)}:${String(item.reason)}`));
|
|
652
764
|
return lines.join("\n");
|
|
653
765
|
}
|
|
654
|
-
const
|
|
655
|
-
"
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
"dev-tunnel": {
|
|
660
|
-
ai: "pnpm exec -- proflow-dev-tunnel setup",
|
|
661
|
-
inputs: "无",
|
|
662
|
-
},
|
|
663
|
-
"model-provider-api": {
|
|
664
|
-
ai: "pnpm exec -- proflow-model-provider-api setup",
|
|
665
|
-
inputs: "无(等待 Deployment resolver 提供 endpoint)",
|
|
666
|
-
},
|
|
667
|
-
"model-runtime": {
|
|
668
|
-
ai: "pnpm exec -- proflow-model-runtime setup",
|
|
669
|
-
inputs: "无(仅等价合格候选歧义时选择)",
|
|
670
|
-
},
|
|
671
|
-
"execution-browser-extension": {
|
|
672
|
-
ai: "pnpm exec -- proflow-execution-browser-extension setup",
|
|
673
|
-
inputs: "无",
|
|
674
|
-
},
|
|
675
|
-
"agent-controller-dev": {
|
|
676
|
-
ai: "pnpm exec -- proflow-agent-controller-dev setup",
|
|
677
|
-
inputs: "无",
|
|
678
|
-
},
|
|
679
|
-
"agent-product": {
|
|
680
|
-
ai: "pnpm exec -- proflow-agent-product setup",
|
|
681
|
-
inputs: "无",
|
|
682
|
-
},
|
|
683
|
-
"agent-test-ops": {
|
|
684
|
-
ai: "pnpm exec -- proflow-agent-test-ops setup",
|
|
685
|
-
inputs: "无",
|
|
686
|
-
},
|
|
766
|
+
const setupModuleLabels = {
|
|
767
|
+
"execution-browser-extension": "浏览器扩展",
|
|
768
|
+
"dev-tunnel": "远程连接",
|
|
769
|
+
"model-provider-api": "模型服务",
|
|
770
|
+
"model-runtime": "FAST / THINK 模型",
|
|
687
771
|
};
|
|
772
|
+
const setupModuleLabel = (moduleRef) => setupModuleLabels[moduleRef] ?? moduleRef;
|
|
688
773
|
function renderSetup(data, theme) {
|
|
689
774
|
if (!isRecord(data) || !Array.isArray(data.results))
|
|
690
775
|
return "没有需要执行的配置步骤。";
|
|
@@ -708,10 +793,14 @@ function renderSetup(data, theme) {
|
|
|
708
793
|
blocked += 1;
|
|
709
794
|
else
|
|
710
795
|
needsAction += 1;
|
|
711
|
-
lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(moduleRef)}`);
|
|
796
|
+
lines.push(`${status === "FAILED" ? theme.failure("✕") : theme.warning("◆")} ${theme.section(setupModuleLabel(moduleRef))}`);
|
|
712
797
|
const plan = moduleSetupPlanDataSchema.safeParse(raw.result.data);
|
|
713
798
|
if (plan.success) {
|
|
799
|
+
const currentStep = plan.data.steps.find((step) => step.state === "BLOCKED") ??
|
|
800
|
+
plan.data.steps.find((step) => step.state === "TODO");
|
|
714
801
|
for (const [index, step] of plan.data.steps.entries()) {
|
|
802
|
+
if (step !== currentStep && step.state !== "DONE")
|
|
803
|
+
continue;
|
|
715
804
|
const marker = step.state === "DONE"
|
|
716
805
|
? theme.success("✓")
|
|
717
806
|
: step.state === "BLOCKED"
|
|
@@ -723,13 +812,10 @@ function renderSetup(data, theme) {
|
|
|
723
812
|
lines.push(` ${marker} ${String(index + 1).padStart(2, "0")} ${step.title}`);
|
|
724
813
|
if (step.description)
|
|
725
814
|
lines.push(` ${step.description}`);
|
|
726
|
-
lines.push(` 运行:${theme.command(step.execution.interactive)}`);
|
|
727
|
-
lines.push(` AI:${theme.command(step.execution.nonInteractive)}`);
|
|
728
815
|
if (step.requiredInputs.length > 0)
|
|
729
816
|
lines.push(` 需要:${step.requiredInputs.map((item) => item.description).join("、")}`);
|
|
730
817
|
if (step.humanAction)
|
|
731
818
|
lines.push(` 人工操作:${step.humanAction}`);
|
|
732
|
-
lines.push(` 验证:${theme.command(step.verify)}`);
|
|
733
819
|
lines.push(` 完成:${step.successCondition}`);
|
|
734
820
|
if (step.blockedReason)
|
|
735
821
|
lines.push(` 原因:${theme.failure(step.blockedReason)}`);
|
|
@@ -740,15 +826,7 @@ function renderSetup(data, theme) {
|
|
|
740
826
|
lines.push(` 1. ${status === "FAILED" ? "等待上游服务信息" : "完成模块配置"}`);
|
|
741
827
|
const error = raw.result.error;
|
|
742
828
|
if (status !== "FAILED") {
|
|
743
|
-
|
|
744
|
-
ai: `proflow-${moduleRef} setup`,
|
|
745
|
-
inputs: "按命令提示提供",
|
|
746
|
-
};
|
|
747
|
-
lines.push(` 人工执行:pnpm exec -- proflow-${moduleRef} setup`);
|
|
748
|
-
lines.push(` AI 执行:${commands.ai}`);
|
|
749
|
-
lines.push(` 需要输入:${commands.inputs}`);
|
|
750
|
-
lines.push(` 验证:proflow-${moduleRef} verify`);
|
|
751
|
-
lines.push(" 完成条件:配置状态变为“已就绪”");
|
|
829
|
+
lines.push(" 请在当前 Platform 向导中完成这一项。完成后会自动验证。");
|
|
752
830
|
}
|
|
753
831
|
else if (isRecord(error)) {
|
|
754
832
|
const code = error.code === undefined ? "FAILED" : String(error.code);
|
|
@@ -756,8 +834,7 @@ function renderSetup(data, theme) {
|
|
|
756
834
|
? "Module setup failed."
|
|
757
835
|
: String(error.message);
|
|
758
836
|
lines.push(` 原因:${code} — ${message}`);
|
|
759
|
-
lines.push(
|
|
760
|
-
lines.push(" 完成条件:配置状态变为“已就绪”");
|
|
837
|
+
lines.push(" 修复当前问题后重新运行 platform setup。");
|
|
761
838
|
}
|
|
762
839
|
lines.push("");
|
|
763
840
|
}
|
|
@@ -771,8 +848,11 @@ function renderSetup(data, theme) {
|
|
|
771
848
|
blocked += 1;
|
|
772
849
|
lines.push(`${theme.info("◇")} ${theme.section(moduleRef)}`, ` 原因:${typeof item.reason === "string" ? item.reason : "等待上游 Module 就绪"}`, ` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${moduleRef}`)}`, "");
|
|
773
850
|
}
|
|
851
|
+
const targetModuleRef = typeof data.targetModuleRef === "string" ? data.targetModuleRef : undefined;
|
|
774
852
|
if (needsAction === 0 && blocked === 0)
|
|
775
|
-
lines.push(
|
|
853
|
+
lines.push(targetModuleRef
|
|
854
|
+
? `${setupModuleLabel(targetModuleRef)}配置已就绪。`
|
|
855
|
+
: "全部模块均已就绪。");
|
|
776
856
|
lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
|
|
777
857
|
return lines.join("\n").trimEnd();
|
|
778
858
|
}
|
|
@@ -795,8 +875,8 @@ function renderHelp(theme, command) {
|
|
|
795
875
|
description: "自动配置并列出全部剩余步骤",
|
|
796
876
|
},
|
|
797
877
|
docs: {
|
|
798
|
-
usage: "platform docs [--workspace <路径>]",
|
|
799
|
-
description: "
|
|
878
|
+
usage: "platform docs [--workspace <路径>] [--module <模块名>]",
|
|
879
|
+
description: "查看文档索引,或阅读一个模块的详细文档",
|
|
800
880
|
},
|
|
801
881
|
start: {
|
|
802
882
|
usage: "platform start [--workspace <路径>]",
|
|
@@ -839,17 +919,17 @@ function renderHelp(theme, command) {
|
|
|
839
919
|
theme.section("推荐流程"),
|
|
840
920
|
` ${theme.command("install → status → docs → setup → start → status → stop")}`,
|
|
841
921
|
"",
|
|
842
|
-
theme.section("
|
|
843
|
-
`
|
|
844
|
-
` AI: ${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
|
|
922
|
+
theme.section("配置入口"),
|
|
923
|
+
` ${theme.command("platform setup")}`,
|
|
845
924
|
"",
|
|
846
925
|
theme.section("状态图例"),
|
|
847
926
|
` ${theme.success("已就绪")} 配置与验证完成`,
|
|
848
927
|
` ${theme.warning("需要操作")} 需要执行 setup 步骤`,
|
|
849
928
|
` ${theme.info("等待依赖")} 等待上游 Module 或外部服务`,
|
|
850
929
|
` ${theme.failure("失败")} 已确认配置或运行故障`,
|
|
851
|
-
" 运行中
|
|
852
|
-
"
|
|
930
|
+
" 运行中 服务进程正在运行",
|
|
931
|
+
" 外部资源可用 Chrome 等外部资源已通过真实探测",
|
|
932
|
+
" 无独立进程 该模块无需启动",
|
|
853
933
|
"",
|
|
854
934
|
theme.section("遇到问题"),
|
|
855
935
|
` 先运行 ${theme.command("platform status")};配置问题运行 ${theme.command("platform setup")}。`,
|
|
@@ -866,24 +946,35 @@ export function renderHumanResult(result, options = {}) {
|
|
|
866
946
|
? result.data.blockers.filter(isRecord)
|
|
867
947
|
: [];
|
|
868
948
|
if (blockers.length > 0) {
|
|
949
|
+
const productOrder = new Map([
|
|
950
|
+
["execution-browser-extension", 0],
|
|
951
|
+
["dev-tunnel", 1],
|
|
952
|
+
["model-provider-api", 2],
|
|
953
|
+
]);
|
|
869
954
|
const groups = [
|
|
870
955
|
{ status: "FAILED", title: "失败", icon: "✕", tone: theme.failure },
|
|
871
956
|
{
|
|
872
957
|
status: "ACTION_REQUIRED",
|
|
873
|
-
title: "
|
|
958
|
+
title: "当前根阻塞",
|
|
874
959
|
icon: "◆",
|
|
875
960
|
tone: theme.warning,
|
|
876
961
|
},
|
|
877
|
-
{ status: "BLOCKED", title: "
|
|
962
|
+
{ status: "BLOCKED", title: "下游等待", icon: "◇", tone: theme.info },
|
|
878
963
|
];
|
|
879
964
|
const lines = [theme.failure("平台未启动:存在未就绪模块"), ""];
|
|
880
965
|
for (const group of groups) {
|
|
881
|
-
const entries = blockers
|
|
966
|
+
const entries = blockers
|
|
967
|
+
.filter((item) => item.setupStatus === group.status)
|
|
968
|
+
.sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
|
|
969
|
+
(productOrder.get(String(b.moduleRef)) ?? 100));
|
|
882
970
|
if (entries.length === 0)
|
|
883
971
|
continue;
|
|
884
972
|
lines.push(theme.section(group.title));
|
|
885
|
-
for (const item of entries)
|
|
886
|
-
lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}
|
|
973
|
+
for (const item of entries) {
|
|
974
|
+
lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}`);
|
|
975
|
+
if (group.status !== "BLOCKED")
|
|
976
|
+
lines.push(` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
|
|
977
|
+
}
|
|
887
978
|
lines.push("");
|
|
888
979
|
}
|
|
889
980
|
const readyCount = Array.isArray(result.data.results)
|
|
@@ -891,7 +982,7 @@ export function renderHumanResult(result, options = {}) {
|
|
|
891
982
|
isRecord(item.result) &&
|
|
892
983
|
item.result.status === "SUCCEEDED").length - blockers.length
|
|
893
984
|
: 0;
|
|
894
|
-
lines.push(theme.section("检查汇总"), `${Math.max(0, readyCount)}
|
|
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}"` : ""}`)}`);
|
|
895
986
|
return lines.join("\n");
|
|
896
987
|
}
|
|
897
988
|
}
|
|
@@ -950,7 +1041,12 @@ export function renderHumanResult(result, options = {}) {
|
|
|
950
1041
|
if (import.meta.main) {
|
|
951
1042
|
const argv = process.argv.slice(2);
|
|
952
1043
|
const reporter = createTerminalProgressReporter();
|
|
953
|
-
const result = await runCli(argv, {
|
|
1044
|
+
const result = await runCli(argv, {
|
|
1045
|
+
onProgress: reporter,
|
|
1046
|
+
...(process.stdin.isTTY && process.stdout.isTTY
|
|
1047
|
+
? { setupInteraction: createClackSetupInteraction() }
|
|
1048
|
+
: {}),
|
|
1049
|
+
});
|
|
954
1050
|
reporter.close();
|
|
955
1051
|
const color = process.stdout.isTTY === true &&
|
|
956
1052
|
process.env.NO_COLOR === undefined &&
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const FROZEN_DEPLOYMENT_INSTALL_ORDER: readonly ["chrome-runtime", "execution-browser-extension", "agent-controller-dev", "agent-product", "agent-test-ops", "agent-gateway", "dev-tunnel", "agent-runtime", "platform-host", "execution-runtime", "execution-local", "model-provider-api", "model-runtime", "task-orchestration", "task-store-sqlite", "task-migration-runner", "execution-contracts", "model-contracts", "module-contract", "module-skill", "module-template", "deployment-conformance", "platform-cli"
|
|
1
|
+
export declare const FROZEN_DEPLOYMENT_INSTALL_ORDER: readonly ["chrome-runtime", "execution-browser-extension", "agent-controller-dev", "agent-product", "agent-test-ops", "agent-gateway", "dev-tunnel", "agent-runtime", "platform-host", "execution-runtime", "execution-local", "model-provider-api", "model-runtime", "task-orchestration", "task-store-sqlite", "task-migration-runner", "execution-contracts", "model-contracts", "module-contract", "module-skill", "module-template", "deployment-conformance", "platform-cli"];
|
|
2
2
|
export declare function orderModuleRefsForInstall(moduleRefs: readonly string[]): string[];
|
|
@@ -22,7 +22,6 @@ export const FROZEN_DEPLOYMENT_INSTALL_ORDER = [
|
|
|
22
22
|
"module-template",
|
|
23
23
|
"deployment-conformance",
|
|
24
24
|
"platform-cli",
|
|
25
|
-
"chatgpt-carrier",
|
|
26
25
|
];
|
|
27
26
|
const installRank = new Map(FROZEN_DEPLOYMENT_INSTALL_ORDER.map((moduleRef, index) => [moduleRef, index]));
|
|
28
27
|
export function orderModuleRefsForInstall(moduleRefs) {
|
|
@@ -71,7 +71,7 @@ export async function readWorkspacePackageManagerSelection(workspaceRoot) {
|
|
|
71
71
|
return { name, source: "declared", declared };
|
|
72
72
|
}
|
|
73
73
|
if (lockManagers.size > 1) {
|
|
74
|
-
throw new PlatformError("PACKAGE_MANAGER_CONFLICT",
|
|
74
|
+
throw new PlatformError("PACKAGE_MANAGER_CONFLICT", `检测到多个包管理器的 lockfile(${[...lockManagers].sort().join(", ")})。Platform 不会自动删除或替你选择;请先保留现场并明确工作区应使用的唯一包管理器。`);
|
|
75
75
|
}
|
|
76
76
|
const [fromLockfile] = lockManagers;
|
|
77
77
|
if (fromLockfile !== undefined) {
|
|
@@ -6,5 +6,6 @@ export interface ModuleDispatchResult {
|
|
|
6
6
|
command: ModuleManagementCommand;
|
|
7
7
|
result: ModuleOperationResult;
|
|
8
8
|
observedEffects: string[];
|
|
9
|
+
externalAvailabilityClaim?: "AVAILABLE" | "UNAVAILABLE";
|
|
9
10
|
}
|
|
10
11
|
export declare function dispatchModuleCommand(catalog: ModuleCatalog, module: ResolvedModule, command: ModuleManagementCommand, context: ModuleCommandContext): Promise<ModuleDispatchResult>;
|
|
@@ -24,6 +24,10 @@ function normalizeInvocation(raw) {
|
|
|
24
24
|
observedEffects: Array.isArray(raw.observedEffects)
|
|
25
25
|
? raw.observedEffects.filter((item) => typeof item === "string")
|
|
26
26
|
: [],
|
|
27
|
+
...(raw.externalAvailabilityClaim === "AVAILABLE" ||
|
|
28
|
+
raw.externalAvailabilityClaim === "UNAVAILABLE"
|
|
29
|
+
? { externalAvailabilityClaim: raw.externalAvailabilityClaim }
|
|
30
|
+
: {}),
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
export async function dispatchModuleCommand(catalog, module, command, context) {
|
|
@@ -32,7 +36,7 @@ export async function dispatchModuleCommand(catalog, module, command, context) {
|
|
|
32
36
|
const invoke = adapter[command];
|
|
33
37
|
if (!isCommandFn(invoke))
|
|
34
38
|
throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} does not implement standard command "${command}"`);
|
|
35
|
-
const { result, observedEffects } = normalizeInvocation(await invoke(context));
|
|
39
|
+
const { result, observedEffects, externalAvailabilityClaim } = normalizeInvocation(await invoke(context));
|
|
36
40
|
const parsed = moduleOperationResultSchema.safeParse(result);
|
|
37
41
|
if (!parsed.success)
|
|
38
42
|
throw new PlatformError("COMMAND_FAILED", `module ${module.moduleRef} "${command}" returned an invalid result: ${parsed.error.message}`);
|
|
@@ -41,5 +45,8 @@ export async function dispatchModuleCommand(catalog, module, command, context) {
|
|
|
41
45
|
command,
|
|
42
46
|
result: parsed.data,
|
|
43
47
|
observedEffects,
|
|
48
|
+
...(externalAvailabilityClaim === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { externalAvailabilityClaim }),
|
|
44
51
|
};
|
|
45
52
|
}
|
|
@@ -30,5 +30,5 @@ export declare function stopModulesThin(catalog: ModuleCatalog, modules: readonl
|
|
|
30
30
|
export declare function setupModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, target?: {
|
|
31
31
|
moduleRef: string;
|
|
32
32
|
input?: unknown;
|
|
33
|
-
}, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
|
|
33
|
+
}, reporter?: PlatformProgressReporter, beforeSetup?: (moduleRef: string) => Promise<void>): Promise<ModuleBatchResult>;
|
|
34
34
|
export declare function startModulesThin(catalog: ModuleCatalog, modules: readonly ResolvedModule[], workspaceRoot: string, reporter?: PlatformProgressReporter): Promise<ModuleBatchResult>;
|
|
@@ -172,38 +172,51 @@ export async function stopModulesThin(catalog, modules, workspaceRoot, reporter)
|
|
|
172
172
|
}
|
|
173
173
|
return { phase: "stop", results, completed: true, skipped };
|
|
174
174
|
}
|
|
175
|
-
export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter) {
|
|
175
|
+
export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter, beforeSetup) {
|
|
176
176
|
const results = [];
|
|
177
177
|
const skipped = [];
|
|
178
178
|
const blockers = [];
|
|
179
179
|
const graph = buildDependencyGraph(modules);
|
|
180
180
|
const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
|
|
181
|
+
if (target !== undefined && !byRef.has(target.moduleRef))
|
|
182
|
+
throw new PlatformError("INVALID_REQUEST", `setup target module ${target.moduleRef} was not discovered`);
|
|
183
|
+
const setupPriority = [
|
|
184
|
+
"execution-browser-extension",
|
|
185
|
+
"dev-tunnel",
|
|
186
|
+
"model-provider-api",
|
|
187
|
+
"model-runtime",
|
|
188
|
+
];
|
|
181
189
|
const dependenciesByRef = new Map();
|
|
182
190
|
for (const edge of graph.edges) {
|
|
183
191
|
const dependencies = dependenciesByRef.get(edge.from) ?? [];
|
|
184
192
|
dependencies.push(edge.to);
|
|
185
193
|
dependenciesByRef.set(edge.from, dependencies);
|
|
186
194
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
if (visitedRefs.has(moduleRef))
|
|
195
|
+
const userOrder = [];
|
|
196
|
+
const included = new Set();
|
|
197
|
+
const appendWithDependencies = (moduleRef) => {
|
|
198
|
+
if (included.has(moduleRef) || !byRef.has(moduleRef))
|
|
192
199
|
return;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
200
|
+
for (const dependency of graph.order)
|
|
201
|
+
if (dependenciesByRef.get(moduleRef)?.includes(dependency))
|
|
202
|
+
appendWithDependencies(dependency);
|
|
203
|
+
included.add(moduleRef);
|
|
204
|
+
userOrder.push(moduleRef);
|
|
196
205
|
};
|
|
197
|
-
if (target
|
|
198
|
-
|
|
199
|
-
|
|
206
|
+
if (target === undefined) {
|
|
207
|
+
for (const moduleRef of setupPriority)
|
|
208
|
+
appendWithDependencies(moduleRef);
|
|
200
209
|
for (const moduleRef of graph.order)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
210
|
+
appendWithDependencies(moduleRef);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
appendWithDependencies(target.moduleRef);
|
|
214
|
+
}
|
|
215
|
+
const selectedRefs = new Set(target === undefined ? userOrder : [target.moduleRef]);
|
|
216
|
+
const modulesInOrder = userOrder
|
|
217
|
+
.filter((moduleRef) => selectedRefs.has(moduleRef))
|
|
204
218
|
.map((moduleRef) => byRef.get(moduleRef))
|
|
205
219
|
.filter((module) => module !== undefined);
|
|
206
|
-
const setupStatusByRef = new Map();
|
|
207
220
|
let completed = true;
|
|
208
221
|
for (const [index, module] of modulesInOrder.entries()) {
|
|
209
222
|
reportProgress(reporter, {
|
|
@@ -218,16 +231,10 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
218
231
|
const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
219
232
|
if (!succeeded(status.result)) {
|
|
220
233
|
results.push(status);
|
|
221
|
-
setupStatusByRef.set(module.moduleRef, "FAILED");
|
|
222
234
|
completed = false;
|
|
223
|
-
|
|
224
|
-
break;
|
|
225
|
-
continue;
|
|
235
|
+
break;
|
|
226
236
|
}
|
|
227
237
|
const observed = moduleStatusObservationSchema.parse(status.result.data);
|
|
228
|
-
setupStatusByRef.set(module.moduleRef, observed.setupStatus);
|
|
229
|
-
if (target !== undefined && module.moduleRef !== target.moduleRef)
|
|
230
|
-
continue;
|
|
231
238
|
if (observed.setupStatus === "READY" && target === undefined) {
|
|
232
239
|
skipped.push({ moduleRef: module.moduleRef, reason: "READY" });
|
|
233
240
|
reportProgress(reporter, {
|
|
@@ -241,29 +248,7 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
241
248
|
});
|
|
242
249
|
continue;
|
|
243
250
|
}
|
|
244
|
-
|
|
245
|
-
if (unavailableDependencies.length > 0) {
|
|
246
|
-
const first = unavailableDependencies[0];
|
|
247
|
-
blockers.push({
|
|
248
|
-
moduleRef: module.moduleRef,
|
|
249
|
-
setupStatus: "BLOCKED",
|
|
250
|
-
reason: `等待依赖模块就绪:${unavailableDependencies.join(", ")}`,
|
|
251
|
-
nextCommand: `platform setup --module ${first}`,
|
|
252
|
-
});
|
|
253
|
-
completed = false;
|
|
254
|
-
reportProgress(reporter, {
|
|
255
|
-
command: "setup",
|
|
256
|
-
phase: "setup",
|
|
257
|
-
current: index + 1,
|
|
258
|
-
total: modulesInOrder.length,
|
|
259
|
-
moduleRef: module.moduleRef,
|
|
260
|
-
status: "SKIPPED",
|
|
261
|
-
message: `${module.moduleRef} 等待 ${unavailableDependencies.join(", ")}`,
|
|
262
|
-
});
|
|
263
|
-
if (target !== undefined)
|
|
264
|
-
break;
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
251
|
+
await beforeSetup?.(module.moduleRef);
|
|
267
252
|
const setup = await dispatchModuleCommand(catalog, module, "setup", context(workspaceRoot, target?.input));
|
|
268
253
|
results.push(setup);
|
|
269
254
|
reportProgress(reporter, {
|
|
@@ -283,14 +268,25 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
|
|
|
283
268
|
completed = false;
|
|
284
269
|
}
|
|
285
270
|
const reconciled = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
|
|
286
|
-
if (succeeded(reconciled.result))
|
|
287
|
-
setupStatusByRef.set(module.moduleRef, moduleStatusObservationSchema.parse(reconciled.result.data).setupStatus);
|
|
288
|
-
else {
|
|
271
|
+
if (!succeeded(reconciled.result)) {
|
|
289
272
|
results.push(reconciled);
|
|
290
|
-
setupStatusByRef.set(module.moduleRef, "FAILED");
|
|
291
273
|
completed = false;
|
|
292
274
|
}
|
|
293
|
-
|
|
275
|
+
else {
|
|
276
|
+
const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
|
|
277
|
+
if (reconciledStatus.setupStatus !== "READY") {
|
|
278
|
+
const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
|
|
279
|
+
blockers.push({
|
|
280
|
+
moduleRef: module.moduleRef,
|
|
281
|
+
setupStatus: reconciledStatus.setupStatus,
|
|
282
|
+
...(issue === undefined
|
|
283
|
+
? {}
|
|
284
|
+
: { reason: issue.message, nextCommand: issue.nextCommand }),
|
|
285
|
+
});
|
|
286
|
+
completed = false;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (!succeeded(setup.result) || blockers.length > 0)
|
|
294
290
|
break;
|
|
295
291
|
}
|
|
296
292
|
return {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { NpmCommandResult, NpmCommandRunner, RegistryDiscoveryResult, RegistryModuleCandidate, RegistryRejectedPackage, } from "./npm-registry.ts";
|
|
2
|
-
export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.ts";
|
|
2
|
+
export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, RETIRED_PRO_FLOW_PACKAGES, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.ts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.js";
|
|
1
|
+
export { discoverRegistryModules, PRO_FLOW_PACKAGE_PREFIX, PRO_FLOW_SCOPE, RETIRED_PRO_FLOW_PACKAGES, resolveScopeRegistry, systemNpmRunner, } from "./npm-registry.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const PRO_FLOW_SCOPE = "@tomflow";
|
|
2
2
|
export declare const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
|
|
3
|
+
export declare const RETIRED_PRO_FLOW_PACKAGES: Set<string>;
|
|
3
4
|
export interface NpmCommandResult {
|
|
4
5
|
stdout: string;
|
|
5
6
|
stderr: string;
|
|
@@ -26,6 +27,7 @@ export interface RegistryDiscoveryResult {
|
|
|
26
27
|
registry: string;
|
|
27
28
|
candidates: RegistryModuleCandidate[];
|
|
28
29
|
rejected: RegistryRejectedPackage[];
|
|
30
|
+
retired: string[];
|
|
29
31
|
}
|
|
30
32
|
export declare function systemNpmRunner(): NpmCommandRunner;
|
|
31
33
|
export declare function resolveScopeRegistry(workspaceRoot: string, runner?: NpmCommandRunner): Promise<string>;
|
|
@@ -6,6 +6,9 @@ import { versionSatisfies } from "../modules.js";
|
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
export const PRO_FLOW_SCOPE = "@tomflow";
|
|
8
8
|
export const PRO_FLOW_PACKAGE_PREFIX = "@tomflow/proflow-";
|
|
9
|
+
export const RETIRED_PRO_FLOW_PACKAGES = new Set([
|
|
10
|
+
"@tomflow/proflow-chatgpt-carrier",
|
|
11
|
+
]);
|
|
9
12
|
export function systemNpmRunner() {
|
|
10
13
|
return {
|
|
11
14
|
async run(args, cwd, timeoutMs) {
|
|
@@ -42,15 +45,17 @@ export async function discoverRegistryModules(options) {
|
|
|
42
45
|
const names = options.packageName === undefined
|
|
43
46
|
? await searchPackageNames(options.workspaceRoot, registry, runner)
|
|
44
47
|
: [validateRequestedPackageName(options.packageName)];
|
|
45
|
-
|
|
48
|
+
const retired = names.filter((name) => RETIRED_PRO_FLOW_PACKAGES.has(name));
|
|
49
|
+
const activeNames = names.filter((name) => !RETIRED_PRO_FLOW_PACKAGES.has(name));
|
|
50
|
+
options.onSearchComplete?.(activeNames.length);
|
|
46
51
|
const candidates = [];
|
|
47
52
|
const rejected = [];
|
|
48
53
|
let nextIndex = 0;
|
|
49
54
|
let completed = 0;
|
|
50
55
|
const worker = async () => {
|
|
51
|
-
while (nextIndex <
|
|
56
|
+
while (nextIndex < activeNames.length) {
|
|
52
57
|
const index = nextIndex++;
|
|
53
|
-
const packageName =
|
|
58
|
+
const packageName = activeNames[index];
|
|
54
59
|
if (packageName === undefined)
|
|
55
60
|
return;
|
|
56
61
|
const manifest = await viewManifest(options.workspaceRoot, registry, packageName, runner);
|
|
@@ -62,15 +67,15 @@ export async function discoverRegistryModules(options) {
|
|
|
62
67
|
completed += 1;
|
|
63
68
|
options.onPackageChecked?.({
|
|
64
69
|
current: completed,
|
|
65
|
-
total:
|
|
70
|
+
total: activeNames.length,
|
|
66
71
|
packageName,
|
|
67
72
|
});
|
|
68
73
|
}
|
|
69
74
|
};
|
|
70
|
-
await Promise.all(Array.from({ length: Math.min(4,
|
|
75
|
+
await Promise.all(Array.from({ length: Math.min(4, activeNames.length) }, () => worker()));
|
|
71
76
|
candidates.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
72
77
|
rejected.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
73
|
-
return { registry, candidates, rejected };
|
|
78
|
+
return { registry, candidates, rejected, retired };
|
|
74
79
|
}
|
|
75
80
|
async function searchPackageNames(workspaceRoot, registry, runner) {
|
|
76
81
|
const result = await runner.run([
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type SetupActionRequest = {
|
|
2
|
+
moduleRef: string;
|
|
3
|
+
action: string;
|
|
4
|
+
description: string;
|
|
5
|
+
modelIds?: readonly string[];
|
|
6
|
+
};
|
|
7
|
+
export interface SetupInteraction {
|
|
8
|
+
begin(): void;
|
|
9
|
+
finish(message: string): void;
|
|
10
|
+
confirmStep?(moduleRef: string): Promise<boolean>;
|
|
11
|
+
collect(request: SetupActionRequest): Promise<Record<string, unknown> | undefined>;
|
|
12
|
+
}
|
|
13
|
+
export declare function validateProviderBaseUrl(value: string): string | undefined;
|
|
14
|
+
export declare function createClackSetupInteraction(): SetupInteraction;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { autocomplete, cancel, confirm, intro, isCancel, outro, password, text, } from "@clack/prompts";
|
|
2
|
+
const stepPrompts = {
|
|
3
|
+
"execution-browser-extension": "下一步将打开 Chrome,并引导你加载浏览器扩展。按 Enter 继续。",
|
|
4
|
+
"dev-tunnel": "下一步将检查远程连接;如需登录,会打开 GitHub 授权。按 Enter 继续。",
|
|
5
|
+
"model-provider-api": "下一步配置 OpenAI-compatible 模型服务地址。按 Enter 继续。",
|
|
6
|
+
"model-runtime": "下一步从真实模型列表选择 FAST 与 THINK。按 Enter 继续。",
|
|
7
|
+
};
|
|
8
|
+
function cancelled(value) {
|
|
9
|
+
if (!isCancel(value))
|
|
10
|
+
return false;
|
|
11
|
+
cancel("已取消配置,没有继续执行后续步骤。");
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
function privateIpv4(hostname) {
|
|
15
|
+
const parts = hostname.split(".").map(Number);
|
|
16
|
+
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part)))
|
|
17
|
+
return false;
|
|
18
|
+
const [first = -1, second = -1] = parts;
|
|
19
|
+
return (first === 10 ||
|
|
20
|
+
first === 127 ||
|
|
21
|
+
(first === 192 && second === 168) ||
|
|
22
|
+
(first === 172 && second >= 16 && second <= 31));
|
|
23
|
+
}
|
|
24
|
+
export function validateProviderBaseUrl(value) {
|
|
25
|
+
let url;
|
|
26
|
+
try {
|
|
27
|
+
url = new URL(value.trim());
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "请输入包含 http:// 或 https:// 的完整 API Base URL。";
|
|
31
|
+
}
|
|
32
|
+
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
33
|
+
return "只支持 HTTP(S) OpenAI-compatible 服务。";
|
|
34
|
+
if (/\/chat\/completions\/?$/i.test(url.pathname))
|
|
35
|
+
return "请填写服务 Base URL,不要包含 /chat/completions。";
|
|
36
|
+
const local = url.hostname === "localhost" ||
|
|
37
|
+
url.hostname.endsWith(".local") ||
|
|
38
|
+
privateIpv4(url.hostname);
|
|
39
|
+
if (!local && url.protocol !== "https:")
|
|
40
|
+
return "公网模型服务必须使用 https://。";
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
async function providerEndpoint() {
|
|
44
|
+
const value = await text({
|
|
45
|
+
message: "OpenAI-compatible 服务 Base URL",
|
|
46
|
+
placeholder: "例如 https://api.deepseek.com 或 http://192.168.0.108:8080/v1",
|
|
47
|
+
validate: (candidate) => candidate === undefined || candidate.trim().length === 0
|
|
48
|
+
? "API Base URL 不能为空。"
|
|
49
|
+
: validateProviderBaseUrl(candidate),
|
|
50
|
+
});
|
|
51
|
+
if (cancelled(value))
|
|
52
|
+
return undefined;
|
|
53
|
+
const providerBaseUrl = String(value).trim().replace(/\/$/, "");
|
|
54
|
+
const approved = await confirm({
|
|
55
|
+
message: `使用 ${providerBaseUrl} 连接整个模型服务?`,
|
|
56
|
+
initialValue: true,
|
|
57
|
+
});
|
|
58
|
+
if (cancelled(approved) || approved !== true)
|
|
59
|
+
return undefined;
|
|
60
|
+
return { providerBaseUrl };
|
|
61
|
+
}
|
|
62
|
+
async function providerCredential() {
|
|
63
|
+
const value = await password({
|
|
64
|
+
message: "模型服务 API Key(输入不会显示)",
|
|
65
|
+
mask: "•",
|
|
66
|
+
validate: (candidate) => candidate === undefined || candidate.trim().length === 0
|
|
67
|
+
? "API Key 不能为空。"
|
|
68
|
+
: undefined,
|
|
69
|
+
});
|
|
70
|
+
if (cancelled(value))
|
|
71
|
+
return undefined;
|
|
72
|
+
return { providerCredential: String(value) };
|
|
73
|
+
}
|
|
74
|
+
async function modelRoles(modelIds) {
|
|
75
|
+
const options = modelIds.map((id) => ({ value: id, label: id }));
|
|
76
|
+
const fastModel = await autocomplete({
|
|
77
|
+
message: "选择 FAST 模型(快速、低延迟)",
|
|
78
|
+
placeholder: "输入关键字搜索模型",
|
|
79
|
+
options,
|
|
80
|
+
});
|
|
81
|
+
if (cancelled(fastModel))
|
|
82
|
+
return undefined;
|
|
83
|
+
const reasonModel = await autocomplete({
|
|
84
|
+
message: "选择 THINK 模型(复杂分析与推理)",
|
|
85
|
+
placeholder: "输入关键字搜索模型",
|
|
86
|
+
options,
|
|
87
|
+
});
|
|
88
|
+
if (cancelled(reasonModel))
|
|
89
|
+
return undefined;
|
|
90
|
+
const approved = await confirm({
|
|
91
|
+
message: `确认 FAST=${String(fastModel)},THINK=${String(reasonModel)}?`,
|
|
92
|
+
initialValue: true,
|
|
93
|
+
});
|
|
94
|
+
if (cancelled(approved) || approved !== true)
|
|
95
|
+
return undefined;
|
|
96
|
+
return { fastModel: String(fastModel), reasonModel: String(reasonModel) };
|
|
97
|
+
}
|
|
98
|
+
export function createClackSetupInteraction() {
|
|
99
|
+
return {
|
|
100
|
+
begin() {
|
|
101
|
+
intro("ProFlow 配置向导");
|
|
102
|
+
},
|
|
103
|
+
finish(message) {
|
|
104
|
+
outro(message);
|
|
105
|
+
},
|
|
106
|
+
async confirmStep(moduleRef) {
|
|
107
|
+
const message = stepPrompts[moduleRef];
|
|
108
|
+
if (!message)
|
|
109
|
+
return true;
|
|
110
|
+
const approved = await confirm({ message, initialValue: true });
|
|
111
|
+
return !cancelled(approved) && approved === true;
|
|
112
|
+
},
|
|
113
|
+
async collect(request) {
|
|
114
|
+
if (request.action === "provide-provider-endpoint") {
|
|
115
|
+
return providerEndpoint();
|
|
116
|
+
}
|
|
117
|
+
if (request.action === "provide-provider-credential") {
|
|
118
|
+
return providerCredential();
|
|
119
|
+
}
|
|
120
|
+
if (request.action.startsWith("select-") && request.modelIds?.length) {
|
|
121
|
+
return modelRoles(request.modelIds);
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomflow/proflow-platform-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -24,11 +24,12 @@
|
|
|
24
24
|
"SETUP.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@clack/prompts": "1.7.0",
|
|
27
28
|
"@tomflow/proflow-module-contract": "^0.1.13"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@tomflow/proflow-
|
|
31
|
-
"@tomflow/proflow-
|
|
31
|
+
"@tomflow/proflow-deployment-conformance": "^0.1.13",
|
|
32
|
+
"@tomflow/proflow-module-template": "^0.1.10"
|
|
32
33
|
},
|
|
33
34
|
"description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
|
|
34
35
|
"keywords": [
|
package/proflow.module.json
CHANGED