@xqyz/xq-cli 0.3.0 → 0.3.2
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 +27 -5
- package/package.json +1 -1
- package/src/cli.mjs +122 -12
package/CHANGELOG.md
CHANGED
|
@@ -10,12 +10,26 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## [0.3.2] - 2026-08-17
|
|
14
|
+
|
|
15
|
+
### xq-cli / WorkBuddy 规划模式
|
|
16
|
+
|
|
17
|
+
- 规划模式首次 `outline --plan-mode 1` 在解析完成后明确返回“智能解读待审核”,不再误报大纲已完成或提前进入正文。
|
|
18
|
+
- WorkBuddy 新增智能解读审核、补充/修正暂存和确认续跑流程;确认时才调用喜鹊 `generateDirectoryByCid` 生成大纲。
|
|
19
|
+
- 新增 CLI 与 WorkBuddy 回归测试,验证同一 `cid` / `runId` 不重复提交。
|
|
20
|
+
|
|
21
|
+
### xq-cli `0.3.1` / WorkBuddy `0.10.1`
|
|
22
|
+
|
|
23
|
+
- 修复普通项目在后端 `task_status=6`(文件解析中)时被误判为多标包/EPC,导致没有调用正常大纲生成接口、任务长时间停留在解析状态的问题。
|
|
24
|
+
- 轮询 GET 请求统一增加网络重试,并识别 `stream has been aborted`、连接提前关闭等瞬时传输错误;不会因一次流中断误报生成失败。
|
|
25
|
+
- WorkBuddy 对大纲阶段的传输中断保留同一 `cid` 继续核验,禁止重复创建任务;新增回归测试覆盖该恢复路径。
|
|
26
|
+
- 本次配套版本:`@xqyz/xq-cli@0.3.1`、`@xqyz/workbuddy-plugin-xique@0.10.1`、Connector `0.10.1`。
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
28
|
+
### WorkBuddy / SkillHub(待发布)
|
|
29
|
+
|
|
30
|
+
- 修复 SkillHub 旧版 Skill 将用户明确选择的“规划”强制改为“快速”的规则冲突;规划模式现在必须传递 `--plan-mode 1`。
|
|
31
|
+
- 增加 Skill 文案回归断言,禁止再次出现“固定使用计划模式=快速”或“不得接受规划模式”。
|
|
32
|
+
- 发布清单新增 SkillHub 同步、客户本地更新和新会话验收步骤,避免 npm Plugin 与 SkillHub Skill 版本脱节。
|
|
19
33
|
|
|
20
34
|
## [0.3.0] - 2026-08-14
|
|
21
35
|
|
|
@@ -26,6 +40,14 @@
|
|
|
26
40
|
- AI 大纲候选不再生成本地伪章节 ID;最终保存请求与前端一致,仅提交章节内容、主题、重要度和续写标记,交由后台分配节点 ID。
|
|
27
41
|
- 增加规划模式模拟 API 回归测试,覆盖意见提交、AI 候选不提前落库、候选确认和最终目录生成的顺序。
|
|
28
42
|
|
|
43
|
+
## [0.10.0] - 2026-08-14
|
|
44
|
+
|
|
45
|
+
### WorkBuddy
|
|
46
|
+
|
|
47
|
+
- 固定依赖 `@xqyz/xq-cli@0.3.0`,支持完整规划模式命令链。
|
|
48
|
+
- 更新 Skill、安装脚本、MCP Server、MCP App 和 Connector 启动参数到 `0.10.0`。
|
|
49
|
+
- 通过 32 项 WorkBuddy 功能测试和 npm 包冒烟测试;npm `latest` 已指向 `0.10.0`。
|
|
50
|
+
|
|
29
51
|
## [0.2.1] - 2026-08-11
|
|
30
52
|
|
|
31
53
|
### 安全性
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1278,7 +1278,10 @@ async function handleOutline(args, state) {
|
|
|
1278
1278
|
}
|
|
1279
1279
|
const params = buildOutlineParams(outlineSnapshot, effectiveArgs);
|
|
1280
1280
|
|
|
1281
|
-
const
|
|
1281
|
+
const planningMode = Number(params.planMode) === 1;
|
|
1282
|
+
const triggerMode = planningMode
|
|
1283
|
+
? await preparePlanningAnalysis(client, params)
|
|
1284
|
+
: await triggerOutlineGeneration(client, outlineSnapshot, params);
|
|
1282
1285
|
saveTaskSnapshot(state, cid, {
|
|
1283
1286
|
...outlineSnapshot,
|
|
1284
1287
|
...pickTaskSnapshot(params),
|
|
@@ -1288,7 +1291,9 @@ async function handleOutline(args, state) {
|
|
|
1288
1291
|
|
|
1289
1292
|
let waitResult = null;
|
|
1290
1293
|
if (booleanOption(effectiveArgs, 'wait')) {
|
|
1291
|
-
waitResult =
|
|
1294
|
+
waitResult = planningMode
|
|
1295
|
+
? await waitForPlanningAnalysisReady(client, state, cid, outlineSnapshot.uuid, effectiveArgs)
|
|
1296
|
+
: await waitForOutlineReady(client, state, cid, outlineSnapshot.uuid, effectiveArgs);
|
|
1292
1297
|
}
|
|
1293
1298
|
|
|
1294
1299
|
const summaryLines = [
|
|
@@ -1299,7 +1304,9 @@ async function handleOutline(args, state) {
|
|
|
1299
1304
|
`triggerMode: ${triggerMode}`,
|
|
1300
1305
|
];
|
|
1301
1306
|
|
|
1302
|
-
if (waitResult?.phase === '
|
|
1307
|
+
if (waitResult?.phase === 'analysis_ready') {
|
|
1308
|
+
summaryLines.push('planning analysis ready: review it, optionally add feedback, then run plan confirm-outline to generate the outline.');
|
|
1309
|
+
} else if (waitResult?.phase === 'ready') {
|
|
1303
1310
|
summaryLines.push(`outline ready: rootChapters=${countOutlineRoots(waitResult)}`);
|
|
1304
1311
|
} else if (waitResult?.phase === 'supplement_required') {
|
|
1305
1312
|
summaryLines.push(`outline blocked: supplement required (${waitResult.issues.join(', ')})`);
|
|
@@ -1849,12 +1856,16 @@ function buildOutlineParams(snapshot, args) {
|
|
|
1849
1856
|
async function triggerOutlineGeneration(client, snapshot, params) {
|
|
1850
1857
|
await postJson(client, '/task/preSet', params);
|
|
1851
1858
|
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1859
|
+
// task_status=6 means "file parsing" in the Xique backend. It is not a
|
|
1860
|
+
// signal that this is a multi-bid/EPC task. Only route through the
|
|
1861
|
+
// special-selection endpoint when the caller actually supplied a
|
|
1862
|
+
// multi-bid or EPC selection; ordinary tasks must use the normal
|
|
1863
|
+
// generation endpoint even while the task is transitioning from parse.
|
|
1864
|
+
const hasSpecialSelection = Boolean(
|
|
1865
|
+
String(params.multiBidId ?? snapshot.multiBidId ?? '').trim()
|
|
1866
|
+
|| String(params.epcEngineerType ?? snapshot.epcEngineerType ?? '').trim()
|
|
1867
|
+
);
|
|
1868
|
+
if (hasSpecialSelection) {
|
|
1858
1869
|
await getJson(client, '/proxy/multiBiddingChoice', compactObject({
|
|
1859
1870
|
uuid: snapshot.uuid,
|
|
1860
1871
|
multiBidId: params.multiBidId ?? snapshot.multiBidId,
|
|
@@ -1867,6 +1878,13 @@ async function triggerOutlineGeneration(client, snapshot, params) {
|
|
|
1867
1878
|
return 'generateByRequirement';
|
|
1868
1879
|
}
|
|
1869
1880
|
|
|
1881
|
+
async function preparePlanningAnalysis(client, params) {
|
|
1882
|
+
// The backend intentionally pauses plan-mode tasks after parsing. The user
|
|
1883
|
+
// must review that analysis before generateDirectoryByCid starts the outline.
|
|
1884
|
+
await postJson(client, '/task/preSet', params);
|
|
1885
|
+
return 'planningAnalysisPrepared';
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1870
1888
|
async function setPlanModePhase(client, cid, planModePhase) {
|
|
1871
1889
|
const response = await postJson(client, '/task/setPlanModePhase', { cid, planModePhase });
|
|
1872
1890
|
const returned = normalizePlanModePhase(response?.data?.planModePhase);
|
|
@@ -2361,6 +2379,79 @@ async function waitForOutlineReady(client, state, cid, uuid, args) {
|
|
|
2361
2379
|
});
|
|
2362
2380
|
}
|
|
2363
2381
|
|
|
2382
|
+
async function waitForPlanningAnalysisReady(client, state, cid, uuid, args) {
|
|
2383
|
+
const handledIssues = new Set();
|
|
2384
|
+
|
|
2385
|
+
return pollUntil(async () => {
|
|
2386
|
+
const taskResponse = await getJson(client, `/task/getTaskDetail/${encodeURIComponent(cid)}`);
|
|
2387
|
+
const taskDetail = taskResponse?.data || {};
|
|
2388
|
+
saveTaskSnapshot(state, cid, pickTaskSnapshot(taskDetail));
|
|
2389
|
+
|
|
2390
|
+
if (Number(taskDetail.task_status) === 7) {
|
|
2391
|
+
return {
|
|
2392
|
+
done: true,
|
|
2393
|
+
data: { phase: 'failed', taskDetail },
|
|
2394
|
+
progressLabel: 'task_status=7',
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
const parseStatus = uuid
|
|
2399
|
+
? await fetchParseStatus(client, state, cid, uuid)
|
|
2400
|
+
: { success: 1 };
|
|
2401
|
+
const parseSuccess = Number(parseStatus.success);
|
|
2402
|
+
const issues = collectParseIssues(parseStatus);
|
|
2403
|
+
if (parseSuccess === 4 && issues.length > 0) {
|
|
2404
|
+
const autoIgnore = resolveAutoIgnoreIssue(issues, args, uuid, handledIssues);
|
|
2405
|
+
if (autoIgnore) {
|
|
2406
|
+
await submitIntermediateChoice(client, autoIgnore.payload);
|
|
2407
|
+
handledIssues.add(autoIgnore.issue);
|
|
2408
|
+
return {
|
|
2409
|
+
done: false,
|
|
2410
|
+
data: null,
|
|
2411
|
+
progressLabel: `ignored ${autoIgnore.issue}, waiting planning analysis`,
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
return {
|
|
2415
|
+
done: true,
|
|
2416
|
+
data: {
|
|
2417
|
+
phase: 'supplement_required',
|
|
2418
|
+
issues,
|
|
2419
|
+
parseStatus,
|
|
2420
|
+
taskDetail,
|
|
2421
|
+
},
|
|
2422
|
+
progressLabel: `supplement_required=${issues.join(',')}`,
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
if (parseSuccess === 1 || (
|
|
2427
|
+
parseSuccess > 0
|
|
2428
|
+
&& String(taskDetail.requirement || '').trim()
|
|
2429
|
+
&& String(taskDetail.rating || '').trim()
|
|
2430
|
+
)) {
|
|
2431
|
+
return {
|
|
2432
|
+
done: true,
|
|
2433
|
+
data: {
|
|
2434
|
+
phase: 'analysis_ready',
|
|
2435
|
+
taskDetail,
|
|
2436
|
+
parseStatus,
|
|
2437
|
+
},
|
|
2438
|
+
progressLabel: 'planning analysis ready; waiting for outline confirmation',
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
return {
|
|
2443
|
+
done: false,
|
|
2444
|
+
data: null,
|
|
2445
|
+
progressLabel: `success=${parseSuccess}, task_status=${taskDetail.task_status ?? '-'}, planning analysis pending`,
|
|
2446
|
+
};
|
|
2447
|
+
}, {
|
|
2448
|
+
intervalMs: secondsToMs(numberOption(args, 'intervalSec', DEFAULT_TASK_INTERVAL_MS / 1000)),
|
|
2449
|
+
timeoutMs: secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_TIMEOUT_MS / 1000)),
|
|
2450
|
+
quiet: booleanOption(args, 'json'),
|
|
2451
|
+
waitingText: 'waiting planning analysis',
|
|
2452
|
+
});
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2364
2455
|
async function pollContentUntilReady(client, state, cid, args) {
|
|
2365
2456
|
let unfinishedTriggered = false;
|
|
2366
2457
|
let lastUnfinishedTriggerAt = 0;
|
|
@@ -2760,7 +2851,23 @@ function isContentRunningOrDone(detail) {
|
|
|
2760
2851
|
async function pollUntil(checkFn, options) {
|
|
2761
2852
|
const startedAt = Date.now();
|
|
2762
2853
|
while (true) {
|
|
2763
|
-
|
|
2854
|
+
let result;
|
|
2855
|
+
try {
|
|
2856
|
+
result = await checkFn();
|
|
2857
|
+
} catch (error) {
|
|
2858
|
+
// A transient connection reset must not turn an already accepted
|
|
2859
|
+
// asynchronous backend task into a false failure. Read requests
|
|
2860
|
+
// are retried by getJson; this catch keeps polling the same cid
|
|
2861
|
+
// when a response stream still closes during that retry window.
|
|
2862
|
+
if (!isRetryableNetworkError(error)) {
|
|
2863
|
+
throw error;
|
|
2864
|
+
}
|
|
2865
|
+
result = {
|
|
2866
|
+
done: false,
|
|
2867
|
+
data: null,
|
|
2868
|
+
progressLabel: `transient network error; retrying (${String(error?.message || error)})`,
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2764
2871
|
if (result.done) {
|
|
2765
2872
|
return result.data;
|
|
2766
2873
|
}
|
|
@@ -3908,7 +4015,10 @@ function normalizeApiKey(value) {
|
|
|
3908
4015
|
async function getJson(client, url, params = {}, options = {}) {
|
|
3909
4016
|
const response = await requestWithRetry(async () => {
|
|
3910
4017
|
return client.get(url, { params });
|
|
3911
|
-
},
|
|
4018
|
+
}, {
|
|
4019
|
+
retries: DEFAULT_RETRY_COUNT,
|
|
4020
|
+
...options,
|
|
4021
|
+
});
|
|
3912
4022
|
return unwrapApiResponse(response);
|
|
3913
4023
|
}
|
|
3914
4024
|
|
|
@@ -3964,7 +4074,7 @@ function isRetryableNetworkError(error) {
|
|
|
3964
4074
|
if (RETRYABLE_NETWORK_CODES.has(code)) {
|
|
3965
4075
|
return true;
|
|
3966
4076
|
}
|
|
3967
|
-
return /socket disconnected|econnreset|tls connection|network error|timeout/i.test(message);
|
|
4077
|
+
return /socket disconnected|econnreset|tls connection|network error|timeout|stream has been aborted|socket hang up|premature close|aborted/i.test(message);
|
|
3968
4078
|
}
|
|
3969
4079
|
|
|
3970
4080
|
function unwrapApiResponse(response) {
|