@tomflow/proflow-platform-cli 0.1.36 → 0.1.38

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tomflow/proflow-platform-cli
2
2
 
3
+ ## 0.1.38
4
+
5
+ ### Patch Changes
6
+
7
+ - 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.
8
+
9
+ ## 0.1.37
10
+
11
+ ### Patch Changes
12
+
13
+ - Ship the Phase 3 browser, tunnel, model automation, platform readiness, and documentation closure as one verifiable platform snapshot.
14
+
3
15
  ## 0.1.36
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -32,7 +32,7 @@ Platform does not interpret Module-private configuration or recreate a cross-Mod
32
32
 
33
33
  ## Install / Uninstall
34
34
 
35
- `platform install` validates or creates the minimal Workspace metadata, discovers the complete governed package set from the Registry, synchronizes that set, validates the installed descriptors, then invokes `Module.install` in dependency order. It does not perform human setup work.
35
+ `platform install` validates or creates the minimal Workspace metadata, discovers the complete governed package set from the Registry, synchronizes that set, validates the installed descriptors, then invokes `Module.install` in the frozen Deployment Install Order. This product deployment order is distinct from Runtime dependency topology: Chrome is first, followed by Browser Extension, Agent packages, Gateway/Tunnel, runtime/data foundations and governance packages. It does not perform human setup work.
36
36
 
37
37
  `platform uninstall` invokes `Module.uninstall` in reverse dependency order before package removal. `.proflow` is Workspace/user data and is preserved unless an owning Module explicitly removes its own artifacts.
38
38
 
@@ -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.36";
9
+ readonly moduleVersion: "0.1.38";
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.36";
19
+ readonly moduleVersion: "0.1.38";
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.36";
29
+ moduleVersion: "0.1.38";
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.36";
43
+ readonly moduleVersion: "0.1.38";
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.36";
53
+ moduleVersion: "0.1.38";
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.36";
66
+ readonly moduleVersion: "0.1.38";
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.36";
76
+ readonly moduleVersion: "0.1.38";
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.36";
6
+ readonly moduleVersion: "0.1.38";
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.36",
6
+ moduleVersion: "0.1.38",
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
@@ -158,6 +158,9 @@ async function handleStatus(root, runtime) {
158
158
  ...(parsed.data.issues === undefined
159
159
  ? {}
160
160
  : { issues: parsed.data.issues }),
161
+ ...(item.externalAvailabilityClaim === undefined
162
+ ? {}
163
+ : { externalAvailabilityClaim: item.externalAvailabilityClaim }),
161
164
  };
162
165
  });
163
166
  reportProgress(runtime.onProgress, {
@@ -558,9 +561,17 @@ function renderStatus(data, theme) {
558
561
  NOT_APPLICABLE: "无独立进程",
559
562
  };
560
563
  const modules = data.modules.filter(isRecord);
564
+ const productOrder = new Map([
565
+ ["execution-browser-extension", 0],
566
+ ["dev-tunnel", 1],
567
+ ["model-provider-api", 2],
568
+ ]);
569
+ const orderedProblems = (entries) => [...entries].sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
570
+ (productOrder.get(String(b.moduleRef)) ?? 100));
561
571
  const failedModules = modules.filter((item) => item.setupStatus === "FAILED" || item.runtimeStatus === "FAILED");
562
- const actionModules = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED");
563
- const blockedModules = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED");
572
+ const actionModules = orderedProblems(modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" &&
573
+ item.runtimeStatus !== "FAILED"));
574
+ const blockedModules = orderedProblems(modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED"));
564
575
  const running = modules.filter((item) => item.setupStatus === "READY" && item.runtimeStatus === "RUNNING");
565
576
  const healthy = modules.filter((item) => item.setupStatus === "READY" &&
566
577
  item.runtimeStatus !== "RUNNING" &&
@@ -574,8 +585,11 @@ function renderStatus(data, theme) {
574
585
  const moduleRef = String(raw.moduleRef);
575
586
  const setupStatus = String(raw.setupStatus);
576
587
  const runtimeStatus = String(raw.runtimeStatus);
588
+ const runtimeLabel = raw.externalAvailabilityClaim === "AVAILABLE"
589
+ ? "外部资源可用"
590
+ : (runtimeLabels[runtimeStatus] ?? "未知");
577
591
  lines.push(`${tone(icon)} ${theme.section(moduleRef)} ${theme.muted(String(raw.version))}`);
578
- lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabels[runtimeStatus] ?? "未知"}`);
592
+ lines.push(` ${tone(setupLabels[setupStatus] ?? "未知")} · ${runtimeLabel}`);
579
593
  const issues = Array.isArray(raw.issues)
580
594
  ? raw.issues.filter(isRecord)
581
595
  : [];
@@ -584,7 +598,8 @@ function renderStatus(data, theme) {
584
598
  if (Array.isArray(issue.relatedModuleRefs) &&
585
599
  issue.relatedModuleRefs.length > 0)
586
600
  lines.push(` 依赖:${issue.relatedModuleRefs.map(String).join("、")}`);
587
- lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
601
+ if (setupStatus !== "BLOCKED")
602
+ lines.push(` 下一步:${theme.command(String(issue.nextCommand))}`);
588
603
  }
589
604
  lines.push("");
590
605
  }
@@ -597,18 +612,20 @@ function renderStatus(data, theme) {
597
612
  return;
598
613
  lines.push(theme.section(title), "");
599
614
  for (const item of entries) {
600
- const runtime = runtimeLabels[String(item.runtimeStatus)] ?? "未知";
615
+ const runtime = item.externalAvailabilityClaim === "AVAILABLE"
616
+ ? "外部资源可用"
617
+ : (runtimeLabels[String(item.runtimeStatus)] ?? "未知");
601
618
  lines.push(`${theme.success(symbol)} ${String(item.moduleRef).padEnd(32)} ${theme.muted(String(item.version).padEnd(9))} ${runtime}`);
602
619
  }
603
620
  lines.push("");
604
621
  };
605
622
  compact("运行中", running, "●");
606
623
  compact("已就绪", healthy, "○");
607
- const ready = modules.filter((item) => item.setupStatus === "READY").length;
608
- const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED").length;
609
- const failed = modules.filter((item) => item.setupStatus === "FAILED").length;
610
- const blocked = modules.filter((item) => item.setupStatus === "BLOCKED").length;
611
- lines.push(theme.section("汇总"), `${theme.success(`${ready} 已就绪`)} · ${theme.warning(`${action} 需要操作`)} · ${theme.info(`${blocked} 等待依赖`)} · ${failed > 0 ? theme.failure(`${failed} 失败`) : `${failed} 失败`}`);
624
+ const ready = healthy.length + running.length;
625
+ const action = modules.filter((item) => item.setupStatus === "ACTION_REQUIRED" && item.runtimeStatus !== "FAILED").length;
626
+ const failed = failedModules.length;
627
+ const blocked = modules.filter((item) => item.setupStatus === "BLOCKED" && item.runtimeStatus !== "FAILED").length;
628
+ 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
629
  return lines.join("\n");
613
630
  }
614
631
  function renderTerminalMarkdown(source, theme) {
@@ -652,37 +669,33 @@ function renderDocs(data, theme) {
652
669
  return lines.join("\n");
653
670
  }
654
671
  const setupCommands = {
655
- "chatgpt-carrier": {
656
- ai: "pnpm exec -- proflow-chatgpt-carrier setup --carrier-url <url>",
657
- inputs: "Custom GPT URL",
658
- },
659
672
  "dev-tunnel": {
660
- ai: "pnpm exec -- proflow-dev-tunnel setup --tunnel-id <id> --public-base-url <url>",
661
- inputs: "Tunnel ID、公开 HTTPS URL",
673
+ ai: "platform setup --module dev-tunnel",
674
+ inputs: "",
662
675
  },
663
676
  "model-provider-api": {
664
- ai: "pnpm exec -- proflow-model-provider-api setup --provider-base-url <url>",
665
- inputs: "模型服务 Base URL",
677
+ ai: "platform setup --module model-provider-api",
678
+ inputs: "无(等待 Deployment resolver 提供 endpoint)",
666
679
  },
667
680
  "model-runtime": {
668
- ai: "pnpm exec -- proflow-model-runtime setup --fast-model <id> --reason-model <id>",
669
- inputs: "FAST 模型 ID、REASON 模型 ID",
681
+ ai: "platform setup --module model-runtime",
682
+ inputs: "无(仅等价合格候选歧义时选择)",
670
683
  },
671
684
  "execution-browser-extension": {
672
- ai: "pnpm exec -- proflow-execution-browser-extension setup --extension-id <id>",
673
- inputs: "Chrome Extension ID",
685
+ ai: "platform setup --module execution-browser-extension",
686
+ inputs: "",
674
687
  },
675
688
  "agent-controller-dev": {
676
- ai: "pnpm exec -- proflow-agent-controller-dev setup --carrier-url <url>",
677
- inputs: "Custom GPT URL",
689
+ ai: "platform setup --module agent-controller-dev",
690
+ inputs: "",
678
691
  },
679
692
  "agent-product": {
680
- ai: "pnpm exec -- proflow-agent-product setup --carrier-url <url>",
681
- inputs: "Custom GPT URL",
693
+ ai: "platform setup --module agent-product",
694
+ inputs: "",
682
695
  },
683
696
  "agent-test-ops": {
684
- ai: "pnpm exec -- proflow-agent-test-ops setup --carrier-url <url>",
685
- inputs: "Custom GPT URL",
697
+ ai: "platform setup --module agent-test-ops",
698
+ inputs: "",
686
699
  },
687
700
  };
688
701
  function renderSetup(data, theme) {
@@ -693,10 +706,12 @@ function renderSetup(data, theme) {
693
706
  let ready = skipped.filter((item) => isRecord(item) && item.reason === "READY").length;
694
707
  let needsAction = 0;
695
708
  let blocked = 0;
709
+ const renderedModuleRefs = new Set();
696
710
  for (const raw of data.results) {
697
711
  if (!isRecord(raw) || !isRecord(raw.result))
698
712
  continue;
699
713
  const moduleRef = String(raw.moduleRef ?? raw.result.moduleRef ?? "unknown");
714
+ renderedModuleRefs.add(moduleRef);
700
715
  const status = String(raw.result.status ?? "UNKNOWN");
701
716
  if (status === "SUCCEEDED") {
702
717
  ready += 1;
@@ -739,13 +754,13 @@ function renderSetup(data, theme) {
739
754
  const error = raw.result.error;
740
755
  if (status !== "FAILED") {
741
756
  const commands = setupCommands[moduleRef] ?? {
742
- ai: `proflow-${moduleRef} setup`,
757
+ ai: `platform setup --module ${moduleRef}`,
743
758
  inputs: "按命令提示提供",
744
759
  };
745
- lines.push(` 人工执行:pnpm exec -- proflow-${moduleRef} setup`);
760
+ lines.push(` 人工执行:${commands.ai}`);
746
761
  lines.push(` AI 执行:${commands.ai}`);
747
762
  lines.push(` 需要输入:${commands.inputs}`);
748
- lines.push(` 验证:proflow-${moduleRef} verify`);
763
+ lines.push(" 验证:platform status");
749
764
  lines.push(" 完成条件:配置状态变为“已就绪”");
750
765
  }
751
766
  else if (isRecord(error)) {
@@ -759,6 +774,16 @@ function renderSetup(data, theme) {
759
774
  }
760
775
  lines.push("");
761
776
  }
777
+ const dependencyBlockers = Array.isArray(data.blockers)
778
+ ? data.blockers.filter(isRecord)
779
+ : [];
780
+ for (const item of dependencyBlockers) {
781
+ const moduleRef = String(item.moduleRef ?? "unknown");
782
+ if (renderedModuleRefs.has(moduleRef))
783
+ continue;
784
+ blocked += 1;
785
+ 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}`)}`, "");
786
+ }
762
787
  if (needsAction === 0 && blocked === 0)
763
788
  lines.push("全部模块均已就绪。");
764
789
  lines.push(theme.section("汇总"), `汇总:${theme.success(`${ready} 个已就绪`)},${theme.warning(`${needsAction} 个需要操作`)},${blocked > 0 ? theme.failure(`${blocked} 个系统阻塞`) : `${blocked} 个系统阻塞`}`);
@@ -827,18 +852,17 @@ function renderHelp(theme, command) {
827
852
  theme.section("推荐流程"),
828
853
  ` ${theme.command("install → status → docs → setup → start → status → stop")}`,
829
854
  "",
830
- theme.section("人工配置示例"),
831
- ` 人工:${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
832
- ` AI: ${theme.command("pnpm exec -- proflow-chatgpt-carrier setup")}`,
833
- ` ${theme.command("--carrier-url <url>")}`,
855
+ theme.section("配置入口"),
856
+ ` ${theme.command("platform setup")}`,
834
857
  "",
835
858
  theme.section("状态图例"),
836
859
  ` ${theme.success("已就绪")} 配置与验证完成`,
837
860
  ` ${theme.warning("需要操作")} 需要执行 setup 步骤`,
838
861
  ` ${theme.info("等待依赖")} 等待上游 Module 或外部服务`,
839
862
  ` ${theme.failure("失败")} 已确认配置或运行故障`,
840
- " 运行中 服务进程正在运行",
841
- " 无独立进程 该模块无需启动",
863
+ " 运行中 服务进程正在运行",
864
+ " 外部资源可用 Chrome 等外部资源已通过真实探测",
865
+ " 无独立进程 该模块无需启动",
842
866
  "",
843
867
  theme.section("遇到问题"),
844
868
  ` 先运行 ${theme.command("platform status")};配置问题运行 ${theme.command("platform setup")}。`,
@@ -855,24 +879,35 @@ export function renderHumanResult(result, options = {}) {
855
879
  ? result.data.blockers.filter(isRecord)
856
880
  : [];
857
881
  if (blockers.length > 0) {
882
+ const productOrder = new Map([
883
+ ["execution-browser-extension", 0],
884
+ ["dev-tunnel", 1],
885
+ ["model-provider-api", 2],
886
+ ]);
858
887
  const groups = [
859
888
  { status: "FAILED", title: "失败", icon: "✕", tone: theme.failure },
860
889
  {
861
890
  status: "ACTION_REQUIRED",
862
- title: "需要操作",
891
+ title: "当前根阻塞",
863
892
  icon: "◆",
864
893
  tone: theme.warning,
865
894
  },
866
- { status: "BLOCKED", title: "等待依赖", icon: "◇", tone: theme.info },
895
+ { status: "BLOCKED", title: "下游等待", icon: "◇", tone: theme.info },
867
896
  ];
868
897
  const lines = [theme.failure("平台未启动:存在未就绪模块"), ""];
869
898
  for (const group of groups) {
870
- const entries = blockers.filter((item) => item.setupStatus === group.status);
899
+ const entries = blockers
900
+ .filter((item) => item.setupStatus === group.status)
901
+ .sort((a, b) => (productOrder.get(String(a.moduleRef)) ?? 100) -
902
+ (productOrder.get(String(b.moduleRef)) ?? 100));
871
903
  if (entries.length === 0)
872
904
  continue;
873
905
  lines.push(theme.section(group.title));
874
- for (const item of entries)
875
- lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}`, ` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
906
+ for (const item of entries) {
907
+ lines.push(`${group.tone(group.icon)} ${theme.section(String(item.moduleRef))}`, ` 原因:${typeof item.reason === "string" ? item.reason : "模块尚未就绪"}`);
908
+ if (group.status !== "BLOCKED")
909
+ lines.push(` 下一步:${theme.command(typeof item.nextCommand === "string" ? item.nextCommand : `platform setup --module ${String(item.moduleRef)}`)}`);
910
+ }
876
911
  lines.push("");
877
912
  }
878
913
  const readyCount = Array.isArray(result.data.results)
@@ -880,7 +915,7 @@ export function renderHumanResult(result, options = {}) {
880
915
  isRecord(item.result) &&
881
916
  item.result.status === "SUCCEEDED").length - blockers.length
882
917
  : 0;
883
- lines.push(theme.section("检查汇总"), `${Math.max(0, readyCount)} 已就绪 · ${blockers.length} 未就绪 · 0 个模块已启动`, `处理方式:${theme.command(`platform setup${result.workspaceRoot ? ` --workspace "${result.workspaceRoot}"` : ""}`)}`);
918
+ 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}"` : ""}`)}`);
884
919
  return lines.join("\n");
885
920
  }
886
921
  }
@@ -0,0 +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"];
2
+ export declare function orderModuleRefsForInstall(moduleRefs: readonly string[]): string[];
@@ -0,0 +1,39 @@
1
+ export const FROZEN_DEPLOYMENT_INSTALL_ORDER = [
2
+ "chrome-runtime",
3
+ "execution-browser-extension",
4
+ "agent-controller-dev",
5
+ "agent-product",
6
+ "agent-test-ops",
7
+ "agent-gateway",
8
+ "dev-tunnel",
9
+ "agent-runtime",
10
+ "platform-host",
11
+ "execution-runtime",
12
+ "execution-local",
13
+ "model-provider-api",
14
+ "model-runtime",
15
+ "task-orchestration",
16
+ "task-store-sqlite",
17
+ "task-migration-runner",
18
+ "execution-contracts",
19
+ "model-contracts",
20
+ "module-contract",
21
+ "module-skill",
22
+ "module-template",
23
+ "deployment-conformance",
24
+ "platform-cli",
25
+ ];
26
+ const installRank = new Map(FROZEN_DEPLOYMENT_INSTALL_ORDER.map((moduleRef, index) => [moduleRef, index]));
27
+ export function orderModuleRefsForInstall(moduleRefs) {
28
+ return [...moduleRefs].sort((left, right) => {
29
+ const leftRank = installRank.get(left);
30
+ const rightRank = installRank.get(right);
31
+ if (leftRank !== undefined && rightRank !== undefined)
32
+ return leftRank - rightRank;
33
+ if (leftRank !== undefined)
34
+ return -1;
35
+ if (rightRank !== undefined)
36
+ return 1;
37
+ return left.localeCompare(right);
38
+ });
39
+ }
@@ -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
  }
@@ -1,4 +1,5 @@
1
1
  import { moduleStatusObservationSchema, } from "@tomflow/proflow-module-contract";
2
+ import { orderModuleRefsForInstall } from "../deployment-order.js";
2
3
  import { PlatformError } from "../errors.js";
3
4
  import { buildDependencyGraph } from "../graph/graph.js";
4
5
  import { reportProgress } from "../progress.js";
@@ -13,6 +14,16 @@ function ordered(modules, reverse = false) {
13
14
  .map((ref) => byRef.get(ref))
14
15
  .filter((item) => item !== undefined);
15
16
  }
17
+ function orderedForInstall(modules) {
18
+ // Keep dependency validation, but do not let dependency depth/alphabetical order
19
+ // dictate the user-facing package installation trajectory.
20
+ buildDependencyGraph(modules);
21
+ const refs = orderModuleRefsForInstall(modules.map((module) => module.moduleRef));
22
+ const byRef = new Map(modules.map((module) => [module.moduleRef, module]));
23
+ return refs
24
+ .map((ref) => byRef.get(ref))
25
+ .filter((item) => item !== undefined);
26
+ }
16
27
  export async function observeStatuses(catalog, modules, workspaceRoot, reporter) {
17
28
  const results = [];
18
29
  const modulesInOrder = [...modules].sort((a, b) => a.moduleRef.localeCompare(b.moduleRef));
@@ -69,7 +80,9 @@ export async function observeDocs(catalog, modules, workspaceRoot, reporter) {
69
80
  }
70
81
  async function runOrdered(catalog, modules, workspaceRoot, command, reverse, reporter) {
71
82
  const results = [];
72
- const modulesInOrder = ordered(modules, reverse);
83
+ const modulesInOrder = command === "install"
84
+ ? orderedForInstall(modules)
85
+ : ordered(modules, reverse);
73
86
  for (const [index, module] of modulesInOrder.entries()) {
74
87
  reportProgress(reporter, {
75
88
  command,
@@ -162,12 +175,18 @@ export async function stopModulesThin(catalog, modules, workspaceRoot, reporter)
162
175
  export async function setupModulesThin(catalog, modules, workspaceRoot, target, reporter) {
163
176
  const results = [];
164
177
  const skipped = [];
165
- let matched = target === undefined;
178
+ const blockers = [];
179
+ const graph = buildDependencyGraph(modules);
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 selectedRefs = new Set(target === undefined ? graph.order : [target.moduleRef]);
184
+ const modulesInOrder = graph.order
185
+ .filter((moduleRef) => selectedRefs.has(moduleRef))
186
+ .map((moduleRef) => byRef.get(moduleRef))
187
+ .filter((module) => module !== undefined);
166
188
  let completed = true;
167
- const modulesInOrder = ordered(modules);
168
189
  for (const [index, module] of modulesInOrder.entries()) {
169
- if (target !== undefined && module.moduleRef !== target.moduleRef)
170
- continue;
171
190
  reportProgress(reporter, {
172
191
  command: "setup",
173
192
  phase: "setup",
@@ -177,17 +196,16 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
177
196
  status: "STARTED",
178
197
  message: module.moduleRef,
179
198
  });
180
- matched = true;
181
199
  const status = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
182
200
  if (!succeeded(status.result)) {
183
201
  results.push(status);
184
202
  completed = false;
185
- if (target !== undefined)
203
+ if (target?.moduleRef === module.moduleRef)
186
204
  break;
187
205
  continue;
188
206
  }
189
207
  const observed = moduleStatusObservationSchema.parse(status.result.data);
190
- if (observed.setupStatus === "READY" && target?.input === undefined) {
208
+ if (observed.setupStatus === "READY" && target === undefined) {
191
209
  skipped.push({ moduleRef: module.moduleRef, reason: "READY" });
192
210
  reportProgress(reporter, {
193
211
  command: "setup",
@@ -217,13 +235,38 @@ export async function setupModulesThin(catalog, modules, workspaceRoot, target,
217
235
  });
218
236
  if (!succeeded(setup.result)) {
219
237
  completed = false;
220
- if (target !== undefined)
221
- break;
222
238
  }
239
+ const reconciled = await dispatchModuleCommand(catalog, module, "status", context(workspaceRoot));
240
+ if (!succeeded(reconciled.result)) {
241
+ results.push(reconciled);
242
+ completed = false;
243
+ }
244
+ else {
245
+ const reconciledStatus = moduleStatusObservationSchema.parse(reconciled.result.data);
246
+ if (reconciledStatus.setupStatus === "BLOCKED" ||
247
+ reconciledStatus.setupStatus === "FAILED") {
248
+ const issue = reconciledStatus.issues?.find((item) => item.scope === "SETUP");
249
+ blockers.push({
250
+ moduleRef: module.moduleRef,
251
+ setupStatus: reconciledStatus.setupStatus,
252
+ ...(issue === undefined
253
+ ? {}
254
+ : { reason: issue.message, nextCommand: issue.nextCommand }),
255
+ });
256
+ completed = false;
257
+ }
258
+ }
259
+ if (target !== undefined &&
260
+ (!succeeded(setup.result) || blockers.length > 0))
261
+ break;
223
262
  }
224
- if (!matched)
225
- throw new PlatformError("INVALID_REQUEST", `setup target module ${target?.moduleRef ?? ""} was not discovered`);
226
- return { phase: "setup", results, completed, skipped };
263
+ return {
264
+ phase: "setup",
265
+ results,
266
+ completed,
267
+ ...(blockers[0] ? { blockedBy: blockers[0], blockers } : {}),
268
+ skipped,
269
+ };
227
270
  }
228
271
  export async function startModulesThin(catalog, modules, workspaceRoot, reporter) {
229
272
  const results = [];
@@ -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
- options.onSearchComplete?.(names.length);
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 < names.length) {
56
+ while (nextIndex < activeNames.length) {
52
57
  const index = nextIndex++;
53
- const packageName = names[index];
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: names.length,
70
+ total: activeNames.length,
66
71
  packageName,
67
72
  });
68
73
  }
69
74
  };
70
- await Promise.all(Array.from({ length: Math.min(4, names.length) }, () => worker()));
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([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-platform-cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,11 +24,11 @@
24
24
  "SETUP.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@tomflow/proflow-module-contract": "^0.1.12"
27
+ "@tomflow/proflow-module-contract": "^0.1.13"
28
28
  },
29
29
  "devDependencies": {
30
- "@tomflow/proflow-deployment-conformance": "^0.1.10",
31
- "@tomflow/proflow-module-template": "^0.1.9"
30
+ "@tomflow/proflow-deployment-conformance": "^0.1.12",
31
+ "@tomflow/proflow-module-template": "^0.1.10"
32
32
  },
33
33
  "description": "Thin Platform CLI for Module discovery, documentation, package synchronization and lifecycle orchestration.",
34
34
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "platform-cli",
5
5
  "packageName": "@tomflow/proflow-platform-cli",
6
- "moduleVersion": "0.1.36",
6
+ "moduleVersion": "0.1.38",
7
7
  "kind": "cli",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",