@xqyz/xq-cli 0.3.7 → 0.3.8
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 +9 -0
- package/package.json +1 -1
- package/src/cli.mjs +87 -5
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.3.8 / WorkBuddy Plugin 0.10.21 / SkillHub 1.0.5] - 2026-08-25
|
|
14
|
+
|
|
15
|
+
### 规划模式卡住保护
|
|
16
|
+
|
|
17
|
+
- 解析唤醒后的 `success=2` 只允许在 30 秒内等待后台一致性同步;状态没有推进时返回可诊断的 `TASK_PARSE_NOT_ADVANCED`,不再继续等待到 10/30 分钟超时。
|
|
18
|
+
- WorkBuddy Plugin 固定依赖 `@xqyz/xq-cli@0.3.8`,并把最低版本校验同步提升,避免旧 CLI 重新进入不完整链路。
|
|
19
|
+
- SkillHub 安装说明明确要求重启后核对实际加载的 Plugin/CLI 版本;旧 MCP 进程仍在运行时不得宣称已更新。
|
|
20
|
+
- 新增回归覆盖:`multiBiddingChoice` 延迟推进、持续 `success=2` 快速失败,以及旧版本运行时拒绝。
|
|
21
|
+
|
|
13
22
|
## [0.3.7 / WorkBuddy Plugin 0.10.20 / SkillHub 1.0.4] - 2026-08-25
|
|
14
23
|
|
|
15
24
|
### 规划模式解析唤醒修复
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import http from 'http';
|
|
|
5
5
|
import https from 'https';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import path from 'path';
|
|
8
|
-
import readline from 'readline/promises';
|
|
8
|
+
import readline from 'node:readline/promises';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
11
|
import axios from 'axios';
|
|
@@ -35,6 +35,12 @@ const DEFAULT_BASE_URL = 'https://ai.bidfile.qianlima.com/api';
|
|
|
35
35
|
const DEFAULT_PARSE_INTERVAL_MS = 10_000;
|
|
36
36
|
const DEFAULT_TASK_INTERVAL_MS = 10_000;
|
|
37
37
|
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;
|
|
38
|
+
// multiBiddingChoice acknowledges the request before the extraction service
|
|
39
|
+
// necessarily updates getFileAsyncResult. Give that short async hand-off a
|
|
40
|
+
// bounded window, then fail with diagnostics instead of entering the long
|
|
41
|
+
// outline polling timeout.
|
|
42
|
+
const DEFAULT_PARSER_WAKEUP_TIMEOUT_MS = 30_000;
|
|
43
|
+
const PARSER_WAKEUP_INTERVAL_MS = 2_000;
|
|
38
44
|
const DEFAULT_SOURCE_FROM = 'xique';
|
|
39
45
|
const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
40
46
|
const DEFAULT_EXPORT_TEMPLATE_INDEX = 1;
|
|
@@ -1409,8 +1415,8 @@ async function handleOutlineUnlocked(args, state) {
|
|
|
1409
1415
|
|
|
1410
1416
|
const planningMode = Number(params.planMode) === 1;
|
|
1411
1417
|
const triggerMode = planningMode
|
|
1412
|
-
? await preparePlanningAnalysis(client, outlineSnapshot, params)
|
|
1413
|
-
: await triggerOutlineGeneration(client, outlineSnapshot, params);
|
|
1418
|
+
? await preparePlanningAnalysis(client, state, outlineSnapshot, params, effectiveArgs)
|
|
1419
|
+
: await triggerOutlineGeneration(client, state, outlineSnapshot, params, effectiveArgs);
|
|
1414
1420
|
saveTaskSnapshot(state, cid, {
|
|
1415
1421
|
...outlineSnapshot,
|
|
1416
1422
|
...pickTaskSnapshot(params),
|
|
@@ -2056,7 +2062,7 @@ function buildOutlineParams(snapshot, args) {
|
|
|
2056
2062
|
});
|
|
2057
2063
|
}
|
|
2058
2064
|
|
|
2059
|
-
async function triggerOutlineGeneration(client, snapshot, params) {
|
|
2065
|
+
async function triggerOutlineGeneration(client, state, snapshot, params, args = {}) {
|
|
2060
2066
|
// The web flow has two deliberately different paths:
|
|
2061
2067
|
//
|
|
2062
2068
|
// * legacy tasks (task_status === 0) already contain the parsed
|
|
@@ -2080,10 +2086,11 @@ async function triggerOutlineGeneration(client, snapshot, params) {
|
|
|
2080
2086
|
multiBidId: params.multiBidId ?? snapshot.multiBidId,
|
|
2081
2087
|
epcEngineerType: normalizeEpcEngineerTypeForProxy(params.epcEngineerType ?? snapshot.epcEngineerType),
|
|
2082
2088
|
}));
|
|
2089
|
+
await verifyParserWakeUp(client, state, snapshot.cid, snapshot.uuid, params, false, args);
|
|
2083
2090
|
return 'multiBiddingChoice';
|
|
2084
2091
|
}
|
|
2085
2092
|
|
|
2086
|
-
async function preparePlanningAnalysis(client, snapshot, params) {
|
|
2093
|
+
async function preparePlanningAnalysis(client, state, snapshot, params, args = {}) {
|
|
2087
2094
|
// Planning mode pauses before outline generation, but new tasks still need
|
|
2088
2095
|
// the same parser wake-up as quick mode. Without multiBiddingChoice,
|
|
2089
2096
|
// autoGenerateDirectoryJobHandler never fills requirement/rating/plan and
|
|
@@ -2100,9 +2107,78 @@ async function preparePlanningAnalysis(client, snapshot, params) {
|
|
|
2100
2107
|
multiBidId: params.multiBidId ?? snapshot.multiBidId,
|
|
2101
2108
|
epcEngineerType: normalizeEpcEngineerTypeForProxy(params.epcEngineerType ?? snapshot.epcEngineerType),
|
|
2102
2109
|
}));
|
|
2110
|
+
await verifyParserWakeUp(client, state, snapshot.cid, snapshot.uuid, params, true, args);
|
|
2103
2111
|
return 'planningAnalysisPrepared';
|
|
2104
2112
|
}
|
|
2105
2113
|
|
|
2114
|
+
async function verifyParserWakeUp(client, state, cid, uuid, params, planningMode, args = {}) {
|
|
2115
|
+
const expectedMultiBidId = String(params.multiBidId || '').trim();
|
|
2116
|
+
const requestedTimeoutMs = secondsToMs(
|
|
2117
|
+
numberOption(args, 'timeoutSec', DEFAULT_TIMEOUT_MS / 1000),
|
|
2118
|
+
);
|
|
2119
|
+
const timeoutMs = Math.min(DEFAULT_PARSER_WAKEUP_TIMEOUT_MS, requestedTimeoutMs);
|
|
2120
|
+
const deadline = Date.now() + timeoutMs;
|
|
2121
|
+
let latest = null;
|
|
2122
|
+
|
|
2123
|
+
// `multiBiddingChoice` returns HTTP 200 before the extraction record is
|
|
2124
|
+
// necessarily visible to the next read. Allow a short consistency window,
|
|
2125
|
+
// but never fall back to the long generation poll while the backend still
|
|
2126
|
+
// advertises "waiting for package selection" (success=2).
|
|
2127
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
2128
|
+
const [taskResponse, parseStatus] = await Promise.all([
|
|
2129
|
+
getJson(client, `/task/getTaskDetail/${encodeURIComponent(cid)}`, {}, { retries: DEFAULT_RETRY_COUNT }),
|
|
2130
|
+
fetchParseStatus(client, state, cid, uuid),
|
|
2131
|
+
]);
|
|
2132
|
+
const taskDetail = taskResponse?.data || {};
|
|
2133
|
+
const parseSuccess = Number(parseStatus.success);
|
|
2134
|
+
const selectedMultiBidId = String(
|
|
2135
|
+
parseStatus.choiceMultiBiddingId
|
|
2136
|
+
|| taskDetail.choiceMultiBiddingId
|
|
2137
|
+
|| taskDetail.multiBidId
|
|
2138
|
+
|| '',
|
|
2139
|
+
).trim();
|
|
2140
|
+
const savedPlanMode = normalizePlanModeValue(taskDetail.planMode);
|
|
2141
|
+
latest = {
|
|
2142
|
+
taskDetail,
|
|
2143
|
+
parseStatus,
|
|
2144
|
+
parseSuccess,
|
|
2145
|
+
selectedMultiBidId,
|
|
2146
|
+
savedPlanMode,
|
|
2147
|
+
};
|
|
2148
|
+
saveTaskSnapshot(state, cid, pickTaskSnapshot({
|
|
2149
|
+
...taskDetail,
|
|
2150
|
+
...parseStatus,
|
|
2151
|
+
cid,
|
|
2152
|
+
uuid,
|
|
2153
|
+
choiceMultiBiddingId: selectedMultiBidId,
|
|
2154
|
+
}));
|
|
2155
|
+
|
|
2156
|
+
const selectionRequired = expectedMultiBidId && expectedMultiBidId !== 'notMultiBid';
|
|
2157
|
+
const selectionMatches = !selectionRequired || selectedMultiBidId === expectedMultiBidId;
|
|
2158
|
+
const parseAdvanced = [1, 3, 4].includes(parseSuccess);
|
|
2159
|
+
const planModeMatches = !planningMode || savedPlanMode === 1;
|
|
2160
|
+
if (parseAdvanced && selectionMatches && planModeMatches) {
|
|
2161
|
+
return latest;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
const remainingMs = deadline - Date.now();
|
|
2165
|
+
if (remainingMs <= 0) {
|
|
2166
|
+
break;
|
|
2167
|
+
}
|
|
2168
|
+
await sleep(Math.min(PARSER_WAKEUP_INTERVAL_MS, remainingMs));
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const selected = latest?.selectedMultiBidId || '-';
|
|
2172
|
+
const expected = expectedMultiBidId || '-';
|
|
2173
|
+
const success = latest?.parseSuccess ?? '-';
|
|
2174
|
+
const savedPlanMode = latest?.savedPlanMode ?? '-';
|
|
2175
|
+
throw new Error(
|
|
2176
|
+
`[TASK_PARSE_NOT_ADVANCED] 标包/规划配置提交后后端状态没有推进,已停止等待。` +
|
|
2177
|
+
`期望标包=${expected},后端标包=${selected},解析状态 success=${success},` +
|
|
2178
|
+
`后端 planMode=${savedPlanMode}。请不要重复提交同一任务;先查询任务状态或重新安装最新版 WorkBuddy 插件。`,
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2106
2182
|
async function setPlanModePhase(client, cid, planModePhase) {
|
|
2107
2183
|
const response = await postJson(client, '/task/setPlanModePhase', { cid, planModePhase });
|
|
2108
2184
|
const returned = normalizePlanModePhase(response?.data?.planModePhase);
|
|
@@ -2620,6 +2696,11 @@ async function waitForPlanningAnalysisReady(client, state, cid, uuid, args) {
|
|
|
2620
2696
|
? await fetchParseStatus(client, state, cid, uuid)
|
|
2621
2697
|
: { success: 1 };
|
|
2622
2698
|
const parseSuccess = Number(parseStatus.success);
|
|
2699
|
+
if (parseSuccess === 2) {
|
|
2700
|
+
throw new Error(
|
|
2701
|
+
'[TASK_PARSE_NOT_ADVANCED] 规划模式仍处于等待标包/EPC选择(success=2),未进入解析阶段,已停止等待。',
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2623
2704
|
const issues = collectParseIssues(parseStatus);
|
|
2624
2705
|
if (parseSuccess === 4 && issues.length > 0) {
|
|
2625
2706
|
const autoIgnore = resolveAutoIgnoreIssue(issues, args, uuid, handledIssues);
|
|
@@ -4925,6 +5006,7 @@ function pickTaskSnapshot(source) {
|
|
|
4925
5006
|
blindGenerationMode: source.blindGenerationMode || source.blindBidConfirm?.blindGenerationMode,
|
|
4926
5007
|
multiBidId: source.multiBidId,
|
|
4927
5008
|
multiBidType: source.multiBidType,
|
|
5009
|
+
choiceMultiBiddingId: source.choiceMultiBiddingId,
|
|
4928
5010
|
epcEngineerType: source.epcEngineerType,
|
|
4929
5011
|
outlineReferenceType: source.outlineReferenceType,
|
|
4930
5012
|
referenceStatus: source.referenceStatus,
|