@nowcrew/daemon 0.5.35 → 0.5.37
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/README.md +69 -9
- package/dist/computer-cli.js +133 -12
- package/dist/computer-service.js +88 -23
- package/dist/daemon-installation-lease.js +86 -0
- package/dist/daemon-installation.js +38 -0
- package/dist/daemon-update-controller.js +30 -2
- package/dist/daemon-update-eligibility.js +70 -39
- package/dist/daemon-updater.js +16 -0
- package/dist/i18n.js +1 -1
- package/dist/local-executor.js +56 -0
- package/dist/main.js +28 -6
- package/dist/managed-service-diagnostics.js +92 -0
- package/dist/managed-service-lifecycle.js +189 -0
- package/dist/managed-service-registry.js +285 -0
- package/dist/managed-service-startup.js +86 -0
- package/dist/memory-prune-diagnostics.js +57 -0
- package/dist/project-skills/controller.js +74 -19
- package/dist/project-skills/registry.js +17 -5
- package/dist/project-skills/scanner.js +34 -11
- package/dist/serve.js +115 -24
- package/package.json +11 -10
|
@@ -32,13 +32,46 @@ const ProjectCommandSchema = z.discriminatedUnion("type", [
|
|
|
32
32
|
}).strict()).max(MAX_AGENT_PROJECT_SKILL_BINDINGS),
|
|
33
33
|
}).strict(),
|
|
34
34
|
]);
|
|
35
|
+
const DEFAULT_PROJECT_SCAN_TIMEOUT_MS = 10_000;
|
|
36
|
+
class ProjectScanTimeoutError extends Error {
|
|
37
|
+
code = "project_scan_timeout";
|
|
38
|
+
constructor() {
|
|
39
|
+
super("project_scan_timeout");
|
|
40
|
+
this.name = "ProjectScanTimeoutError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const withinScanDeadline = async (operation, timeoutMs) => {
|
|
44
|
+
let timer;
|
|
45
|
+
try {
|
|
46
|
+
return await Promise.race([
|
|
47
|
+
operation,
|
|
48
|
+
new Promise((_resolve, reject) => {
|
|
49
|
+
timer = setTimeout(() => reject(new ProjectScanTimeoutError()), timeoutMs);
|
|
50
|
+
timer.unref?.();
|
|
51
|
+
}),
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
if (timer !== undefined)
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
35
59
|
export function createProjectSkillsController(deps) {
|
|
36
60
|
let scanned = Object.freeze([]);
|
|
37
61
|
const operationTail = createPromiseTail();
|
|
38
62
|
let initialization = null;
|
|
39
63
|
let initializationState = "idle";
|
|
40
|
-
const
|
|
41
|
-
|
|
64
|
+
const scan = deps.scan ?? scanProjects;
|
|
65
|
+
const scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_PROJECT_SCAN_TIMEOUT_MS;
|
|
66
|
+
const scanCurrent = async (deferredProjectId) => withinScanDeadline(scan(await deps.registry.list(), undefined, [
|
|
67
|
+
...scanned
|
|
68
|
+
.map((project) => project.inventory.projectId)
|
|
69
|
+
.filter((projectId) => projectId !== deferredProjectId),
|
|
70
|
+
...(deferredProjectId === undefined ? [] : [deferredProjectId]),
|
|
71
|
+
]), scanTimeoutMs);
|
|
72
|
+
const refresh = async (deferredProjectId) => {
|
|
73
|
+
const next = await scanCurrent(deferredProjectId);
|
|
74
|
+
scanned = next;
|
|
42
75
|
};
|
|
43
76
|
const publishCurrent = async () => {
|
|
44
77
|
await deps.publish(Object.freeze({
|
|
@@ -47,27 +80,38 @@ export function createProjectSkillsController(deps) {
|
|
|
47
80
|
}));
|
|
48
81
|
};
|
|
49
82
|
const enqueue = (operation) => operationTail.enqueue(operation);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (initialization !== null)
|
|
53
|
-
return initialization;
|
|
54
|
-
initializationState = "initializing";
|
|
55
|
-
initialization = enqueue(refresh).then(() => { initializationState = "ready"; }, (error) => {
|
|
56
|
-
initializationState = "failed";
|
|
57
|
-
throw error;
|
|
58
|
-
});
|
|
83
|
+
const initialize = () => {
|
|
84
|
+
if (initializationState === "ready" && initialization !== null)
|
|
59
85
|
return initialization;
|
|
60
|
-
|
|
86
|
+
if (initializationState === "initializing" && initialization !== null)
|
|
87
|
+
return initialization;
|
|
88
|
+
initializationState = "initializing";
|
|
89
|
+
const attempt = enqueue(refresh).then(() => { initializationState = "ready"; }, (error) => {
|
|
90
|
+
initializationState = "failed";
|
|
91
|
+
if (initialization === attempt)
|
|
92
|
+
initialization = null;
|
|
93
|
+
throw error;
|
|
94
|
+
});
|
|
95
|
+
initialization = attempt;
|
|
96
|
+
return attempt;
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
initialize,
|
|
61
100
|
publishCurrent: () => enqueue(publishCurrent),
|
|
62
101
|
scannedProjects: () => scanned,
|
|
63
|
-
handle(input) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
async handle(input) {
|
|
103
|
+
const parsed = ProjectCommandSchema.safeParse(input);
|
|
104
|
+
if (!parsed.success)
|
|
105
|
+
return { ok: false, error: "invalid_project_command" };
|
|
106
|
+
if (parsed.data.type === "agent:skills:sync") {
|
|
107
|
+
try {
|
|
108
|
+
await initialize();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
69
111
|
return { ok: false, error: "project_skills_unavailable" };
|
|
70
112
|
}
|
|
113
|
+
}
|
|
114
|
+
return enqueue(async () => {
|
|
71
115
|
const command = parsed.data;
|
|
72
116
|
try {
|
|
73
117
|
if (command.type === "agent:skills:sync") {
|
|
@@ -79,7 +123,16 @@ export function createProjectSkillsController(deps) {
|
|
|
79
123
|
};
|
|
80
124
|
}
|
|
81
125
|
if (command.type === "project:add") {
|
|
126
|
+
const existed = (await deps.registry.list())
|
|
127
|
+
.some((project) => project.projectId === command.projectId);
|
|
82
128
|
await deps.registry.add(command.projectId, command.root);
|
|
129
|
+
const next = await scanCurrent();
|
|
130
|
+
const added = next.find((project) => project.inventory.projectId === command.projectId);
|
|
131
|
+
if (!existed && added?.inventory.errorCode === "machine_project_skill_limit_exceeded") {
|
|
132
|
+
await deps.registry.remove(command.projectId);
|
|
133
|
+
return { ok: false, error: "machine_project_skill_limit_exceeded" };
|
|
134
|
+
}
|
|
135
|
+
scanned = next;
|
|
83
136
|
}
|
|
84
137
|
else if (command.type === "project:remove") {
|
|
85
138
|
await deps.registry.remove(command.projectId);
|
|
@@ -90,7 +143,9 @@ export function createProjectSkillsController(deps) {
|
|
|
90
143
|
return { ok: false, error: "project_not_found" };
|
|
91
144
|
}
|
|
92
145
|
}
|
|
93
|
-
|
|
146
|
+
if (command.type !== "project:add") {
|
|
147
|
+
await refresh(command.type === "project:rescan" ? command.projectId : undefined);
|
|
148
|
+
}
|
|
94
149
|
await publishCurrent();
|
|
95
150
|
return { ok: true, data: { projectId: command.projectId } };
|
|
96
151
|
}
|
|
@@ -6,8 +6,7 @@ import { createPromiseTail } from "../promise-tail.js";
|
|
|
6
6
|
import { atomicPrivateWrite } from "../atomic-private-write.js";
|
|
7
7
|
const RegistryFileSchema = z.object({
|
|
8
8
|
version: z.literal(1),
|
|
9
|
-
projects: z.record(z.object({ root: z.string().min(1).max(4_096) }).strict())
|
|
10
|
-
.refine((projects) => Object.keys(projects).length <= MAX_MACHINE_PROJECTS),
|
|
9
|
+
projects: z.record(z.object({ root: z.string().min(1).max(4_096) }).strict()),
|
|
11
10
|
}).strict();
|
|
12
11
|
export class ProjectRegistryError extends Error {
|
|
13
12
|
code;
|
|
@@ -17,9 +16,22 @@ export class ProjectRegistryError extends Error {
|
|
|
17
16
|
this.name = "ProjectRegistryError";
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
const immutableList = (projects) =>
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const immutableList = (projects) => {
|
|
20
|
+
const sorted = Object.entries(projects)
|
|
21
|
+
.map(([projectId, project]) => Object.freeze({ projectId, root: project.root }))
|
|
22
|
+
.sort((left, right) => left.projectId.localeCompare(right.projectId));
|
|
23
|
+
if (sorted.length <= MAX_MACHINE_PROJECTS)
|
|
24
|
+
return Object.freeze(sorted);
|
|
25
|
+
const visible = sorted.slice(0, MAX_MACHINE_PROJECTS);
|
|
26
|
+
const boundary = visible.at(-1);
|
|
27
|
+
if (boundary !== undefined) {
|
|
28
|
+
visible[visible.length - 1] = Object.freeze({
|
|
29
|
+
...boundary,
|
|
30
|
+
errorCode: "project_registry_limit_exceeded",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze(visible);
|
|
34
|
+
};
|
|
23
35
|
export function createProjectRegistry(agentsRoot) {
|
|
24
36
|
const path = join(agentsRoot, ".crew", "projects.json");
|
|
25
37
|
const writeTail = createPromiseTail();
|
|
@@ -15,6 +15,8 @@ const unavailable = (projectId, scannedAt, errorCode) => Object.freeze({
|
|
|
15
15
|
});
|
|
16
16
|
export async function scanProject(project, now = () => new Date()) {
|
|
17
17
|
const scannedAt = now().toISOString();
|
|
18
|
+
if (project.errorCode !== undefined)
|
|
19
|
+
return unavailable(project.projectId, scannedAt, project.errorCode);
|
|
18
20
|
const projectStat = await stat(project.root).catch(() => null);
|
|
19
21
|
if (!projectStat?.isDirectory())
|
|
20
22
|
return unavailable(project.projectId, scannedAt, "project_path_unavailable");
|
|
@@ -51,8 +53,12 @@ export async function scanProject(project, now = () => new Date()) {
|
|
|
51
53
|
continue;
|
|
52
54
|
}
|
|
53
55
|
const frontmatter = parseSkillFrontmatter(markdown);
|
|
54
|
-
const name = frontmatter
|
|
55
|
-
const description = frontmatter
|
|
56
|
+
const name = frontmatter.name;
|
|
57
|
+
const description = frontmatter.description;
|
|
58
|
+
if (name === undefined || description === undefined) {
|
|
59
|
+
invalidSkillCount += 1;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
56
62
|
if (!isProjectSkillName(name) || description.length > MAX_PROJECT_SKILL_DESCRIPTION_LENGTH) {
|
|
57
63
|
invalidSkillCount += 1;
|
|
58
64
|
continue;
|
|
@@ -84,20 +90,37 @@ export async function scanProject(project, now = () => new Date()) {
|
|
|
84
90
|
resolved: Object.freeze(resolved),
|
|
85
91
|
});
|
|
86
92
|
}
|
|
87
|
-
export function boundScannedProjects(projects) {
|
|
93
|
+
export function boundScannedProjects(projects, retainedProjectIds = []) {
|
|
94
|
+
const retainedOrder = new Map(retainedProjectIds.map((projectId, index) => [projectId, index]));
|
|
95
|
+
const allocationOrder = [...projects].sort((left, right) => {
|
|
96
|
+
const leftOrder = retainedOrder.get(left.inventory.projectId);
|
|
97
|
+
const rightOrder = retainedOrder.get(right.inventory.projectId);
|
|
98
|
+
if (leftOrder !== undefined && rightOrder !== undefined)
|
|
99
|
+
return leftOrder - rightOrder;
|
|
100
|
+
if (leftOrder !== undefined)
|
|
101
|
+
return -1;
|
|
102
|
+
if (rightOrder !== undefined)
|
|
103
|
+
return 1;
|
|
104
|
+
return left.inventory.projectId.localeCompare(right.inventory.projectId);
|
|
105
|
+
});
|
|
88
106
|
let skillCount = 0;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
const bounded = new Map();
|
|
108
|
+
for (const project of allocationOrder) {
|
|
109
|
+
if (project.inventory.status !== "available") {
|
|
110
|
+
bounded.set(project.inventory.projectId, project);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
92
113
|
const nextCount = skillCount + project.inventory.skills.length;
|
|
93
114
|
if (nextCount <= MAX_MACHINE_PROJECT_SKILLS) {
|
|
94
115
|
skillCount = nextCount;
|
|
95
|
-
|
|
116
|
+
bounded.set(project.inventory.projectId, project);
|
|
117
|
+
continue;
|
|
96
118
|
}
|
|
97
|
-
|
|
98
|
-
}
|
|
119
|
+
bounded.set(project.inventory.projectId, unavailable(project.inventory.projectId, project.inventory.scannedAt, "machine_project_skill_limit_exceeded"));
|
|
120
|
+
}
|
|
121
|
+
return Object.freeze(projects.map((project) => bounded.get(project.inventory.projectId) ?? project));
|
|
99
122
|
}
|
|
100
|
-
export async function scanProjects(projects, now = () => new Date()) {
|
|
123
|
+
export async function scanProjects(projects, now = () => new Date(), retainedProjectIds = []) {
|
|
101
124
|
const sorted = [...projects].sort((left, right) => left.projectId.localeCompare(right.projectId));
|
|
102
|
-
return boundScannedProjects(await Promise.all(sorted.map((project) => scanProject(project, now))));
|
|
125
|
+
return boundScannedProjects(await Promise.all(sorted.map((project) => scanProject(project, now))), retainedProjectIds);
|
|
103
126
|
}
|
package/dist/serve.js
CHANGED
|
@@ -40,6 +40,7 @@ import { createProjectSkillsController, } from "./project-skills/controller.js";
|
|
|
40
40
|
import { PROJECT_SKILLS_CAPABILITY } from "./project-skills/types.js";
|
|
41
41
|
import { createAgentProjectionCoordinator } from "./project-skills/agent-projection-coordinator.js";
|
|
42
42
|
import { createProjectSkillsReconciler, ProjectProjectionError, } from "./project-skills/reconciler.js";
|
|
43
|
+
import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
|
|
43
44
|
// normalize.ts 的活动种类 → activity 枚举
|
|
44
45
|
const ACTIVITY_MAP = {
|
|
45
46
|
init: "working", text: "thinking", reading: "reading", sending: "sending",
|
|
@@ -126,21 +127,58 @@ export function serve(config, opts = {}) {
|
|
|
126
127
|
},
|
|
127
128
|
reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
|
|
128
129
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
let projectSkillsStatus = "initializing";
|
|
131
|
+
let projectSkillsInitialization = null;
|
|
132
|
+
let latestMachineHello = null;
|
|
133
|
+
let latestHelloSocket = null;
|
|
134
|
+
const effectiveMachineHello = (hello) => ({
|
|
135
|
+
...hello,
|
|
136
|
+
projectSkillsStatus,
|
|
137
|
+
capabilities: projectSkillsStatus === "ready"
|
|
138
|
+
? hello.capabilities
|
|
139
|
+
: hello.capabilities.filter((capability) => capability !== PROJECT_SKILLS_CAPABILITY),
|
|
134
140
|
});
|
|
135
|
-
|
|
141
|
+
const sendEffectiveMachineHello = () => {
|
|
142
|
+
if (latestMachineHello === null || latestHelloSocket?.readyState !== WebSocket.OPEN)
|
|
143
|
+
return;
|
|
144
|
+
const hello = effectiveMachineHello(latestMachineHello);
|
|
145
|
+
try {
|
|
146
|
+
latestHelloSocket.send(JSON.stringify(hello));
|
|
147
|
+
log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`);
|
|
148
|
+
}
|
|
149
|
+
catch { /* 非 OPEN,忽略 */ }
|
|
150
|
+
};
|
|
151
|
+
const ensureProjectSkillsInitialized = () => {
|
|
152
|
+
if (projectSkillsStatus === "ready")
|
|
153
|
+
return Promise.resolve(true);
|
|
154
|
+
if (projectSkillsInitialization !== null)
|
|
155
|
+
return projectSkillsInitialization;
|
|
156
|
+
projectSkillsStatus = "initializing";
|
|
157
|
+
const attempt = projectSkillsController.initialize().then(() => {
|
|
158
|
+
projectSkillsStatus = "ready";
|
|
159
|
+
return true;
|
|
160
|
+
}, (error) => {
|
|
161
|
+
projectSkillsStatus = "unavailable";
|
|
162
|
+
dslog("project_skills.registry_failed", "本机项目注册表读取失败", {
|
|
163
|
+
level: "ERROR", error_code: error.code ?? "project_registry_corrupt",
|
|
164
|
+
});
|
|
165
|
+
return false;
|
|
166
|
+
}).finally(() => {
|
|
167
|
+
if (projectSkillsInitialization === attempt)
|
|
168
|
+
projectSkillsInitialization = null;
|
|
169
|
+
sendEffectiveMachineHello();
|
|
170
|
+
});
|
|
171
|
+
projectSkillsInitialization = attempt;
|
|
172
|
+
return attempt;
|
|
173
|
+
};
|
|
136
174
|
const initializedProjectSkillsReconciler = {
|
|
137
|
-
reconcile(handle, bindings) {
|
|
138
|
-
return
|
|
175
|
+
async reconcile(handle, bindings) {
|
|
176
|
+
return await ensureProjectSkillsInitialized()
|
|
139
177
|
? projectSkillsReconciler.reconcile(handle, bindings)
|
|
140
178
|
: Promise.reject(new ProjectProjectionError("skill_projection_failed"));
|
|
141
179
|
},
|
|
142
|
-
prepareAndLaunch(agentsRoot, handle, bindings, launch) {
|
|
143
|
-
return
|
|
180
|
+
async prepareAndLaunch(agentsRoot, handle, bindings, launch) {
|
|
181
|
+
return await ensureProjectSkillsInitialized()
|
|
144
182
|
? projectSkillsReconciler.prepareAndLaunch(agentsRoot, handle, bindings, launch)
|
|
145
183
|
: Promise.reject(new ProjectProjectionError("skill_projection_failed"));
|
|
146
184
|
},
|
|
@@ -248,12 +286,13 @@ export function serve(config, opts = {}) {
|
|
|
248
286
|
// scheduled 重复 run 仍由 running 去重;普通同线程 wake 用 legacyTaskTails 串成 FIFO。
|
|
249
287
|
// 每 agent 超过并行上限的任务继续进入 sharedQueues(不丢)。
|
|
250
288
|
const running = new Set();
|
|
289
|
+
const activeExecutionTaskKeys = new Map();
|
|
251
290
|
const legacyTaskTails = new Map();
|
|
252
291
|
const log = (s) => process.stdout.write(formatDaemonLogLine(s) + "\n");
|
|
253
292
|
function connect() {
|
|
254
293
|
if (stopped)
|
|
255
294
|
return;
|
|
256
|
-
const wsUrl = buildControlPlaneUrl(config.serverUrl, config.machineToken, process.platform, undefined,
|
|
295
|
+
const wsUrl = buildControlPlaneUrl(config.serverUrl, config.machineToken, process.platform, undefined, projectSkillsStatus === "ready");
|
|
257
296
|
ws = createWebSocket(wsUrl);
|
|
258
297
|
ws.on("open", () => {
|
|
259
298
|
const openedSocket = ws;
|
|
@@ -282,18 +321,12 @@ export function serve(config, opts = {}) {
|
|
|
282
321
|
});
|
|
283
322
|
void helloPromise
|
|
284
323
|
.then((hello) => {
|
|
324
|
+
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
325
|
+
return;
|
|
285
326
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
...hello,
|
|
290
|
-
capabilities: hello.capabilities.filter((capability) => capability !== PROJECT_SKILLS_CAPABILITY),
|
|
291
|
-
};
|
|
292
|
-
try {
|
|
293
|
-
ws?.send(JSON.stringify(effectiveHello));
|
|
294
|
-
log(`📤 已上报机器信息: ${effectiveHello.hostname} · ${effectiveHello.os} · installed=[${effectiveHello.runtimes.join(",")}] · executable=[${effectiveHello.executionRuntimes.join(",")}] · agents=[${effectiveHello.agentHandles.join(",")}]`);
|
|
295
|
-
}
|
|
296
|
-
catch { /* 非 OPEN,忽略 */ }
|
|
327
|
+
latestMachineHello = hello;
|
|
328
|
+
latestHelloSocket = openedSocket;
|
|
329
|
+
sendEffectiveMachineHello();
|
|
297
330
|
})
|
|
298
331
|
.catch(() => { });
|
|
299
332
|
opts.onOpen?.(ws);
|
|
@@ -440,6 +473,18 @@ export function serve(config, opts = {}) {
|
|
|
440
473
|
}
|
|
441
474
|
return;
|
|
442
475
|
}
|
|
476
|
+
const pruneTraceId = parseMemoryPruneTraceId(spec.instructions.wakePrompt);
|
|
477
|
+
if (pruneTraceId !== null) {
|
|
478
|
+
dslog("memory_prune.received", "Daemon 已收到长期记忆收尾 execution", {
|
|
479
|
+
protocol: "execution_v1",
|
|
480
|
+
prune_trace_id: pruneTraceId,
|
|
481
|
+
execution_id: spec.executionId,
|
|
482
|
+
agent_handle: spec.agent.handle,
|
|
483
|
+
channel_id: spec.context.channelId,
|
|
484
|
+
thread_id: spec.context.threadId ?? null,
|
|
485
|
+
task_key: spec.workspace.taskKey,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
443
488
|
const reservation = sharedSlots.reserve(spec.agent.handle, "execution");
|
|
444
489
|
if (!reservation.accepted) {
|
|
445
490
|
dslog("execution.machine_queue_rejected", "机器执行队列已满", {
|
|
@@ -465,9 +510,41 @@ export function serve(config, opts = {}) {
|
|
|
465
510
|
});
|
|
466
511
|
}
|
|
467
512
|
executionReservations.set(spec.executionId, reservation);
|
|
513
|
+
const executionTaskKey = `${spec.agent.handle}:${spec.workspace.taskKey}`;
|
|
514
|
+
let executionTaskKeyActive = false;
|
|
515
|
+
let executionTaskKeyFinished = false;
|
|
516
|
+
const markExecutionTaskKeyActive = () => {
|
|
517
|
+
if (executionTaskKeyActive || executionTaskKeyFinished)
|
|
518
|
+
return;
|
|
519
|
+
executionTaskKeyActive = true;
|
|
520
|
+
const activeSameTask = (activeExecutionTaskKeys.get(executionTaskKey) ?? 0) + 1;
|
|
521
|
+
activeExecutionTaskKeys.set(executionTaskKey, activeSameTask);
|
|
522
|
+
dslog(activeSameTask > 1 ? "execution.task_key_overlap_detected" : "execution.task_key_started", activeSameTask > 1 ? "同一 Agent 任务键存在重叠 execution" : "Agent 任务键 execution 已开始", {
|
|
523
|
+
...(activeSameTask > 1 ? { level: "WARN" } : {}),
|
|
524
|
+
execution_id: spec.executionId,
|
|
525
|
+
agent_handle: spec.agent.handle,
|
|
526
|
+
task_key: spec.workspace.taskKey,
|
|
527
|
+
active_same_task: activeSameTask,
|
|
528
|
+
...reservation.facts,
|
|
529
|
+
});
|
|
530
|
+
};
|
|
468
531
|
const cancellation = cancellationFor(spec.executionId);
|
|
469
532
|
const cleanupExecutionReservation = () => {
|
|
533
|
+
executionTaskKeyFinished = true;
|
|
470
534
|
reservation.release();
|
|
535
|
+
if (executionTaskKeyActive) {
|
|
536
|
+
const remainingSameTask = Math.max(0, (activeExecutionTaskKeys.get(executionTaskKey) ?? 1) - 1);
|
|
537
|
+
if (remainingSameTask === 0)
|
|
538
|
+
activeExecutionTaskKeys.delete(executionTaskKey);
|
|
539
|
+
else
|
|
540
|
+
activeExecutionTaskKeys.set(executionTaskKey, remainingSameTask);
|
|
541
|
+
dslog("execution.task_key_finished", "Agent 任务键 execution 已结束", {
|
|
542
|
+
execution_id: spec.executionId,
|
|
543
|
+
agent_handle: spec.agent.handle,
|
|
544
|
+
task_key: spec.workspace.taskKey,
|
|
545
|
+
active_same_task: remainingSameTask,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
471
548
|
cancellations.delete(spec.executionId);
|
|
472
549
|
executionReservations.delete(spec.executionId);
|
|
473
550
|
knownExecutionHashes.delete(spec.executionId);
|
|
@@ -506,6 +583,7 @@ export function serve(config, opts = {}) {
|
|
|
506
583
|
slot: {
|
|
507
584
|
state: reservation.state,
|
|
508
585
|
ready: reservation.ready.then(() => {
|
|
586
|
+
markExecutionTaskKeyActive();
|
|
509
587
|
const snapshot = sharedSlots.snapshot();
|
|
510
588
|
dslog("execution.machine_slot_ready", "execution 获得机器执行名额", {
|
|
511
589
|
execution_id: spec.executionId,
|
|
@@ -567,7 +645,7 @@ export function serve(config, opts = {}) {
|
|
|
567
645
|
? r.serverCapabilities.filter((c) => typeof c === "string")
|
|
568
646
|
: []);
|
|
569
647
|
if (serverCapabilities.has(PROJECT_SKILLS_CAPABILITY)) {
|
|
570
|
-
if (await
|
|
648
|
+
if (await ensureProjectSkillsInitialized())
|
|
571
649
|
await projectSkillsController.publishCurrent();
|
|
572
650
|
}
|
|
573
651
|
return;
|
|
@@ -591,6 +669,10 @@ export function serve(config, opts = {}) {
|
|
|
591
669
|
const result = serverCapabilities.has(PROJECT_SKILLS_CAPABILITY)
|
|
592
670
|
? await projectSkillsController.handle(msg)
|
|
593
671
|
: { ok: false, error: "capability_unavailable" };
|
|
672
|
+
if (result.ok && (msg.type === "project:remove"
|
|
673
|
+
|| msg.type === "project:rescan")) {
|
|
674
|
+
void ensureProjectSkillsInitialized();
|
|
675
|
+
}
|
|
594
676
|
try {
|
|
595
677
|
ws?.send(JSON.stringify({ type: "fs:result", reqId: request.reqId, ...result }));
|
|
596
678
|
}
|
|
@@ -671,6 +753,14 @@ export function serve(config, opts = {}) {
|
|
|
671
753
|
run_id: runId, agent_handle: msg.agentHandle, channel_id: msg.channelId,
|
|
672
754
|
thread_id: threadId ?? null, task_key: taskKey,
|
|
673
755
|
};
|
|
756
|
+
const pruneTraceId = parseMemoryPruneTraceId(msg.wake?.content ?? "");
|
|
757
|
+
if (pruneTraceId !== null) {
|
|
758
|
+
dslog("memory_prune.received", "Daemon 已收到长期记忆收尾唤醒", {
|
|
759
|
+
...runKeys,
|
|
760
|
+
protocol: "legacy",
|
|
761
|
+
prune_trace_id: pruneTraceId,
|
|
762
|
+
});
|
|
763
|
+
}
|
|
674
764
|
dslog("run.wake_received", `收到唤醒 ${msg.agentHandle}`, {
|
|
675
765
|
...runKeys, reason: msg.reason ?? "", sender: msg.wake?.senderHandle,
|
|
676
766
|
wake_origin: msg.wake?.origin ?? null,
|
|
@@ -1013,7 +1103,7 @@ export function serve(config, opts = {}) {
|
|
|
1013
1103
|
flush: flushSlog,
|
|
1014
1104
|
writeStderr: (line) => process.stderr.write(line),
|
|
1015
1105
|
});
|
|
1016
|
-
|
|
1106
|
+
void ensureProjectSkillsInitialized();
|
|
1017
1107
|
connect();
|
|
1018
1108
|
})();
|
|
1019
1109
|
const stop = () => {
|
|
@@ -1030,6 +1120,7 @@ export function serve(config, opts = {}) {
|
|
|
1030
1120
|
const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
|
|
1031
1121
|
const pending = [
|
|
1032
1122
|
webSocketClosed,
|
|
1123
|
+
updateController.drain(),
|
|
1033
1124
|
executionFrameQueue,
|
|
1034
1125
|
...executionRuns.values(),
|
|
1035
1126
|
...[...legacyRuns.values()].map((run) => run.done),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nowcrew/daemon",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.37",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -16,12 +16,19 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
19
26
|
"dependencies": {
|
|
20
27
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
28
|
+
"@nowcrew/cli": "workspace:^",
|
|
21
29
|
"cross-spawn": "^7.0.6",
|
|
22
30
|
"ws": "^8",
|
|
23
|
-
"zod": "^3.23.0"
|
|
24
|
-
"@nowcrew/cli": "^0.4.13"
|
|
31
|
+
"zod": "^3.23.0"
|
|
25
32
|
},
|
|
26
33
|
"optionalDependencies": {
|
|
27
34
|
"koffi": "^2.9.0"
|
|
@@ -33,11 +40,5 @@
|
|
|
33
40
|
"tsx": "^4.19.0",
|
|
34
41
|
"typescript": "^5.6.0",
|
|
35
42
|
"vitest": "^2.1.0"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
|
|
39
|
-
"build": "tsc -p tsconfig.json",
|
|
40
|
-
"test": "vitest run",
|
|
41
|
-
"typecheck": "tsc --noEmit"
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
}
|