@mmerterden/multi-agent-pipeline 20.29.0 → 20.33.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/CHANGELOG.md +86 -0
- package/README.md +50 -5
- package/README.tr.md +64 -5
- package/docs/architecture.md +3 -3
- package/docs/ecosystem.md +5 -5
- package/docs/facts.json +9 -9
- package/install/_unattended-profile.mjs +61 -0
- package/install/catalog-history.json +1 -1
- package/install/claude.mjs +19 -3
- package/install/codex.mjs +2 -0
- package/install/copilot.mjs +13 -0
- package/install/index.mjs +10 -1
- package/install/templates/claude-hooks.json +12 -1
- package/manifest.json +230 -88
- package/package.json +3 -1
- package/pipeline/commands/multi-agent/analysis/SKILL.md +4 -4
- package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +13 -15
- package/pipeline/commands/multi-agent/office-off/SKILL.md +27 -0
- package/pipeline/commands/multi-agent/office-on/SKILL.md +51 -0
- package/pipeline/commands/multi-agent/office-status/SKILL.md +29 -0
- package/pipeline/commands/multi-agent/prune-logs/SKILL.md +4 -1
- package/pipeline/commands/multi-agent/review/SKILL.md +4 -3
- package/pipeline/commands/multi-agent/scenario-audit/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/serve/SKILL.md +4 -2
- package/pipeline/commands/multi-agent/stack/SKILL.md +15 -8
- package/pipeline/commands/multi-agent/steer/SKILL.md +10 -9
- package/pipeline/commands/multi-agent/sync/SKILL.md +15 -3
- package/pipeline/commands/multi-agent/test-gen/SKILL.md +29 -3
- package/pipeline/contract/CHANGELOG.md +108 -0
- package/pipeline/contract/README.md +72 -12
- package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
- package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
- package/pipeline/contract/fixtures/error-unsigned.json +1 -1
- package/pipeline/contract/fixtures/launch-plan.json +1 -1
- package/pipeline/contract/fixtures/runs-awaiting-question.json +6 -1
- package/pipeline/contract/fixtures/runs-empty.json +1 -1
- package/pipeline/contract/fixtures/runs-failed.json +6 -1
- package/pipeline/contract/fixtures/runs-old-schema.json +6 -1
- package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +5 -1
- package/pipeline/contract/fixtures/runs-pr-opened.json +6 -1
- package/pipeline/contract/fixtures/runs-running.json +6 -1
- package/pipeline/contract/manifest.json +506 -27
- package/pipeline/contract/types/index.d.ts +829 -4
- package/pipeline/lib/bg-launch.mjs +106 -0
- package/pipeline/lib/code-digest.mjs +49 -0
- package/pipeline/lib/credential-resolve.mjs +42 -2
- package/pipeline/multi-agent-refs/cross-cli-contract.md +5 -4
- package/pipeline/multi-agent-refs/features/office-threat-model.md +109 -0
- package/pipeline/multi-agent-refs/features/unattended-security.md +1 -1
- package/pipeline/multi-agent-refs/help/en.md +3 -0
- package/pipeline/multi-agent-refs/help/tr.md +3 -0
- package/pipeline/multi-agent-refs/phases/modes.md +7 -7
- package/pipeline/multi-agent-refs/phases/phase-5-report.md +16 -10
- package/pipeline/multi-agent-refs/review-standalone-tracker.md +37 -0
- package/pipeline/multi-agent-refs/tracker-contract.md +8 -3
- package/pipeline/office/NOTICE.md +45 -0
- package/pipeline/office/assets/ARCHIVE_BOXES.png +0 -0
- package/pipeline/office/assets/BIN.png +0 -0
- package/pipeline/office/assets/BOOKSHELF.png +0 -0
- package/pipeline/office/assets/CACTUS.png +0 -0
- package/pipeline/office/assets/CLOCK.png +0 -0
- package/pipeline/office/assets/COFFEE.png +0 -0
- package/pipeline/office/assets/COFFEE_MACHINE.png +0 -0
- package/pipeline/office/assets/COFFEE_TABLE.png +0 -0
- package/pipeline/office/assets/COUNTER_SINK.png +0 -0
- package/pipeline/office/assets/CUSHIONED_BENCH.png +0 -0
- package/pipeline/office/assets/CUSHIONED_CHAIR_BACK.png +0 -0
- package/pipeline/office/assets/CUSHIONED_CHAIR_FRONT.png +0 -0
- package/pipeline/office/assets/CUSHIONED_CHAIR_SIDE.png +0 -0
- package/pipeline/office/assets/DESK_FRONT.png +0 -0
- package/pipeline/office/assets/DESK_SIDE.png +0 -0
- package/pipeline/office/assets/DEVICE_STAND.png +0 -0
- package/pipeline/office/assets/DOUBLE_BOOKSHELF.png +0 -0
- package/pipeline/office/assets/FILING_CABINET.png +0 -0
- package/pipeline/office/assets/FRIDGE.png +0 -0
- package/pipeline/office/assets/HANGING_PLANT.png +0 -0
- package/pipeline/office/assets/LARGE_PAINTING.png +0 -0
- package/pipeline/office/assets/LARGE_PLANT.png +0 -0
- package/pipeline/office/assets/MAILBOX.png +0 -0
- package/pipeline/office/assets/MOP_BUCKET.png +0 -0
- package/pipeline/office/assets/PC_BACK.png +0 -0
- package/pipeline/office/assets/PC_FRONT_OFF.png +0 -0
- package/pipeline/office/assets/PC_FRONT_ON_1.png +0 -0
- package/pipeline/office/assets/PC_FRONT_ON_2.png +0 -0
- package/pipeline/office/assets/PC_FRONT_ON_3.png +0 -0
- package/pipeline/office/assets/PC_SIDE.png +0 -0
- package/pipeline/office/assets/PLANT.png +0 -0
- package/pipeline/office/assets/PLANT_2.png +0 -0
- package/pipeline/office/assets/POT.png +0 -0
- package/pipeline/office/assets/PRINTER.png +0 -0
- package/pipeline/office/assets/RECEPTION_DESK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK.png +0 -0
- package/pipeline/office/assets/SERVER_RACK_2.png +0 -0
- package/pipeline/office/assets/SMALL_PAINTING.png +0 -0
- package/pipeline/office/assets/SMALL_PAINTING_2.png +0 -0
- package/pipeline/office/assets/SMALL_TABLE_FRONT.png +0 -0
- package/pipeline/office/assets/SMALL_TABLE_SIDE.png +0 -0
- package/pipeline/office/assets/SOFA_BACK.png +0 -0
- package/pipeline/office/assets/SOFA_FRONT.png +0 -0
- package/pipeline/office/assets/SOFA_SIDE.png +0 -0
- package/pipeline/office/assets/TABLE_FRONT.png +0 -0
- package/pipeline/office/assets/TOILET_STALL.png +0 -0
- package/pipeline/office/assets/VENDING.png +0 -0
- package/pipeline/office/assets/WASHBASIN.png +0 -0
- package/pipeline/office/assets/WATER_COOLER.png +0 -0
- package/pipeline/office/assets/WHITEBOARD.png +0 -0
- package/pipeline/office/assets/WOODEN_BENCH.png +0 -0
- package/pipeline/office/assets/WOODEN_CHAIR_BACK.png +0 -0
- package/pipeline/office/assets/WOODEN_CHAIR_FRONT.png +0 -0
- package/pipeline/office/assets/WOODEN_CHAIR_SIDE.png +0 -0
- package/pipeline/office/assets/char_0.png +0 -0
- package/pipeline/office/assets/char_1.png +0 -0
- package/pipeline/office/assets/char_2.png +0 -0
- package/pipeline/office/assets/char_3.png +0 -0
- package/pipeline/office/assets/char_4.png +0 -0
- package/pipeline/office/assets/char_5.png +0 -0
- package/pipeline/office/assets/floor_0.png +0 -0
- package/pipeline/office/assets/floor_1.png +0 -0
- package/pipeline/office/assets/floor_2.png +0 -0
- package/pipeline/office/assets/floor_3.png +0 -0
- package/pipeline/office/assets/floor_4.png +0 -0
- package/pipeline/office/assets/floor_5.png +0 -0
- package/pipeline/office/assets/floor_6.png +0 -0
- package/pipeline/office/assets/floor_7.png +0 -0
- package/pipeline/office/assets/floor_8.png +0 -0
- package/pipeline/office/assets/wall_0.png +0 -0
- package/pipeline/office/index.html +8431 -0
- package/pipeline/office/toolkit.json +3 -0
- package/pipeline/schemas/activity-chat.schema.json +78 -0
- package/pipeline/schemas/activity-roles.json +48 -0
- package/pipeline/schemas/activity-roles.schema.json +49 -0
- package/pipeline/schemas/activity.schema.json +613 -0
- package/pipeline/schemas/agent-state.schema.json +493 -115
- package/pipeline/schemas/autopilot-on-request.schema.json +33 -0
- package/pipeline/schemas/autopilot-on.schema.json +111 -0
- package/pipeline/schemas/contract-error.schema.json +45 -4
- package/pipeline/schemas/issue-maturity.schema.json +68 -0
- package/pipeline/schemas/job-change.schema.json +72 -0
- package/pipeline/schemas/job-request.schema.json +38 -0
- package/pipeline/schemas/launch.json +4 -4
- package/pipeline/schemas/ledger.schema.json +202 -0
- package/pipeline/schemas/office-credentials.schema.json +71 -0
- package/pipeline/schemas/office-jobs.json +253 -0
- package/pipeline/schemas/office-jobs.schema.json +62 -0
- package/pipeline/schemas/office-memory.schema.json +105 -0
- package/pipeline/schemas/office-settings-request.schema.json +30 -0
- package/pipeline/schemas/office-settings.schema.json +67 -0
- package/pipeline/schemas/permissions.schema.json +47 -0
- package/pipeline/schemas/phases.json +10 -2
- package/pipeline/schemas/phases.schema.json +86 -19
- package/pipeline/schemas/prefs.schema.json +19 -0
- package/pipeline/schemas/prs.schema.json +78 -0
- package/pipeline/schemas/prune-logs-request.schema.json +26 -0
- package/pipeline/schemas/prune-logs.schema.json +52 -0
- package/pipeline/schemas/repo-change.schema.json +54 -0
- package/pipeline/schemas/repo-request.schema.json +29 -0
- package/pipeline/schemas/repos.schema.json +49 -1
- package/pipeline/schemas/review-verdict-request.schema.json +20 -0
- package/pipeline/schemas/review-verdict.schema.json +62 -0
- package/pipeline/schemas/routine-delete.schema.json +59 -0
- package/pipeline/schemas/routines.schema.json +38 -0
- package/pipeline/schemas/run-evidence.schema.json +29 -0
- package/pipeline/schemas/run-results.schema.json +218 -0
- package/pipeline/schemas/runs-index.schema.json +257 -46
- package/pipeline/schemas/service-read.schema.json +58 -0
- package/pipeline/schemas/stack-change.schema.json +82 -0
- package/pipeline/schemas/stack-request.schema.json +33 -0
- package/pipeline/schemas/stack.schema.json +59 -0
- package/pipeline/schemas/steer-request.schema.json +25 -0
- package/pipeline/schemas/steer.schema.json +59 -0
- package/pipeline/schemas/system-status.schema.json +111 -0
- package/pipeline/schemas/tool-call-request.schema.json +19 -0
- package/pipeline/schemas/tool-call.schema.json +47 -0
- package/pipeline/schemas/tools.schema.json +65 -0
- package/pipeline/schemas/unattended-profile.json +7 -2
- package/pipeline/scripts/_active-run.mjs +2 -1
- package/pipeline/scripts/_cost.mjs +66 -1
- package/pipeline/scripts/_office-smoke-lib.sh +115 -0
- package/pipeline/scripts/_spawn-async.mjs +77 -0
- package/pipeline/scripts/_transcript-tail.mjs +274 -0
- package/pipeline/scripts/activity-snapshot.mjs +1223 -0
- package/pipeline/scripts/autopilot-control.mjs +214 -9
- package/pipeline/scripts/autopilot-runner.mjs +7 -5
- package/pipeline/scripts/contract-server.mjs +1853 -197
- package/pipeline/scripts/cost-analyze.mjs +2 -53
- package/pipeline/scripts/cost-table.json +1 -0
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -1
- package/pipeline/scripts/job-launch.mjs +477 -0
- package/pipeline/scripts/launch-request.mjs +94 -9
- package/pipeline/scripts/office-build.mjs +226 -0
- package/pipeline/scripts/office-control.mjs +410 -0
- package/pipeline/scripts/office-credentials.mjs +106 -0
- package/pipeline/scripts/office-ledger.mjs +409 -0
- package/pipeline/scripts/office-memory.mjs +200 -0
- package/pipeline/scripts/office-prune.mjs +125 -0
- package/pipeline/scripts/office-settings.mjs +145 -0
- package/pipeline/scripts/phase-tracker.sh +12 -5
- package/pipeline/scripts/prune-logs.sh +53 -2
- package/pipeline/scripts/routine-registry.mjs +42 -6
- package/pipeline/scripts/run-results.mjs +373 -0
- package/pipeline/scripts/runs-index.mjs +62 -2
- package/pipeline/scripts/service-read.mjs +244 -0
- package/pipeline/scripts/stack-toggle.mjs +285 -0
- package/pipeline/scripts/steer-run.mjs +97 -0
- package/pipeline/scripts/system-status.mjs +196 -0
- package/pipeline/scripts/task-mirror.mjs +150 -0
- package/pipeline/scripts/task-mirror.sh +20 -0
- package/pipeline/scripts/toolkit-tools.mjs +625 -0
- package/pipeline/scripts/transcript-index.mjs +4 -1
- package/pipeline/scripts/uninstall.mjs +3 -0
- package/pipeline/scripts/work-items.mjs +473 -0
- package/pipeline/skills/.skill-manifest.json +23 -11
- package/pipeline/skills/.skills-index.json +34 -1
- package/pipeline/skills/shared/README.md +6 -3
- package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +13 -15
- package/pipeline/skills/shared/core/multi-agent-office-off/SKILL.md +16 -0
- package/pipeline/skills/shared/core/multi-agent-office-on/SKILL.md +21 -0
- package/pipeline/skills/shared/core/multi-agent-office-status/SKILL.md +17 -0
- package/pipeline/skills/shared/core/multi-agent-prune-logs/SKILL.md +4 -1
- package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +19 -0
- package/pipeline/skills/shared/core/multi-agent-scenario-audit/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-serve/SKILL.md +4 -2
- package/pipeline/skills/shared/core/multi-agent-stack/SKILL.md +14 -6
- package/pipeline/skills/shared/core/multi-agent-steer/SKILL.md +10 -9
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-test-gen/SKILL.md +26 -3
- package/pipeline/skills/skills-index.md +4 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file office-prune.mjs - old run logs, cleared from the restroom.
|
|
4
|
+
*
|
|
5
|
+
* A thin wrapper over prune-logs.sh, the same script /multi-agent:prune-logs
|
|
6
|
+
* runs: its dry run is the preview (which task log folders would go and the
|
|
7
|
+
* space freed). On the office's route (pruneApply) the confirmed step lists
|
|
8
|
+
* the folders again, refuses with code "changed" when they differ from the
|
|
9
|
+
* preview, and then prunes only that list (prune-logs.sh --only-from); the
|
|
10
|
+
* CLI's `apply` prunes the age directly, as /multi-agent:prune-logs does.
|
|
11
|
+
* Everything prune-logs.sh guarantees holds here: the audit trail,
|
|
12
|
+
* the metrics corpus and the task counters are never touched, the log root is
|
|
13
|
+
* refused when it is /, the home folder or a git checkout, a folder written
|
|
14
|
+
* in the last minutes is spared so a live run keeps its log, and so is a run
|
|
15
|
+
* that can still be resumed.
|
|
16
|
+
*
|
|
17
|
+
* The age is one of a fixed set of days; nothing else reaches the script.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* office-prune.mjs preview <days> [--json]
|
|
21
|
+
* office-prune.mjs apply <days> [--json]
|
|
22
|
+
*
|
|
23
|
+
* PRUNE_LOG_ROOT overrides the log root, as for prune-logs.sh.
|
|
24
|
+
*
|
|
25
|
+
* Exit: 0 printed, 1 prune-logs.sh refused, 2 usage.
|
|
26
|
+
*
|
|
27
|
+
* @module pipeline/scripts/office-prune
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
31
|
+
import { tmpdir } from "node:os";
|
|
32
|
+
import { dirname, join } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
|
|
35
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
36
|
+
import { spawnAsync } from "./_spawn-async.mjs";
|
|
37
|
+
|
|
38
|
+
export const CONTRACT_VERSION = "1.0.0";
|
|
39
|
+
export const AGES = [7, 30, 90];
|
|
40
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const MAX_LISTED = 200;
|
|
42
|
+
|
|
43
|
+
/** What a prune-logs.sh run printed: the folders and the space freed. */
|
|
44
|
+
export function parsePrune(text) {
|
|
45
|
+
const lines = String(text).split("\n");
|
|
46
|
+
const head = lines.find((l) => /would remove|removed/i.test(l)) || "";
|
|
47
|
+
const removed = /removed (\d+)\/\d+ task log dir/.exec(head);
|
|
48
|
+
const n = Number(removed ? removed[1] : ((/(\d+) task log dir/.exec(head) || [])[1] ?? 0));
|
|
49
|
+
const size = /~(\d+(?:\.\d+)?) ?(KB|MB)/.exec(head);
|
|
50
|
+
const mb = size
|
|
51
|
+
? size[2] === "KB"
|
|
52
|
+
? Math.round((Number(size[1]) / 1024) * 10) / 10
|
|
53
|
+
: Number(size[1])
|
|
54
|
+
: 0;
|
|
55
|
+
const all = lines
|
|
56
|
+
.map((l) => /^\s{2}(\S(?:.*\S)?)\s*$/.exec(l))
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.map((m) => m[1]);
|
|
59
|
+
return { count: n || all.length, freedMb: mb, folders: all.slice(0, MAX_LISTED), all };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// The page sees at most MAX_LISTED folders; the confirmation compares the
|
|
63
|
+
// whole list.
|
|
64
|
+
async function run(days, apply, env, { keepAll = false, only = null } = {}) {
|
|
65
|
+
if (!AGES.includes(days)) throw new Error(`age must be one of ${AGES.join(", ")}`);
|
|
66
|
+
const dir = only ? mkdtempSync(join(tmpdir(), "office-prune-")) : null;
|
|
67
|
+
const list = dir ? join(dir, "only.txt") : null;
|
|
68
|
+
if (list) writeFileSync(list, only.join("\n") + "\n", { mode: 0o600 });
|
|
69
|
+
const r = await spawnAsync(
|
|
70
|
+
"bash",
|
|
71
|
+
[
|
|
72
|
+
join(HERE, "prune-logs.sh"),
|
|
73
|
+
`--older-than=${days}`,
|
|
74
|
+
...(list ? [`--only-from=${list}`] : []),
|
|
75
|
+
...(apply ? ["--yes"] : []),
|
|
76
|
+
],
|
|
77
|
+
{ env, timeoutMs: 120_000 },
|
|
78
|
+
).finally(() => dir && rmSync(dir, { recursive: true, force: true }));
|
|
79
|
+
if (r.status !== 0) throw new Error("prune-logs.sh refused (exit " + r.status + ")");
|
|
80
|
+
const { all, ...parsed } = parsePrune(r.stdout);
|
|
81
|
+
return {
|
|
82
|
+
contractVersion: CONTRACT_VERSION,
|
|
83
|
+
olderThanDays: days,
|
|
84
|
+
applied: apply,
|
|
85
|
+
...parsed,
|
|
86
|
+
...(keepAll ? { all } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const prunePreview = (days, env = process.env, opts = {}) => run(days, false, env, opts);
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Prunes only what the preview showed: the folders are listed again and the
|
|
94
|
+
* prune runs only when they are the same set, and then only on that list, so
|
|
95
|
+
* a run that aged into the window or a folder that appeared since is never
|
|
96
|
+
* removed unseen.
|
|
97
|
+
*/
|
|
98
|
+
export async function pruneApply(days, env = process.env, expected = null) {
|
|
99
|
+
if (expected && Array.isArray(expected.all)) {
|
|
100
|
+
const now = await run(days, false, env, { keepAll: true });
|
|
101
|
+
const want = new Set(expected.all);
|
|
102
|
+
const same =
|
|
103
|
+
now.count === expected.count &&
|
|
104
|
+
now.all.length === want.size &&
|
|
105
|
+
now.all.every((f) => want.has(f));
|
|
106
|
+
if (!same) {
|
|
107
|
+
const e = new Error("the folders changed since the preview; preview again");
|
|
108
|
+
e.code = "changed";
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
return run(days, true, env, { only: expected.all });
|
|
112
|
+
}
|
|
113
|
+
return run(days, true, env);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (invokedDirectly(import.meta.url)) {
|
|
117
|
+
const [cmd, d, flag] = process.argv.slice(2);
|
|
118
|
+
const days = Number(d);
|
|
119
|
+
if (!["preview", "apply"].includes(cmd) || !AGES.includes(days) || (flag && flag !== "--json")) {
|
|
120
|
+
console.error(`usage: office-prune.mjs preview|apply <${AGES.join("|")}> [--json]`);
|
|
121
|
+
process.exit(2);
|
|
122
|
+
}
|
|
123
|
+
const doc = await run(days, cmd === "apply", process.env);
|
|
124
|
+
process.stdout.write(JSON.stringify(doc) + "\n");
|
|
125
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @file office-settings.mjs - the three switches the office can flip, read
|
|
4
|
+
* and changed through the same code the terminal commands use.
|
|
5
|
+
*
|
|
6
|
+
* language prefs.global.outputLanguage, en or tr, as /multi-agent:language
|
|
7
|
+
* writes it (promptLanguage forced back to en in the same write),
|
|
8
|
+
* then localize-commands.mjs swaps the installed picker text
|
|
9
|
+
* model the fable rung, through lib/model-rung.sh on|off
|
|
10
|
+
* route model routing, through lib/route-state.sh on|off; `on` keeps
|
|
11
|
+
* the saved configuration, and anything that would widen it (a
|
|
12
|
+
* free provider, new rules) stays in the terminal
|
|
13
|
+
*
|
|
14
|
+
* Every value is one of a fixed set; nothing else reaches a script.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* office-settings.mjs [--json]
|
|
18
|
+
* office-settings.mjs set <language|model|route> <value> [--json]
|
|
19
|
+
*
|
|
20
|
+
* MULTI_AGENT_PREFS overrides the preferences file, as for the scripts it calls.
|
|
21
|
+
*
|
|
22
|
+
* Exit: 0 printed or applied, 1 the change failed, 2 usage.
|
|
23
|
+
*
|
|
24
|
+
* @module pipeline/scripts/office-settings
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync } from "node:fs";
|
|
28
|
+
import { homedir } from "node:os";
|
|
29
|
+
import { dirname, join } from "node:path";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
33
|
+
import { updateJsonFileSync } from "../lib/json-file-lock.mjs";
|
|
34
|
+
import { readRegularJson } from "../lib/regular-file.mjs";
|
|
35
|
+
import { spawnAsync } from "./_spawn-async.mjs";
|
|
36
|
+
|
|
37
|
+
export const CONTRACT_VERSION = "1.0.0";
|
|
38
|
+
export const CHOICES = {
|
|
39
|
+
language: ["en", "tr"],
|
|
40
|
+
model: ["on", "off"],
|
|
41
|
+
route: ["on", "off"],
|
|
42
|
+
};
|
|
43
|
+
const STRATEGIES = ["manual", "task-fit", "cost-ceiling"];
|
|
44
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
|
|
46
|
+
const prefsPath = (env) =>
|
|
47
|
+
env.MULTI_AGENT_PREFS || join(env.HOME || homedir(), ".claude", "multi-agent-preferences.json");
|
|
48
|
+
|
|
49
|
+
function libScript(name) {
|
|
50
|
+
const p = join(HERE, "..", "lib", name);
|
|
51
|
+
return existsSync(p) ? p : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What the switches are set to now. */
|
|
55
|
+
export function readSettings(env = process.env) {
|
|
56
|
+
const g = readRegularJson(prefsPath(env), {})?.global ?? {};
|
|
57
|
+
return {
|
|
58
|
+
contractVersion: CONTRACT_VERSION,
|
|
59
|
+
language: g.outputLanguage === "tr" ? "tr" : "en",
|
|
60
|
+
model: g.modelFallback?.fableEnabled === true ? "on" : "off",
|
|
61
|
+
route: g.modelRouting?.enabled === true ? "on" : "off",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The free provider chain a saved routing configuration keeps across `route
|
|
67
|
+
* off`, which `route on` brings back; null when none is saved.
|
|
68
|
+
*/
|
|
69
|
+
export function freeChainOf(env = process.env) {
|
|
70
|
+
const free = readRegularJson(prefsPath(env), {})?.global?.modelRouting?.free;
|
|
71
|
+
if (free?.enabled !== true) return null;
|
|
72
|
+
return { enabled: true, allowTraining: free.allowTraining === true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class SettingsError extends Error {}
|
|
76
|
+
|
|
77
|
+
// route-state.sh on resets the strategy to manual unless told otherwise; the
|
|
78
|
+
// saved one is passed back so `on` changes only the switch.
|
|
79
|
+
function routeArgs(setting, value, env) {
|
|
80
|
+
if (setting !== "route" || value !== "on") return [];
|
|
81
|
+
const saved = readRegularJson(prefsPath(env), {})?.global?.modelRouting?.strategy;
|
|
82
|
+
return [`--strategy=${STRATEGIES.includes(saved) ? saved : "manual"}`];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Flip one switch to one of its values; returns the settings afterwards. */
|
|
86
|
+
export async function applySetting(setting, value, env = process.env) {
|
|
87
|
+
if (!CHOICES[setting]?.includes(value)) throw new SettingsError(`${setting}: not a choice`);
|
|
88
|
+
if (setting === "language") {
|
|
89
|
+
const path = prefsPath(env);
|
|
90
|
+
if (!existsSync(path)) throw new SettingsError("no preferences file");
|
|
91
|
+
let written = false;
|
|
92
|
+
updateJsonFileSync(path, (cur) => {
|
|
93
|
+
if (!cur || typeof cur !== "object" || Array.isArray(cur)) return undefined;
|
|
94
|
+
written = true;
|
|
95
|
+
const global = { ...(cur.global ?? {}), promptLanguage: "en", outputLanguage: value };
|
|
96
|
+
return { ...cur, global };
|
|
97
|
+
});
|
|
98
|
+
if (!written)
|
|
99
|
+
throw new SettingsError("language: the preferences file is not an object, not written");
|
|
100
|
+
const loc = join(HERE, "localize-commands.mjs");
|
|
101
|
+
if (existsSync(loc)) {
|
|
102
|
+
const r = await spawnAsync(process.execPath, [loc, value === "tr" ? "apply" : "restore"], {
|
|
103
|
+
env,
|
|
104
|
+
timeoutMs: 30_000,
|
|
105
|
+
});
|
|
106
|
+
if (r.status !== 0)
|
|
107
|
+
throw new SettingsError(
|
|
108
|
+
`language: saved, but the command descriptions were not switched (exit ${r.status})`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
const script = libScript(setting === "model" ? "model-rung.sh" : "route-state.sh");
|
|
113
|
+
if (!script) throw new SettingsError(`${setting}: script not installed`);
|
|
114
|
+
const r = await spawnAsync("bash", [script, value, ...routeArgs(setting, value, env)], {
|
|
115
|
+
env,
|
|
116
|
+
timeoutMs: 30_000,
|
|
117
|
+
});
|
|
118
|
+
if (r.status !== 0)
|
|
119
|
+
throw new SettingsError(`${setting}: the script refused (exit ${r.status})`);
|
|
120
|
+
}
|
|
121
|
+
return readSettings(env);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (invokedDirectly(import.meta.url)) {
|
|
125
|
+
const args = process.argv.slice(2).filter((a) => a !== "--json");
|
|
126
|
+
const json = process.argv.includes("--json");
|
|
127
|
+
const out = (doc) =>
|
|
128
|
+
process.stdout.write(
|
|
129
|
+
json
|
|
130
|
+
? JSON.stringify(doc) + "\n"
|
|
131
|
+
: `language ${doc.language}, model ${doc.model}, route ${doc.route}\n`,
|
|
132
|
+
);
|
|
133
|
+
if (!args.length) out(readSettings());
|
|
134
|
+
else if (args[0] === "set" && args.length === 3) {
|
|
135
|
+
try {
|
|
136
|
+
out(await applySetting(args[1], args[2]));
|
|
137
|
+
} catch (e) {
|
|
138
|
+
console.error(String(e.message));
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
console.error("usage: office-settings.mjs [set <language|model|route> <value>] [--json]");
|
|
143
|
+
process.exit(2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -86,7 +86,7 @@ usage:
|
|
|
86
86
|
phase-tracker.sh model <phase_id> <model_name>
|
|
87
87
|
phase-tracker.sh meta <phase_id> <key> <value>
|
|
88
88
|
phase-tracker.sh now <phase_id> "<text>"
|
|
89
|
-
phase-tracker.sh task <phase_id> <task_id> <pending|in_progress|completed> [--subject S] [--active-form A]
|
|
89
|
+
phase-tracker.sh task <phase_id> <task_id> <pending|in_progress|completed|deleted> [--subject S] [--active-form A]
|
|
90
90
|
phase-tracker.sh cost <phase_id>|total
|
|
91
91
|
phase-tracker.sh tiles
|
|
92
92
|
phase-tracker.sh report
|
|
@@ -1427,11 +1427,11 @@ EOF
|
|
|
1427
1427
|
# client behind it - sees the same list. Quiet like `now`: the host already
|
|
1428
1428
|
# rendered the change, so this only records it.
|
|
1429
1429
|
need_jq
|
|
1430
|
-
[ "$#" -ge 3 ] || { echo "task needs <phase_id> <task_id> <status> [--subject S] [--active-form A]" >&2; exit 64; }
|
|
1430
|
+
[ "$#" -ge 3 ] || { echo "task needs <phase_id> <task_id> <status|deleted> [--subject S] [--active-form A]" >&2; exit 64; }
|
|
1431
1431
|
PID="$1"; TID="$2"; TSTATUS="$3"; shift 3
|
|
1432
1432
|
case "$TSTATUS" in
|
|
1433
|
-
pending|in_progress|completed) ;;
|
|
1434
|
-
*) echo "task: status must be pending, in_progress or
|
|
1433
|
+
pending|in_progress|completed|deleted) ;;
|
|
1434
|
+
*) echo "task: status must be pending, in_progress, completed or deleted" >&2; exit 64 ;;
|
|
1435
1435
|
esac
|
|
1436
1436
|
TSUBJECT=""; HAS_SUBJECT=0; TACTIVE=""; HAS_ACTIVE=0
|
|
1437
1437
|
while [ "$#" -gt 0 ]; do
|
|
@@ -1452,6 +1452,11 @@ EOF
|
|
|
1452
1452
|
fi
|
|
1453
1453
|
EXISTS=$(printf '%s' "$state" | jq -r --arg pid "$PID" --arg tid "$TID" \
|
|
1454
1454
|
'any(.phases[] | select(.id == $pid) | .tasks[]?; .id == $tid)')
|
|
1455
|
+
# A deleted task leaves the list; one never recorded is nothing to delete.
|
|
1456
|
+
if [ "$TSTATUS" = "deleted" ] && [ "$EXISTS" != "true" ]; then
|
|
1457
|
+
release_state_lock
|
|
1458
|
+
exit 0
|
|
1459
|
+
fi
|
|
1455
1460
|
if [ "$EXISTS" != "true" ] && [ "$HAS_SUBJECT" -eq 0 ]; then
|
|
1456
1461
|
release_state_lock
|
|
1457
1462
|
echo "task: a new task needs --subject" >&2
|
|
@@ -1463,7 +1468,9 @@ EOF
|
|
|
1463
1468
|
--arg act "$TACTIVE" --argjson ha "$HAS_ACTIVE" '
|
|
1464
1469
|
.phases |= map(
|
|
1465
1470
|
if .id == $pid then
|
|
1466
|
-
if
|
|
1471
|
+
if $st == "deleted" then
|
|
1472
|
+
.tasks |= map(select(.id != $tid))
|
|
1473
|
+
elif any(.tasks[]?; .id == $tid) then
|
|
1467
1474
|
.tasks |= map(if .id == $tid then
|
|
1468
1475
|
.status = $st
|
|
1469
1476
|
| (if $hs == 1 then .subject = $subj else . end)
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
# PRUNE_GRACE_MIN minutes (default 10) are spared, so pruning while a
|
|
24
24
|
# run is still writing its log dir cannot break resume. An explicit
|
|
25
25
|
# --task=ID bypasses the grace; PRUNE_GRACE_MIN=0 disables it.
|
|
26
|
+
# - resumable runs: a task dir whose agent-state.json status is not
|
|
27
|
+
# finished, abandoned or cancelled is spared however old it is
|
|
28
|
+
# (an unreadable status counts as resumable); --task bypasses this too.
|
|
26
29
|
#
|
|
27
30
|
# Usage:
|
|
28
31
|
# prune-logs.sh # dry-run: every idle task dir
|
|
@@ -31,6 +34,8 @@
|
|
|
31
34
|
# prune-logs.sh --project=my-ios-app # only one project's task dirs
|
|
32
35
|
# prune-logs.sh --task=PROJ-123 # only one task dir
|
|
33
36
|
# prune-logs.sh --older-than=30 --yes
|
|
37
|
+
# prune-logs.sh --only-from=FILE # only the folders FILE lists, one per
|
|
38
|
+
# # line as the dry run prints them
|
|
34
39
|
#
|
|
35
40
|
# Env:
|
|
36
41
|
# PRUNE_LOG_ROOT override the log root (default $HOME/.claude/logs/multi-agent)
|
|
@@ -46,6 +51,7 @@ DELETE=0
|
|
|
46
51
|
OLDER_DAYS=0
|
|
47
52
|
PROJECT=""
|
|
48
53
|
TASK=""
|
|
54
|
+
ONLY_FROM=""
|
|
49
55
|
|
|
50
56
|
usage() {
|
|
51
57
|
grep -E '^#( |$)' "$0" | sed -E 's/^# ?//'
|
|
@@ -82,6 +88,13 @@ for arg in "$@"; do
|
|
|
82
88
|
exit 2
|
|
83
89
|
fi
|
|
84
90
|
;;
|
|
91
|
+
--only-from=*)
|
|
92
|
+
ONLY_FROM="${arg#*=}"
|
|
93
|
+
if [ ! -f "$ONLY_FROM" ]; then
|
|
94
|
+
echo "prune-logs: --only-from needs a readable list file" >&2
|
|
95
|
+
exit 2
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
85
98
|
-h | --help)
|
|
86
99
|
usage
|
|
87
100
|
exit 0
|
|
@@ -132,6 +145,31 @@ is_reserved_name() {
|
|
|
132
145
|
return 1
|
|
133
146
|
}
|
|
134
147
|
|
|
148
|
+
# A run that can still be resumed keeps its log however old it is: only a
|
|
149
|
+
# finished, abandoned or cancelled run (or a dir with no recorded status) is
|
|
150
|
+
# pruned; a failed run is one /multi-agent:resume picks up again. --task is
|
|
151
|
+
# an explicit target and bypasses this too.
|
|
152
|
+
run_status() {
|
|
153
|
+
local f="$1/agent-state.json"
|
|
154
|
+
[ -f "$f" ] || return 0
|
|
155
|
+
# A state that cannot be read is treated as resumable ("?"): pruning is
|
|
156
|
+
# the one step here that cannot be undone.
|
|
157
|
+
node -e 'try{const j=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(j&&typeof j==="object"&&typeof j.status==="string"?j.status:"")}catch{process.stdout.write("?")}' "$f" 2>/dev/null || printf '?'
|
|
158
|
+
}
|
|
159
|
+
is_resumable() {
|
|
160
|
+
case "$(run_status "$1")" in
|
|
161
|
+
"" | complete | completed | done | abandoned | cancelled) return 1 ;;
|
|
162
|
+
esac
|
|
163
|
+
return 0
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# --only-from narrows the match to a confirmed list; every other check still
|
|
167
|
+
# applies, so a listed folder that became active or resumable is spared.
|
|
168
|
+
ONLY_LIST=""
|
|
169
|
+
if [ -n "$ONLY_FROM" ]; then
|
|
170
|
+
ONLY_LIST=$'\n'"$(cat "$ONLY_FROM")"$'\n'
|
|
171
|
+
fi
|
|
172
|
+
|
|
135
173
|
MTIME_ARGS=()
|
|
136
174
|
if [ "$OLDER_DAYS" -gt 0 ]; then
|
|
137
175
|
MTIME_ARGS=(-mtime "+$OLDER_DAYS")
|
|
@@ -139,6 +177,7 @@ fi
|
|
|
139
177
|
|
|
140
178
|
matches=()
|
|
141
179
|
skipped_active=0
|
|
180
|
+
skipped_resumable=0
|
|
142
181
|
while IFS= read -r -d '' d; do
|
|
143
182
|
base="$(basename "$d")"
|
|
144
183
|
if is_reserved_name "$base"; then continue; fi
|
|
@@ -147,6 +186,12 @@ while IFS= read -r -d '' d; do
|
|
|
147
186
|
if [ -n "$TASK" ] && [ "$base" != "$TASK" ]; then continue; fi
|
|
148
187
|
# --project: the immediate parent dir name must equal the project
|
|
149
188
|
if [ -n "$PROJECT" ] && [ "$(basename "$(dirname "$d")")" != "$PROJECT" ]; then continue; fi
|
|
189
|
+
if [ -n "$ONLY_FROM" ]; then
|
|
190
|
+
case "$ONLY_LIST" in
|
|
191
|
+
*$'\n'"${d#"$LOG_ROOT"/}"$'\n'*) ;;
|
|
192
|
+
*) continue ;;
|
|
193
|
+
esac
|
|
194
|
+
fi
|
|
150
195
|
# Active-task grace: spare dirs a run touched in the last GRACE_MIN minutes
|
|
151
196
|
# so a concurrent run keeps a resumable log dir. --task is an explicit,
|
|
152
197
|
# deliberate target and bypasses the grace.
|
|
@@ -155,12 +200,16 @@ while IFS= read -r -d '' d; do
|
|
|
155
200
|
skipped_active=$((skipped_active + 1))
|
|
156
201
|
continue
|
|
157
202
|
fi
|
|
203
|
+
if [ -z "$TASK" ] && is_resumable "$d"; then
|
|
204
|
+
skipped_resumable=$((skipped_resumable + 1))
|
|
205
|
+
continue
|
|
206
|
+
fi
|
|
158
207
|
matches+=("$d")
|
|
159
208
|
done < <(find "$LOG_ROOT" -mindepth 1 -maxdepth 2 -type d ${MTIME_ARGS[@]+"${MTIME_ARGS[@]}"} -print0 2>/dev/null)
|
|
160
209
|
|
|
161
210
|
if [ "${#matches[@]}" -eq 0 ]; then
|
|
162
|
-
if [ "$skipped_active" -gt 0 ]; then
|
|
163
|
-
echo "prune-logs: only active task dirs matched (touched in the last ${GRACE_MIN}m) - spared, nothing to do"
|
|
211
|
+
if [ "$skipped_active" -gt 0 ] || [ "$skipped_resumable" -gt 0 ]; then
|
|
212
|
+
echo "prune-logs: only active or resumable task dirs matched ($skipped_active touched in the last ${GRACE_MIN}m, $skipped_resumable resumable) - spared, nothing to do"
|
|
164
213
|
else
|
|
165
214
|
echo "prune-logs: no task logs matched under $LOG_ROOT - nothing to do"
|
|
166
215
|
fi
|
|
@@ -179,6 +228,7 @@ if [ "$DELETE" -eq 0 ]; then
|
|
|
179
228
|
echo "DRY-RUN - would remove ${#matches[@]} task log dir(s) (frees ~${human}):"
|
|
180
229
|
for d in "${matches[@]}"; do echo " ${d#"$LOG_ROOT"/}"; done
|
|
181
230
|
[ "$skipped_active" -gt 0 ] && echo "Spared $skipped_active active dir(s) touched in the last ${GRACE_MIN}m."
|
|
231
|
+
[ "$skipped_resumable" -gt 0 ] && echo "Spared $skipped_resumable run(s) that can still be resumed."
|
|
182
232
|
echo "Audit trail + metrics corpus are preserved. Re-run with --yes to delete."
|
|
183
233
|
exit 0
|
|
184
234
|
fi
|
|
@@ -199,5 +249,6 @@ for d in "${matches[@]}"; do
|
|
|
199
249
|
done
|
|
200
250
|
spared_note=""
|
|
201
251
|
[ "$skipped_active" -gt 0 ] && spared_note=", spared $skipped_active active"
|
|
252
|
+
[ "$skipped_resumable" -gt 0 ] && spared_note="${spared_note}, spared $skipped_resumable resumable"
|
|
202
253
|
echo "══ prune-logs: removed ${removed}/${#matches[@]} task log dir(s)${spared_note}, freed ~${human} (audit + metrics preserved) ══"
|
|
203
254
|
exit 0
|
|
@@ -24,11 +24,17 @@
|
|
|
24
24
|
// node routine-registry.mjs remove --name <n> [--home <dir>]
|
|
25
25
|
//
|
|
26
26
|
// Exit: 0 ok, 1 error (validation / collision / not-found), 64 usage.
|
|
27
|
+
//
|
|
28
|
+
// listRoutines() and removeRoutine() are exported for contract-server.mjs
|
|
29
|
+
// (GET /v1/routines, DELETE /v1/routines/{name}), so the panel and the
|
|
30
|
+
// terminal commands run the same code; they throw RoutineError where the CLI
|
|
31
|
+
// prints and exits 1.
|
|
27
32
|
|
|
28
33
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, renameSync } from "node:fs";
|
|
29
34
|
import { join, dirname } from "node:path";
|
|
30
35
|
import { runMain } from "../lib/fatal.mjs";
|
|
31
36
|
import { hostCommand } from "../lib/user-facing.mjs";
|
|
37
|
+
import { invokedDirectly } from "../lib/invoked-directly.mjs";
|
|
32
38
|
|
|
33
39
|
const NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
34
40
|
const MAX_NAME = 40;
|
|
@@ -54,9 +60,10 @@ function parseArgs(argv) {
|
|
|
54
60
|
return out;
|
|
55
61
|
}
|
|
56
62
|
|
|
63
|
+
export class RoutineError extends Error {}
|
|
64
|
+
|
|
57
65
|
function fail(msg) {
|
|
58
|
-
|
|
59
|
-
process.exit(1);
|
|
66
|
+
throw new RoutineError(msg);
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
function homeDir(args) {
|
|
@@ -184,8 +191,20 @@ function cmdAdd(home, args) {
|
|
|
184
191
|
);
|
|
185
192
|
}
|
|
186
193
|
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
/** The registered routines, as `list --json` prints them. */
|
|
195
|
+
export function listRoutines(home) {
|
|
196
|
+
return getRoutines(readPrefs(home));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Remove a routine: its local-only command directory and its registry entry.
|
|
201
|
+
* A shipped command directory is refused, and so is a name that is neither
|
|
202
|
+
* registered nor on disk.
|
|
203
|
+
*
|
|
204
|
+
* @returns {{name: string, removedDir: boolean, removedEntry: boolean}}
|
|
205
|
+
*/
|
|
206
|
+
export function removeRoutine(home, name) {
|
|
207
|
+
if (!name || !NAME_RE.test(name) || name.length > MAX_NAME) fail(`invalid routine name: ${name}`);
|
|
189
208
|
const prefs = readPrefs(home);
|
|
190
209
|
const routines = getRoutines(prefs);
|
|
191
210
|
const idx = routines.findIndex((r) => r.name === name);
|
|
@@ -195,12 +214,19 @@ function cmdRemove(home, name) {
|
|
|
195
214
|
fail(`"${name}" is a shipped command, not a routine; refusing to remove`);
|
|
196
215
|
}
|
|
197
216
|
if (idx < 0 && !existsSync(dir)) fail(`no routine named "${name}"`);
|
|
198
|
-
|
|
217
|
+
const removedDir = existsSync(dir) && isLocalOnlyDir(dir);
|
|
218
|
+
if (removedDir) rmSync(dir, { recursive: true, force: true });
|
|
199
219
|
if (idx >= 0) {
|
|
200
220
|
routines.splice(idx, 1);
|
|
201
221
|
prefs.global.routines = routines;
|
|
202
222
|
writePrefs(home, prefs);
|
|
203
223
|
}
|
|
224
|
+
return { name, removedDir, removedEntry: idx >= 0 };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cmdRemove(home, name) {
|
|
228
|
+
if (!name) fail("--name is required");
|
|
229
|
+
removeRoutine(home, name);
|
|
204
230
|
console.log(
|
|
205
231
|
`routine-registry: removed "${name}" (clears from autocomplete on next session reload)`,
|
|
206
232
|
);
|
|
@@ -226,4 +252,14 @@ function main() {
|
|
|
226
252
|
}
|
|
227
253
|
}
|
|
228
254
|
|
|
229
|
-
|
|
255
|
+
if (invokedDirectly(import.meta.url)) {
|
|
256
|
+
runMain("routine-registry", () => {
|
|
257
|
+
try {
|
|
258
|
+
main();
|
|
259
|
+
} catch (err) {
|
|
260
|
+
if (!(err instanceof RoutineError)) throw err;
|
|
261
|
+
console.error(`routine-registry: ${err.message}`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|