patchwork-os 0.2.0-beta.4 → 0.2.0-beta.5
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/dist/index.js +74 -40
- package/dist/index.js.map +1 -1
- package/dist/recipeOrchestration.js +58 -8
- package/dist/recipeOrchestration.js.map +1 -1
- package/dist/recipeRoutes.js +95 -14
- package/dist/recipeRoutes.js.map +1 -1
- package/dist/server.js +36 -18
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/scripts/start-all.ps1 +209 -209
- package/scripts/start-orchestrator.ps1 +158 -158
package/dist/index.js
CHANGED
|
@@ -193,8 +193,12 @@ if (process.argv[2] === "--help" ||
|
|
|
193
193
|
` recipe --help Full recipe subcommand index\n\n` +
|
|
194
194
|
`Diagnose\n` +
|
|
195
195
|
` halts [--window 1h|24h|overnight|7d] Morning summary of recent recipe halts\n` +
|
|
196
|
+
` judgments [--window ...] [--recipe N] Recent judge-step verdicts across runs\n` +
|
|
196
197
|
` traces export Bundle approval / recipe / decision traces\n` +
|
|
197
198
|
` print-token [--port N] Print the active bridge auth token\n\n` +
|
|
199
|
+
`Safety\n` +
|
|
200
|
+
` kill-switch <engage|release|status> Block / resume write-tier tools across bridges\n` +
|
|
201
|
+
` panic [--reason "..."] Shorthand for kill-switch engage\n\n` +
|
|
198
202
|
`Daemon (no subcommand)\n` +
|
|
199
203
|
` --workspace <dir> Start the bridge in foreground\n` +
|
|
200
204
|
` --watch Auto-restart supervisor\n` +
|
|
@@ -1763,11 +1767,22 @@ if (process.argv[2] === "kill-switch") {
|
|
|
1763
1767
|
// form is `kill-switch engage`; this alias matches it so shell history six
|
|
1764
1768
|
// months later still makes sense. Does not accept sub-verbs — just runs engage.
|
|
1765
1769
|
if (process.argv[2] === "panic") {
|
|
1770
|
+
const extra = process.argv.slice(3); // e.g. --reason "..." --force-local
|
|
1771
|
+
// Guard against `panic --help` engaging the kill switch — a real
|
|
1772
|
+
// footgun if you tab-completed the verb to confirm syntax before
|
|
1773
|
+
// committing to the action. `panic` is an alias, so we honor --help
|
|
1774
|
+
// here ourselves rather than forwarding to kill-switch engage.
|
|
1775
|
+
if (extra.includes("--help") || extra.includes("-h")) {
|
|
1776
|
+
console.log('Usage: patchwork panic [--reason "..."] [--force-local]\n\n' +
|
|
1777
|
+
" Alias for `patchwork kill-switch engage` — blocks all write-tier\n" +
|
|
1778
|
+
" tool calls across every running bridge. Use --reason to leave a\n" +
|
|
1779
|
+
" note in the audit trail. Release with `patchwork kill-switch release`.\n");
|
|
1780
|
+
process.exit(0);
|
|
1781
|
+
}
|
|
1766
1782
|
// Spawn self with kill-switch engage to reuse the full handler without
|
|
1767
1783
|
// duplicating 200+ LOC. Passes through any flags (--reason, --force-local).
|
|
1768
1784
|
import("node:child_process").then(({ spawnSync }) => {
|
|
1769
1785
|
const self = process.argv[1] ?? process.execPath;
|
|
1770
|
-
const extra = process.argv.slice(3); // e.g. --reason "..." --force-local
|
|
1771
1786
|
const result = spawnSync(process.execPath, [self, "kill-switch", "engage", ...extra], { stdio: "inherit" });
|
|
1772
1787
|
process.exit(result.status ?? 1);
|
|
1773
1788
|
});
|
|
@@ -1834,14 +1849,6 @@ if (process.argv[2] === "halts") {
|
|
|
1834
1849
|
process.stderr.write("No running bridge found. Start one with `patchwork start` (or `--driver subprocess`).\n");
|
|
1835
1850
|
process.exit(2);
|
|
1836
1851
|
}
|
|
1837
|
-
// Single-bridge default: query the first. Multi-bridge users will
|
|
1838
|
-
// typically have one orchestrator anyway; expanding to fan-out is a
|
|
1839
|
-
// follow-up if needed.
|
|
1840
|
-
const lock = liveLocks[0];
|
|
1841
|
-
if (!lock) {
|
|
1842
|
-
process.stderr.write("No running bridge found.\n");
|
|
1843
|
-
process.exit(2);
|
|
1844
|
-
}
|
|
1845
1852
|
const sinceMs = windowSinceMs(window);
|
|
1846
1853
|
const params = [];
|
|
1847
1854
|
if (sinceMs != null)
|
|
@@ -1849,20 +1856,35 @@ if (process.argv[2] === "halts") {
|
|
|
1849
1856
|
if (recipeFilter)
|
|
1850
1857
|
params.push(`recipe=${encodeURIComponent(recipeFilter)}`);
|
|
1851
1858
|
const qs = params.length > 0 ? `?${params.join("&")}` : "";
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1859
|
+
// Walk live bridges in order; first responsive one wins. See the
|
|
1860
|
+
// matching block in the `judgments` handler — findAllLiveBridges
|
|
1861
|
+
// can include stale entries when a recycled PID still answers
|
|
1862
|
+
// `kill(pid, 0)` but the lock points at a dead bridge.
|
|
1863
|
+
let res = null;
|
|
1864
|
+
let lastStatus = 0;
|
|
1865
|
+
for (const lock of liveLocks) {
|
|
1866
|
+
const controller = new AbortController();
|
|
1867
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
1868
|
+
try {
|
|
1869
|
+
const candidate = await fetch(`http://127.0.0.1:${lock.port}/runs/halt-summary${qs}`, {
|
|
1870
|
+
headers: { Authorization: `Bearer ${lock.authToken}` },
|
|
1871
|
+
signal: controller.signal,
|
|
1872
|
+
});
|
|
1873
|
+
if (candidate.ok) {
|
|
1874
|
+
res = candidate;
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1877
|
+
lastStatus = candidate.status;
|
|
1878
|
+
}
|
|
1879
|
+
catch {
|
|
1880
|
+
/* unreachable lock — try next */
|
|
1881
|
+
}
|
|
1882
|
+
finally {
|
|
1883
|
+
clearTimeout(timer);
|
|
1884
|
+
}
|
|
1863
1885
|
}
|
|
1864
|
-
if (!res
|
|
1865
|
-
process.stderr.write(`
|
|
1886
|
+
if (!res) {
|
|
1887
|
+
process.stderr.write(`No live bridge served /runs/halt-summary (last status: ${lastStatus || "unreachable"}).\n`);
|
|
1866
1888
|
process.exit(1);
|
|
1867
1889
|
}
|
|
1868
1890
|
const summary = (await res.json());
|
|
@@ -1974,11 +1996,6 @@ if (process.argv[2] === "judgments") {
|
|
|
1974
1996
|
process.stderr.write("No running bridge found. Start one with `patchwork start` (or `--driver subprocess`).\n");
|
|
1975
1997
|
process.exit(2);
|
|
1976
1998
|
}
|
|
1977
|
-
const lock = liveLocks[0];
|
|
1978
|
-
if (!lock) {
|
|
1979
|
-
process.stderr.write("No running bridge found.\n");
|
|
1980
|
-
process.exit(2);
|
|
1981
|
-
}
|
|
1982
1999
|
const sinceMs = windowSinceMs(window);
|
|
1983
2000
|
const params = [];
|
|
1984
2001
|
if (sinceMs != null)
|
|
@@ -1986,20 +2003,37 @@ if (process.argv[2] === "judgments") {
|
|
|
1986
2003
|
if (recipeFilter)
|
|
1987
2004
|
params.push(`recipe=${encodeURIComponent(recipeFilter)}`);
|
|
1988
2005
|
const qs = params.length > 0 ? `?${params.join("&")}` : "";
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2006
|
+
// Walk live bridges in order; the first responsive one wins.
|
|
2007
|
+
// findAllLiveBridges uses `kill(pid, 0)` for liveness, which
|
|
2008
|
+
// returns true for any recycled PID — so liveLocks can contain
|
|
2009
|
+
// stale entries from dead bridges. Previously we picked [0]
|
|
2010
|
+
// unconditionally and surfaced a confusing 404; now we try each
|
|
2011
|
+
// and only fall through to the error path when *all* fail.
|
|
2012
|
+
let res = null;
|
|
2013
|
+
let lastStatus = 0;
|
|
2014
|
+
for (const lock of liveLocks) {
|
|
2015
|
+
const controller = new AbortController();
|
|
2016
|
+
const timer = setTimeout(() => controller.abort(), 10_000);
|
|
2017
|
+
try {
|
|
2018
|
+
const candidate = await fetch(`http://127.0.0.1:${lock.port}/runs/judge-summary${qs}`, {
|
|
2019
|
+
headers: { Authorization: `Bearer ${lock.authToken}` },
|
|
2020
|
+
signal: controller.signal,
|
|
2021
|
+
});
|
|
2022
|
+
if (candidate.ok) {
|
|
2023
|
+
res = candidate;
|
|
2024
|
+
break;
|
|
2025
|
+
}
|
|
2026
|
+
lastStatus = candidate.status;
|
|
2027
|
+
}
|
|
2028
|
+
catch {
|
|
2029
|
+
/* unreachable lock — try next */
|
|
2030
|
+
}
|
|
2031
|
+
finally {
|
|
2032
|
+
clearTimeout(timer);
|
|
2033
|
+
}
|
|
2000
2034
|
}
|
|
2001
|
-
if (!res
|
|
2002
|
-
process.stderr.write(`
|
|
2035
|
+
if (!res) {
|
|
2036
|
+
process.stderr.write(`No live bridge served /runs/judge-summary (last status: ${lastStatus || "unreachable"}).\n`);
|
|
2003
2037
|
process.exit(1);
|
|
2004
2038
|
}
|
|
2005
2039
|
const summary = (await res.json());
|