@llblab/pi-actors 0.22.5 → 0.24.0
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/AGENTS.md +125 -59
- package/BACKLOG.md +88 -403
- package/CHANGELOG.md +39 -0
- package/README.md +14 -1
- package/dist/fixtures/protocol/actor-message-branch.json +13 -0
- package/dist/fixtures/protocol/artifact-manifest.json +9 -0
- package/dist/fixtures/protocol/mailbox-contract.json +15 -0
- package/dist/fixtures/protocol/recipe-summary.json +16 -0
- package/dist/fixtures/protocol/room-message.json +11 -0
- package/dist/fixtures/protocol/room-roster.json +11 -0
- package/dist/fixtures/protocol/run-inbox-message.json +9 -0
- package/dist/fixtures/protocol/run-outbox-event.json +9 -0
- package/dist/fixtures/protocol/run-state.json +10 -0
- package/dist/index.js +18 -2
- package/dist/lib/actor-inspector-tui.d.ts +4 -0
- package/dist/lib/actor-inspector-tui.js +58 -44
- package/dist/lib/actor-rooms.d.ts +16 -0
- package/dist/lib/actor-rooms.js +123 -28
- package/dist/lib/actor-worker.d.ts +13 -0
- package/dist/lib/actor-worker.js +86 -0
- package/dist/lib/async-runner.d.ts +5 -0
- package/dist/lib/async-runner.js +134 -0
- package/dist/lib/async-runs.d.ts +34 -2
- package/dist/lib/async-runs.js +224 -50
- package/dist/lib/build-dist.d.ts +5 -0
- package/dist/lib/build-dist.js +24 -0
- package/dist/lib/command-templates.js +5 -5
- package/dist/lib/conformance.d.ts +12 -0
- package/dist/lib/conformance.js +28 -0
- package/dist/lib/coordinator.d.ts +5 -0
- package/dist/lib/coordinator.js +557 -0
- package/dist/lib/limits.d.ts +11 -0
- package/dist/lib/limits.js +11 -0
- package/dist/lib/locker.d.ts +5 -0
- package/dist/lib/locker.js +310 -0
- package/dist/lib/mailbox-loop.d.ts +41 -0
- package/dist/lib/mailbox-loop.js +62 -0
- package/dist/lib/observability.d.ts +2 -2
- package/dist/lib/observability.js +57 -57
- package/dist/lib/output.js +4 -5
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-discovery.js +1 -0
- package/dist/lib/recipe-references.d.ts +11 -2
- package/dist/lib/recipe-references.js +29 -4
- package/dist/lib/recipe-usage.d.ts +2 -1
- package/dist/lib/recipe-usage.js +15 -3
- package/dist/lib/recipe-utils.d.ts +5 -0
- package/dist/lib/recipe-utils.js +385 -0
- package/dist/lib/runtime-notifier.js +3 -7
- package/dist/lib/runtime.js +12 -2
- package/dist/lib/state-readers.d.ts +21 -0
- package/dist/lib/state-readers.js +74 -0
- package/dist/lib/tools.js +198 -39
- package/dist/lib/validate-recipe.d.ts +6 -0
- package/dist/lib/validate-recipe.js +104 -0
- package/dist/recipes/actor-worker.json +35 -0
- package/dist/recipes/coordinator-locker.json +45 -0
- package/dist/recipes/lens-swarm.json +66 -0
- package/dist/recipes/locker.json +45 -0
- package/dist/recipes/music-player.json +38 -0
- package/dist/recipes/pipeline-architect-coordinator.json +95 -0
- package/dist/recipes/pipeline-artifact-bundle.json +100 -0
- package/dist/recipes/pipeline-artifact-report.json +58 -0
- package/dist/recipes/pipeline-artifact-write.json +72 -0
- package/dist/recipes/pipeline-async-run-ops.json +70 -0
- package/dist/recipes/pipeline-checkpoint-continuation.json +67 -0
- package/dist/recipes/pipeline-development-tasking.json +81 -0
- package/dist/recipes/pipeline-docs-maintenance.json +80 -0
- package/dist/recipes/pipeline-media-library.json +59 -0
- package/dist/recipes/pipeline-quorum-review.json +79 -0
- package/dist/recipes/pipeline-release-readiness.json +110 -0
- package/dist/recipes/pipeline-release-summary.json +88 -0
- package/dist/recipes/pipeline-repo-health.json +89 -0
- package/dist/recipes/pipeline-research-synthesis.json +94 -0
- package/dist/recipes/pipeline-review-readiness.json +54 -0
- package/dist/recipes/pipeline-room-swarm.json +50 -0
- package/dist/recipes/subagent-artifact.json +32 -0
- package/dist/recipes/subagent-checkpoint.json +33 -0
- package/dist/recipes/subagent-conflict-report.json +32 -0
- package/dist/recipes/subagent-contradiction-map.json +33 -0
- package/dist/recipes/subagent-critic.json +35 -0
- package/dist/recipes/subagent-evidence-map.json +33 -0
- package/dist/recipes/subagent-followup.json +33 -0
- package/dist/recipes/subagent-judge.json +33 -0
- package/dist/recipes/subagent-merge.json +33 -0
- package/dist/recipes/subagent-message.json +34 -0
- package/dist/recipes/subagent-normalize.json +31 -0
- package/dist/recipes/subagent-plan.json +33 -0
- package/dist/recipes/subagent-prompt.json +28 -0
- package/dist/recipes/subagent-quorum.json +43 -0
- package/dist/recipes/subagent-review-coordinator.json +114 -0
- package/dist/recipes/subagent-review.json +37 -0
- package/dist/recipes/subagent-task-card.json +35 -0
- package/dist/recipes/subagent-tools.json +27 -0
- package/dist/recipes/subagent-verify.json +34 -0
- package/dist/recipes/subagents-prompts.json +51 -0
- package/dist/recipes/utility-actor-message.json +23 -0
- package/dist/recipes/utility-artifact-manifest.json +16 -0
- package/dist/recipes/utility-artifact-write.json +16 -0
- package/dist/recipes/utility-changelog-head.json +11 -0
- package/dist/recipes/utility-changelog-section.json +13 -0
- package/dist/recipes/utility-coordinator-lock-snapshot.json +13 -0
- package/dist/recipes/utility-git-log.json +11 -0
- package/dist/recipes/utility-git-status.json +9 -0
- package/dist/recipes/utility-jsonl-tail.json +10 -0
- package/dist/recipes/utility-markdown-index.json +14 -0
- package/dist/recipes/utility-package-summary.json +11 -0
- package/dist/recipes/utility-playlist-build.json +17 -0
- package/dist/recipes/utility-playlist-scan.json +11 -0
- package/dist/recipes/utility-run-ops-snapshot.json +17 -0
- package/dist/recipes/utility-run-state-files.json +13 -0
- package/dist/recipes/utility-run-summary.json +11 -0
- package/dist/recipes/utility-skill-summary.json +13 -0
- package/dist/recipes/utility-validate-recipe.json +13 -0
- package/dist/recipes/utility-validation-wrapper.json +13 -0
- package/dist/scripts/actor-worker.mjs +31 -0
- package/dist/scripts/async-runner.mjs +31 -0
- package/dist/scripts/build-dist.mjs +25 -0
- package/dist/scripts/conformance.mjs +33 -0
- package/dist/scripts/coordinator.mjs +31 -0
- package/dist/scripts/locker.mjs +33 -0
- package/dist/scripts/music-player.mjs +964 -0
- package/dist/scripts/recipe-utils.mjs +31 -0
- package/dist/scripts/validate-recipe.mjs +34 -0
- package/dist/skills/actors/SKILL.md +375 -0
- package/dist/skills/swarm/SKILL.md +467 -0
- package/dist/skills/swarm/references/development-swarm.md +596 -0
- package/docs/actor-messages.md +2 -2
- package/docs/async-runs.md +13 -1
- package/docs/template-recipes.md +2 -2
- package/docs/tool-registry.md +0 -1
- package/fixtures/protocol/actor-message-branch.json +13 -0
- package/fixtures/protocol/artifact-manifest.json +9 -0
- package/fixtures/protocol/mailbox-contract.json +15 -0
- package/fixtures/protocol/recipe-summary.json +16 -0
- package/fixtures/protocol/room-message.json +11 -0
- package/fixtures/protocol/room-roster.json +11 -0
- package/fixtures/protocol/run-inbox-message.json +9 -0
- package/fixtures/protocol/run-outbox-event.json +9 -0
- package/fixtures/protocol/run-state.json +10 -0
- package/index.ts +21 -0
- package/lib/actor-inspector-tui.ts +138 -59
- package/lib/actor-rooms.ts +241 -60
- package/lib/actor-worker.ts +118 -0
- package/lib/async-runner.ts +173 -0
- package/lib/async-runs.ts +302 -53
- package/lib/build-dist.ts +30 -0
- package/lib/command-templates.ts +5 -5
- package/lib/conformance.ts +46 -0
- package/lib/coordinator.ts +649 -0
- package/lib/limits.ts +12 -0
- package/lib/locker.ts +340 -0
- package/lib/mailbox-loop.ts +148 -0
- package/lib/observability.ts +34 -23
- package/lib/output.ts +4 -6
- package/lib/prompts.ts +1 -1
- package/lib/recipe-discovery.ts +1 -0
- package/lib/recipe-references.ts +57 -6
- package/lib/recipe-usage.ts +31 -4
- package/lib/recipe-utils.ts +486 -0
- package/lib/runtime-notifier.ts +4 -6
- package/lib/runtime.ts +31 -7
- package/lib/state-readers.ts +93 -0
- package/lib/tools.ts +297 -58
- package/lib/validate-recipe.ts +110 -0
- package/package.json +11 -2
- package/recipes/actor-worker.json +35 -0
- package/recipes/pipeline-quorum-review.json +12 -7
- package/scripts/actor-worker.mjs +31 -0
- package/scripts/async-runner.mjs +11 -195
- package/scripts/build-dist.mjs +25 -0
- package/scripts/conformance.mjs +33 -0
- package/scripts/coordinator.mjs +19 -616
- package/scripts/locker.mjs +23 -322
- package/scripts/music-player.mjs +21 -2
- package/scripts/recipe-utils.mjs +20 -467
- package/scripts/validate-recipe.mjs +23 -113
- package/skills/actors/SKILL.md +7 -4
- package/skills/swarm/SKILL.md +3 -1
package/scripts/locker.mjs
CHANGED
|
@@ -1,332 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import {
|
|
5
|
-
appendFileSync,
|
|
6
|
-
existsSync,
|
|
7
|
-
mkdirSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
rmSync,
|
|
10
|
-
writeFileSync,
|
|
11
|
-
} from "node:fs";
|
|
12
|
-
import { createServer } from "node:net";
|
|
13
|
-
import { dirname, join, resolve } from "node:path";
|
|
14
|
-
import readline from "node:readline";
|
|
15
2
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
else if (arg === "--lease-ms")
|
|
23
|
-
args.leaseMs = Number(argv[++index] ?? args.leaseMs);
|
|
24
|
-
else if (arg === "--lines")
|
|
25
|
-
args.lines = Number(argv[++index] ?? args.lines);
|
|
26
|
-
}
|
|
27
|
-
if (!args.stateDir) throw new Error("--state-dir is required");
|
|
28
|
-
if (!Number.isFinite(args.leaseMs) || args.leaseMs <= 0)
|
|
29
|
-
args.leaseMs = 600000;
|
|
30
|
-
if (!Number.isFinite(args.lines) || args.lines <= 0) args.lines = 20;
|
|
31
|
-
return args;
|
|
32
|
-
}
|
|
3
|
+
/**
|
|
4
|
+
* Local coordination locker service shim.
|
|
5
|
+
*
|
|
6
|
+
* Runtime logic lives in lib/locker.ts and is compiled to dist/lib/locker.js
|
|
7
|
+
* for installed JS-only packages.
|
|
8
|
+
*/
|
|
33
9
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const journalPath = join(stateDir, "journal.jsonl");
|
|
38
|
-
const outboxPath = join(stateDir, "outbox.jsonl");
|
|
39
|
-
const controlPath = join(stateDir, "control.fifo");
|
|
40
|
-
mkdirSync(stateDir, { recursive: true });
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, join } from "node:path";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
41
13
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} catch {
|
|
47
|
-
return fallback;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function writeJson(path, value) {
|
|
51
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
52
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
53
|
-
}
|
|
54
|
-
function getControlEndpoint() {
|
|
55
|
-
if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
|
|
56
|
-
const hash = createHash("sha256")
|
|
57
|
-
.update(resolve(stateDir))
|
|
58
|
-
.digest("hex")
|
|
59
|
-
.slice(0, 20);
|
|
60
|
-
return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
|
|
61
|
-
}
|
|
62
|
-
function writeControlEndpoint(endpoint) {
|
|
63
|
-
writeJson(join(stateDir, "control.json"), endpoint);
|
|
64
|
-
const runPath = join(stateDir, "run.json");
|
|
65
|
-
const run = readJson(runPath, undefined);
|
|
66
|
-
if (run && typeof run === "object")
|
|
67
|
-
writeJson(runPath, { ...run, control: endpoint });
|
|
68
|
-
}
|
|
69
|
-
function journal(event, data = {}) {
|
|
70
|
-
appendFileSync(
|
|
71
|
-
journalPath,
|
|
72
|
-
`${JSON.stringify({ event, ts: new Date().toISOString(), ...data })}\n`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
function outbox(type, summary, body = {}, level = "info") {
|
|
76
|
-
appendFileSync(
|
|
77
|
-
outboxPath,
|
|
78
|
-
`${JSON.stringify({ to: "coordinator", from: `run:${process.env.run_id ?? "locker"}`, type, event: type, summary, body, data: body, delivery: "followup", level, ts: new Date().toISOString() })}\n`,
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
function now() {
|
|
82
|
-
return Date.now();
|
|
83
|
-
}
|
|
84
|
-
function cleanExpiredLocks(locks) {
|
|
85
|
-
const current = now();
|
|
86
|
-
const kept = {};
|
|
87
|
-
for (const [key, lock] of Object.entries(locks)) {
|
|
88
|
-
if (Number(lock.expiresAt) > current) kept[key] = lock;
|
|
89
|
-
else journal("lock.expired", { resource: key, owner: lock.owner });
|
|
90
|
-
}
|
|
91
|
-
return kept;
|
|
92
|
-
}
|
|
93
|
-
function normalizeMessage(line) {
|
|
94
|
-
const trimmed = line.trim();
|
|
95
|
-
if (!trimmed) return undefined;
|
|
96
|
-
if (["stop", "quit", "exit", "cancel"].includes(trimmed.toLowerCase())) {
|
|
97
|
-
return { type: "control.stop", body: {} };
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
return JSON.parse(trimmed);
|
|
101
|
-
} catch {
|
|
102
|
-
return { type: "lock.enqueue", body: { task: trimmed } };
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function tailJournal(count) {
|
|
106
|
-
if (!existsSync(journalPath)) return [];
|
|
107
|
-
return readFileSync(journalPath, "utf8")
|
|
108
|
-
.trimEnd()
|
|
109
|
-
.split("\n")
|
|
110
|
-
.filter(Boolean)
|
|
111
|
-
.slice(-count)
|
|
112
|
-
.map((line) => {
|
|
113
|
-
try {
|
|
114
|
-
return JSON.parse(line);
|
|
115
|
-
} catch {
|
|
116
|
-
return { raw: line };
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
function printSnapshot() {
|
|
121
|
-
const locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
122
|
-
writeJson(locksPath, locks);
|
|
123
|
-
const queue = readJson(queuePath, { items: [] });
|
|
124
|
-
console.log(
|
|
125
|
-
JSON.stringify(
|
|
126
|
-
{
|
|
127
|
-
queueDepth: Array.isArray(queue.items) ? queue.items.length : 0,
|
|
128
|
-
queue,
|
|
129
|
-
locks,
|
|
130
|
-
journal: tailJournal(lines),
|
|
131
|
-
},
|
|
132
|
-
null,
|
|
133
|
-
2,
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
function nextTask(queue, locks) {
|
|
138
|
-
const items = Array.isArray(queue.items) ? queue.items : [];
|
|
139
|
-
const index = items.findIndex((item) => {
|
|
140
|
-
const resources = Array.isArray(item.resources) ? item.resources : [];
|
|
141
|
-
return resources.every((resource) => !locks[resource]);
|
|
142
|
-
});
|
|
143
|
-
if (index < 0) return undefined;
|
|
144
|
-
return items.splice(index, 1)[0];
|
|
145
|
-
}
|
|
146
|
-
function handle(message) {
|
|
147
|
-
const type = message.type || message.event || "lock.enqueue";
|
|
148
|
-
const body =
|
|
149
|
-
message.body && typeof message.body === "object" ? message.body : message;
|
|
150
|
-
let queue = readJson(queuePath, { items: [] });
|
|
151
|
-
let locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
152
|
-
if (type === "control.stop" || type === "control.cancel") {
|
|
153
|
-
writeJson(locksPath, locks);
|
|
154
|
-
journal("control.stop", {});
|
|
155
|
-
outbox("lock.stopped", "Locker stopped", {
|
|
156
|
-
queueDepth: queue.items?.length ?? 0,
|
|
157
|
-
});
|
|
158
|
-
process.exit(0);
|
|
159
|
-
}
|
|
160
|
-
if (type === "lock.enqueue" || type === "coord.enqueue") {
|
|
161
|
-
const item = {
|
|
162
|
-
id: body.id || `task-${Date.now()}`,
|
|
163
|
-
task: body.task ?? body,
|
|
164
|
-
resources: body.resources ?? [],
|
|
165
|
-
enqueuedAt: new Date().toISOString(),
|
|
166
|
-
};
|
|
167
|
-
queue.items = [...(queue.items ?? []), item];
|
|
168
|
-
writeJson(queuePath, queue);
|
|
169
|
-
writeJson(locksPath, locks);
|
|
170
|
-
journal("lock.enqueued", { id: item.id, resources: item.resources });
|
|
171
|
-
outbox("lock.enqueued", `Queued task ${item.id}`, {
|
|
172
|
-
id: item.id,
|
|
173
|
-
queueDepth: queue.items.length,
|
|
174
|
-
});
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
if (type === "lock.claim" || type === "coord.claim") {
|
|
178
|
-
const owner = body.owner || message.from || "worker";
|
|
179
|
-
const item = nextTask(queue, locks);
|
|
180
|
-
if (!item) {
|
|
181
|
-
writeJson(queuePath, queue);
|
|
182
|
-
writeJson(locksPath, locks);
|
|
183
|
-
outbox("lock.empty", "No claimable task", {
|
|
184
|
-
owner,
|
|
185
|
-
queueDepth: queue.items?.length ?? 0,
|
|
186
|
-
});
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
for (const resource of item.resources ?? [])
|
|
190
|
-
locks[resource] = { owner, task: item.id, expiresAt: now() + leaseMs };
|
|
191
|
-
writeJson(queuePath, queue);
|
|
192
|
-
writeJson(locksPath, locks);
|
|
193
|
-
journal("lock.assigned", {
|
|
194
|
-
id: item.id,
|
|
195
|
-
owner,
|
|
196
|
-
resources: item.resources,
|
|
197
|
-
});
|
|
198
|
-
outbox("lock.assigned", `Assigned task ${item.id}`, { owner, task: item });
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
if (type === "lock.acquire") {
|
|
202
|
-
const resource = body.resource;
|
|
203
|
-
const owner = body.owner || message.from || "worker";
|
|
204
|
-
if (!resource) throw new Error("lock.acquire body.resource is required");
|
|
205
|
-
if (locks[resource])
|
|
206
|
-
outbox(
|
|
207
|
-
"lock.denied",
|
|
208
|
-
`Lock denied ${resource}`,
|
|
209
|
-
{ resource, owner, current: locks[resource] },
|
|
210
|
-
"warning",
|
|
211
|
-
);
|
|
212
|
-
else {
|
|
213
|
-
locks[resource] = { owner, expiresAt: now() + leaseMs };
|
|
214
|
-
outbox("lock.granted", `Lock granted ${resource}`, { resource, owner });
|
|
215
|
-
}
|
|
216
|
-
writeJson(locksPath, locks);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (type === "lock.renew") {
|
|
220
|
-
const resource = body.resource;
|
|
221
|
-
const owner = body.owner || message.from || "worker";
|
|
222
|
-
if (!resource) throw new Error("lock.renew body.resource is required");
|
|
223
|
-
const current = locks[resource];
|
|
224
|
-
if (!current) {
|
|
225
|
-
outbox(
|
|
226
|
-
"lock.denied",
|
|
227
|
-
`Lock renew denied ${resource}`,
|
|
228
|
-
{ resource, owner, reason: "missing" },
|
|
229
|
-
"warning",
|
|
230
|
-
);
|
|
231
|
-
} else if (current.owner !== owner) {
|
|
232
|
-
outbox(
|
|
233
|
-
"lock.denied",
|
|
234
|
-
`Lock renew denied ${resource}`,
|
|
235
|
-
{ resource, owner, current },
|
|
236
|
-
"warning",
|
|
237
|
-
);
|
|
238
|
-
} else {
|
|
239
|
-
locks[resource] = { ...current, expiresAt: now() + leaseMs };
|
|
240
|
-
outbox("lock.renewed", `Lock renewed ${resource}`, { resource, owner });
|
|
241
|
-
}
|
|
242
|
-
writeJson(locksPath, locks);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (type === "lock.release") {
|
|
246
|
-
const resource = body.resource;
|
|
247
|
-
if (resource) delete locks[resource];
|
|
248
|
-
writeJson(locksPath, locks);
|
|
249
|
-
outbox("lock.released", `Lock released ${resource}`, { resource });
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (
|
|
253
|
-
type === "lock.complete" ||
|
|
254
|
-
type === "lock.fail" ||
|
|
255
|
-
type === "coord.complete" ||
|
|
256
|
-
type === "coord.fail"
|
|
257
|
-
) {
|
|
258
|
-
const eventType = type.startsWith("coord.")
|
|
259
|
-
? type.replace("coord.", "lock.")
|
|
260
|
-
: type;
|
|
261
|
-
journal(eventType, body);
|
|
262
|
-
outbox(
|
|
263
|
-
eventType,
|
|
264
|
-
`${eventType} ${body.id ?? ""}`.trim(),
|
|
265
|
-
body,
|
|
266
|
-
eventType === "lock.fail" ? "error" : "info",
|
|
267
|
-
);
|
|
268
|
-
writeJson(locksPath, locks);
|
|
269
|
-
writeJson(queuePath, queue);
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
journal("lock.unknown", { type, body });
|
|
273
|
-
outbox("lock.unknown", `Unknown message ${type}`, { type, body }, "warning");
|
|
14
|
+
function packageRoot() {
|
|
15
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const parent = dirname(scriptDir);
|
|
17
|
+
return basename(parent) === "dist" ? dirname(parent) : parent;
|
|
274
18
|
}
|
|
275
19
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
20
|
+
function mainModulePath() {
|
|
21
|
+
const root = packageRoot();
|
|
22
|
+
const compiled = join(root, "dist", "lib", "locker.js");
|
|
23
|
+
return existsSync(compiled) ? compiled : join(root, "lib", "locker.ts");
|
|
279
24
|
}
|
|
280
25
|
|
|
281
|
-
|
|
282
|
-
const message = normalizeMessage(line);
|
|
283
|
-
if (!message) return;
|
|
284
|
-
try {
|
|
285
|
-
handle(message);
|
|
286
|
-
} catch (error) {
|
|
287
|
-
const text = error instanceof Error ? error.message : String(error);
|
|
288
|
-
journal("lock.error", { error: text });
|
|
289
|
-
outbox("lock.error", text, { error: text }, "error");
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function serveFifo(endpoint) {
|
|
294
|
-
if (!existsSync(endpoint.path)) {
|
|
295
|
-
const result = spawnSync("mkfifo", [endpoint.path]);
|
|
296
|
-
if (result.status !== 0)
|
|
297
|
-
throw new Error(`mkfifo failed: ${result.stderr?.toString?.() ?? ""}`);
|
|
298
|
-
}
|
|
299
|
-
writeControlEndpoint(endpoint);
|
|
300
|
-
while (true) {
|
|
301
|
-
const stream = await import("node:fs").then((fs) =>
|
|
302
|
-
fs.createReadStream(endpoint.path, { encoding: "utf8" }),
|
|
303
|
-
);
|
|
304
|
-
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
305
|
-
for await (const line of rl) handleLine(line);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
26
|
+
const { runLocker } = await import(pathToFileURL(mainModulePath()).href);
|
|
308
27
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
});
|
|
315
|
-
await new Promise((resolveReady, rejectReady) => {
|
|
316
|
-
server.once("error", rejectReady);
|
|
317
|
-
server.listen(endpoint.path, () => {
|
|
318
|
-
server.off("error", rejectReady);
|
|
319
|
-
writeControlEndpoint(endpoint);
|
|
320
|
-
resolveReady();
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
await new Promise(() => {});
|
|
28
|
+
try {
|
|
29
|
+
await runLocker(process.argv.slice(2));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
32
|
+
process.exit(1);
|
|
324
33
|
}
|
|
325
|
-
|
|
326
|
-
const endpoint = getControlEndpoint();
|
|
327
|
-
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
328
|
-
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
|
329
|
-
journal("lock.started", { leaseMs, control: endpoint.type });
|
|
330
|
-
outbox("lock.started", "Locker ready", { leaseMs, control: endpoint.type });
|
|
331
|
-
if (endpoint.type === "named-pipe") await serveNamedPipe(endpoint);
|
|
332
|
-
else await serveFifo(endpoint);
|
package/scripts/music-player.mjs
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Packaged local music-player actor helper.
|
|
5
|
+
*
|
|
6
|
+
* This script backs the standard music-player recipe. It scans local music
|
|
7
|
+
* sources, builds playback queues, launches an available backend player, and
|
|
8
|
+
* consumes run-mailbox control messages such as play, pause, next, previous,
|
|
9
|
+
* stop, and status.
|
|
10
|
+
*
|
|
11
|
+
* Keep the helper focused on one maintained player actor implementation; recipe
|
|
12
|
+
* metadata and invocation arguments choose source paths and backend behavior.
|
|
13
|
+
*/
|
|
14
|
+
|
|
2
15
|
import { spawn } from "node:child_process";
|
|
3
16
|
import { randomUUID } from "node:crypto";
|
|
4
17
|
import {
|
|
@@ -156,7 +169,9 @@ function windowsMediaPlayerExecutable() {
|
|
|
156
169
|
const roots = [
|
|
157
170
|
process.env.ProgramFiles,
|
|
158
171
|
process.env["ProgramFiles(x86)"],
|
|
159
|
-
process.env.SystemDrive
|
|
172
|
+
process.env.SystemDrive
|
|
173
|
+
? join(process.env.SystemDrive, "Program Files")
|
|
174
|
+
: undefined,
|
|
160
175
|
process.env.SystemDrive
|
|
161
176
|
? join(process.env.SystemDrive, "Program Files (x86)")
|
|
162
177
|
: undefined,
|
|
@@ -728,7 +743,11 @@ function startControlLoop(ctx) {
|
|
|
728
743
|
try {
|
|
729
744
|
watcher = watch(ctx.stateDir, { persistent: false }, (_eventType, file) => {
|
|
730
745
|
const name = file ? String(file) : "";
|
|
731
|
-
if (
|
|
746
|
+
if (
|
|
747
|
+
!name ||
|
|
748
|
+
name === basename(ctx.inboxFile) ||
|
|
749
|
+
name === basename(runtimeWakeFile(ctx))
|
|
750
|
+
) {
|
|
732
751
|
dirty = true;
|
|
733
752
|
}
|
|
734
753
|
});
|