@xiaohhhh1/canvas-agent 0.4.7 → 0.4.9
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const AGENT_REPLACED_EXIT_CODE = 75;
|
|
2
2
|
export declare function shouldRestartAgent(code: number | null, stopping: boolean): boolean;
|
|
3
|
+
export declare function shouldReplaceSupervisorLock(ownerPid: number, ageMs: number, ownerAlive: boolean): boolean;
|
|
3
4
|
/** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
|
|
4
5
|
export declare function startHttpSupervisor(): void;
|
|
@@ -1,17 +1,112 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { CONFIG_DIR } from "../config.js";
|
|
2
5
|
export const AGENT_REPLACED_EXIT_CODE = 75;
|
|
3
6
|
const RESTART_DELAY_MS = 1000;
|
|
7
|
+
const SUPERVISOR_LOCK_FILE = path.join(CONFIG_DIR, "canvas-agent-watch.lock");
|
|
8
|
+
const SUPERVISOR_HEARTBEAT_MS = 5000;
|
|
9
|
+
const SUPERVISOR_LOCK_STALE_MS = 15000;
|
|
10
|
+
const SUPERVISOR_TAKEOVER_WAIT_MS = 6000;
|
|
11
|
+
const SUPERVISOR_TAKEOVER_RETRY_MS = 100;
|
|
4
12
|
export function shouldRestartAgent(code, stopping) {
|
|
5
13
|
return !stopping && code !== AGENT_REPLACED_EXIT_CODE;
|
|
6
14
|
}
|
|
15
|
+
export function shouldReplaceSupervisorLock(ownerPid, ageMs, ownerAlive) {
|
|
16
|
+
return !Number.isInteger(ownerPid) || ownerPid <= 0 || !ownerAlive || ageMs > SUPERVISOR_LOCK_STALE_MS;
|
|
17
|
+
}
|
|
18
|
+
function processIsAlive(pid) {
|
|
19
|
+
try {
|
|
20
|
+
process.kill(pid, 0);
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function sleepSync(delayMs) {
|
|
28
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
|
|
29
|
+
}
|
|
30
|
+
function acquireSupervisorLock() {
|
|
31
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
32
|
+
const deadline = Date.now() + SUPERVISOR_TAKEOVER_WAIT_MS;
|
|
33
|
+
while (Date.now() <= deadline) {
|
|
34
|
+
let descriptor;
|
|
35
|
+
try {
|
|
36
|
+
descriptor = fs.openSync(SUPERVISOR_LOCK_FILE, "wx");
|
|
37
|
+
fs.writeFileSync(descriptor, String(process.pid));
|
|
38
|
+
return descriptor;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (descriptor !== undefined) {
|
|
42
|
+
try {
|
|
43
|
+
fs.closeSync(descriptor);
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
try {
|
|
47
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
}
|
|
51
|
+
if (error.code !== "EEXIST")
|
|
52
|
+
throw error;
|
|
53
|
+
try {
|
|
54
|
+
const ownerPid = Number(fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim());
|
|
55
|
+
const ageMs = Date.now() - fs.statSync(SUPERVISOR_LOCK_FILE).mtimeMs;
|
|
56
|
+
if (shouldReplaceSupervisorLock(ownerPid, ageMs, processIsAlive(ownerPid))) {
|
|
57
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (readError) {
|
|
62
|
+
if (readError.code !== "ENOENT") {
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
sleepSync(SUPERVISOR_TAKEOVER_RETRY_MS);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
function releaseSupervisorLock(descriptor) {
|
|
76
|
+
try {
|
|
77
|
+
fs.closeSync(descriptor);
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
try {
|
|
81
|
+
if (fs.readFileSync(SUPERVISOR_LOCK_FILE, "utf8").trim() === String(process.pid)) {
|
|
82
|
+
fs.unlinkSync(SUPERVISOR_LOCK_FILE);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
7
87
|
/** Keep the local HTTP worker alive, but step aside when a newer package replaces it. */
|
|
8
88
|
export function startHttpSupervisor() {
|
|
9
89
|
const entry = process.argv[1];
|
|
10
90
|
if (!entry)
|
|
11
91
|
throw new Error("无法定位 Canvas Agent 启动文件");
|
|
92
|
+
const lockDescriptor = acquireSupervisorLock();
|
|
93
|
+
if (lockDescriptor === undefined)
|
|
94
|
+
return;
|
|
12
95
|
let child;
|
|
13
96
|
let stopping = false;
|
|
14
97
|
let restartTimer;
|
|
98
|
+
const lockHeartbeat = setInterval(() => {
|
|
99
|
+
try {
|
|
100
|
+
fs.utimesSync(SUPERVISOR_LOCK_FILE, new Date(), new Date());
|
|
101
|
+
}
|
|
102
|
+
catch { }
|
|
103
|
+
}, SUPERVISOR_HEARTBEAT_MS);
|
|
104
|
+
lockHeartbeat.unref();
|
|
105
|
+
const exit = () => {
|
|
106
|
+
clearInterval(lockHeartbeat);
|
|
107
|
+
releaseSupervisorLock(lockDescriptor);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
};
|
|
15
110
|
const launch = () => {
|
|
16
111
|
if (stopping)
|
|
17
112
|
return;
|
|
@@ -19,7 +114,7 @@ export function startHttpSupervisor() {
|
|
|
19
114
|
child.once("exit", (code) => {
|
|
20
115
|
child = undefined;
|
|
21
116
|
if (!shouldRestartAgent(code, stopping))
|
|
22
|
-
return
|
|
117
|
+
return exit();
|
|
23
118
|
restartTimer = setTimeout(launch, RESTART_DELAY_MS);
|
|
24
119
|
});
|
|
25
120
|
};
|
|
@@ -30,7 +125,7 @@ export function startHttpSupervisor() {
|
|
|
30
125
|
if (restartTimer)
|
|
31
126
|
clearTimeout(restartTimer);
|
|
32
127
|
child?.kill();
|
|
33
|
-
setTimeout(
|
|
128
|
+
setTimeout(exit, 2000).unref();
|
|
34
129
|
};
|
|
35
130
|
process.once("SIGINT", stop);
|
|
36
131
|
process.once("SIGTERM", stop);
|
package/dist/workflow/manager.js
CHANGED
|
@@ -391,6 +391,7 @@ function scriptChunkPrompt(id, task, ordinals) {
|
|
|
391
391
|
本段 productIndex 必须严格按此映射填写:${scriptProductAssignments(task.product_quantities, ordinals)}。不得凭产品名称猜测或把相邻产品编号混用。
|
|
392
392
|
脚本质量绝不能因批量而降低:每条都必须独立构思、完整、真实合规,严格遵守任务 instructions、产品图片顺序和 productIndex;使用目标市场 ${task.market} 的自然本地语言与偏快但清晰的 10 秒节奏。
|
|
393
393
|
用户没有指定带货方向时,不得随意只用一种泛化形式;必须按任务 instructions 中“已批准的创意方向”逐条做产品适配轮换。轮换必须按每个产品自己的序号连续计算,不能因 30/15/10 条分段、换会话或跨产品边界而从第一个方向重新开始;让同一产品在重复某一方向前优先覆盖其他适配方向。
|
|
394
|
+
工厂风格 A/B 是默认轮换中的演绎带货布景,不是商品来源声明;不得因用户未提供真实工厂资料而跳过,也绝不能写成我们的真实工厂、真实生产流程、真实产地、工厂直销、厂家出货或仓库现货。
|
|
394
395
|
写完后必须调用 flow_c_submit_script_chunk 一次回传这 ${ordinals.length} 条,handoffId=${id}。不要创建付费批次,不要调用供应商模型,不要在聊天输出大段 JSON。工具返回成功后仅简短结束。`;
|
|
395
396
|
}
|
|
396
397
|
function scriptProductAssignments(productQuantities, ordinals) {
|