huaweicloud-devkit 1.1.4-next.2 → 1.1.4-next.4
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/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/auth/credentials.mjs +48 -23
- package/plugins/huaweicloud-core/src/auth/reconcile.mjs +9 -2
- package/plugins/huaweicloud-core/src/mcp-protocol.mjs +14 -6
- package/plugins/huaweicloud-core/src/mcp-server-remote.mjs +2 -1
- package/plugins/huaweicloud-core/src/mcp-server.mjs +1 -1
- package/plugins/huaweicloud-core/src/tools.mjs +11 -9
- package/plugins/huaweicloud-core/src/update-check.mjs +25 -8
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-next.
|
|
4
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
20
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
23
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
20
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
3
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
3
|
+
"version": "1.1.4-next.4",
|
|
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.4-next.
|
|
5
|
+
"version": "1.1.4-next.4",
|
|
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.",
|
|
@@ -58,6 +58,33 @@ export function readGlobalCredentials() {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// R11: Placeholder / masked credential values are treated as "not configured".
|
|
62
|
+
// A vendor/IDE marketplace that pre-fills mcp_settings env with template text
|
|
63
|
+
// (e.g. "<HW_ACCESS_KEY>" or "${SECRET_KEY}") must not shadow the user's real
|
|
64
|
+
// S1 vault. Real AK/SK are >=20 char alphanumeric strings, so none of the
|
|
65
|
+
// template/masked patterns below can match a genuine credential.
|
|
66
|
+
export function isPlaceholder(value) {
|
|
67
|
+
if (typeof value !== 'string' || value.length === 0) return false;
|
|
68
|
+
// <HW_ACCESS_KEY> / <your-ak> / <...>
|
|
69
|
+
if (/^<[^>]*>$/.test(value)) return true;
|
|
70
|
+
// ${HW_ACCESS_KEY}
|
|
71
|
+
if (/^\$\{[^}]*\}$/.test(value)) return true;
|
|
72
|
+
// YOUR_AK / ACCESS_KEY / SECRET_KEY / SECURITY_TOKEN / REGION (bare keyword)
|
|
73
|
+
if (/^(YOUR_)?(AK|SK|ACCESS[_-]?KEY|SECRET[_-]?KEY|TOKEN|SECURITY[_-]?TOKEN|REGION)$/i.test(value)) return true;
|
|
74
|
+
// placeholder / replace_me / change.me
|
|
75
|
+
if (/\b(placeholder|replace[._-]?me|change[._-]?me)\b/i.test(value)) return true;
|
|
76
|
+
// masked value: short alnum prefix + asterisk run (abc**** / token****) — the
|
|
77
|
+
// S4 mirror writes the SK slot this way so the secret never lands in plaintext.
|
|
78
|
+
if (/^[A-Za-z0-9]{3,8}\*+$/.test(value)) return true;
|
|
79
|
+
// bare asterisks
|
|
80
|
+
if (/^\*+$/.test(value)) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function present(v) {
|
|
85
|
+
return typeof v === 'string' && v.length > 0 && !isPlaceholder(v);
|
|
86
|
+
}
|
|
87
|
+
|
|
61
88
|
export function writeGlobalCredentials(credentials = {}) {
|
|
62
89
|
const path = globalCredentialsPath();
|
|
63
90
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -99,9 +126,10 @@ export function writeObsConfig(credentials = {}) {
|
|
|
99
126
|
}
|
|
100
127
|
|
|
101
128
|
export function resolveCredentials(options = {}) {
|
|
102
|
-
|
|
103
|
-
let
|
|
104
|
-
let
|
|
129
|
+
// R11: placeholder/masked env values are "not configured", never credentials.
|
|
130
|
+
let ak = present(process.env.HW_ACCESS_KEY) ? process.env.HW_ACCESS_KEY : '';
|
|
131
|
+
let sk = present(process.env.HW_SECRET_KEY) ? process.env.HW_SECRET_KEY : '';
|
|
132
|
+
let securityToken = present(process.env.HW_SECURITY_TOKEN) ? process.env.HW_SECURITY_TOKEN : '';
|
|
105
133
|
let region = process.env.HW_REGION || process.env.HUAWEICLOUD_REGION || '';
|
|
106
134
|
|
|
107
135
|
const codeartsCreds = isCodeArtsContext() ? readCodeArtsCredentials() : null;
|
|
@@ -113,36 +141,33 @@ export function resolveCredentials(options = {}) {
|
|
|
113
141
|
|
|
114
142
|
const stored = readGlobalCredentials();
|
|
115
143
|
if (stored) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (!
|
|
144
|
+
// R11: skip placeholder/masked values from the vault too (templates must
|
|
145
|
+
// not win the truthiness check and shadow another store).
|
|
146
|
+
if (!ak && present(stored.ak)) ak = stored.ak;
|
|
147
|
+
if (!sk && present(stored.sk)) sk = stored.sk;
|
|
148
|
+
if (!securityToken && present(stored.securityToken)) securityToken = stored.securityToken;
|
|
119
149
|
if (!region && stored.region) region = stored.region;
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
// R9: S1 written by `auth_switch persist` (configuredBySession) is the session's
|
|
123
153
|
// source of truth and wins over env-injected defaults. Plain S1 (`auth init`)
|
|
124
154
|
// still yields to env so devspace-style env injection keeps working.
|
|
125
|
-
if (stored && stored.configuredBySession === true && stored.ak && stored.sk) {
|
|
155
|
+
if (stored && stored.configuredBySession === true && present(stored.ak) && present(stored.sk)) {
|
|
126
156
|
ak = stored.ak;
|
|
127
157
|
sk = stored.sk;
|
|
128
|
-
if (!securityToken) securityToken = stored.securityToken
|
|
158
|
+
if (!securityToken) securityToken = present(stored.securityToken) ? stored.securityToken : '';
|
|
129
159
|
if (!region) region = stored.region || '';
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
// Sandbox/platform-injected temporary STS credentials (env vars carrying a
|
|
133
163
|
// security token) must not shadow the user's explicit permanent credentials
|
|
134
164
|
// from `auth init`. Prefer the stored file when both exist.
|
|
135
|
-
|
|
136
|
-
process.env.HW_ACCESS_KEY &&
|
|
137
|
-
|
|
138
|
-
process.env.HW_SECURITY_TOKEN &&
|
|
139
|
-
stored &&
|
|
140
|
-
stored.ak &&
|
|
141
|
-
stored.sk
|
|
142
|
-
) {
|
|
165
|
+
const envHasFullTriplet =
|
|
166
|
+
present(process.env.HW_ACCESS_KEY) && present(process.env.HW_SECRET_KEY) && present(process.env.HW_SECURITY_TOKEN);
|
|
167
|
+
if (envHasFullTriplet && stored && present(stored.ak) && present(stored.sk)) {
|
|
143
168
|
ak = stored.ak;
|
|
144
169
|
sk = stored.sk;
|
|
145
|
-
securityToken = stored.securityToken
|
|
170
|
+
securityToken = present(stored.securityToken) ? stored.securityToken : '';
|
|
146
171
|
region = stored.region || region;
|
|
147
172
|
}
|
|
148
173
|
|
|
@@ -189,16 +214,16 @@ export function readCodeArtsCredentials() {
|
|
|
189
214
|
try {
|
|
190
215
|
if (!existsSync(path)) continue;
|
|
191
216
|
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
192
|
-
const server = config?.mcpServers?.['huaweicloud-devkit'];
|
|
217
|
+
const server = config?.mcpServers?.['huaweicloud-devkit'] || config?.mcpServers?.['HuaweiCloud DevKit'];
|
|
193
218
|
if (!server?.env) continue;
|
|
194
219
|
|
|
195
220
|
const ak = server.env.HW_ACCESS_KEY;
|
|
196
221
|
const sk = server.env.HW_SECRET_KEY;
|
|
197
|
-
if (ak && sk) {
|
|
222
|
+
if (present(ak) && present(sk)) {
|
|
198
223
|
return {
|
|
199
224
|
ak,
|
|
200
225
|
sk,
|
|
201
|
-
securityToken: server.env.HW_SECURITY_TOKEN
|
|
226
|
+
securityToken: present(server.env.HW_SECURITY_TOKEN) ? server.env.HW_SECURITY_TOKEN : '',
|
|
202
227
|
region: server.env.HW_REGION || server.env.HUAWEICLOUD_REGION || '',
|
|
203
228
|
};
|
|
204
229
|
}
|
|
@@ -213,15 +238,15 @@ export function readCodeArtsCredentials() {
|
|
|
213
238
|
try {
|
|
214
239
|
if (existsSync(path)) {
|
|
215
240
|
const config = JSON.parse(readFileSync(path, 'utf8'));
|
|
216
|
-
const server = config?.mcp?.['huaweicloud-devkit'];
|
|
241
|
+
const server = config?.mcp?.['huaweicloud-devkit'] || config?.mcp?.['HuaweiCloud DevKit'];
|
|
217
242
|
if (server?.environment) {
|
|
218
243
|
const ak = server.environment.HW_ACCESS_KEY;
|
|
219
244
|
const sk = server.environment.HW_SECRET_KEY;
|
|
220
|
-
if (ak && sk) {
|
|
245
|
+
if (present(ak) && present(sk)) {
|
|
221
246
|
return {
|
|
222
247
|
ak,
|
|
223
248
|
sk,
|
|
224
|
-
securityToken: server.environment.HW_SECURITY_TOKEN
|
|
249
|
+
securityToken: present(server.environment.HW_SECURITY_TOKEN) ? server.environment.HW_SECURITY_TOKEN : '',
|
|
225
250
|
region: server.environment.HW_REGION || server.environment.HUAWEICLOUD_REGION || '',
|
|
226
251
|
};
|
|
227
252
|
}
|
|
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
hasRuntimeCredentials,
|
|
9
|
+
isPlaceholder,
|
|
9
10
|
obsConfigPath,
|
|
10
11
|
readGlobalCredentials,
|
|
11
12
|
readLastSync,
|
|
@@ -104,10 +105,13 @@ function s2CurrentMatchesLastDevkitSync(kooCli, s1, s1Fingerprint) {
|
|
|
104
105
|
|
|
105
106
|
export function scanState() {
|
|
106
107
|
const s1 = readGlobalCredentials() || {};
|
|
107
|
-
const envAk = process.env.HW_ACCESS_KEY || '';
|
|
108
|
-
const envSk = process.env.HW_SECRET_KEY || '';
|
|
108
|
+
const envAk = isPlaceholder(process.env.HW_ACCESS_KEY) ? '' : process.env.HW_ACCESS_KEY || '';
|
|
109
|
+
const envSk = isPlaceholder(process.env.HW_SECRET_KEY) ? '' : process.env.HW_SECRET_KEY || '';
|
|
109
110
|
const kooCli = readKooCliProfiles();
|
|
110
111
|
const inconsistencies = [];
|
|
112
|
+
const envIsPlaceholder =
|
|
113
|
+
Boolean(process.env.HW_ACCESS_KEY && isPlaceholder(process.env.HW_ACCESS_KEY)) ||
|
|
114
|
+
Boolean(process.env.HW_SECRET_KEY && isPlaceholder(process.env.HW_SECRET_KEY));
|
|
111
115
|
|
|
112
116
|
const s1Fingerprint = fingerprint(s1.ak, s1.sk);
|
|
113
117
|
const currentFp = currentFingerprintFromHcloud(kooCli);
|
|
@@ -153,6 +157,9 @@ export function scanState() {
|
|
|
153
157
|
runtimeFingerprint,
|
|
154
158
|
},
|
|
155
159
|
kooCliCurrent: kooCli.error ? null : kooCli.current,
|
|
160
|
+
// R11: env carries placeholder/masked values → surface that explicitly so
|
|
161
|
+
// diagnostics don't show a bogus fingerprint derived from template text.
|
|
162
|
+
envIsPlaceholder,
|
|
156
163
|
// S2 is encrypted storage (authEncrypt) → its fingerprint is unavailable,
|
|
157
164
|
// so the S2-current drift check is intentionally skipped (#533).
|
|
158
165
|
s2Encrypted: Boolean(!kooCli.error && kooCli.authEncrypt),
|
|
@@ -22,21 +22,29 @@ for (const base of [pluginRoot, packageRoot]) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
// 会话内首个非 check/upgrade 工具调用附加 _updateInfo,只消费一次。
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// 按会话隔离:同进程内不同会话(A/B)各自首次提示;stdio 用固定 'stdin'。
|
|
26
|
+
const consumedBySession = new Map();
|
|
27
|
+
export function _decorateResult(sessionId, name, result) {
|
|
28
|
+
if (consumedBySession.get(sessionId)) return result;
|
|
28
29
|
try {
|
|
29
30
|
const hint = peekCachedUpdateInfo();
|
|
30
31
|
if (!hint) return result;
|
|
31
32
|
const decorated = applyUpdateHint(result, name, hint);
|
|
32
|
-
if (decorated !== result)
|
|
33
|
+
if (decorated !== result) consumedBySession.set(sessionId, true);
|
|
33
34
|
return decorated;
|
|
34
35
|
} catch {
|
|
35
36
|
return result; // 兜底装饰失败绝不影响工具调用
|
|
36
37
|
}
|
|
37
38
|
}
|
|
39
|
+
export function _resetHintConsumption() {
|
|
40
|
+
consumedBySession.clear();
|
|
41
|
+
}
|
|
42
|
+
export function _isHintConsumed(sessionId) {
|
|
43
|
+
return Boolean(consumedBySession.get(sessionId));
|
|
44
|
+
}
|
|
38
45
|
|
|
39
|
-
export async function dispatch(method, params) {
|
|
46
|
+
export async function dispatch(method, params, opts = {}) {
|
|
47
|
+
const sessionId = opts?.sessionId || 'default';
|
|
40
48
|
if (method === 'initialize') {
|
|
41
49
|
const ci = params.clientInfo || {};
|
|
42
50
|
|
|
@@ -68,7 +76,7 @@ export async function dispatch(method, params) {
|
|
|
68
76
|
|
|
69
77
|
if (method === 'tools/call') {
|
|
70
78
|
const result = await callTool(params.name, params.arguments || {});
|
|
71
|
-
const decorated =
|
|
79
|
+
const decorated = _decorateResult(sessionId, params.name, result);
|
|
72
80
|
return {
|
|
73
81
|
content: [
|
|
74
82
|
{
|
|
@@ -50,7 +50,8 @@ export async function startRemoteServer({ port = DEFAULT_PORT, host = DEFAULT_HO
|
|
|
50
50
|
|
|
51
51
|
let response;
|
|
52
52
|
try {
|
|
53
|
-
const
|
|
53
|
+
const sessionId = (req.headers['mcp-session-id'] || '').trim() || 'default';
|
|
54
|
+
const result = await dispatch(message.method, message.params || {}, { sessionId });
|
|
54
55
|
response = { jsonrpc: '2.0', id: message.id, result };
|
|
55
56
|
if (message.method === 'initialize') {
|
|
56
57
|
res.setHeader('MCP-Protocol-Version', result.protocolVersion || '2024-11-05');
|
|
@@ -159,7 +159,7 @@ function runStdioServer() {
|
|
|
159
159
|
return;
|
|
160
160
|
}
|
|
161
161
|
try {
|
|
162
|
-
const result = await dispatch(message.method, message.params || {});
|
|
162
|
+
const result = await dispatch(message.method, message.params || {}, { sessionId: 'stdin' });
|
|
163
163
|
writeMessage({ jsonrpc: '2.0', id: message.id, result });
|
|
164
164
|
} catch (error) {
|
|
165
165
|
writeMessage({
|
|
@@ -1049,7 +1049,7 @@ function normalizeNumericArgs(args) {
|
|
|
1049
1049
|
return out;
|
|
1050
1050
|
}
|
|
1051
1051
|
|
|
1052
|
-
export async function callTool(name, rawArgs = {}) {
|
|
1052
|
+
export async function callTool(name, rawArgs = {}, opts = {}) {
|
|
1053
1053
|
const args = normalizeNumericArgs(rawArgs);
|
|
1054
1054
|
const toolValue = toolInvokeValue(name, args);
|
|
1055
1055
|
trackToolInvoke(name, toolValue);
|
|
@@ -1448,33 +1448,35 @@ export async function callTool(name, rawArgs = {}) {
|
|
|
1448
1448
|
case 'huaweicloud_voucher_claim':
|
|
1449
1449
|
return await hdkitVoucherClaim(args.domain_id);
|
|
1450
1450
|
case 'huaweicloud_check_update':
|
|
1451
|
-
return await handleCheckUpdate(args);
|
|
1451
|
+
return await handleCheckUpdate(args, { sessionId: opts?.sessionId });
|
|
1452
1452
|
case 'huaweicloud_upgrade':
|
|
1453
|
-
return await handleUpgrade(args);
|
|
1453
|
+
return await handleUpgrade(args, { sessionId: opts?.sessionId });
|
|
1454
1454
|
default:
|
|
1455
1455
|
throw new Error(`Unknown tool: ${name}`);
|
|
1456
1456
|
}
|
|
1457
1457
|
}
|
|
1458
1458
|
|
|
1459
|
-
async function handleCheckUpdate(args = {}) {
|
|
1459
|
+
async function handleCheckUpdate(args = {}, opts = {}) {
|
|
1460
|
+
const sessionId = opts?.sessionId || null;
|
|
1460
1461
|
const current = readInstalledVersion() || '0.0.0';
|
|
1461
1462
|
if (args.dismiss === true) {
|
|
1462
|
-
const distTags = await getUpdateDistTags(current);
|
|
1463
|
+
const distTags = await getUpdateDistTags(current, { sessionId });
|
|
1463
1464
|
const target = determineTarget(current, distTags);
|
|
1464
1465
|
const dismissedVersion =
|
|
1465
1466
|
typeof args.dismissVersion === 'string' && args.dismissVersion ? args.dismissVersion : target || current;
|
|
1466
|
-
const state = writeSkipState(resolveSkipFilePath(), dismissedVersion);
|
|
1467
|
+
const state = writeSkipState(resolveSkipFilePath(sessionId), dismissedVersion);
|
|
1467
1468
|
invalidateUpdateCache();
|
|
1468
1469
|
return judgeUpdate(current, distTags, state);
|
|
1469
1470
|
}
|
|
1470
|
-
return getCachedUpdateInfo(current);
|
|
1471
|
+
return getCachedUpdateInfo(current, { sessionId });
|
|
1471
1472
|
}
|
|
1472
1473
|
|
|
1473
|
-
async function handleUpgrade(args = {}) {
|
|
1474
|
+
async function handleUpgrade(args = {}, opts = {}) {
|
|
1475
|
+
const sessionId = opts?.sessionId || null;
|
|
1474
1476
|
const target = typeof args.target === 'string' && args.target ? args.target : 'all';
|
|
1475
1477
|
const version = typeof args.version === 'string' && args.version ? args.version : 'latest';
|
|
1476
1478
|
const current = readInstalledVersion() || '0.0.0';
|
|
1477
|
-
const info = await getCachedUpdateInfo(current);
|
|
1479
|
+
const info = await getCachedUpdateInfo(current, { sessionId });
|
|
1478
1480
|
if (info && info.result === 'up_to_date') {
|
|
1479
1481
|
return {
|
|
1480
1482
|
success: false,
|
|
@@ -152,12 +152,26 @@ export function fallbackSkipFilePath() {
|
|
|
152
152
|
return join(base, '.config', 'huaweicloud', 'devkit-skip.json');
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// 会话化 skip 文件:remote 多会话按 sessionId 拆分,stdio/默认保持原文件(向后兼容)。
|
|
156
|
+
function sanitizeSessionId(sessionId) {
|
|
157
|
+
return String(sessionId || '').replace(/[^0-9a-zA-Z-]/g, '_');
|
|
158
|
+
}
|
|
159
|
+
|
|
155
160
|
// A1 定稿: 标准 agent 用插件目录副本(有 package.json); codex 等无副本时回退共享文件
|
|
156
|
-
export function resolveSkipFilePath() {
|
|
161
|
+
export function resolveSkipFilePath(sessionId = null) {
|
|
162
|
+
let base;
|
|
157
163
|
try {
|
|
158
|
-
if (existsSync(join(dirname(skipFilePath()), 'package.json')))
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
if (existsSync(join(dirname(skipFilePath()), 'package.json'))) {
|
|
165
|
+
base = skipFilePath();
|
|
166
|
+
} else {
|
|
167
|
+
base = fallbackSkipFilePath();
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
base = fallbackSkipFilePath();
|
|
171
|
+
}
|
|
172
|
+
// stdio / 默认 / 无 session → 原文件(保持向后兼容)
|
|
173
|
+
if (!sessionId || sessionId === 'default' || sessionId === 'stdin') return base;
|
|
174
|
+
return `${base}.${sanitizeSessionId(sessionId)}`;
|
|
161
175
|
}
|
|
162
176
|
|
|
163
177
|
export function readSkipState(file) {
|
|
@@ -293,12 +307,15 @@ function cacheValid(now = Date.now()) {
|
|
|
293
307
|
return Boolean(cachedDistTags) && now - cachedAt <= TTL_MS;
|
|
294
308
|
}
|
|
295
309
|
|
|
296
|
-
export async function getCachedUpdateInfo(
|
|
310
|
+
export async function getCachedUpdateInfo(
|
|
311
|
+
current,
|
|
312
|
+
{ doQuery = queryDistTags, now = Date.now(), sessionId = null } = {},
|
|
313
|
+
) {
|
|
297
314
|
if (process.env.HUAWEICLOUD_DEVKIT_SKIP_UPDATE === '1') {
|
|
298
315
|
lastHint = judgeUpdate(current, null, undefined, now);
|
|
299
316
|
return lastHint;
|
|
300
317
|
}
|
|
301
|
-
const skipState = readSkipState(resolveSkipFilePath());
|
|
318
|
+
const skipState = readSkipState(resolveSkipFilePath(sessionId));
|
|
302
319
|
if (!cacheValid(now)) {
|
|
303
320
|
if (!cachedDistTags && now - failedAt < FAIL_THROTTLE_MS) {
|
|
304
321
|
lastHint = judgeUpdate(current, null, skipState, now);
|
|
@@ -331,8 +348,8 @@ export function peekCachedUpdateInfo() {
|
|
|
331
348
|
return lastHint && lastHint.updateAvailable && lastHint.targetVersion ? lastHint : null;
|
|
332
349
|
}
|
|
333
350
|
|
|
334
|
-
export async function getUpdateDistTags(current) {
|
|
335
|
-
const result = await getCachedUpdateInfo(current);
|
|
351
|
+
export async function getUpdateDistTags(current, { sessionId = null } = {}) {
|
|
352
|
+
const result = await getCachedUpdateInfo(current, { sessionId });
|
|
336
353
|
if (!result) return null;
|
|
337
354
|
return { latest: result.latestStable ?? null, next: result.latestNext ?? null };
|
|
338
355
|
}
|