@nowcrew/daemon 0.5.34 → 0.5.36
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 +46 -0
- package/dist/atomic-private-write.js +18 -0
- package/dist/computer-profile.js +3 -17
- package/dist/execution-protocol.js +7 -1
- package/dist/execution-runner.js +2 -0
- package/dist/local-executor.js +65 -2
- package/dist/machine-info.js +5 -1
- package/dist/memory-prune-diagnostics.js +57 -0
- package/dist/project-skills/agent-projection-coordinator.js +10 -0
- package/dist/project-skills/controller.js +167 -0
- package/dist/project-skills/reconciler.js +116 -0
- package/dist/project-skills/registry.js +113 -0
- package/dist/project-skills/scanner.js +126 -0
- package/dist/project-skills/types.js +12 -0
- package/dist/promise-tail.js +25 -0
- package/dist/runner.js +4 -0
- package/dist/runtimes/claude.js +2 -0
- package/dist/runtimes/codex-app-server-runner.js +10 -1
- package/dist/scheduled-report.js +3 -0
- package/dist/serve.js +188 -10
- package/dist/skill-frontmatter.js +23 -0
- package/dist/skills.js +2 -19
- package/dist/supervised-runtime.js +7 -0
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -8,7 +8,7 @@ import { randomUUID } from "node:crypto";
|
|
|
8
8
|
import { initSlog, dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
|
|
9
9
|
import { mergeRunAgentResults, reportScheduledStartFailure, runAgent } from "./runner.js";
|
|
10
10
|
import { buildOriginDecisionRetryPrompt, buildScheduledPrompt } from "./prompt.js";
|
|
11
|
-
import { collectMachineHello,
|
|
11
|
+
import { collectMachineHello, daemonCapabilities, EXECUTION_PROTOCOL, } from "./machine-info.js";
|
|
12
12
|
import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
|
|
13
13
|
import { listSkills } from "./skills.js";
|
|
14
14
|
import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
|
|
@@ -35,26 +35,36 @@ import { detectDaemonUpdateEligibility, } from "./daemon-update-eligibility.js";
|
|
|
35
35
|
import { createDaemonUpdateController } from "./daemon-update-controller.js";
|
|
36
36
|
import { installExactDaemonUpdate } from "./daemon-updater.js";
|
|
37
37
|
import { scheduleServiceRestart } from "./computer-service.js";
|
|
38
|
+
import { createProjectRegistry } from "./project-skills/registry.js";
|
|
39
|
+
import { createProjectSkillsController, } from "./project-skills/controller.js";
|
|
40
|
+
import { PROJECT_SKILLS_CAPABILITY } from "./project-skills/types.js";
|
|
41
|
+
import { createAgentProjectionCoordinator } from "./project-skills/agent-projection-coordinator.js";
|
|
42
|
+
import { createProjectSkillsReconciler, ProjectProjectionError, } from "./project-skills/reconciler.js";
|
|
43
|
+
import { parseMemoryPruneTraceId } from "./memory-prune-diagnostics.js";
|
|
38
44
|
// normalize.ts 的活动种类 → activity 枚举
|
|
39
45
|
const ACTIVITY_MAP = {
|
|
40
46
|
init: "working", text: "thinking", reading: "reading", sending: "sending",
|
|
41
47
|
checking: "checking", claiming: "claiming", crew: "working", tool: "working",
|
|
42
48
|
tool_result: "working", done: "done", error: "error",
|
|
43
49
|
};
|
|
44
|
-
export function buildControlPlaneUrl(serverUrl, machineToken, runtimePlatform = process.platform, jobObjectProbe) {
|
|
50
|
+
export function buildControlPlaneUrl(serverUrl, machineToken, runtimePlatform = process.platform, jobObjectProbe, projectSkillsAvailable = true) {
|
|
45
51
|
const query = new URLSearchParams({ key: machineToken });
|
|
46
52
|
if (executionBackendCapability(runtimePlatform, jobObjectProbe).supported) {
|
|
47
53
|
query.set("execution_min", String(EXECUTION_PROTOCOL.min));
|
|
48
54
|
query.set("execution_max", String(EXECUTION_PROTOCOL.max));
|
|
49
55
|
}
|
|
50
|
-
for (const capability of
|
|
51
|
-
|
|
56
|
+
for (const capability of daemonCapabilities(runtimePlatform)) {
|
|
57
|
+
if (capability !== PROJECT_SKILLS_CAPABILITY || projectSkillsAvailable) {
|
|
58
|
+
query.append("capability", capability);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
52
61
|
return `${serverUrl.replace(/^http/, "ws").replace(/\/+$/, "")}/daemon/connect?${query.toString()}`;
|
|
53
62
|
}
|
|
54
63
|
export function serve(config, opts = {}) {
|
|
55
|
-
const wsUrl = buildControlPlaneUrl(config.serverUrl, config.machineToken);
|
|
56
64
|
let stopped = false;
|
|
57
65
|
let ws = null;
|
|
66
|
+
// 当前连接的 server 能力(ready 帧下发;重连后由新 ready 帧刷新)。
|
|
67
|
+
let serverCapabilities = new Set();
|
|
58
68
|
let backoff = 1000;
|
|
59
69
|
const maxBackoff = opts.maxBackoffMs ?? 30_000;
|
|
60
70
|
const testShutdown = readTestShutdownConfiguration(process.env);
|
|
@@ -97,6 +107,82 @@ export function serve(config, opts = {}) {
|
|
|
97
107
|
catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
|
|
98
108
|
},
|
|
99
109
|
});
|
|
110
|
+
const projectionCoordinator = createAgentProjectionCoordinator();
|
|
111
|
+
let projectSkillsController;
|
|
112
|
+
const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
|
|
113
|
+
agentsRoot: config.agentsRoot,
|
|
114
|
+
coordinator: projectionCoordinator,
|
|
115
|
+
scannedProjects: () => projectSkillsController.scannedProjects(),
|
|
116
|
+
});
|
|
117
|
+
projectSkillsController = opts.projectSkills?.controller ?? createProjectSkillsController({
|
|
118
|
+
registry: createProjectRegistry(config.agentsRoot),
|
|
119
|
+
publish: (frame) => {
|
|
120
|
+
if (!serverCapabilities.has(PROJECT_SKILLS_CAPABILITY))
|
|
121
|
+
return;
|
|
122
|
+
try {
|
|
123
|
+
if (ws?.readyState === WebSocket.OPEN)
|
|
124
|
+
ws.send(JSON.stringify(frame));
|
|
125
|
+
}
|
|
126
|
+
catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
|
|
127
|
+
},
|
|
128
|
+
reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
|
|
129
|
+
});
|
|
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),
|
|
140
|
+
});
|
|
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
|
+
};
|
|
174
|
+
const initializedProjectSkillsReconciler = {
|
|
175
|
+
async reconcile(handle, bindings) {
|
|
176
|
+
return await ensureProjectSkillsInitialized()
|
|
177
|
+
? projectSkillsReconciler.reconcile(handle, bindings)
|
|
178
|
+
: Promise.reject(new ProjectProjectionError("skill_projection_failed"));
|
|
179
|
+
},
|
|
180
|
+
async prepareAndLaunch(agentsRoot, handle, bindings, launch) {
|
|
181
|
+
return await ensureProjectSkillsInitialized()
|
|
182
|
+
? projectSkillsReconciler.prepareAndLaunch(agentsRoot, handle, bindings, launch)
|
|
183
|
+
: Promise.reject(new ProjectProjectionError("skill_projection_failed"));
|
|
184
|
+
},
|
|
185
|
+
};
|
|
100
186
|
const safeExecutionSend = (frame) => {
|
|
101
187
|
try {
|
|
102
188
|
if (ws?.readyState !== WebSocket.OPEN)
|
|
@@ -200,11 +286,13 @@ export function serve(config, opts = {}) {
|
|
|
200
286
|
// scheduled 重复 run 仍由 running 去重;普通同线程 wake 用 legacyTaskTails 串成 FIFO。
|
|
201
287
|
// 每 agent 超过并行上限的任务继续进入 sharedQueues(不丢)。
|
|
202
288
|
const running = new Set();
|
|
289
|
+
const activeExecutionTaskKeys = new Map();
|
|
203
290
|
const legacyTaskTails = new Map();
|
|
204
291
|
const log = (s) => process.stdout.write(formatDaemonLogLine(s) + "\n");
|
|
205
292
|
function connect() {
|
|
206
293
|
if (stopped)
|
|
207
294
|
return;
|
|
295
|
+
const wsUrl = buildControlPlaneUrl(config.serverUrl, config.machineToken, process.platform, undefined, projectSkillsStatus === "ready");
|
|
208
296
|
ws = createWebSocket(wsUrl);
|
|
209
297
|
ws.on("open", () => {
|
|
210
298
|
const openedSocket = ws;
|
|
@@ -233,12 +321,12 @@ export function serve(config, opts = {}) {
|
|
|
233
321
|
});
|
|
234
322
|
void helloPromise
|
|
235
323
|
.then((hello) => {
|
|
324
|
+
if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
|
|
325
|
+
return;
|
|
236
326
|
detectedExecutionRuntimes = hello.executionRuntimes;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
catch { /* 非 OPEN,忽略 */ }
|
|
327
|
+
latestMachineHello = hello;
|
|
328
|
+
latestHelloSocket = openedSocket;
|
|
329
|
+
sendEffectiveMachineHello();
|
|
242
330
|
})
|
|
243
331
|
.catch(() => { });
|
|
244
332
|
opts.onOpen?.(ws);
|
|
@@ -385,6 +473,18 @@ export function serve(config, opts = {}) {
|
|
|
385
473
|
}
|
|
386
474
|
return;
|
|
387
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
|
+
}
|
|
388
488
|
const reservation = sharedSlots.reserve(spec.agent.handle, "execution");
|
|
389
489
|
if (!reservation.accepted) {
|
|
390
490
|
dslog("execution.machine_queue_rejected", "机器执行队列已满", {
|
|
@@ -410,9 +510,41 @@ export function serve(config, opts = {}) {
|
|
|
410
510
|
});
|
|
411
511
|
}
|
|
412
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
|
+
};
|
|
413
531
|
const cancellation = cancellationFor(spec.executionId);
|
|
414
532
|
const cleanupExecutionReservation = () => {
|
|
533
|
+
executionTaskKeyFinished = true;
|
|
415
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
|
+
}
|
|
416
548
|
cancellations.delete(spec.executionId);
|
|
417
549
|
executionReservations.delete(spec.executionId);
|
|
418
550
|
knownExecutionHashes.delete(spec.executionId);
|
|
@@ -437,6 +569,7 @@ export function serve(config, opts = {}) {
|
|
|
437
569
|
}
|
|
438
570
|
const execution = executeProtocol(config, spec, {
|
|
439
571
|
...opts.execution?.dependencies,
|
|
572
|
+
projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkillsReconciler,
|
|
440
573
|
...(agentMemory === undefined ? {} : { agentMemory }),
|
|
441
574
|
journal: executionJournal,
|
|
442
575
|
facts: {
|
|
@@ -450,6 +583,7 @@ export function serve(config, opts = {}) {
|
|
|
450
583
|
slot: {
|
|
451
584
|
state: reservation.state,
|
|
452
585
|
ready: reservation.ready.then(() => {
|
|
586
|
+
markExecutionTaskKeyActive();
|
|
453
587
|
const snapshot = sharedSlots.snapshot();
|
|
454
588
|
dslog("execution.machine_slot_ready", "execution 获得机器执行名额", {
|
|
455
589
|
execution_id: spec.executionId,
|
|
@@ -506,6 +640,14 @@ export function serve(config, opts = {}) {
|
|
|
506
640
|
// ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
|
|
507
641
|
const r = msg;
|
|
508
642
|
setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
|
|
643
|
+
// 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
|
|
644
|
+
serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
|
|
645
|
+
? r.serverCapabilities.filter((c) => typeof c === "string")
|
|
646
|
+
: []);
|
|
647
|
+
if (serverCapabilities.has(PROJECT_SKILLS_CAPABILITY)) {
|
|
648
|
+
if (await ensureProjectSkillsInitialized())
|
|
649
|
+
await projectSkillsController.publishCurrent();
|
|
650
|
+
}
|
|
509
651
|
return;
|
|
510
652
|
}
|
|
511
653
|
if (msg.type === "error") {
|
|
@@ -517,6 +659,26 @@ export function serve(config, opts = {}) {
|
|
|
517
659
|
}
|
|
518
660
|
return;
|
|
519
661
|
}
|
|
662
|
+
if (msg.type === "project:add"
|
|
663
|
+
|| msg.type === "project:remove"
|
|
664
|
+
|| msg.type === "project:rescan"
|
|
665
|
+
|| msg.type === "agent:skills:sync") {
|
|
666
|
+
const request = msg;
|
|
667
|
+
if (typeof request.reqId !== "string")
|
|
668
|
+
return;
|
|
669
|
+
const result = serverCapabilities.has(PROJECT_SKILLS_CAPABILITY)
|
|
670
|
+
? await projectSkillsController.handle(msg)
|
|
671
|
+
: { ok: false, error: "capability_unavailable" };
|
|
672
|
+
if (result.ok && (msg.type === "project:remove"
|
|
673
|
+
|| msg.type === "project:rescan")) {
|
|
674
|
+
void ensureProjectSkillsInitialized();
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
ws?.send(JSON.stringify({ type: "fs:result", reqId: request.reqId, ...result }));
|
|
678
|
+
}
|
|
679
|
+
catch { /* server 会按请求超时处理 */ }
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
520
682
|
// 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
|
|
521
683
|
if (msg.type === "raft:inspect" || msg.type === "raft:import") {
|
|
522
684
|
const req = msg;
|
|
@@ -579,6 +741,9 @@ export function serve(config, opts = {}) {
|
|
|
579
741
|
: {}),
|
|
580
742
|
})
|
|
581
743
|
: null;
|
|
744
|
+
// 活取值:发报告的时刻按"当前连接"的 server 能力判定(滚动发布中 run 可能跨重连;
|
|
745
|
+
// 断线时集合被清空 → 回退为 daemon 自行发通知,方向安全)。
|
|
746
|
+
const serverOwnsFailureNotice = () => serverCapabilities.has("scheduled_failure_notice_v1");
|
|
582
747
|
const threadId = msg.wake?.threadId;
|
|
583
748
|
const taskKey = scheduled ? scheduled.runId : (threadId ?? msg.channelId);
|
|
584
749
|
const key = `${msg.agentHandle}:${taskKey}`;
|
|
@@ -588,6 +753,14 @@ export function serve(config, opts = {}) {
|
|
|
588
753
|
run_id: runId, agent_handle: msg.agentHandle, channel_id: msg.channelId,
|
|
589
754
|
thread_id: threadId ?? null, task_key: taskKey,
|
|
590
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
|
+
}
|
|
591
764
|
dslog("run.wake_received", `收到唤醒 ${msg.agentHandle}`, {
|
|
592
765
|
...runKeys, reason: msg.reason ?? "", sender: msg.wake?.senderHandle,
|
|
593
766
|
wake_origin: msg.wake?.origin ?? null,
|
|
@@ -744,6 +917,7 @@ export function serve(config, opts = {}) {
|
|
|
744
917
|
title: scheduled.title,
|
|
745
918
|
outputPolicy: scheduled.outputPolicy,
|
|
746
919
|
externalNotificationPolicy: scheduled.externalNotificationPolicy,
|
|
920
|
+
serverOwnsFailureNotice,
|
|
747
921
|
},
|
|
748
922
|
} : {}),
|
|
749
923
|
...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
|
|
@@ -852,6 +1026,7 @@ export function serve(config, opts = {}) {
|
|
|
852
1026
|
channelId: msg.channelId,
|
|
853
1027
|
scheduled,
|
|
854
1028
|
errorMessage: e.message,
|
|
1029
|
+
serverOwnsFailureNotice,
|
|
855
1030
|
});
|
|
856
1031
|
}
|
|
857
1032
|
catch { /* token 也不可用时只能让 server 按 runtime failure 终态化 */ }
|
|
@@ -889,6 +1064,8 @@ export function serve(config, opts = {}) {
|
|
|
889
1064
|
ws.on("close", (code) => {
|
|
890
1065
|
if (stopped)
|
|
891
1066
|
return;
|
|
1067
|
+
// server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
|
|
1068
|
+
serverCapabilities = new Set();
|
|
892
1069
|
completionRetransmitter.pause();
|
|
893
1070
|
// 4001 = 控制面应用级「鉴权失败」关闭码(见 server control-plane.ts)。即使上面的
|
|
894
1071
|
// error 帧因 close 抢先而丢失,也能据关闭码识别这是凭证失效——退避拉满,不再每秒热循环。
|
|
@@ -926,6 +1103,7 @@ export function serve(config, opts = {}) {
|
|
|
926
1103
|
flush: flushSlog,
|
|
927
1104
|
writeStderr: (line) => process.stderr.write(line),
|
|
928
1105
|
});
|
|
1106
|
+
void ensureProjectSkillsInitialized();
|
|
929
1107
|
connect();
|
|
930
1108
|
})();
|
|
931
1109
|
const stop = () => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function parseSkillFrontmatter(markdown) {
|
|
2
|
+
const match = markdown.match(/^---[\t ]*\r?\n([\s\S]*?)\r?\n---(?:[\t ]*\r?\n|$)/u);
|
|
3
|
+
if (!match)
|
|
4
|
+
return Object.freeze({});
|
|
5
|
+
let name;
|
|
6
|
+
let description;
|
|
7
|
+
for (const line of match[1]?.split(/\r?\n/u) ?? []) {
|
|
8
|
+
const field = line.match(/^(name|description)[\t ]*:[\t ]*(.*?)[\t ]*$/u);
|
|
9
|
+
if (!field)
|
|
10
|
+
continue;
|
|
11
|
+
const value = (field[2] ?? "")
|
|
12
|
+
.replace(/^(?:"([\s\S]*)"|'([\s\S]*)')$/u, "$1$2")
|
|
13
|
+
.trim();
|
|
14
|
+
if (field[1] === "name")
|
|
15
|
+
name = value;
|
|
16
|
+
if (field[1] === "description")
|
|
17
|
+
description = value;
|
|
18
|
+
}
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
...(name === undefined ? {} : { name }),
|
|
21
|
+
...(description === undefined ? {} : { description }),
|
|
22
|
+
});
|
|
23
|
+
}
|
package/dist/skills.js
CHANGED
|
@@ -9,25 +9,8 @@
|
|
|
9
9
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { parseSkillFrontmatter } from "./skill-frontmatter.js";
|
|
12
13
|
const globalSkillsDir = () => process.env.CREW_GLOBAL_SKILLS_DIR ?? join(homedir(), ".claude", "skills");
|
|
13
|
-
/** 从 SKILL.md 顶部 YAML frontmatter 取 name / description (简易解析,够用)。 */
|
|
14
|
-
function parseFrontmatter(md) {
|
|
15
|
-
const m = md.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
16
|
-
if (!m)
|
|
17
|
-
return {};
|
|
18
|
-
const out = {};
|
|
19
|
-
for (const line of m[1].split("\n")) {
|
|
20
|
-
const kv = line.match(/^(name|description)\s*:\s*(.+?)\s*$/);
|
|
21
|
-
if (kv) {
|
|
22
|
-
const val = kv[2].replace(/^["']|["']$/g, "");
|
|
23
|
-
if (kv[1] === "name")
|
|
24
|
-
out.name = val;
|
|
25
|
-
else
|
|
26
|
-
out.description = val;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return out;
|
|
30
|
-
}
|
|
31
14
|
async function readSkillsFrom(dir, scope) {
|
|
32
15
|
const names = await readdir(dir).catch(() => []);
|
|
33
16
|
const skills = [];
|
|
@@ -39,7 +22,7 @@ async function readSkillsFrom(dir, scope) {
|
|
|
39
22
|
if (!s || !s.isFile())
|
|
40
23
|
continue;
|
|
41
24
|
const md = await readFile(skillMd, "utf8").catch(() => "");
|
|
42
|
-
const fm =
|
|
25
|
+
const fm = parseSkillFrontmatter(md);
|
|
43
26
|
skills.push({ scope, name: fm.name || name, description: fm.description ?? "" });
|
|
44
27
|
}
|
|
45
28
|
return skills;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { startDormantSupervisor, } from "./execution-supervisor.js";
|
|
3
4
|
import { buildClaudeArgs } from "./runtimes/claude.js";
|
|
4
5
|
import { RuntimeCancelledError } from "./runtime-cancellation.js";
|
|
@@ -19,6 +20,9 @@ export function supervisorLaunch(request) {
|
|
|
19
20
|
cwd: request.cwd,
|
|
20
21
|
env: request.env,
|
|
21
22
|
systemPromptPath: request.systemPromptPath,
|
|
23
|
+
...(request.agentRoot === undefined ? {} : {
|
|
24
|
+
projectSkillsDirectory: join(request.agentRoot, ".crew", "claude-skills"),
|
|
25
|
+
}),
|
|
22
26
|
...(request.sessionId === undefined ? {} : {
|
|
23
27
|
sessionId: request.sessionId,
|
|
24
28
|
resume: request.resume,
|
|
@@ -45,6 +49,9 @@ export function supervisorLaunch(request) {
|
|
|
45
49
|
...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
|
|
46
50
|
...(request.sessionId === undefined ? {} : { sessionId: request.sessionId }),
|
|
47
51
|
...(request.imagePaths === undefined ? {} : { imagePaths: request.imagePaths }),
|
|
52
|
+
...(request.agentRoot === undefined ? {} : {
|
|
53
|
+
projectRootMarkers: [".git", ".nowwork-root"],
|
|
54
|
+
}),
|
|
48
55
|
resume: request.resume,
|
|
49
56
|
}),
|
|
50
57
|
};
|