@manturhub/cli 0.9.10 → 0.9.11
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/bin/cli.js +36 -11
- package/lib/api.js +48 -8
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { getBaseUrl, saveConfig, loadConfig } from "../lib/config.js";
|
|
3
|
-
import { apiFetch, pollJob } from "../lib/api.js";
|
|
3
|
+
import { apiFetch, classifyJobResponse, isFailedJobStatus, pollJob } from "../lib/api.js";
|
|
4
4
|
import { runInit } from "../lib/setup.js";
|
|
5
5
|
import { skillLs, skillAdd, skillOutdated, skillUpdate } from "../lib/skill-install.js";
|
|
6
6
|
import { suiteLs, suiteInstall } from "../lib/suite-install.js";
|
|
@@ -456,17 +456,31 @@ async function main() {
|
|
|
456
456
|
process.stderr.write(
|
|
457
457
|
`⏳ 异步任务 ${r.json.job_id || ""} 已提交,轮询结果中(预计 ${r.json.estimated_seconds || "?"}s;加 --no-wait 可只拿 job_id)…\n`
|
|
458
458
|
);
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
459
|
+
let final;
|
|
460
|
+
try {
|
|
461
|
+
final = await pollJob(pollUrl, {
|
|
462
|
+
initialBilling: r.json._billing,
|
|
463
|
+
onTick: (j) =>
|
|
464
|
+
process.stderr.write(
|
|
465
|
+
` ${j.status}${j.elapsed_ms ? " " + Math.round(j.elapsed_ms / 1000) + "s" : ""}\n`
|
|
466
|
+
),
|
|
467
|
+
});
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.error(
|
|
470
|
+
`异步任务 ${r.json.job_id || ""} 已提交,但状态查询失败:${error.message}`
|
|
471
|
+
);
|
|
472
|
+
if (error.billing) {
|
|
473
|
+
printBillingResult({ _billing: error.billing }, process.stderr, operator.name);
|
|
474
|
+
}
|
|
475
|
+
console.error("请勿重新调用算子,以免重复生成和重复扣费。");
|
|
476
|
+
if (r.json.job_id) console.error(`任务 ID:${r.json.job_id}`);
|
|
477
|
+
console.error(`轮询地址:${pollUrl}`);
|
|
478
|
+
console.error("请稍后使用 `manturhub status <poll_url>` 继续查询。");
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
466
481
|
console.log(JSON.stringify(final, null, 2));
|
|
467
482
|
printBillingResult(final, process.stderr, operator.name);
|
|
468
|
-
|
|
469
|
-
if (st === "failed" || st === "error" || (final && final._timeout)) process.exit(1);
|
|
483
|
+
if (isFailedJobStatus(final.status) || final._timeout) process.exit(1);
|
|
470
484
|
} else {
|
|
471
485
|
console.log(JSON.stringify(r.json, null, 2));
|
|
472
486
|
printBillingResult(r.json, process.stderr, operator.name);
|
|
@@ -538,9 +552,20 @@ async function main() {
|
|
|
538
552
|
process.exit(1);
|
|
539
553
|
}
|
|
540
554
|
const r = await apiFetch(pu);
|
|
555
|
+
if (!r.ok) {
|
|
556
|
+
console.error(`查询失败(HTTP ${r.status})`);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
let state;
|
|
560
|
+
try {
|
|
561
|
+
state = classifyJobResponse(r.json);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error(`任务状态响应无效:${error.message}`);
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
541
566
|
console.log(JSON.stringify(r.json, null, 2));
|
|
542
567
|
printBillingResult(r.json);
|
|
543
|
-
if (
|
|
568
|
+
if (state.failed) process.exit(1);
|
|
544
569
|
break;
|
|
545
570
|
}
|
|
546
571
|
|
package/lib/api.js
CHANGED
|
@@ -62,14 +62,40 @@ export async function apiFetch(
|
|
|
62
62
|
return { ok: res.ok, status: res.status, json };
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
const ACTIVE = new Set(["queued", "running", "pending", "processing", "in_progress"]);
|
|
65
|
+
// 异步算子只接受已知状态。HTTP 错误、非 JSON object、缺失 status 和未知状态
|
|
66
|
+
// 都必须失败关闭,不能因为“不在活动状态集合里”就被误判为成功终态。
|
|
67
|
+
const ACTIVE = new Set(["created", "queued", "running", "pending", "processing", "in_progress"]);
|
|
68
|
+
const SUCCEEDED = new Set(["succeeded", "success", "completed", "done"]);
|
|
69
|
+
const FAILED = new Set(["failed", "error", "cancelled", "canceled"]);
|
|
70
|
+
|
|
71
|
+
export function classifyJobResponse(result) {
|
|
72
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
73
|
+
throw new Error("异步任务状态响应必须是 JSON object");
|
|
74
|
+
}
|
|
75
|
+
if (typeof result.status !== "string" || !result.status.trim()) {
|
|
76
|
+
throw new Error("异步任务状态响应缺少合法的 status");
|
|
77
|
+
}
|
|
78
|
+
const status = result.status.trim().toLowerCase();
|
|
79
|
+
if (ACTIVE.has(status)) return { status, terminal: false, failed: false };
|
|
80
|
+
if (SUCCEEDED.has(status)) return { status, terminal: true, failed: false };
|
|
81
|
+
if (FAILED.has(status)) return { status, terminal: true, failed: true };
|
|
82
|
+
throw new Error(`异步任务返回未知状态: ${result.status}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function isFailedJobStatus(status) {
|
|
86
|
+
return FAILED.has(String(status || "").trim().toLowerCase());
|
|
87
|
+
}
|
|
88
|
+
|
|
68
89
|
export function retainBilling(result, fallbackBilling) {
|
|
69
90
|
if (!fallbackBilling || !result || typeof result !== "object" || Array.isArray(result)) return result;
|
|
70
91
|
return result._billing ? result : { ...result, _billing: fallbackBilling };
|
|
71
92
|
}
|
|
72
93
|
|
|
94
|
+
function attachBilling(error, billing) {
|
|
95
|
+
if (billing && error && typeof error === "object" && !error.billing) error.billing = billing;
|
|
96
|
+
return error;
|
|
97
|
+
}
|
|
98
|
+
|
|
73
99
|
export async function pollJob(
|
|
74
100
|
pollUrl,
|
|
75
101
|
{ intervalMs = 8000, maxMs = 1200000, onTick, initialBilling } = {}
|
|
@@ -78,14 +104,28 @@ export async function pollJob(
|
|
|
78
104
|
let last = null;
|
|
79
105
|
let lastBilling = initialBilling || null;
|
|
80
106
|
while (Date.now() - start < maxMs) {
|
|
81
|
-
|
|
107
|
+
let r;
|
|
108
|
+
try {
|
|
109
|
+
r = await apiFetch(pollUrl, { timeoutMs: 30000 });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw attachBilling(error, lastBilling);
|
|
112
|
+
}
|
|
113
|
+
if (!r.ok) {
|
|
114
|
+
throw attachBilling(
|
|
115
|
+
new Error(`异步任务状态查询失败(HTTP ${r.status})`),
|
|
116
|
+
lastBilling
|
|
117
|
+
);
|
|
118
|
+
}
|
|
82
119
|
last = r.json;
|
|
83
120
|
if (r.json?._billing) lastBilling = r.json._billing;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
121
|
+
let state;
|
|
122
|
+
try {
|
|
123
|
+
state = classifyJobResponse(r.json);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw attachBilling(error, lastBilling);
|
|
88
126
|
}
|
|
127
|
+
if (onTick) onTick(r.json);
|
|
128
|
+
if (state.terminal) return retainBilling(r.json, lastBilling);
|
|
89
129
|
await new Promise((res) => setTimeout(res, intervalMs));
|
|
90
130
|
}
|
|
91
131
|
return retainBilling(
|