huaweicloud-devkit 1.1.5 → 1.1.6-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +2 -18
- package/plugins/huaweicloud-core/src/officeace-paths.mjs +51 -0
- package/plugins/huaweicloud-core/src/risk-rule-engine.mjs +43 -1
- package/plugins/huaweicloud-core/src/setup-cli.mjs +62 -24
- package/plugins/huaweicloud-core/src/tools.mjs +9 -1
- package/plugins/huaweicloud-core/src/update-check.mjs +19 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
-
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
8
8
|
|
|
9
9
|
**[中文](README.zh-CN.md) | English**
|
|
10
10
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
-
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
8
8
|
|
|
9
9
|
**中文 | [English](README.md)**
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6-next.0",
|
|
5
5
|
"kooCliVersion": "7.2.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.
|
|
20
|
+
"version": "1.1.6-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.
|
|
23
|
+
"version": "1.1.6-next.0",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.
|
|
20
|
+
"version": "1.1.6-next.0",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6-next.0",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.6-next.0",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -1,25 +1,9 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { resolve, dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
1
|
import { TOOL_DEFINITIONS, callTool } from './tools.mjs';
|
|
6
|
-
import { peekCachedUpdateInfo, applyUpdateHint } from './update-check.mjs';
|
|
2
|
+
import { peekCachedUpdateInfo, applyUpdateHint, readInstalledVersion } from './update-check.mjs';
|
|
7
3
|
import { initTelemetry } from './telemetry/telemetry.mjs';
|
|
8
4
|
import { detectAgent } from './telemetry/agent-detect.mjs';
|
|
9
5
|
|
|
10
|
-
const
|
|
11
|
-
const pluginRoot = resolve(__dirname, '..');
|
|
12
|
-
const packageRoot = resolve(pluginRoot, '..', '..');
|
|
13
|
-
let pkgVersion = '0.0.0';
|
|
14
|
-
for (const base of [pluginRoot, packageRoot]) {
|
|
15
|
-
try {
|
|
16
|
-
const version = JSON.parse(readFileSync(join(base, 'package.json'), 'utf8')).version;
|
|
17
|
-
if (version) {
|
|
18
|
-
pkgVersion = version;
|
|
19
|
-
break;
|
|
20
|
-
}
|
|
21
|
-
} catch {}
|
|
22
|
-
}
|
|
6
|
+
const pkgVersion = readInstalledVersion() || '0.0.0';
|
|
23
7
|
|
|
24
8
|
// 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
|
|
25
9
|
// 按会话隔离:同进程内不同会话(A/B)各自首次提示;stdio 用固定 'stdin'。
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Shared OfficeAce config-root resolution used by both the installer
|
|
2
|
+
// (setup-cli.mjs) and the MCP runtime (tools.mjs), so skills are installed
|
|
3
|
+
// to and read from the same location (#559).
|
|
4
|
+
//
|
|
5
|
+
// OfficeAce exposes no API for the plugin to query the current user or
|
|
6
|
+
// install directory, so the root is located by probing. The probe result is
|
|
7
|
+
// persisted in a marker file so a non-standard directory (entered via prompt,
|
|
8
|
+
// whose OFFICE_CLAW_CONFIG_ROOT env does not survive across processes) is
|
|
9
|
+
// still found by later uninstall/update runs.
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
12
|
+
import { join, resolve, dirname } from 'node:path';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
|
|
15
|
+
function markerPath() {
|
|
16
|
+
return join(process.env.HUAWEICLOUD_HOME || homedir(), '.config', 'huaweicloud', 'devkit-officeace-root.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sqliteSibling(dir) {
|
|
20
|
+
return join(resolve(dir, '..'), 'data', 'mcp-connectors.sqlite');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A directory is a usable OfficeAce config root when it exists and carries
|
|
24
|
+
// OfficeAce evidence — capabilities.json, the connector DB, or DevKit's own
|
|
25
|
+
// installed artifacts. Deliberately more lenient than "capabilities.json must
|
|
26
|
+
// exist" so uninstall/update still find the root after it was removed.
|
|
27
|
+
export function isUsableOfficeaceRoot(dir) {
|
|
28
|
+
if (!dir || !existsSync(dir)) return false;
|
|
29
|
+
return (
|
|
30
|
+
existsSync(join(dir, 'capabilities.json')) ||
|
|
31
|
+
existsSync(sqliteSibling(dir)) ||
|
|
32
|
+
existsSync(join(dir, 'skills')) ||
|
|
33
|
+
existsSync(join(dir, 'huaweicloud-plugins'))
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function readOfficeaceRootMarker() {
|
|
38
|
+
try {
|
|
39
|
+
const marker = JSON.parse(readFileSync(markerPath(), 'utf8'));
|
|
40
|
+
return isUsableOfficeaceRoot(marker.root) ? marker.root : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function writeOfficeaceRootMarker(root) {
|
|
47
|
+
try {
|
|
48
|
+
mkdirSync(dirname(markerPath()), { recursive: true });
|
|
49
|
+
writeFileSync(markerPath(), JSON.stringify({ root, savedAt: new Date().toISOString() }, null, 2));
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
@@ -108,15 +108,57 @@ function evaluate(stage, inputs, options = {}) {
|
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
function isNonEmptyString(value) {
|
|
112
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isNonEmptyContainer(value) {
|
|
116
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
117
|
+
if (value && typeof value === 'object') return Object.keys(value).length > 0;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A risk check that could not inspect its input must never report "allow"
|
|
122
|
+
// (fail-open) — it reports deny with an `invalid` risk and a synthetic finding
|
|
123
|
+
// so the reason is visible through the tool response (#564).
|
|
124
|
+
function invalidRiskResult(stage, reason) {
|
|
125
|
+
return {
|
|
126
|
+
decision: 'deny',
|
|
127
|
+
risk: 'invalid',
|
|
128
|
+
findings: [
|
|
129
|
+
{
|
|
130
|
+
ruleId: 'invalid-input',
|
|
131
|
+
title: 'Invalid input for risk check',
|
|
132
|
+
category: 'invalid',
|
|
133
|
+
severity: 'deny',
|
|
134
|
+
message: reason,
|
|
135
|
+
remediation: 'Provide a valid, non-empty input before re-running the check.',
|
|
136
|
+
source: stage,
|
|
137
|
+
evidence: '',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
111
143
|
export function evaluateCommandRisk(command, options = {}) {
|
|
144
|
+
if (!isNonEmptyString(command)) {
|
|
145
|
+
return invalidRiskResult('command', 'command must be a non-empty string.');
|
|
146
|
+
}
|
|
112
147
|
return evaluate('command', { command }, options);
|
|
113
148
|
}
|
|
114
149
|
|
|
115
150
|
export function evaluateArtifacts(artifacts, options = {}) {
|
|
116
|
-
|
|
151
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0) {
|
|
152
|
+
return invalidRiskResult('artifact', 'artifacts must be a non-empty array.');
|
|
153
|
+
}
|
|
154
|
+
return evaluate('artifact', artifacts, options);
|
|
117
155
|
}
|
|
118
156
|
|
|
119
157
|
export function evaluateDeployPlan(plan, options = {}) {
|
|
158
|
+
const valid = isNonEmptyString(plan) || (plan != null && typeof plan === 'object' && isNonEmptyContainer(plan));
|
|
159
|
+
if (!valid) {
|
|
160
|
+
return invalidRiskResult('deploy_plan', 'plan must be a non-empty object, array, or string.');
|
|
161
|
+
}
|
|
120
162
|
return evaluate('deploy_plan', { plan }, options);
|
|
121
163
|
}
|
|
122
164
|
|
|
@@ -37,7 +37,8 @@ import {
|
|
|
37
37
|
} from './proxy/proxy-config.mjs';
|
|
38
38
|
import { removeKooCli, removeObsConfig } from './sandbox/uninstall-cleanup.mjs';
|
|
39
39
|
import { mergeCommandStyle, mergeArgsStyle, extractUserDelta, applyUserDelta } from './mcp-config-merge.mjs';
|
|
40
|
-
import { saveAgentDelta, takeAgentDelta, purgeBackup } from './mcp-config-backup.mjs';
|
|
40
|
+
import { readAgentDelta, saveAgentDelta, takeAgentDelta, purgeBackup } from './mcp-config-backup.mjs';
|
|
41
|
+
import { isUsableOfficeaceRoot, readOfficeaceRootMarker, writeOfficeaceRootMarker } from './officeace-paths.mjs';
|
|
41
42
|
import { queryDistTagsFetch, determineTarget, semverCompare } from './update-check.mjs';
|
|
42
43
|
import { getKooCliVersion, compareVersion, kooCliDownloadBase, KOO_CLI_BASE } from './koocli-version.mjs';
|
|
43
44
|
import { findHcloudBin, hcloudProbeNextStep, probeHcloud } from './hcloud-probe.mjs';
|
|
@@ -185,11 +186,13 @@ function readOfficeaceRegistryInstallDir() {
|
|
|
185
186
|
|
|
186
187
|
function officeaceCapabilitiesDir() {
|
|
187
188
|
const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
|
|
188
|
-
if (
|
|
189
|
+
if (isUsableOfficeaceRoot(configRoot)) return configRoot;
|
|
190
|
+
const markerRoot = readOfficeaceRootMarker();
|
|
191
|
+
if (markerRoot) return markerRoot;
|
|
189
192
|
const regDir = readOfficeaceRegistryInstallDir();
|
|
190
193
|
if (regDir) {
|
|
191
194
|
const dir = join(regDir, '.office-claw');
|
|
192
|
-
if (
|
|
195
|
+
if (isUsableOfficeaceRoot(dir)) return dir;
|
|
193
196
|
}
|
|
194
197
|
if (platform() === 'win32') {
|
|
195
198
|
const bases = [process.env.ProgramFiles, 'C:\\Program Files', 'D:\\Program Files'];
|
|
@@ -197,7 +200,7 @@ function officeaceCapabilitiesDir() {
|
|
|
197
200
|
for (const base of bases) {
|
|
198
201
|
if (!base) continue;
|
|
199
202
|
const dir = join(base, 'OfficeAce', '.office-claw');
|
|
200
|
-
if (
|
|
203
|
+
if (isUsableOfficeaceRoot(dir)) return dir;
|
|
201
204
|
}
|
|
202
205
|
}
|
|
203
206
|
return null;
|
|
@@ -242,7 +245,7 @@ function ensureOfficeaceMcpInSqlite() {
|
|
|
242
245
|
if (!existsSync(dbPath)) {
|
|
243
246
|
console.log(` \x1b[31mOfficeAce database not found: ${dbPath}\x1b[0m`);
|
|
244
247
|
console.log(` \x1b[33mPlease ensure OfficeAce is installed and has been launched at least once.\x1b[0m`);
|
|
245
|
-
return
|
|
248
|
+
return 'db-missing';
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
const mcpPath = join(officeacePluginsDir(), 'src', 'mcp-server.mjs').replace(/\\/g, '/');
|
|
@@ -281,21 +284,27 @@ function ensureOfficeaceMcpInSqlite() {
|
|
|
281
284
|
if (existing.command === 'node' && existing.args_json === nextArgsJson && existing.env_json === nextEnvJson) {
|
|
282
285
|
console.log(` MCP config unchanged: ${dbPath}`);
|
|
283
286
|
db.close();
|
|
284
|
-
return
|
|
287
|
+
return 'ok';
|
|
285
288
|
}
|
|
286
289
|
db.prepare(
|
|
287
290
|
'UPDATE mcp_connectors SET command = ?, args_json = ?, env_json = ?, updated_at = ?, status = ?, enabled = 1 WHERE id = ?',
|
|
288
291
|
).run('node', nextArgsJson, nextEnvJson, now, 'disconnected', existing.id);
|
|
289
292
|
console.log(` MCP config merged (user fields preserved): ${dbPath}`);
|
|
290
293
|
} else {
|
|
291
|
-
|
|
294
|
+
// Restore user identity + args saved by a previous uninstall (#559).
|
|
295
|
+
// owner_user_id has no queryable source: we copy it from an existing
|
|
296
|
+
// row, and fall back to the value backed up before a prior uninstall so
|
|
297
|
+
// an emptied table does not make registration permanently impossible.
|
|
298
|
+
const officeaceDelta = readAgentDelta('officeace');
|
|
299
|
+
const ownerUserId = officeaceGetOwnerUserId() || (officeaceDelta && officeaceDelta.ownerUserId);
|
|
292
300
|
if (!ownerUserId) {
|
|
293
|
-
console.log(` \x1b[31mCannot determine owner_user_id
|
|
301
|
+
console.log(` \x1b[31mCannot determine owner_user_id for the huaweicloud-devkit connector.\x1b[0m`);
|
|
302
|
+
console.log(
|
|
303
|
+
` \x1b[33mThe OfficeAce connector database has no rows to copy the user id from and no prior registration is backed up. Open OfficeAce, add/enable any connector once, then re-run the install.\x1b[0m`,
|
|
304
|
+
);
|
|
294
305
|
db.close();
|
|
295
|
-
return
|
|
306
|
+
return 'owner-missing';
|
|
296
307
|
}
|
|
297
|
-
// Restore user args saved by a previous uninstall (issue #615).
|
|
298
|
-
const officeaceDelta = takeAgentDelta('officeace');
|
|
299
308
|
const insertArgsJson =
|
|
300
309
|
officeaceDelta && Array.isArray(officeaceDelta.argsExtra)
|
|
301
310
|
? JSON.stringify([mcpPath, ...officeaceDelta.argsExtra])
|
|
@@ -304,10 +313,12 @@ function ensureOfficeaceMcpInSqlite() {
|
|
|
304
313
|
`INSERT INTO mcp_connectors (id, owner_user_id, type, name, normalized_name, transport, timeout_ms, command, args_json, env_json, enabled, status, created_at, updated_at, version, seeded)
|
|
305
314
|
VALUES (?, ?, 'custom', 'huaweicloud-devkit', 'huaweicloud-devkit', 'stdio', 60000, 'node', ?, ?, 1, 'disconnected', ?, ?, 1, 0)`,
|
|
306
315
|
).run(randomUUID(), ownerUserId, insertArgsJson, envJson, now, now);
|
|
316
|
+
// Consume the backup only after a successful insert so a DB failure keeps it for the next retry.
|
|
317
|
+
takeAgentDelta('officeace');
|
|
307
318
|
console.log(` MCP config created: ${dbPath}`);
|
|
308
319
|
}
|
|
309
320
|
db.close();
|
|
310
|
-
return
|
|
321
|
+
return 'ok';
|
|
311
322
|
} catch (error) {
|
|
312
323
|
if (db) {
|
|
313
324
|
try {
|
|
@@ -315,7 +326,7 @@ function ensureOfficeaceMcpInSqlite() {
|
|
|
315
326
|
} catch {}
|
|
316
327
|
}
|
|
317
328
|
console.log(` \x1b[31mFailed to write MCP config: ${error.message}\x1b[0m`);
|
|
318
|
-
return
|
|
329
|
+
return 'error';
|
|
319
330
|
}
|
|
320
331
|
}
|
|
321
332
|
|
|
@@ -325,17 +336,28 @@ function removeOfficeaceMcpFromSqlite() {
|
|
|
325
336
|
let db;
|
|
326
337
|
try {
|
|
327
338
|
db = openOfficeaceDb();
|
|
328
|
-
// Back up user args before deleting the connector row
|
|
339
|
+
// Back up user identity + args before deleting the connector row:
|
|
340
|
+
// owner_user_id is copied from existing rows on reinstall, so an empty
|
|
341
|
+
// table after this delete would make registration impossible (#559).
|
|
329
342
|
try {
|
|
330
|
-
const row = db
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
343
|
+
const row = db
|
|
344
|
+
.prepare("SELECT owner_user_id, args_json FROM mcp_connectors WHERE name = 'huaweicloud-devkit'")
|
|
345
|
+
.get();
|
|
346
|
+
const delta = {};
|
|
347
|
+
if (row?.owner_user_id) delta.ownerUserId = row.owner_user_id;
|
|
348
|
+
try {
|
|
349
|
+
const parsed = row ? JSON.parse(row.args_json || '[]') : [];
|
|
350
|
+
if (Array.isArray(parsed) && parsed.length > 1) delta.argsExtra = parsed.slice(1);
|
|
351
|
+
} catch {}
|
|
352
|
+
if (Object.keys(delta).length > 0) saveAgentDelta('officeace', delta);
|
|
353
|
+
} catch {}
|
|
354
|
+
// mcp_connector_tools may not exist on minimal installs — deleting it must
|
|
355
|
+
// not abort deleting the connector row itself.
|
|
356
|
+
try {
|
|
357
|
+
db.prepare(
|
|
358
|
+
"DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
|
|
359
|
+
).run();
|
|
335
360
|
} catch {}
|
|
336
|
-
db.prepare(
|
|
337
|
-
"DELETE FROM mcp_connector_tools WHERE connector_id IN (SELECT id FROM mcp_connectors WHERE name = 'huaweicloud-devkit')",
|
|
338
|
-
).run();
|
|
339
361
|
const result2 = db.prepare("DELETE FROM mcp_connectors WHERE name = 'huaweicloud-devkit'").run();
|
|
340
362
|
if (result2.changes > 0) {
|
|
341
363
|
console.log(` MCP config removed: ${dbPath}`);
|
|
@@ -2377,8 +2399,17 @@ async function installOfficeAce() {
|
|
|
2377
2399
|
console.log(` Safety Policy -> ${join(pluginDest, 'safety')}`);
|
|
2378
2400
|
|
|
2379
2401
|
installRuntimeDeps(pluginDest);
|
|
2380
|
-
ensureOfficeaceMcpInSqlite()
|
|
2402
|
+
if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
'OfficeAce MCP connector registration failed (owner_user_id unavailable). ' +
|
|
2405
|
+
'Open OfficeAce, add/enable any connector once, then re-run install.',
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2381
2408
|
registerOfficeaceSkillEntries();
|
|
2409
|
+
// Persist the resolved root so a later uninstall/update (new process, no
|
|
2410
|
+
// OFFICE_CLAW_CONFIG_ROOT env) still targets the same directory (#559).
|
|
2411
|
+
const resolvedRoot = officeaceCapabilitiesDir();
|
|
2412
|
+
if (resolvedRoot) writeOfficeaceRootMarker(resolvedRoot);
|
|
2382
2413
|
}
|
|
2383
2414
|
|
|
2384
2415
|
async function updateOfficeAce() {
|
|
@@ -2395,8 +2426,15 @@ async function updateOfficeAce() {
|
|
|
2395
2426
|
copyDir(safetyDir, join(pluginDest, 'safety'));
|
|
2396
2427
|
console.log(` Safety Policy updated -> ${join(pluginDest, 'safety')}`);
|
|
2397
2428
|
installRuntimeDeps(pluginDest);
|
|
2398
|
-
ensureOfficeaceMcpInSqlite()
|
|
2429
|
+
if (ensureOfficeaceMcpInSqlite() === 'owner-missing') {
|
|
2430
|
+
console.log(
|
|
2431
|
+
` \x1b[31m[WARN]\x1b[0m OfficeAce MCP connector registration failed (owner_user_id unavailable). ` +
|
|
2432
|
+
`The connector may stay disconnected until you add/enable any connector once in OfficeAce and re-run update.`,
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2399
2435
|
registerOfficeaceSkillEntries();
|
|
2436
|
+
const resolvedRoot = officeaceCapabilitiesDir();
|
|
2437
|
+
if (resolvedRoot) writeOfficeaceRootMarker(resolvedRoot);
|
|
2400
2438
|
mkdirSync(pluginDest, { recursive: true });
|
|
2401
2439
|
writeFileSync(join(pluginDest, '.installed'), new Date().toISOString());
|
|
2402
2440
|
}
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
upgradePackage,
|
|
64
64
|
} from './update-check.mjs';
|
|
65
65
|
import { hcloudProbeNextStep, probeHcloud } from './hcloud-probe.mjs';
|
|
66
|
+
import { isUsableOfficeaceRoot, readOfficeaceRootMarker } from './officeace-paths.mjs';
|
|
66
67
|
|
|
67
68
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
68
69
|
const SKILLS_ROOT_DEV = join(__dirname, '..', 'skills');
|
|
@@ -104,7 +105,14 @@ function readOfficeaceRegistryInstallDir() {
|
|
|
104
105
|
|
|
105
106
|
function officeaceSkillsRoot() {
|
|
106
107
|
const configRoot = process.env.OFFICE_CLAW_CONFIG_ROOT;
|
|
107
|
-
|
|
108
|
+
// Same lenient probe as the installer (isUsableOfficeaceRoot) so env pointing
|
|
109
|
+
// at "skills present but capabilities.json absent" resolves identically at
|
|
110
|
+
// install and at runtime (#559 review).
|
|
111
|
+
if (configRoot && isUsableOfficeaceRoot(configRoot)) return join(configRoot, 'skills');
|
|
112
|
+
// Same persisted root used by the installer keeps install-time and
|
|
113
|
+
// runtime skill discovery consistent (#559).
|
|
114
|
+
const markerRoot = readOfficeaceRootMarker();
|
|
115
|
+
if (markerRoot) return join(markerRoot, 'skills');
|
|
108
116
|
const regDir = readOfficeaceRegistryInstallDir();
|
|
109
117
|
if (regDir) {
|
|
110
118
|
const dir = join(regDir, '.office-claw', 'skills');
|
|
@@ -135,6 +135,19 @@ function selfDir() {
|
|
|
135
135
|
return dirname(fileURLToPath(import.meta.url));
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Plugin manifests also carry the version and are kept in sync with package.json
|
|
139
|
+
// by `npm run validate`. Codex marketplace installs (`.codex/plugins/cache/...`)
|
|
140
|
+
// contain only the plugin directory — no package.json — so fall back to these
|
|
141
|
+
// manifests when neither candidate package.json exists (#576).
|
|
142
|
+
const VERSION_MANIFEST_RELS = [
|
|
143
|
+
'.codex-plugin/plugin.json',
|
|
144
|
+
'.claude-plugin/plugin.json',
|
|
145
|
+
'.cursor-plugin/plugin.json',
|
|
146
|
+
'.workbuddy-plugin/plugin.json',
|
|
147
|
+
'.hermes-plugin/plugin.json',
|
|
148
|
+
'openclaw.plugin.json',
|
|
149
|
+
];
|
|
150
|
+
|
|
138
151
|
export function readInstalledVersion() {
|
|
139
152
|
const pluginRoot = resolve(selfDir(), '..');
|
|
140
153
|
const packageRoot = resolve(pluginRoot, '..', '..');
|
|
@@ -144,6 +157,12 @@ export function readInstalledVersion() {
|
|
|
144
157
|
if (typeof version === 'string' && version) return version;
|
|
145
158
|
} catch {}
|
|
146
159
|
}
|
|
160
|
+
for (const rel of VERSION_MANIFEST_RELS) {
|
|
161
|
+
try {
|
|
162
|
+
const version = JSON.parse(readFileSync(join(pluginRoot, rel), 'utf8')).version;
|
|
163
|
+
if (typeof version === 'string' && version) return version;
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
147
166
|
return null;
|
|
148
167
|
}
|
|
149
168
|
|