@xqyz/xq-cli 0.3.6 → 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 +35 -2
- package/package.json +1 -1
- package/src/cli.mjs +102 -7
package/CHANGELOG.md
CHANGED
|
@@ -10,12 +10,45 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
-
|
|
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
|
+
|
|
22
|
+
## [0.3.7 / WorkBuddy Plugin 0.10.20 / SkillHub 1.0.4] - 2026-08-25
|
|
23
|
+
|
|
24
|
+
### 规划模式解析唤醒修复
|
|
25
|
+
|
|
26
|
+
- 修复规划模式新任务只调用 `/task/preSet`、没有调用 `/proxy/multiBiddingChoice` 的问题。
|
|
27
|
+
- 规划模式现在与 Web 端一致:先保存用户配置,再唤醒异步解析;解析结果补齐后才展示“智能解读确认”,不会提前生成大纲。
|
|
28
|
+
- 保留规划模式确认闸门:用户确认智能解读后才调用 `plan confirm-outline`,不会因为修复唤醒链路而跳过大纲、目录审阅。
|
|
29
|
+
- 新增真实模拟回归测试,覆盖解析字段为空时的唤醒顺序和禁止提前调用正式生成接口。
|
|
30
|
+
|
|
31
|
+
## [0.3.6 / WorkBuddy Plugin 0.10.19] - 2026-08-24
|
|
32
|
+
|
|
33
|
+
### 安装和二次更新闭环
|
|
34
|
+
|
|
35
|
+
- SkillHub 正式身份统一为 `@org-kkw9656k/xique-ai`;安装器会同步完整 Skill 到正式目录、修正 `.skills_store_lock.json`,并把历史 `xique-bid` 重复目录移入可恢复的 `.skill-backup`。
|
|
36
|
+
- 用户明确安装或更新时通过 npm 受控标签 `workbuddy-stable` 获取最新稳定安装器;安装器解析自身版本后,仍将精确版本写入 `mcp.json`,所以 WorkBuddy 重启不会因浮动标签加载不同构建。
|
|
37
|
+
- 一次更新同时覆盖 Skill、MCP Plugin 和内置 xq-cli。MCP 配置保留其他服务,更新前备份;新 MCP 必须完成 `initialize` 握手,失败会恢复旧配置且不宣称成功。
|
|
38
|
+
- Windows 支持带空格的 Node/npx 路径;macOS/Linux 不依赖 `sort -V`,支持 WorkBuddy 的 `bin/node` 和扁平 Node 布局,并检查执行权限。
|
|
39
|
+
- 新增新装、旧 Skill 身份迁移、锁记录修复、重复更新幂等、MCP 启动失败回滚及 Windows 安装回归测试。
|
|
40
|
+
- npm 正式包并发安装验收发现共享 npx 缓存会在 Windows 产生 `ENOTEMPTY` 和包文件缺失;`0.10.16` 起为每次 bootstrap 使用独立缓存,并为安装器增加跨进程锁和喜鹊专属 MCP 运行缓存。
|
|
41
|
+
- `0.10.17` 在 Windows 上通过隐藏 Node 进程延迟清理 bootstrap 缓存,并在下次安装时回收陈旧目录,避免 npx 子进程短暂占用造成残留。
|
|
42
|
+
- `0.10.18` 查询 `workbuddy-stable` 时强制 `--prefer-online`,避免 npm 本地元数据缓存让已移动的稳定标签继续解析到旧版本。
|
|
43
|
+
- `0.10.19` 改为读取 npm `dist-tags --json` 中的 `workbuddy-stable` 字段,不再依赖 `包@标签 version` 的 CDN 别名解析,避免标签移动后的短暂旧值。
|
|
44
|
+
- 安装前先从 npm 官方 registry 解析 `workbuddy-stable` 的精确版本,安装器再校验自身版本;即使 npx 复用错误缓存,也会在写配置前失败。`0.10.15` 已发布但不作为稳定安装版本。
|
|
45
|
+
|
|
46
|
+
### 正文阶段安全闸门
|
|
14
47
|
|
|
15
48
|
- 大纲和目录实际保存前强制读取喜鹊后台权威任务状态,不再因为本机没有任务缓存而绕过“正文已开始/已完成”保护。
|
|
16
49
|
- 状态校验放在 `--dry-run` 和用户确认之后、写接口之前,保留离线差异预览能力,同时覆盖确认后发生的并发状态变化。
|
|
17
50
|
- 新增回归测试:无本地缓存的已完成任务不会调用大纲或目录保存接口;纯预览不会额外依赖任务详情接口。
|
|
18
|
-
-
|
|
51
|
+
- `@xqyz/xq-cli@0.3.6` 包含该修复,WorkBuddy Plugin `0.10.19` 固定依赖此版本。
|
|
19
52
|
|
|
20
53
|
### WorkBuddy Plugin 0.10.14 / SkillHub 1.0.11 发布记录
|
|
21
54
|
|
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, 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,16 +2086,99 @@ 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, params) {
|
|
2087
|
-
//
|
|
2088
|
-
//
|
|
2093
|
+
async function preparePlanningAnalysis(client, state, snapshot, params, args = {}) {
|
|
2094
|
+
// Planning mode pauses before outline generation, but new tasks still need
|
|
2095
|
+
// the same parser wake-up as quick mode. Without multiBiddingChoice,
|
|
2096
|
+
// autoGenerateDirectoryJobHandler never fills requirement/rating/plan and
|
|
2097
|
+
// the analysis poll remains pending until the local timeout.
|
|
2098
|
+
if (Number(snapshot.task_status) === 0) {
|
|
2099
|
+
// Legacy tasks already contain parsed fields and do not need the
|
|
2100
|
+
// asynchronous parser wake-up endpoint.
|
|
2101
|
+
await postJson(client, '/task/preSet', params);
|
|
2102
|
+
return 'planningAnalysisPrepared';
|
|
2103
|
+
}
|
|
2089
2104
|
await postJson(client, '/task/preSet', params);
|
|
2105
|
+
await getJson(client, '/proxy/multiBiddingChoice', compactObject({
|
|
2106
|
+
uuid: snapshot.uuid,
|
|
2107
|
+
multiBidId: params.multiBidId ?? snapshot.multiBidId,
|
|
2108
|
+
epcEngineerType: normalizeEpcEngineerTypeForProxy(params.epcEngineerType ?? snapshot.epcEngineerType),
|
|
2109
|
+
}));
|
|
2110
|
+
await verifyParserWakeUp(client, state, snapshot.cid, snapshot.uuid, params, true, args);
|
|
2090
2111
|
return 'planningAnalysisPrepared';
|
|
2091
2112
|
}
|
|
2092
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
|
+
|
|
2093
2182
|
async function setPlanModePhase(client, cid, planModePhase) {
|
|
2094
2183
|
const response = await postJson(client, '/task/setPlanModePhase', { cid, planModePhase });
|
|
2095
2184
|
const returned = normalizePlanModePhase(response?.data?.planModePhase);
|
|
@@ -2607,6 +2696,11 @@ async function waitForPlanningAnalysisReady(client, state, cid, uuid, args) {
|
|
|
2607
2696
|
? await fetchParseStatus(client, state, cid, uuid)
|
|
2608
2697
|
: { success: 1 };
|
|
2609
2698
|
const parseSuccess = Number(parseStatus.success);
|
|
2699
|
+
if (parseSuccess === 2) {
|
|
2700
|
+
throw new Error(
|
|
2701
|
+
'[TASK_PARSE_NOT_ADVANCED] 规划模式仍处于等待标包/EPC选择(success=2),未进入解析阶段,已停止等待。',
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2610
2704
|
const issues = collectParseIssues(parseStatus);
|
|
2611
2705
|
if (parseSuccess === 4 && issues.length > 0) {
|
|
2612
2706
|
const autoIgnore = resolveAutoIgnoreIssue(issues, args, uuid, handledIssues);
|
|
@@ -4912,6 +5006,7 @@ function pickTaskSnapshot(source) {
|
|
|
4912
5006
|
blindGenerationMode: source.blindGenerationMode || source.blindBidConfirm?.blindGenerationMode,
|
|
4913
5007
|
multiBidId: source.multiBidId,
|
|
4914
5008
|
multiBidType: source.multiBidType,
|
|
5009
|
+
choiceMultiBiddingId: source.choiceMultiBiddingId,
|
|
4915
5010
|
epcEngineerType: source.epcEngineerType,
|
|
4916
5011
|
outlineReferenceType: source.outlineReferenceType,
|
|
4917
5012
|
referenceStatus: source.referenceStatus,
|