create-principles-disciple 1.132.2 → 1.132.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/console/dist/server/routes/update.js +187 -19
  2. package/console/dist/server/update/mutation-controller.d.ts +18 -0
  3. package/console/dist/server/update/mutation-controller.js +29 -0
  4. package/console/dist/server/utils/installed-layout.d.ts +1 -0
  5. package/console/dist/server/utils/installed-layout.js +2 -0
  6. package/console/package.json +1 -0
  7. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +15 -0
  8. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  9. package/core/dist/runtime-v2/adapter/__tests__/pd-stream-simple.test.d.ts +2 -0
  10. package/core/dist/runtime-v2/adapter/__tests__/pd-stream-simple.test.d.ts.map +1 -0
  11. package/core/dist/runtime-v2/adapter/__tests__/pd-stream-simple.test.js +71 -0
  12. package/core/dist/runtime-v2/adapter/__tests__/pd-stream-simple.test.js.map +1 -0
  13. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-http-transport.test.d.ts +2 -0
  14. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-http-transport.test.d.ts.map +1 -0
  15. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-http-transport.test.js +219 -0
  16. package/core/dist/runtime-v2/adapter/__tests__/pi-ai-http-transport.test.js.map +1 -0
  17. package/core/dist/runtime-v2/adapter/artificer-l2-adapter.d.ts.map +1 -1
  18. package/core/dist/runtime-v2/adapter/artificer-l2-adapter.js +2 -3
  19. package/core/dist/runtime-v2/adapter/artificer-l2-adapter.js.map +1 -1
  20. package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.d.ts +12 -1
  21. package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.d.ts.map +1 -1
  22. package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.js +15 -2
  23. package/core/dist/runtime-v2/adapter/l2-agent-loop-adapter.js.map +1 -1
  24. package/core/dist/runtime-v2/adapter/pi-ai-http-transport.d.ts +50 -0
  25. package/core/dist/runtime-v2/adapter/pi-ai-http-transport.d.ts.map +1 -0
  26. package/core/dist/runtime-v2/adapter/pi-ai-http-transport.js +93 -0
  27. package/core/dist/runtime-v2/adapter/pi-ai-http-transport.js.map +1 -0
  28. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  29. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +5 -0
  30. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  31. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.js +152 -0
  32. package/core/dist/runtime-v2/internalization/__tests__/owner-decision.test.js.map +1 -1
  33. package/core/dist/runtime-v2/internalization/owner-review.d.ts +7 -1
  34. package/core/dist/runtime-v2/internalization/owner-review.d.ts.map +1 -1
  35. package/core/dist/runtime-v2/internalization/owner-review.js +22 -3
  36. package/core/dist/runtime-v2/internalization/owner-review.js.map +1 -1
  37. package/core/package.json +3 -2
  38. package/dist/installer.d.ts.map +1 -1
  39. package/dist/installer.js +50 -0
  40. package/dist/installer.js.map +1 -1
  41. package/dist/update/release-manager-authority.d.ts +69 -0
  42. package/dist/update/release-manager-authority.d.ts.map +1 -0
  43. package/dist/update/release-manager-authority.js +125 -0
  44. package/dist/update/release-manager-authority.js.map +1 -0
  45. package/dist/utils/workspace-divergence.d.ts +36 -0
  46. package/dist/utils/workspace-divergence.d.ts.map +1 -0
  47. package/dist/utils/workspace-divergence.js +87 -0
  48. package/dist/utils/workspace-divergence.js.map +1 -0
  49. package/install-layout/dist/index.d.ts +5 -0
  50. package/install-layout/dist/index.js +1 -0
  51. package/install-layout/package.json +1 -1
  52. package/package.json +3 -2
  53. package/plugin/dist/bundle.js +870 -734
  54. package/plugin/dist/governance-audit.js +381 -247
  55. package/plugin/dist/openclaw-sdk.d.ts +3 -0
  56. package/plugin/dist/rulehost-evidence.js +420 -286
  57. package/plugin/dist/service/subagent-workflow/types.d.ts +2 -2
  58. package/plugin/dist/utils/retry.d.ts +1 -1
  59. package/plugin/dist/utils/workspace-resolver.d.ts +35 -4
  60. package/plugin/openclaw.plugin.json +1 -1
  61. package/plugin/package.json +1 -1
  62. package/release-manager/dist/i18n.d.ts +6 -0
  63. package/release-manager/dist/i18n.d.ts.map +1 -0
  64. package/release-manager/dist/i18n.js +234 -0
  65. package/release-manager/dist/i18n.js.map +1 -0
  66. package/release-manager/dist/index.d.ts +7 -0
  67. package/release-manager/dist/index.d.ts.map +1 -0
  68. package/release-manager/dist/index.js +474 -0
  69. package/release-manager/dist/index.js.map +1 -0
  70. package/release-manager/dist/installer.d.ts +185 -0
  71. package/release-manager/dist/installer.d.ts.map +1 -0
  72. package/release-manager/dist/installer.js +2305 -0
  73. package/release-manager/dist/installer.js.map +1 -0
  74. package/release-manager/dist/installers/codex-host-installer.d.ts +29 -0
  75. package/release-manager/dist/installers/codex-host-installer.d.ts.map +1 -0
  76. package/release-manager/dist/installers/codex-host-installer.js +494 -0
  77. package/release-manager/dist/installers/codex-host-installer.js.map +1 -0
  78. package/release-manager/dist/installers/index.d.ts +40 -0
  79. package/release-manager/dist/installers/index.d.ts.map +1 -0
  80. package/release-manager/dist/installers/index.js +35 -0
  81. package/release-manager/dist/installers/index.js.map +1 -0
  82. package/release-manager/dist/installers/openclaw-host-installer.d.ts +18 -0
  83. package/release-manager/dist/installers/openclaw-host-installer.d.ts.map +1 -0
  84. package/release-manager/dist/installers/openclaw-host-installer.js +414 -0
  85. package/release-manager/dist/installers/openclaw-host-installer.js.map +1 -0
  86. package/release-manager/dist/mvp-config.d.ts +197 -0
  87. package/release-manager/dist/mvp-config.d.ts.map +1 -0
  88. package/release-manager/dist/mvp-config.js +509 -0
  89. package/release-manager/dist/mvp-config.js.map +1 -0
  90. package/release-manager/dist/prompts.d.ts +30 -0
  91. package/release-manager/dist/prompts.d.ts.map +1 -0
  92. package/release-manager/dist/prompts.js +231 -0
  93. package/release-manager/dist/prompts.js.map +1 -0
  94. package/release-manager/dist/skill-language.d.ts +32 -0
  95. package/release-manager/dist/skill-language.d.ts.map +1 -0
  96. package/release-manager/dist/skill-language.js +107 -0
  97. package/release-manager/dist/skill-language.js.map +1 -0
  98. package/release-manager/dist/uninstaller.d.ts +55 -0
  99. package/release-manager/dist/uninstaller.d.ts.map +1 -0
  100. package/release-manager/dist/uninstaller.js +409 -0
  101. package/release-manager/dist/uninstaller.js.map +1 -0
  102. package/release-manager/dist/update/atomic-file.d.ts +35 -0
  103. package/release-manager/dist/update/atomic-file.d.ts.map +1 -0
  104. package/release-manager/dist/update/atomic-file.js +120 -0
  105. package/release-manager/dist/update/atomic-file.js.map +1 -0
  106. package/release-manager/dist/update/bootstrap-protocol.d.ts +55 -0
  107. package/release-manager/dist/update/bootstrap-protocol.d.ts.map +1 -0
  108. package/release-manager/dist/update/bootstrap-protocol.js +132 -0
  109. package/release-manager/dist/update/bootstrap-protocol.js.map +1 -0
  110. package/release-manager/dist/update/channel-metadata.d.ts +28 -0
  111. package/release-manager/dist/update/channel-metadata.d.ts.map +1 -0
  112. package/release-manager/dist/update/channel-metadata.js +90 -0
  113. package/release-manager/dist/update/channel-metadata.js.map +1 -0
  114. package/release-manager/dist/update/channel-promotion.d.ts +39 -0
  115. package/release-manager/dist/update/channel-promotion.d.ts.map +1 -0
  116. package/release-manager/dist/update/channel-promotion.js +55 -0
  117. package/release-manager/dist/update/channel-promotion.js.map +1 -0
  118. package/release-manager/dist/update/data-compatibility.d.ts +35 -0
  119. package/release-manager/dist/update/data-compatibility.d.ts.map +1 -0
  120. package/release-manager/dist/update/data-compatibility.js +45 -0
  121. package/release-manager/dist/update/data-compatibility.js.map +1 -0
  122. package/release-manager/dist/update/install-layout.d.ts +59 -0
  123. package/release-manager/dist/update/install-layout.d.ts.map +1 -0
  124. package/release-manager/dist/update/install-layout.js +127 -0
  125. package/release-manager/dist/update/install-layout.js.map +1 -0
  126. package/release-manager/dist/update/legacy-migration.d.ts +56 -0
  127. package/release-manager/dist/update/legacy-migration.d.ts.map +1 -0
  128. package/release-manager/dist/update/legacy-migration.js +254 -0
  129. package/release-manager/dist/update/legacy-migration.js.map +1 -0
  130. package/release-manager/dist/update/product-identity.d.ts +50 -0
  131. package/release-manager/dist/update/product-identity.d.ts.map +1 -0
  132. package/release-manager/dist/update/product-identity.js +63 -0
  133. package/release-manager/dist/update/product-identity.js.map +1 -0
  134. package/release-manager/dist/update/release-asset-manifest.d.ts +31 -0
  135. package/release-manager/dist/update/release-asset-manifest.d.ts.map +1 -0
  136. package/release-manager/dist/update/release-asset-manifest.js +161 -0
  137. package/release-manager/dist/update/release-asset-manifest.js.map +1 -0
  138. package/release-manager/dist/update/release-identity.d.ts +33 -0
  139. package/release-manager/dist/update/release-identity.d.ts.map +1 -0
  140. package/release-manager/dist/update/release-identity.js +108 -0
  141. package/release-manager/dist/update/release-identity.js.map +1 -0
  142. package/release-manager/dist/update/release-manager-authority.d.ts +69 -0
  143. package/release-manager/dist/update/release-manager-authority.d.ts.map +1 -0
  144. package/release-manager/dist/update/release-manager-authority.js +125 -0
  145. package/release-manager/dist/update/release-manager-authority.js.map +1 -0
  146. package/release-manager/dist/update/release-manager.d.ts +96 -0
  147. package/release-manager/dist/update/release-manager.d.ts.map +1 -0
  148. package/release-manager/dist/update/release-manager.js +253 -0
  149. package/release-manager/dist/update/release-manager.js.map +1 -0
  150. package/release-manager/dist/update/release-metadata.d.ts +57 -0
  151. package/release-manager/dist/update/release-metadata.d.ts.map +1 -0
  152. package/release-manager/dist/update/release-metadata.js +200 -0
  153. package/release-manager/dist/update/release-metadata.js.map +1 -0
  154. package/release-manager/dist/update/release-policy.d.ts +51 -0
  155. package/release-manager/dist/update/release-policy.d.ts.map +1 -0
  156. package/release-manager/dist/update/release-policy.js +103 -0
  157. package/release-manager/dist/update/release-policy.js.map +1 -0
  158. package/release-manager/dist/update/rollback-policy.d.ts +56 -0
  159. package/release-manager/dist/update/rollback-policy.d.ts.map +1 -0
  160. package/release-manager/dist/update/rollback-policy.js +61 -0
  161. package/release-manager/dist/update/rollback-policy.js.map +1 -0
  162. package/release-manager/dist/update/transaction-journal.d.ts +131 -0
  163. package/release-manager/dist/update/transaction-journal.d.ts.map +1 -0
  164. package/release-manager/dist/update/transaction-journal.js +346 -0
  165. package/release-manager/dist/update/transaction-journal.js.map +1 -0
  166. package/release-manager/dist/update/trust-metadata.d.ts +55 -0
  167. package/release-manager/dist/update/trust-metadata.d.ts.map +1 -0
  168. package/release-manager/dist/update/trust-metadata.js +170 -0
  169. package/release-manager/dist/update/trust-metadata.js.map +1 -0
  170. package/release-manager/dist/update/update-history.d.ts +46 -0
  171. package/release-manager/dist/update/update-history.d.ts.map +1 -0
  172. package/release-manager/dist/update/update-history.js +123 -0
  173. package/release-manager/dist/update/update-history.js.map +1 -0
  174. package/release-manager/dist/utils/config-file-io.d.ts +26 -0
  175. package/release-manager/dist/utils/config-file-io.d.ts.map +1 -0
  176. package/release-manager/dist/utils/config-file-io.js +128 -0
  177. package/release-manager/dist/utils/config-file-io.js.map +1 -0
  178. package/release-manager/dist/utils/env.d.ts +60 -0
  179. package/release-manager/dist/utils/env.d.ts.map +1 -0
  180. package/release-manager/dist/utils/env.js +308 -0
  181. package/release-manager/dist/utils/env.js.map +1 -0
  182. package/release-manager/dist/utils/logger.d.ts +15 -0
  183. package/release-manager/dist/utils/logger.d.ts.map +1 -0
  184. package/release-manager/dist/utils/logger.js +30 -0
  185. package/release-manager/dist/utils/logger.js.map +1 -0
  186. package/release-manager/dist/utils/workspace-divergence.d.ts +36 -0
  187. package/release-manager/dist/utils/workspace-divergence.d.ts.map +1 -0
  188. package/release-manager/dist/utils/workspace-divergence.js +87 -0
  189. package/release-manager/dist/utils/workspace-divergence.js.map +1 -0
  190. package/release-manager/package.json +78 -0
@@ -0,0 +1,409 @@
1
+ /**
2
+ * Uninstaller module
3
+ *
4
+ * ⚠️ Safety principles:
5
+ * 1. Only delete plugin system files (~/.openclaw/extensions/principles-disciple)
6
+ * 2. Only delete plugin config files (~/.openclaw/principles-disciple.json)
7
+ * 3. Never delete any files from user workspace:
8
+ * - MD files (AGENTS.md, SOUL.md, etc.)
9
+ * - Memory files (.principles/ directory)
10
+ * - State files (.state/ directory)
11
+ * - Any user data
12
+ *
13
+ * ADR-0020 §2.3: Multi-host uninstall. Host-side config cleanup (openclaw.json
14
+ * entries, ~/.codex/hooks.json entries, wrapper scripts) is delegated to
15
+ * HostInstaller.uninstall() implementations. Workspace user data is always
16
+ * preserved regardless of host target.
17
+ */
18
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
19
+ import fse from 'fs-extra';
20
+ import * as path from 'path';
21
+ import * as os from 'os';
22
+ import { confirm } from '@inquirer/prompts';
23
+ import { logger } from './utils/logger.js';
24
+ import { getOpenClawConfigDir, getPluginExtDir, checkOpenClawGateway } from './utils/env.js';
25
+ import { getGlobalShimPaths, getInstalledBinDir, getPdRuntimeDir, getInstallManifestPath, isWindows } from './mvp-config.js';
26
+ import { parseInstallManifest } from '@principles/install-layout';
27
+ import { setLanguage, t, getLanguage } from './i18n.js';
28
+ import { getHostInstallers } from './installers/index.js';
29
+ export function planSharedRuntimeUninstall(host) {
30
+ if (host === 'all') {
31
+ return { removeSharedRuntime: true, remainingHosts: [], manifestHasTarget: true };
32
+ }
33
+ try {
34
+ const raw = JSON.parse(readFileSync(getInstallManifestPath(), 'utf8'));
35
+ const parsed = parseInstallManifest(raw);
36
+ if (!parsed.manifest) {
37
+ return {
38
+ removeSharedRuntime: false,
39
+ remainingHosts: [],
40
+ manifestHasTarget: false,
41
+ warning: `${parsed.error ?? 'install_manifest_malformed'}; shared runtime preserved. Re-run uninstall --host all after repairing ~/.pd/install.json.`,
42
+ };
43
+ }
44
+ const manifestHasTarget = parsed.manifest.hosts.includes(host);
45
+ const remainingHosts = parsed.manifest.hosts.filter(candidate => candidate !== host);
46
+ return { removeSharedRuntime: remainingHosts.length === 0, remainingHosts, manifestHasTarget };
47
+ }
48
+ catch (error) {
49
+ return {
50
+ removeSharedRuntime: false,
51
+ remainingHosts: [],
52
+ manifestHasTarget: false,
53
+ warning: `install_manifest_unreadable: ${error instanceof Error ? error.message : String(error)}; shared runtime preserved. Re-run uninstall --host all after repairing ~/.pd/install.json.`,
54
+ };
55
+ }
56
+ }
57
+ async function detectAndSetLanguage(lang) {
58
+ setLanguage(lang === 'en' ? 'en' : 'zh');
59
+ }
60
+ /**
61
+ * Check install status across all hosts (OpenClaw + Codex).
62
+ *
63
+ * ADR-0020 §2.3: Detects PD install markers for each registered host.
64
+ * Returns aggregated status so `pd status` / `npx create-principles-disciple
65
+ * status` shows the full picture regardless of which host was selected
66
+ * during install.
67
+ */
68
+ export function checkInstallStatus(host = 'all') {
69
+ const configDir = getOpenClawConfigDir();
70
+ const lang = getLanguage();
71
+ // OpenClaw paths
72
+ const openclawPaths = [
73
+ { path: getPluginExtDir(), name: lang === 'zh' ? '插件扩展目录' : 'Plugin extension directory', type: 'dir' },
74
+ { path: path.join(configDir, 'principles-disciple.json'), name: lang === 'zh' ? 'OpenClaw 配置文件' : 'OpenClaw config file', type: 'file' },
75
+ ];
76
+ // Codex paths (ADR-0020).
77
+ // NOTE: ~/.codex/hooks.json is intentionally NOT in this deletion set.
78
+ // It is a shared host config file — CodexHostInstaller.uninstall() removes
79
+ // only PD-owned entries (matched by __pd_marker) from it. If it were listed
80
+ // here, the generic delete loop below would delete the entire file,
81
+ // destroying the user's non-PD Codex hooks. Also, including it would make
82
+ // isInstalled=true for any user who merely has a hooks.json, even if PD
83
+ // was never installed.
84
+ const pdCodexDir = path.join(os.homedir(), '.pd', 'codex');
85
+ const codexPaths = [
86
+ { path: path.join(pdCodexDir, 'pd-hooks.marker'), name: lang === 'zh' ? 'Codex 安装标记' : 'Codex install marker', type: 'file' },
87
+ { path: path.join(pdCodexDir, 'pd-hook-entry.cjs'), name: lang === 'zh' ? 'Codex 钩子入口' : 'Codex hook entry script', type: 'file' },
88
+ ];
89
+ const allPaths = host === 'openclaw'
90
+ ? openclawPaths
91
+ : host === 'codex'
92
+ ? codexPaths
93
+ : [...openclawPaths, ...codexPaths];
94
+ const checkedPaths = allPaths.map(p => ({
95
+ exists: existsSync(p.path),
96
+ path: p.path,
97
+ name: p.name,
98
+ type: p.type,
99
+ }));
100
+ const isInstalled = checkedPaths.some(p => p.exists);
101
+ return { isInstalled, paths: checkedPaths };
102
+ }
103
+ /**
104
+ * 检查 shim 文件是否由 Principles Disciple 创建
105
+ * 通过读取文件内容,确认其目标指向 PD 安装目录
106
+ */
107
+ export function isPdOwnedShim(shimPath) {
108
+ try {
109
+ const content = readFileSync(shimPath, 'utf-8');
110
+ const pdBinDir = getInstalledBinDir();
111
+ const pdBinPath = isWindows()
112
+ ? path.join(pdBinDir, 'pd.cmd')
113
+ : path.join(pdBinDir, 'pd');
114
+ if (isWindows()) {
115
+ // Windows cmd/ps1 shim 应包含 PD 安装路径
116
+ return content.includes(pdBinDir) || content.includes(pdBinPath);
117
+ }
118
+ else {
119
+ // Unix shim 通常是符号链接或脚本,检查是否指向 PD 安装目录
120
+ return content.includes(pdBinDir) || content.includes(pdBinPath);
121
+ }
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
127
+ /**
128
+ * Clean up global pd shim files
129
+ * Only deletes shims that were created by Principles Disciple
130
+ */
131
+ async function removeGlobalPdShim() {
132
+ const removed = [];
133
+ const skipped = [];
134
+ const shimPaths = getGlobalShimPaths();
135
+ if (shimPaths.length === 0) {
136
+ logger.info(t('cannot_detect_npm_global'));
137
+ return { removed, skipped };
138
+ }
139
+ for (const shimPath of shimPaths) {
140
+ if (!existsSync(shimPath)) {
141
+ skipped.push(shimPath);
142
+ continue;
143
+ }
144
+ if (!isPdOwnedShim(shimPath)) {
145
+ logger.warn(`${t('global_command_skip_not_owned')}: ${shimPath}`);
146
+ skipped.push(shimPath);
147
+ continue;
148
+ }
149
+ try {
150
+ await fse.remove(shimPath);
151
+ removed.push(shimPath);
152
+ logger.success(`${t('global_command_deleted')}: ${shimPath}`);
153
+ }
154
+ catch (error) {
155
+ logger.warn(`${t('global_command_delete_failed')}: ${shimPath} - ${error instanceof Error ? error.message : String(error)}`);
156
+ skipped.push(shimPath);
157
+ }
158
+ }
159
+ return { removed, skipped };
160
+ }
161
+ /**
162
+ * Get user workspace path (for showing preservation notice)
163
+ */
164
+ function getWorkspacePath() {
165
+ const configDir = getOpenClawConfigDir();
166
+ const configPath = path.join(configDir, 'principles-disciple.json');
167
+ if (!existsSync(configPath))
168
+ return null;
169
+ // Read and parse JSON directly — require() would execute the file if its
170
+ // extension ever changed, and the path must stay inside the config dir.
171
+ const resolved = path.resolve(configPath);
172
+ if (resolved !== configPath && !resolved.startsWith(configDir + path.sep)) {
173
+ return null;
174
+ }
175
+ try {
176
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
177
+ if (config && typeof config === 'object' && !Array.isArray(config)) {
178
+ const workspace = Reflect.get(config, 'workspace');
179
+ return typeof workspace === 'string' && workspace.length > 0 ? workspace : null;
180
+ }
181
+ return null;
182
+ }
183
+ catch {
184
+ return null;
185
+ }
186
+ }
187
+ /**
188
+ * Execute uninstall
189
+ *
190
+ * @param options.force Skip confirmation prompt (dangerous, for scripts only)
191
+ * @param options.host ADR-0020: 'openclaw' | 'codex' | 'all' (default 'all')
192
+ */
193
+ const REMOVE_RETRY_ATTEMPTS = 3;
194
+ const REMOVE_RETRY_DELAY_MS = 1000;
195
+ async function removeWithRetry(targetPath, _type) {
196
+ for (let attempt = 1; attempt <= REMOVE_RETRY_ATTEMPTS; attempt++) {
197
+ try {
198
+ await fse.remove(targetPath);
199
+ return;
200
+ }
201
+ catch (error) {
202
+ const errMsg = error instanceof Error ? error.message : String(error);
203
+ const isLockError = errMsg.includes('EPERM') || errMsg.includes('EBUSY') || errMsg.includes('permission');
204
+ if (!isLockError || attempt === REMOVE_RETRY_ATTEMPTS)
205
+ throw error;
206
+ await new Promise(r => setTimeout(r, REMOVE_RETRY_DELAY_MS * attempt));
207
+ }
208
+ }
209
+ }
210
+ export async function uninstall(options = {}) {
211
+ await detectAndSetLanguage(options.lang);
212
+ const hostTarget = options.host ?? 'all';
213
+ const result = {
214
+ success: false,
215
+ removedDirs: [],
216
+ removedFiles: [],
217
+ removedGlobalShims: [],
218
+ skippedGlobalShims: [],
219
+ preservedPaths: [],
220
+ };
221
+ try {
222
+ const gatewayStatus = hostTarget === 'codex'
223
+ ? { isRunning: false, port: undefined, pid: undefined }
224
+ : await checkOpenClawGateway();
225
+ if (gatewayStatus.isRunning) {
226
+ const portInfo = gatewayStatus.port ? ` (port ${gatewayStatus.port})` : '';
227
+ const pidInfo = gatewayStatus.pid ? `, PID ${gatewayStatus.pid}` : '';
228
+ logger.warn(`OpenClaw gateway is running${portInfo}${pidInfo}.`);
229
+ logger.warn(isWindows()
230
+ ? '文件可能被占用导致删除失败。建议先关闭 OpenClaw(openclaw gateway stop),然后重新运行卸载。'
231
+ : 'Files may be locked. Stop OpenClaw first (openclaw gateway stop), then re-run uninstall.');
232
+ console.log('');
233
+ }
234
+ // 1. Check install status
235
+ const status = checkInstallStatus(hostTarget);
236
+ const runtimePlan = planSharedRuntimeUninstall(hostTarget);
237
+ if (runtimePlan.warning)
238
+ logger.warn(runtimePlan.warning);
239
+ if (!status.isInstalled && !runtimePlan.manifestHasTarget) {
240
+ logger.warn(t('no_install_detected'));
241
+ result.success = true;
242
+ return result;
243
+ }
244
+ // 2. Get workspace path and show preservation notice
245
+ const workspaceDir = getWorkspacePath();
246
+ console.log('\n');
247
+ logger.warn(t('uninstall_warning_title'));
248
+ console.log(` ${t('uninstall_warning_msg')}`);
249
+ console.log('\n');
250
+ if (workspaceDir && existsSync(workspaceDir)) {
251
+ logger.info(t('workspace_files_preserved'));
252
+ console.log(` 📁 ${t('workspace_dir_label')}: ${workspaceDir}`);
253
+ console.log(` 📄 ${t('md_files_preserved')}`);
254
+ console.log(` 📁 ${t('principles_dir_preserved')}`);
255
+ console.log(` 📁 ${t('state_dir_preserved')}`);
256
+ console.log('\n');
257
+ }
258
+ // 3. Show what will be deleted
259
+ logger.info(t('plugin_files_will_delete'));
260
+ for (const p of status.paths) {
261
+ if (p.exists) {
262
+ const icon = p.type === 'dir' ? '📁' : '📄';
263
+ console.log(` ${icon} ${p.name}: ${p.path}`);
264
+ }
265
+ }
266
+ console.log('\n');
267
+ // 4. Confirm uninstall (unless --force)
268
+ if (!options.force) {
269
+ const confirmed = await confirm({
270
+ message: t('confirm_uninstall'),
271
+ default: false,
272
+ });
273
+ if (!confirmed) {
274
+ logger.info(t('uninstall_cancelled'));
275
+ result.success = true;
276
+ return result;
277
+ }
278
+ }
279
+ // 5. Execute deletion (only plugin system files)
280
+ const deleteErrors = [];
281
+ for (const p of status.paths) {
282
+ if (!p.exists)
283
+ continue;
284
+ try {
285
+ await removeWithRetry(p.path, p.type);
286
+ if (p.type === 'dir') {
287
+ result.removedDirs.push(p.path);
288
+ }
289
+ else {
290
+ result.removedFiles.push(p.path);
291
+ }
292
+ logger.success(`${t('deleted')}: ${p.name}`);
293
+ }
294
+ catch (error) {
295
+ const errMsg = error instanceof Error ? error.message : String(error);
296
+ const isPermError = errMsg.includes('EPERM') || errMsg.includes('permission') || errMsg.includes('access denied') || errMsg.includes('EBUSY');
297
+ if (isPermError) {
298
+ logger.error(`${t('delete_failed')}: ${p.name} - ${errMsg}`);
299
+ logger.warn(isWindows()
300
+ ? '文件被占用,请先关闭 OpenClaw(openclaw gateway stop),然后手动删除目录或重新运行卸载。'
301
+ : 'File is locked. Stop OpenClaw first (openclaw gateway stop), then re-run uninstall or delete manually.');
302
+ }
303
+ else {
304
+ logger.error(`${t('delete_failed')}: ${p.name} - ${errMsg}`);
305
+ }
306
+ deleteErrors.push({ name: p.name, error: errMsg });
307
+ }
308
+ }
309
+ // 6. The pd shim belongs to the shared runtime. Keep it while another host
310
+ // remains attached; otherwise a host-scoped uninstall breaks that host.
311
+ if (runtimePlan.removeSharedRuntime) {
312
+ console.log('\n');
313
+ logger.info(t('cleaning_global_commands'));
314
+ const { removed, skipped } = await removeGlobalPdShim();
315
+ result.removedGlobalShims = removed;
316
+ result.skippedGlobalShims = skipped;
317
+ }
318
+ else {
319
+ logger.info('Keeping the shared pd command because another host still uses the runtime.');
320
+ }
321
+ // 6.5. ADR-0020 §2.3: Clean up host-side configs via HostInstallers.
322
+ // Replaces the old cleanupOpenClawConfig() with multi-host delegation.
323
+ const hostInstallers = getHostInstallers(hostTarget);
324
+ for (const installer of hostInstallers) {
325
+ try {
326
+ const ctx = {
327
+ language: getLanguage(),
328
+ force: options.force === true,
329
+ };
330
+ const hostResult = await installer.uninstall(ctx);
331
+ if (hostResult.success) {
332
+ if (hostResult.removedPaths.length > 0) {
333
+ logger.success(`Host "${installer.hostId}": cleaned ${hostResult.removedPaths.length} path(s)`);
334
+ }
335
+ }
336
+ else {
337
+ logger.warn(`Host "${installer.hostId}" cleanup: ${hostResult.reason ?? 'partial failure'}. Next: ${hostResult.nextAction}`);
338
+ }
339
+ }
340
+ catch (err) {
341
+ // rc-9: never silently swallow — surface the failure.
342
+ logger.warn(`Host "${installer.hostId}" cleanup threw: ${err instanceof Error ? err.message : String(err)}`);
343
+ }
344
+ }
345
+ // Shared runtime is removed only after the final host is uninstalled.
346
+ let sharedRuntimeRemovalFailed = false;
347
+ if (runtimePlan.removeSharedRuntime && existsSync(getPdRuntimeDir())) {
348
+ try {
349
+ await removeWithRetry(getPdRuntimeDir(), 'dir');
350
+ result.removedDirs.push(getPdRuntimeDir());
351
+ }
352
+ catch (err) {
353
+ sharedRuntimeRemovalFailed = true;
354
+ deleteErrors.push({ name: 'PD shared runtime', error: err instanceof Error ? err.message : String(err) });
355
+ }
356
+ }
357
+ if (runtimePlan.removeSharedRuntime && !sharedRuntimeRemovalFailed && existsSync(getInstallManifestPath())) {
358
+ try {
359
+ await removeWithRetry(getInstallManifestPath(), 'file');
360
+ result.removedFiles.push(getInstallManifestPath());
361
+ }
362
+ catch (err) {
363
+ deleteErrors.push({ name: 'PD install manifest', error: err instanceof Error ? err.message : String(err) });
364
+ }
365
+ }
366
+ else if (!runtimePlan.removeSharedRuntime && runtimePlan.remainingHosts.length > 0) {
367
+ writeFileSync(getInstallManifestPath(), JSON.stringify({
368
+ layoutVersion: 1,
369
+ mode: 'canonical',
370
+ hosts: runtimePlan.remainingHosts,
371
+ }, null, 2) + '\n', 'utf8');
372
+ }
373
+ // 7. Record preserved paths
374
+ if (workspaceDir) {
375
+ result.preservedPaths.push(workspaceDir);
376
+ if (existsSync(path.join(workspaceDir, '.principles'))) {
377
+ result.preservedPaths.push(path.join(workspaceDir, '.principles'));
378
+ }
379
+ if (existsSync(path.join(workspaceDir, '.state'))) {
380
+ result.preservedPaths.push(path.join(workspaceDir, '.state'));
381
+ }
382
+ }
383
+ result.success = deleteErrors.length === 0;
384
+ if (deleteErrors.length > 0) {
385
+ result.error = deleteErrors.map(e => `${e.name}: ${e.error}`).join('; ');
386
+ }
387
+ console.log('\n');
388
+ if (deleteErrors.length > 0) {
389
+ logger.warn(t('uninstall_partial') || '卸载部分完成 — 部分文件未能删除,请按上方提示处理。');
390
+ }
391
+ else {
392
+ logger.success(t('uninstall_complete'));
393
+ }
394
+ console.log('\n');
395
+ if (result.preservedPaths.length > 0) {
396
+ logger.info(t('personal_data_preserved'));
397
+ result.preservedPaths.forEach(p => console.log(` ${p}`));
398
+ console.log('\n');
399
+ logger.info(t('manual_cleanup_hint'));
400
+ }
401
+ return result;
402
+ }
403
+ catch (error) {
404
+ result.error = error instanceof Error ? error.message : String(error);
405
+ logger.error(`${t('uninstall_failed')}: ${result.error}`);
406
+ return result;
407
+ }
408
+ }
409
+ //# sourceMappingURL=uninstaller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uninstaller.js","sourceRoot":"","sources":["../src/uninstaller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Atomic record adapter (SPEC §8).
3
+ *
4
+ * Atomicity: readers see either the old record or the new record — never a
5
+ * partial write. Durability: the selected record survives a crash. The two
6
+ * are tested separately; rename alone does NOT guarantee durability, so the
7
+ * writer fsyncs the record file before renaming and flushes the containing
8
+ * directory where the platform allows it.
9
+ *
10
+ * Platform notes (deliberate, documented):
11
+ * - POSIX: file fsync + directory fd fsync after rename.
12
+ * - Windows: file fsync + rename; Node cannot fsync a directory handle on
13
+ * Windows, so durability of the RENAME itself relies on the filesystem
14
+ * journal. As a compensating control the record is REREAD and verified
15
+ * after the rename — a torn state fails loud instead of silently lying.
16
+ * - Windows rename after large writes can transiently fail EPERM/EACCES
17
+ * while antivirus holds handles; the rename is retried within a bounded
18
+ * window and fails loud afterwards (never falls back to non-atomic copy).
19
+ */
20
+ export declare class AtomicRecordError extends Error {
21
+ readonly code: string;
22
+ constructor(code: string, message: string);
23
+ }
24
+ export interface AtomicWriteResult {
25
+ readonly path: string;
26
+ /** True when the post-rename reread reproduced the intended record bytes. */
27
+ readonly verified: boolean;
28
+ }
29
+ /**
30
+ * Replaces `recordPath` with `recordText` atomically:
31
+ * temp file (same directory) → fsync → rename → directory flush → reread.
32
+ * `recordText` must be the exact intended bytes; the reread must match.
33
+ */
34
+ export declare function writeRecordAtomically(recordPath: string, recordText: string): AtomicWriteResult;
35
+ //# sourceMappingURL=atomic-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-file.d.ts","sourceRoot":"","sources":["../../src/update/atomic-file.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAIxC;CACF;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AA0DD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAiC/F"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Atomic record adapter (SPEC §8).
3
+ *
4
+ * Atomicity: readers see either the old record or the new record — never a
5
+ * partial write. Durability: the selected record survives a crash. The two
6
+ * are tested separately; rename alone does NOT guarantee durability, so the
7
+ * writer fsyncs the record file before renaming and flushes the containing
8
+ * directory where the platform allows it.
9
+ *
10
+ * Platform notes (deliberate, documented):
11
+ * - POSIX: file fsync + directory fd fsync after rename.
12
+ * - Windows: file fsync + rename; Node cannot fsync a directory handle on
13
+ * Windows, so durability of the RENAME itself relies on the filesystem
14
+ * journal. As a compensating control the record is REREAD and verified
15
+ * after the rename — a torn state fails loud instead of silently lying.
16
+ * - Windows rename after large writes can transiently fail EPERM/EACCES
17
+ * while antivirus holds handles; the rename is retried within a bounded
18
+ * window and fails loud afterwards (never falls back to non-atomic copy).
19
+ */
20
+ import { closeSync, existsSync, fsyncSync, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
21
+ import { randomUUID } from 'node:crypto';
22
+ import { dirname, join } from 'node:path';
23
+ export class AtomicRecordError extends Error {
24
+ code;
25
+ constructor(code, message) {
26
+ super(message);
27
+ this.name = 'AtomicRecordError';
28
+ this.code = code;
29
+ }
30
+ }
31
+ function isDirectory(directoryPath) {
32
+ return existsSync(directoryPath) && statSync(directoryPath).isDirectory();
33
+ }
34
+ function fsyncDirectory(directoryPath) {
35
+ // Directory fd fsync is POSIX-only; on Windows this is a documented no-op
36
+ // compensated by the post-rename reread verification below.
37
+ if (process.platform === 'win32')
38
+ return;
39
+ let descriptor;
40
+ try {
41
+ descriptor = openSync(directoryPath, 'r');
42
+ fsyncSync(descriptor);
43
+ }
44
+ catch {
45
+ // Some filesystems refuse directory fsync entirely; the file-level fsync
46
+ // already ran. Failing the whole write here would trade a durable-enough
47
+ // record for no record.
48
+ }
49
+ finally {
50
+ if (descriptor !== undefined)
51
+ closeSync(descriptor);
52
+ }
53
+ }
54
+ const RENAME_RETRY_DELAYS_MS = [50, 100, 200, 400, 800, 800, 800, 800];
55
+ /**
56
+ * Synchronous bounded sleep for the rename retry loop. Atomics.wait is
57
+ * intentional here: this module is a synchronous CLI code path where
58
+ * blocking the thread IS the requested behaviour (there is no concurrent
59
+ * work to yield to), and Node.js — unlike browsers — permits Atomics.wait on
60
+ * the main thread. It returns 'timed-out' after the delay, which is exactly
61
+ * the sleep semantics we need.
62
+ */
63
+ function sleepSync(delayMs) {
64
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
65
+ }
66
+ function renameWithWindowsRetry(source, destination) {
67
+ for (let attempt = 0;; attempt += 1) {
68
+ try {
69
+ renameSync(source, destination);
70
+ return;
71
+ }
72
+ catch (error) {
73
+ const code = typeof error === 'object' && error !== null && Object.hasOwn(error, 'code')
74
+ ? String(Reflect.get(error, 'code'))
75
+ : 'unknown';
76
+ const delayMs = RENAME_RETRY_DELAYS_MS[attempt];
77
+ if (!['EPERM', 'EACCES', 'EAGAIN'].includes(code) || delayMs === undefined) {
78
+ throw new AtomicRecordError('atomic_rename_failed', `Atomic record replacement failed (${code}): ${source} -> ${destination}. The previous record is untouched.`);
79
+ }
80
+ sleepSync(delayMs);
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * Replaces `recordPath` with `recordText` atomically:
86
+ * temp file (same directory) → fsync → rename → directory flush → reread.
87
+ * `recordText` must be the exact intended bytes; the reread must match.
88
+ */
89
+ export function writeRecordAtomically(recordPath, recordText) {
90
+ const directory = dirname(recordPath);
91
+ if (!isDirectory(directory)) {
92
+ throw new AtomicRecordError('atomic_directory_missing', `Record directory does not exist: ${directory}`);
93
+ }
94
+ // UUID-named temp file: pid+timestamp naming collides when two atomic
95
+ // writes land in the same millisecond, and the 'wx' exclusive flag then
96
+ // fails the second write spuriously.
97
+ const tempPath = join(directory, `.record-${randomUUID()}.tmp`);
98
+ let descriptor;
99
+ try {
100
+ descriptor = openSync(tempPath, 'wx');
101
+ writeFileSync(descriptor, recordText);
102
+ fsyncSync(descriptor);
103
+ closeSync(descriptor);
104
+ descriptor = undefined;
105
+ renameWithWindowsRetry(tempPath, recordPath);
106
+ fsyncDirectory(directory);
107
+ const reread = existsSync(recordPath) ? readFileSync(recordPath, 'utf8') : null;
108
+ if (reread !== recordText) {
109
+ throw new AtomicRecordError('atomic_verify_failed', `Post-rename verification failed for ${recordPath}: the record on disk does not match the intended bytes. The previous record may or may not be in place — recovery must consult the journal.`);
110
+ }
111
+ return { path: recordPath, verified: true };
112
+ }
113
+ catch (error) {
114
+ if (descriptor !== undefined)
115
+ closeSync(descriptor);
116
+ rmSync(tempPath, { force: true });
117
+ throw error;
118
+ }
119
+ }
120
+ //# sourceMappingURL=atomic-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-file.js","sourceRoot":"","sources":["../../src/update/atomic-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Bootstrap process protocol (SPEC §6.1) — strict single-JSON-object in,
3
+ * strict single-JSON-object out.
4
+ *
5
+ * Companion, Console, and the installer speak to the bootstrap through this
6
+ * one protocol. The transport layer (stdin/stdout around this pure handler)
7
+ * stays thin so the contract itself is testable: exactly one parseable JSON
8
+ * object per direction, stable reason codes, and a next action on every
9
+ * failure (rc-9, cli-1 semantics applied to the protocol boundary).
10
+ */
11
+ import type { ReleaseChannelName } from './product-identity.js';
12
+ import type { ReleaseManager } from './release-manager.js';
13
+ export type BootstrapRequestOp = 'inspect' | 'check' | 'apply' | 'rollback';
14
+ export type BootstrapRequest = {
15
+ readonly op: 'inspect';
16
+ } | {
17
+ readonly op: 'check';
18
+ readonly channel: ReleaseChannelName;
19
+ } | {
20
+ readonly op: 'apply';
21
+ readonly releaseId: string;
22
+ } | {
23
+ readonly op: 'rollback';
24
+ };
25
+ export interface BootstrapOkResponse {
26
+ readonly ok: true;
27
+ readonly result: unknown;
28
+ }
29
+ export interface BootstrapFailureResponse {
30
+ readonly ok: false;
31
+ readonly reason: string;
32
+ readonly message: string;
33
+ readonly nextAction: string;
34
+ }
35
+ export type BootstrapResponse = BootstrapOkResponse | BootstrapFailureResponse;
36
+ export declare class BootstrapProtocolError extends Error {
37
+ readonly reason: string;
38
+ constructor(reason: string, message: string);
39
+ }
40
+ /**
41
+ * Parses exactly ONE JSON object from the raw request text. Trailing data,
42
+ * non-objects, arrays, and unknown/missing fields are hard errors — a
43
+ * protocol peer that emits garbage gets a structured refusal, never a
44
+ * best-effort guess.
45
+ */
46
+ export declare function parseBootstrapRequest(raw: string): BootstrapRequest;
47
+ /** Serializes exactly one JSON object for stdout (no banners, no extra text). */
48
+ export declare function serializeBootstrapResponse(response: BootstrapResponse): string;
49
+ /**
50
+ * Dispatches a parsed request against a ReleaseManager and produces the
51
+ * protocol response. Manager failures become structured refusals — the
52
+ * protocol never leaks stack traces into the response object.
53
+ */
54
+ export declare function handleBootstrapRequest(request: BootstrapRequest, manager: ReleaseManager): Promise<BootstrapResponse>;
55
+ //# sourceMappingURL=bootstrap-protocol.d.ts.map