muse-crew 0.6.8 → 0.7.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/lib/crew-api.js +6 -0
- package/lib/test-version-write.sh +18 -1
- package/package.json +1 -1
- package/seed/cron-body-template.md +4 -5
- package/workflows/crew-init.js +60 -11
package/lib/crew-api.js
CHANGED
|
@@ -529,6 +529,12 @@ commands["claim-task"] = (db, args) => {
|
|
|
529
529
|
const task = requireTask(db, args.task_id);
|
|
530
530
|
const project = requireProject(db, task.project);
|
|
531
531
|
if (project.quiesced) throw conflict("This project is paused. Resume it before claiming tasks.");
|
|
532
|
+
// Mechanical guard: never claim a task that is already in a terminal state.
|
|
533
|
+
// The dispatcher may recommend a task based on stale state (e.g. a task that
|
|
534
|
+
// was marked done between the dispatcher's read and the workflow's claim).
|
|
535
|
+
// The claim must fail closed here, not launch a duplicate run.
|
|
536
|
+
if (task.state === "done") throw conflict("Task is already done and cannot be claimed.");
|
|
537
|
+
if (task.state === "cancelled") throw conflict("Task is cancelled and cannot be claimed.");
|
|
532
538
|
|
|
533
539
|
const row = {
|
|
534
540
|
id: uuid(), task_id: args.task_id, identity,
|
|
@@ -30,12 +30,29 @@ TMPBASE="$(mktemp -d)"
|
|
|
30
30
|
trap 'rm -rf "$TMPBASE"' EXIT
|
|
31
31
|
|
|
32
32
|
# --- Fixture: temp git repo seeded with the repo's current package.json ---
|
|
33
|
+
# The test injects a \u2014 escape into the description field so the fixture
|
|
34
|
+
# carries the escape this regression is about, regardless of whether the
|
|
35
|
+
# repo's own package.json still has one. We write the JSON manually to ensure
|
|
36
|
+
# the escape appears as literal backslash-u bytes, not the decoded character.
|
|
33
37
|
FIX="$TMPBASE/fixture"
|
|
34
38
|
mkdir -p "$FIX"
|
|
35
39
|
git init -q -b main "$FIX" >/dev/null
|
|
36
40
|
git -C "$FIX" config user.email "test@example.com"
|
|
37
41
|
git -C "$FIX" config user.name "test"
|
|
38
|
-
|
|
42
|
+
node -e '
|
|
43
|
+
const fs = require("fs");
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
|
|
45
|
+
pkg.version = pkg.version; // keep version from repo
|
|
46
|
+
const lines = JSON.stringify(pkg, null, 2).split("\n");
|
|
47
|
+
// Replace the description line with one carrying a literal \u2014 escape
|
|
48
|
+
const out = lines.map(function (l) {
|
|
49
|
+
if (l.match(/"description":/)) {
|
|
50
|
+
return " \"description\": \"Test description with em-dash escape \\u2014 here\",";
|
|
51
|
+
}
|
|
52
|
+
return l;
|
|
53
|
+
});
|
|
54
|
+
fs.writeFileSync(process.argv[2], out.join("\n") + "\n");
|
|
55
|
+
' "$REPO_DIR/package.json" "$FIX/package.json"
|
|
39
56
|
git -C "$FIX" add package.json
|
|
40
57
|
git -C "$FIX" commit -qm "initial"
|
|
41
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Muse Crew Dispatcher
|
|
2
2
|
|
|
3
|
-
You are the dispatch trigger for Muse Crew. Run the authoritative dispatcher workflow and
|
|
3
|
+
You are the dispatch trigger for Muse Crew. Run the authoritative dispatcher workflow and launch its claims autonomously.
|
|
4
4
|
|
|
5
5
|
### Steps
|
|
6
6
|
|
|
@@ -12,11 +12,10 @@ You are the dispatch trigger for Muse Crew. Run the authoritative dispatcher wor
|
|
|
12
12
|
|
|
13
13
|
Wait for it to complete. It reads the crew's task state, determines eligibility, claims tasks, acknowledges the poll, and returns structured results.
|
|
14
14
|
|
|
15
|
-
4. **
|
|
16
|
-
|
|
15
|
+
4. **Launch claims autonomously:** Extract the `claims` array from the dispatcher result. For each claim (up to 3 per tick — if more than 3, launch the first 3 and log the rest as deferred):
|
|
16
|
+
- Call workflow_launch_async with scriptPath={claim.scriptPath} and args={claim.args}.
|
|
17
|
+
- The launched workflow self-claims the task as its first action. If the task was already claimed or is done, the claim fails closed and the run stands down quietly — this is the mechanical duplicate protection, not an error.
|
|
17
18
|
|
|
18
19
|
If the dispatcher returned no claims or the claims array is empty, report: NO_DISPATCH
|
|
19
20
|
|
|
20
|
-
Do NOT call workflow_launch_async for any task workflow. The main chat agent does that.
|
|
21
|
-
|
|
22
21
|
5. Exit silently.
|
package/workflows/crew-init.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: "crew-init",
|
|
3
|
-
description: "Initialize Muse Crew: bootstrap release, scaffold orchestration, set up cron jobs. Idempotent — safe to re-run.",
|
|
3
|
+
description: "Initialize Muse Crew: bootstrap release, scaffold orchestration, register dashboard project, set up cron jobs. Idempotent — safe to re-run.",
|
|
4
4
|
phases: [
|
|
5
5
|
{ name: "release", title: "Bootstrap release system" },
|
|
6
6
|
{ name: "scaffold", title: "Scaffold orchestration directory" },
|
|
7
|
+
{ name: "project", title: "Register dashboard project" },
|
|
7
8
|
{ name: "crons", title: "Create and converge cron jobs" }
|
|
8
9
|
]
|
|
9
10
|
};
|
|
@@ -52,14 +53,17 @@ function gate0Decide(facts) {
|
|
|
52
53
|
var gateFacts;
|
|
53
54
|
try {
|
|
54
55
|
gateFacts = await agent(
|
|
55
|
-
"
|
|
56
|
+
"Ensure the requested crew home exists as a git repository. Report raw facts.\\n\\n" +
|
|
56
57
|
"Requested crewHome: " + crewHome + "\\n\\n" +
|
|
57
58
|
"Steps (run in shell):\\n" +
|
|
58
59
|
"1. home=$(echo $HOME)\\n" +
|
|
59
60
|
"2. Expand a leading ~/ in the requested crewHome against $HOME (leave other paths untouched).\\n" +
|
|
60
|
-
"3.
|
|
61
|
-
"4.
|
|
62
|
-
"
|
|
61
|
+
"3. If the expanded path does not exist: mkdir -p <expanded crewHome>\\n" +
|
|
62
|
+
"4. Run: git -C <expanded crewHome> rev-parse --git-dir; echo EXIT:$?\\n" +
|
|
63
|
+
"5. If EXIT is not 0: run git -C <expanded crewHome> init -b main (or git init if -b not supported)\\n" +
|
|
64
|
+
"6. Re-run: git -C <expanded crewHome> rev-parse --git-dir; echo EXIT:$?\\n" +
|
|
65
|
+
"7. Return JSON { home, crewHomeExpanded, gitExitCode } where gitExitCode is the FINAL exit code after init.\\n\\n" +
|
|
66
|
+
"This ensures the crew home is a valid git repository. Do not judge — just ensure and report.",
|
|
63
67
|
{
|
|
64
68
|
key: "gate-0",
|
|
65
69
|
label: "Validate crew home",
|
|
@@ -91,8 +95,8 @@ if (!gateResult.repoValid) {
|
|
|
91
95
|
return {
|
|
92
96
|
__hatchWorkflowControl: "blocked",
|
|
93
97
|
result: {
|
|
94
|
-
blocked_reason: "crewHome
|
|
95
|
-
message: "crewHome (" + gateFacts.crewHomeExpanded + ")
|
|
98
|
+
blocked_reason: "crewHome git init failed",
|
|
99
|
+
message: "crewHome (" + gateFacts.crewHomeExpanded + ") could not be initialized as a git repository (git exit code " + gateFacts.gitExitCode + "). Check permissions and try again."
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
102
|
}
|
|
@@ -109,11 +113,11 @@ try {
|
|
|
109
113
|
"Release script (in repo): " + crewRepoPath + "/lib/crew-release.sh\n\n" +
|
|
110
114
|
"Steps:\n" +
|
|
111
115
|
"1. Run: test -f " + crewHome + "/crew-release.sh && echo EXISTS || echo MISSING\n" +
|
|
112
|
-
"2. If EXISTS, run: " + crewHome + "/crew-release.sh current\n" +
|
|
116
|
+
"2. If EXISTS, run: " + crewHome + "/crew-release.sh current " + crewHome + "\n" +
|
|
113
117
|
" Return { existed: true, hash: <current hash> }\n" +
|
|
114
118
|
"3. If MISSING, bootstrap:\n" +
|
|
115
119
|
" a. Run: bash " + crewRepoPath + "/lib/crew-release.sh init " + crewHome + " " + crewRepoPath + "\n" +
|
|
116
|
-
" b. Verify: " + crewHome + "/crew-release.sh current\n" +
|
|
120
|
+
" b. Verify: " + crewHome + "/crew-release.sh current " + crewHome + "\n" +
|
|
117
121
|
" c. Return { existed: false, hash: <hash from current> }\n\n" +
|
|
118
122
|
"Return JSON with existed (boolean) and hash (string).",
|
|
119
123
|
{
|
|
@@ -179,7 +183,48 @@ var scaffoldCreated = scaffoldResult.created ? scaffoldResult.created.length : 0
|
|
|
179
183
|
var scaffoldSkipped = scaffoldResult.skipped ? scaffoldResult.skipped.length : 0;
|
|
180
184
|
log("Scaffold: " + scaffoldCreated + " created, " + scaffoldSkipped + " skipped");
|
|
181
185
|
|
|
182
|
-
// ── Phase 3:
|
|
186
|
+
// ── Phase 3: Project registration ─────────────────────────────────────
|
|
187
|
+
// Registers the dashboard as the crew's first project in crew-state.db via
|
|
188
|
+
// the Crew API. The dashboard becomes its own first project, so the crew can
|
|
189
|
+
// work on the dashboard itself. Idempotent: if the project already exists,
|
|
190
|
+
// it is left alone.
|
|
191
|
+
phase("project");
|
|
192
|
+
var projectResult;
|
|
193
|
+
var safeDashboardName = dashboardName.split('"').join('\\"');
|
|
194
|
+
try {
|
|
195
|
+
projectResult = await agent(
|
|
196
|
+
"Register the dashboard as a project in the crew's task database.\n\n" +
|
|
197
|
+
"Crew home: " + crewHome + "\n" +
|
|
198
|
+
"Crew API: " + crewHome + "/current/lib/crew-api.js\n" +
|
|
199
|
+
"Dashboard slug (project id): " + dashboardSlug + "\n" +
|
|
200
|
+
"Dashboard name (display_name): " + dashboardName + "\n\n" +
|
|
201
|
+
"Steps:\n" +
|
|
202
|
+
"1. Check if the project already exists:\n" +
|
|
203
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " get-project --json '{\"id\": \"" + dashboardSlug + "\"}'\n" +
|
|
204
|
+
" If it returns a project (ok: true), return { action: \"exists\", project_id: \"" + dashboardSlug + "\" }.\n" +
|
|
205
|
+
"2. If not found (error), create it:\n" +
|
|
206
|
+
" node " + crewHome + "/current/lib/crew-api.js --crew-home " + crewHome + " create-project --json '{\"id\": \"" + dashboardSlug + "\", \"display_name\": \"" + safeDashboardName + "\", \"repo_path\": \"" + crewHome + "\", \"deploy_type\": \"artifact\", \"deploy_slug\": \"" + dashboardSlug + "\", \"description\": \"The dashboard task service — the crew\\u0027s first project\"}'\n" +
|
|
207
|
+
" Return { action: \"created\", project_id: \"" + dashboardSlug + "\" }.\n\n" +
|
|
208
|
+
"Return JSON with action (\"exists\" or \"created\") and project_id (string).",
|
|
209
|
+
{
|
|
210
|
+
key: "project-1",
|
|
211
|
+
label: "Register dashboard project",
|
|
212
|
+
schema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
action: { type: "string", enum: ["exists", "created"] },
|
|
216
|
+
project_id: { type: "string" }
|
|
217
|
+
},
|
|
218
|
+
required: ["action", "project_id"]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
return { __hatchWorkflowControl: "blocked", result: { blocked_reason: "Project registration failed", message: String(e.message || e) } };
|
|
224
|
+
}
|
|
225
|
+
log("Project: " + dashboardSlug + " " + projectResult.action);
|
|
226
|
+
|
|
227
|
+
// ── Phase 4: Cron jobs (declarative manifest) ──────────────────────────
|
|
183
228
|
// The manifest lives in the repo at seed/crons.json. Body templates live
|
|
184
229
|
// in seed/ with {crewHome} placeholders. Missing jobs are
|
|
185
230
|
// created from the manifest; existing jobs converge to it — except `enabled`,
|
|
@@ -199,7 +244,10 @@ try {
|
|
|
199
244
|
" or any entry lacks a required field (id, title, enabled, mode, schedule,\n" +
|
|
200
245
|
" owner, body_template).\n" +
|
|
201
246
|
"2. For each manifest entry, in order:\n" +
|
|
202
|
-
" a. The live id is the override for entry.id when present
|
|
247
|
+
" a. The live id is the override for entry.id when present (inputs.cronIds[entry.id]).\n" +
|
|
248
|
+
" Otherwise, the live id is entry.id + '-' + dashboardSlug (instance-safe by\n" +
|
|
249
|
+
" construction — two crew instances never share a cron id). The bare manifest\n" +
|
|
250
|
+
" id is never used as a live id.\n" +
|
|
203
251
|
" b. Read the body template at " + crewRepoPath + "/seed/<entry.body_template>.\n" +
|
|
204
252
|
" c. Replace all occurrences of {crewHome} with: " + crewHome + ".\n" +
|
|
205
253
|
" Replace all occurrences of {dashboardSlug} with: " + dashboardSlug + ".\n" +
|
|
@@ -271,5 +319,6 @@ return {
|
|
|
271
319
|
dashboardName: dashboardName,
|
|
272
320
|
releaseHash: releaseResult.hash,
|
|
273
321
|
scaffold: { created: scaffoldCreated, skipped: scaffoldSkipped },
|
|
322
|
+
project: projectResult.action,
|
|
274
323
|
crons: cronsResult.summary.crons
|
|
275
324
|
};
|