cruo-agent 0.1.5 → 0.1.6
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/VERSION +1 -1
- package/dist/cli.js +443 -79
- package/package.json +1 -1
package/dist/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.6
|
package/dist/cli.js
CHANGED
|
@@ -16166,7 +16166,7 @@ var require_FunctionsClient = __commonJS({
|
|
|
16166
16166
|
let timeoutId;
|
|
16167
16167
|
let timeoutController;
|
|
16168
16168
|
try {
|
|
16169
|
-
const { headers, method, body: functionArgs, signal, timeout } = options2;
|
|
16169
|
+
const { headers, method, body: functionArgs, signal: signal2, timeout } = options2;
|
|
16170
16170
|
let _headers = {};
|
|
16171
16171
|
let { region } = options2;
|
|
16172
16172
|
if (!region) {
|
|
@@ -16199,13 +16199,13 @@ var require_FunctionsClient = __commonJS({
|
|
|
16199
16199
|
body = functionArgs;
|
|
16200
16200
|
}
|
|
16201
16201
|
}
|
|
16202
|
-
let effectiveSignal =
|
|
16202
|
+
let effectiveSignal = signal2;
|
|
16203
16203
|
if (timeout) {
|
|
16204
16204
|
timeoutController = new AbortController();
|
|
16205
16205
|
timeoutId = setTimeout(() => timeoutController.abort(), timeout);
|
|
16206
|
-
if (
|
|
16206
|
+
if (signal2) {
|
|
16207
16207
|
effectiveSignal = timeoutController.signal;
|
|
16208
|
-
|
|
16208
|
+
signal2.addEventListener("abort", () => timeoutController.abort());
|
|
16209
16209
|
} else {
|
|
16210
16210
|
effectiveSignal = timeoutController.signal;
|
|
16211
16211
|
}
|
|
@@ -16291,21 +16291,21 @@ var require_main = __commonJS({
|
|
|
16291
16291
|
});
|
|
16292
16292
|
|
|
16293
16293
|
// ../../node_modules/.pnpm/@supabase+postgrest-js@2.110.7/node_modules/@supabase/postgrest-js/dist/index.mjs
|
|
16294
|
-
function sleep(ms2,
|
|
16294
|
+
function sleep(ms2, signal2) {
|
|
16295
16295
|
return new Promise((resolve2) => {
|
|
16296
|
-
if (
|
|
16296
|
+
if (signal2 === null || signal2 === void 0 ? void 0 : signal2.aborted) {
|
|
16297
16297
|
resolve2();
|
|
16298
16298
|
return;
|
|
16299
16299
|
}
|
|
16300
16300
|
const id = setTimeout(() => {
|
|
16301
|
-
|
|
16301
|
+
signal2 === null || signal2 === void 0 || signal2.removeEventListener("abort", onAbort);
|
|
16302
16302
|
resolve2();
|
|
16303
16303
|
}, ms2);
|
|
16304
16304
|
function onAbort() {
|
|
16305
16305
|
clearTimeout(id);
|
|
16306
16306
|
resolve2();
|
|
16307
16307
|
}
|
|
16308
|
-
|
|
16308
|
+
signal2 === null || signal2 === void 0 || signal2.addEventListener("abort", onAbort);
|
|
16309
16309
|
});
|
|
16310
16310
|
}
|
|
16311
16311
|
function shouldRetry(method, status, attemptCount, retryEnabled) {
|
|
@@ -17334,8 +17334,8 @@ ${cause.stack}`;
|
|
|
17334
17334
|
*
|
|
17335
17335
|
* ```
|
|
17336
17336
|
*/
|
|
17337
|
-
abortSignal(
|
|
17338
|
-
this.signal =
|
|
17337
|
+
abortSignal(signal2) {
|
|
17338
|
+
this.signal = signal2;
|
|
17339
17339
|
return this;
|
|
17340
17340
|
}
|
|
17341
17341
|
/**
|
|
@@ -23494,8 +23494,8 @@ var require_RealtimeChannel = __commonJS({
|
|
|
23494
23494
|
}
|
|
23495
23495
|
/** @internal */
|
|
23496
23496
|
_notThisChannelEvent(event, ref) {
|
|
23497
|
-
const { close, error: error51, leave, join:
|
|
23498
|
-
const events = [close, error51, leave,
|
|
23497
|
+
const { close, error: error51, leave, join: join5 } = constants_1.CHANNEL_EVENTS;
|
|
23498
|
+
const events = [close, error51, leave, join5];
|
|
23499
23499
|
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
|
23500
23500
|
}
|
|
23501
23501
|
/** @internal */
|
|
@@ -30517,7 +30517,7 @@ var require_webauthn = __commonJS({
|
|
|
30517
30517
|
* @see {@link https://w3c.github.io/webauthn/#sctn-credential-creation W3C WebAuthn Spec - Credential Creation}
|
|
30518
30518
|
* @see {@link https://w3c.github.io/webauthn/#sctn-verifying-assertion W3C WebAuthn Spec - Verifying Assertion}
|
|
30519
30519
|
*/
|
|
30520
|
-
async _challenge({ factorId, webauthn, friendlyName, signal }, overrides) {
|
|
30520
|
+
async _challenge({ factorId, webauthn, friendlyName, signal: signal2 }, overrides) {
|
|
30521
30521
|
var _a3;
|
|
30522
30522
|
try {
|
|
30523
30523
|
const { data: challengeResponse, error: challengeError } = await this.client.mfa.challenge({
|
|
@@ -30527,7 +30527,7 @@ var require_webauthn = __commonJS({
|
|
|
30527
30527
|
if (!challengeResponse) {
|
|
30528
30528
|
return { data: null, error: challengeError };
|
|
30529
30529
|
}
|
|
30530
|
-
const abortSignal =
|
|
30530
|
+
const abortSignal = signal2 !== null && signal2 !== void 0 ? signal2 : exports.webAuthnAbortService.createNewAbortSignal();
|
|
30531
30531
|
if (challengeResponse.webauthn.type === "create") {
|
|
30532
30532
|
const { user } = challengeResponse.webauthn.credential_options.publicKey;
|
|
30533
30533
|
if (!user.name) {
|
|
@@ -30631,7 +30631,7 @@ var require_webauthn = __commonJS({
|
|
|
30631
30631
|
* @see {@link https://w3c.github.io/webauthn/#sctn-authentication W3C WebAuthn Spec - Authentication Ceremony}
|
|
30632
30632
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialRequestOptions MDN - PublicKeyCredentialRequestOptions}
|
|
30633
30633
|
*/
|
|
30634
|
-
async _authenticate({ factorId, webauthn: { rpId = typeof window !== "undefined" ? window.location.hostname : void 0, rpOrigins = typeof window !== "undefined" ? [window.location.origin] : void 0, signal } = {} }, overrides) {
|
|
30634
|
+
async _authenticate({ factorId, webauthn: { rpId = typeof window !== "undefined" ? window.location.hostname : void 0, rpOrigins = typeof window !== "undefined" ? [window.location.origin] : void 0, signal: signal2 } = {} }, overrides) {
|
|
30635
30635
|
if (!rpId) {
|
|
30636
30636
|
return {
|
|
30637
30637
|
data: null,
|
|
@@ -30648,7 +30648,7 @@ var require_webauthn = __commonJS({
|
|
|
30648
30648
|
const { data: challengeResponse, error: challengeError } = await this.challenge({
|
|
30649
30649
|
factorId,
|
|
30650
30650
|
webauthn: { rpId, rpOrigins },
|
|
30651
|
-
signal
|
|
30651
|
+
signal: signal2
|
|
30652
30652
|
}, { request: overrides });
|
|
30653
30653
|
if (!challengeResponse) {
|
|
30654
30654
|
return { data: null, error: challengeError };
|
|
@@ -30689,7 +30689,7 @@ var require_webauthn = __commonJS({
|
|
|
30689
30689
|
* @see {@link https://w3c.github.io/webauthn/#sctn-registering-a-new-credential W3C WebAuthn Spec - Registration Ceremony}
|
|
30690
30690
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredentialCreationOptions MDN - PublicKeyCredentialCreationOptions}
|
|
30691
30691
|
*/
|
|
30692
|
-
async _register({ friendlyName, webauthn: { rpId = typeof window !== "undefined" ? window.location.hostname : void 0, rpOrigins = typeof window !== "undefined" ? [window.location.origin] : void 0, signal } = {} }, overrides) {
|
|
30692
|
+
async _register({ friendlyName, webauthn: { rpId = typeof window !== "undefined" ? window.location.hostname : void 0, rpOrigins = typeof window !== "undefined" ? [window.location.origin] : void 0, signal: signal2 } = {} }, overrides) {
|
|
30693
30693
|
if (!rpId) {
|
|
30694
30694
|
return {
|
|
30695
30695
|
data: null,
|
|
@@ -30717,7 +30717,7 @@ var require_webauthn = __commonJS({
|
|
|
30717
30717
|
factorId: factor.id,
|
|
30718
30718
|
friendlyName: factor.friendly_name,
|
|
30719
30719
|
webauthn: { rpId, rpOrigins },
|
|
30720
|
-
signal
|
|
30720
|
+
signal: signal2
|
|
30721
30721
|
}, {
|
|
30722
30722
|
create: overrides
|
|
30723
30723
|
});
|
|
@@ -35593,10 +35593,10 @@ var require_GoTrueClient = __commonJS({
|
|
|
35593
35593
|
return this._returnResult({ data: null, error: optionsError });
|
|
35594
35594
|
}
|
|
35595
35595
|
const publicKeyOptions = (0, webauthn_1.deserializeCredentialRequestOptions)(options2.options);
|
|
35596
|
-
const
|
|
35596
|
+
const signal2 = (_c = (_b = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _b === void 0 ? void 0 : _b.signal) !== null && _c !== void 0 ? _c : webauthn_1.webAuthnAbortService.createNewAbortSignal();
|
|
35597
35597
|
const { data: credential, error: credentialError } = await (0, webauthn_1.getCredential)({
|
|
35598
35598
|
publicKey: publicKeyOptions,
|
|
35599
|
-
signal
|
|
35599
|
+
signal: signal2
|
|
35600
35600
|
});
|
|
35601
35601
|
if (credentialError || !credential) {
|
|
35602
35602
|
return this._returnResult({
|
|
@@ -35641,10 +35641,10 @@ var require_GoTrueClient = __commonJS({
|
|
|
35641
35641
|
return this._returnResult({ data: null, error: optionsError });
|
|
35642
35642
|
}
|
|
35643
35643
|
const publicKeyOptions = (0, webauthn_1.deserializeCredentialCreationOptions)(options2.options);
|
|
35644
|
-
const
|
|
35644
|
+
const signal2 = (_b = (_a3 = credentials === null || credentials === void 0 ? void 0 : credentials.options) === null || _a3 === void 0 ? void 0 : _a3.signal) !== null && _b !== void 0 ? _b : webauthn_1.webAuthnAbortService.createNewAbortSignal();
|
|
35645
35645
|
const { data: credential, error: credentialError } = await (0, webauthn_1.createCredential)({
|
|
35646
35646
|
publicKey: publicKeyOptions,
|
|
35647
|
-
signal
|
|
35647
|
+
signal: signal2
|
|
35648
35648
|
});
|
|
35649
35649
|
if (credentialError || !credential) {
|
|
35650
35650
|
return this._returnResult({
|
|
@@ -36027,8 +36027,8 @@ function parseTraceParent(traceparent) {
|
|
|
36027
36027
|
isSampled: (parseInt(traceFlags, 16) & 1) === 1
|
|
36028
36028
|
};
|
|
36029
36029
|
}
|
|
36030
|
-
function shouldPropagateToTarget(targetUrl,
|
|
36031
|
-
if (!targetUrl || !
|
|
36030
|
+
function shouldPropagateToTarget(targetUrl, targets2) {
|
|
36031
|
+
if (!targetUrl || !targets2 || targets2.length === 0) return false;
|
|
36032
36032
|
let url3;
|
|
36033
36033
|
if (targetUrl instanceof URL) url3 = targetUrl;
|
|
36034
36034
|
else try {
|
|
@@ -36036,7 +36036,7 @@ function shouldPropagateToTarget(targetUrl, targets) {
|
|
|
36036
36036
|
} catch (error51) {
|
|
36037
36037
|
return false;
|
|
36038
36038
|
}
|
|
36039
|
-
for (const target of
|
|
36039
|
+
for (const target of targets2) try {
|
|
36040
36040
|
if (typeof target === "string") {
|
|
36041
36041
|
if (matchStringTarget(url3.hostname, target)) return true;
|
|
36042
36042
|
} else if (target instanceof RegExp) {
|
|
@@ -36060,15 +36060,15 @@ function matchStringTarget(hostname3, target) {
|
|
|
36060
36060
|
return false;
|
|
36061
36061
|
}
|
|
36062
36062
|
function getDefaultPropagationTargets(supabaseUrl) {
|
|
36063
|
-
const
|
|
36063
|
+
const targets2 = [];
|
|
36064
36064
|
try {
|
|
36065
36065
|
const url3 = new URL(supabaseUrl);
|
|
36066
|
-
|
|
36066
|
+
targets2.push(url3.hostname);
|
|
36067
36067
|
} catch (error51) {
|
|
36068
36068
|
}
|
|
36069
|
-
|
|
36070
|
-
|
|
36071
|
-
return
|
|
36069
|
+
targets2.push("*.supabase.co", "*.supabase.in");
|
|
36070
|
+
targets2.push("localhost", "127.0.0.1", "[::1]");
|
|
36071
|
+
return targets2;
|
|
36072
36072
|
}
|
|
36073
36073
|
function _typeof3(o) {
|
|
36074
36074
|
"@babel/helpers - typeof";
|
|
@@ -36121,8 +36121,8 @@ function _objectSpread23(e) {
|
|
|
36121
36121
|
}
|
|
36122
36122
|
return e;
|
|
36123
36123
|
}
|
|
36124
|
-
async function getTraceHeaders(input,
|
|
36125
|
-
if (!shouldPropagateToTarget(typeof input === "string" ? input : input instanceof URL ? input : input.url,
|
|
36124
|
+
async function getTraceHeaders(input, targets2, respectSampling) {
|
|
36125
|
+
if (!shouldPropagateToTarget(typeof input === "string" ? input : input instanceof URL ? input : input.url, targets2)) return null;
|
|
36126
36126
|
const traceContext = await extractTraceContext();
|
|
36127
36127
|
if (!traceContext || !traceContext.traceparent) return null;
|
|
36128
36128
|
if (respectSampling) {
|
|
@@ -45957,6 +45957,110 @@ var init_options = __esm({
|
|
|
45957
45957
|
}
|
|
45958
45958
|
});
|
|
45959
45959
|
|
|
45960
|
+
// src/process.ts
|
|
45961
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
45962
|
+
import { execFile } from "node:child_process";
|
|
45963
|
+
import { homedir } from "node:os";
|
|
45964
|
+
import { join } from "node:path";
|
|
45965
|
+
import { promisify } from "node:util";
|
|
45966
|
+
function safeName(agent) {
|
|
45967
|
+
const cleaned = agent.toLowerCase().replace(/[^a-z0-9._-]/g, "").replace(/^\.+/, "");
|
|
45968
|
+
return cleaned === "" ? "default" : cleaned;
|
|
45969
|
+
}
|
|
45970
|
+
async function processStartedAt(pid) {
|
|
45971
|
+
try {
|
|
45972
|
+
const { stdout } = await exec("ps", ["-p", String(pid), "-o", "lstart="]);
|
|
45973
|
+
const value = stdout.trim();
|
|
45974
|
+
return value === "" ? null : value;
|
|
45975
|
+
} catch {
|
|
45976
|
+
return null;
|
|
45977
|
+
}
|
|
45978
|
+
}
|
|
45979
|
+
function isAlive(pid) {
|
|
45980
|
+
try {
|
|
45981
|
+
process.kill(pid, 0);
|
|
45982
|
+
return true;
|
|
45983
|
+
} catch (error51) {
|
|
45984
|
+
return error51.code === "EPERM";
|
|
45985
|
+
}
|
|
45986
|
+
}
|
|
45987
|
+
async function writeRecord(record2) {
|
|
45988
|
+
await mkdir(runDir(), { recursive: true });
|
|
45989
|
+
await writeFile(recordPath(record2.agent), JSON.stringify(record2, null, 2) + "\n", {
|
|
45990
|
+
mode: 384
|
|
45991
|
+
});
|
|
45992
|
+
}
|
|
45993
|
+
async function removeRecord(agent) {
|
|
45994
|
+
await rm(recordPath(agent), { force: true });
|
|
45995
|
+
}
|
|
45996
|
+
async function readRecord(agent) {
|
|
45997
|
+
try {
|
|
45998
|
+
const parsed = JSON.parse(await readFile(recordPath(agent), "utf8"));
|
|
45999
|
+
return typeof parsed?.pid === "number" ? parsed : null;
|
|
46000
|
+
} catch {
|
|
46001
|
+
return null;
|
|
46002
|
+
}
|
|
46003
|
+
}
|
|
46004
|
+
async function liveness(agent, tidy = false) {
|
|
46005
|
+
const record2 = await readRecord(agent);
|
|
46006
|
+
if (!record2) return { state: "absent" };
|
|
46007
|
+
if (!isAlive(record2.pid)) {
|
|
46008
|
+
if (tidy) await removeRecord(agent);
|
|
46009
|
+
return { state: "gone", record: record2 };
|
|
46010
|
+
}
|
|
46011
|
+
const now = await processStartedAt(record2.pid);
|
|
46012
|
+
if (record2.procStartedAt !== null && now !== null && now !== record2.procStartedAt) {
|
|
46013
|
+
if (tidy) await removeRecord(agent);
|
|
46014
|
+
return { state: "recycled", record: record2 };
|
|
46015
|
+
}
|
|
46016
|
+
return { state: "running", record: record2 };
|
|
46017
|
+
}
|
|
46018
|
+
async function listRecords() {
|
|
46019
|
+
let names;
|
|
46020
|
+
try {
|
|
46021
|
+
names = (await readdir(runDir())).filter((n2) => n2.endsWith(".json"));
|
|
46022
|
+
} catch {
|
|
46023
|
+
return [];
|
|
46024
|
+
}
|
|
46025
|
+
const out = [];
|
|
46026
|
+
for (const file2 of names.sort()) {
|
|
46027
|
+
const agent = file2.slice(0, -".json".length);
|
|
46028
|
+
out.push({ agent, live: await liveness(agent) });
|
|
46029
|
+
}
|
|
46030
|
+
return out;
|
|
46031
|
+
}
|
|
46032
|
+
function signal(pid, which) {
|
|
46033
|
+
try {
|
|
46034
|
+
process.kill(pid, SIGNALS[which]);
|
|
46035
|
+
return true;
|
|
46036
|
+
} catch {
|
|
46037
|
+
return false;
|
|
46038
|
+
}
|
|
46039
|
+
}
|
|
46040
|
+
function since(iso) {
|
|
46041
|
+
const ms2 = Date.now() - Date.parse(iso);
|
|
46042
|
+
if (!Number.isFinite(ms2) || ms2 < 0) return "?";
|
|
46043
|
+
if (ms2 < 6e4) return `${Math.round(ms2 / 1e3)}s`;
|
|
46044
|
+
if (ms2 < 36e5) return `${Math.round(ms2 / 6e4)}m`;
|
|
46045
|
+
if (ms2 < 864e5) return `${Math.round(ms2 / 36e5)}h`;
|
|
46046
|
+
return `${Math.round(ms2 / 864e5)}d`;
|
|
46047
|
+
}
|
|
46048
|
+
var exec, runDir, logDir, recordPath, SIGNALS;
|
|
46049
|
+
var init_process = __esm({
|
|
46050
|
+
"src/process.ts"() {
|
|
46051
|
+
"use strict";
|
|
46052
|
+
exec = promisify(execFile);
|
|
46053
|
+
runDir = () => join(homedir(), ".cruo", "run");
|
|
46054
|
+
logDir = () => join(homedir(), ".cruo", "logs");
|
|
46055
|
+
recordPath = (agent) => join(runDir(), `${safeName(agent)}.json`);
|
|
46056
|
+
SIGNALS = {
|
|
46057
|
+
stop: "SIGTERM",
|
|
46058
|
+
pause: "SIGUSR1",
|
|
46059
|
+
resume: "SIGUSR2"
|
|
46060
|
+
};
|
|
46061
|
+
}
|
|
46062
|
+
});
|
|
46063
|
+
|
|
45960
46064
|
// src/env.ts
|
|
45961
46065
|
function applyCloudDefaults() {
|
|
45962
46066
|
const url3 = process.env.SUPABASE_URL ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
@@ -46048,8 +46152,8 @@ function compact(value) {
|
|
|
46048
46152
|
}
|
|
46049
46153
|
async function reportUsage(opts) {
|
|
46050
46154
|
const ctx = await contextOrExplain();
|
|
46051
|
-
const
|
|
46052
|
-
let query = ctx.client.from("harness_runs").select("*").gte("started_at",
|
|
46155
|
+
const since2 = new Date(Date.now() - opts.days * 864e5).toISOString();
|
|
46156
|
+
let query = ctx.client.from("harness_runs").select("*").gte("started_at", since2).order("started_at", { ascending: false });
|
|
46053
46157
|
let issueTitle = null;
|
|
46054
46158
|
if (opts.issue) {
|
|
46055
46159
|
const [key, num3] = opts.issue.split("-");
|
|
@@ -46363,12 +46467,12 @@ var init_pacing = __esm({
|
|
|
46363
46467
|
});
|
|
46364
46468
|
|
|
46365
46469
|
// src/worktree.ts
|
|
46366
|
-
import { execFile } from "node:child_process";
|
|
46367
|
-
import { mkdir, readdir, rm, stat } from "node:fs/promises";
|
|
46368
|
-
import { join } from "node:path";
|
|
46369
|
-
import { promisify } from "node:util";
|
|
46470
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
46471
|
+
import { mkdir as mkdir2, readdir as readdir2, rm as rm2, stat } from "node:fs/promises";
|
|
46472
|
+
import { join as join2 } from "node:path";
|
|
46473
|
+
import { promisify as promisify2 } from "node:util";
|
|
46370
46474
|
async function git(repo, args) {
|
|
46371
|
-
const { stdout } = await
|
|
46475
|
+
const { stdout } = await exec2("git", ["-C", repo, ...args], { maxBuffer: 8 * 1024 * 1024 });
|
|
46372
46476
|
return stdout.trim();
|
|
46373
46477
|
}
|
|
46374
46478
|
async function hasRef(repo, ref) {
|
|
@@ -46407,16 +46511,16 @@ async function remoteUrl(repo, remote) {
|
|
|
46407
46511
|
}
|
|
46408
46512
|
async function createWorktree(ref, config3) {
|
|
46409
46513
|
const branch = branchFor(ref);
|
|
46410
|
-
const path =
|
|
46411
|
-
await
|
|
46412
|
-
await
|
|
46514
|
+
const path = join2(config3.root, ref);
|
|
46515
|
+
await mkdir2(config3.root, { recursive: true });
|
|
46516
|
+
await rm2(path, { recursive: true, force: true });
|
|
46413
46517
|
await git(config3.repo, ["worktree", "prune"]);
|
|
46414
46518
|
const existing = await hasRef(config3.repo, branch);
|
|
46415
46519
|
const startPoint = existing ? [branch] : ["-b", branch, config3.base];
|
|
46416
46520
|
await git(config3.repo, ["worktree", "add", path, ...startPoint]);
|
|
46417
46521
|
const startCommit = await git(config3.repo, ["rev-parse", branch]);
|
|
46418
46522
|
if (config3.prepare) {
|
|
46419
|
-
await
|
|
46523
|
+
await exec2("/bin/sh", ["-c", config3.prepare], {
|
|
46420
46524
|
cwd: path,
|
|
46421
46525
|
maxBuffer: 32 * 1024 * 1024,
|
|
46422
46526
|
timeout: config3.prepareTimeoutMs ?? 15 * 6e4,
|
|
@@ -46459,7 +46563,7 @@ Cruo-Wip: ${reason}
|
|
|
46459
46563
|
},
|
|
46460
46564
|
async remove() {
|
|
46461
46565
|
await git(config3.repo, ["worktree", "remove", "--force", path]).catch(async () => {
|
|
46462
|
-
await
|
|
46566
|
+
await rm2(path, { recursive: true, force: true });
|
|
46463
46567
|
});
|
|
46464
46568
|
await git(config3.repo, ["worktree", "prune"]);
|
|
46465
46569
|
}
|
|
@@ -46469,14 +46573,14 @@ async function sweep(config3, maxRunMs = 0) {
|
|
|
46469
46573
|
await git(config3.repo, ["worktree", "prune"]);
|
|
46470
46574
|
let entries;
|
|
46471
46575
|
try {
|
|
46472
|
-
entries = await
|
|
46576
|
+
entries = await readdir2(config3.root);
|
|
46473
46577
|
} catch {
|
|
46474
46578
|
return [];
|
|
46475
46579
|
}
|
|
46476
46580
|
const cutoff = Date.now() - Math.max(maxRunMs + 5 * 6e4, 60 * 6e4);
|
|
46477
46581
|
const removed = [];
|
|
46478
46582
|
for (const name of entries) {
|
|
46479
|
-
const path =
|
|
46583
|
+
const path = join2(config3.root, name);
|
|
46480
46584
|
let touched;
|
|
46481
46585
|
try {
|
|
46482
46586
|
touched = (await stat(path)).mtimeMs;
|
|
@@ -46485,18 +46589,18 @@ async function sweep(config3, maxRunMs = 0) {
|
|
|
46485
46589
|
}
|
|
46486
46590
|
if (touched >= cutoff) continue;
|
|
46487
46591
|
await git(config3.repo, ["worktree", "remove", "--force", path]).catch(async () => {
|
|
46488
|
-
await
|
|
46592
|
+
await rm2(path, { recursive: true, force: true });
|
|
46489
46593
|
});
|
|
46490
46594
|
removed.push(name);
|
|
46491
46595
|
}
|
|
46492
46596
|
if (removed.length) await git(config3.repo, ["worktree", "prune"]);
|
|
46493
46597
|
return removed;
|
|
46494
46598
|
}
|
|
46495
|
-
var
|
|
46599
|
+
var exec2;
|
|
46496
46600
|
var init_worktree = __esm({
|
|
46497
46601
|
"src/worktree.ts"() {
|
|
46498
46602
|
"use strict";
|
|
46499
|
-
|
|
46603
|
+
exec2 = promisify2(execFile2);
|
|
46500
46604
|
}
|
|
46501
46605
|
});
|
|
46502
46606
|
|
|
@@ -46504,9 +46608,9 @@ var init_worktree = __esm({
|
|
|
46504
46608
|
var supervisor_exports = {};
|
|
46505
46609
|
import { spawn } from "node:child_process";
|
|
46506
46610
|
import { createRequire } from "node:module";
|
|
46507
|
-
import { mkdtemp, readdir as
|
|
46508
|
-
import { homedir, tmpdir } from "node:os";
|
|
46509
|
-
import { join as
|
|
46611
|
+
import { mkdtemp, readdir as readdir3, readFile as readFile2, rm as rm3, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
46612
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
46613
|
+
import { join as join3 } from "node:path";
|
|
46510
46614
|
import { fileURLToPath } from "node:url";
|
|
46511
46615
|
async function identify(ctx) {
|
|
46512
46616
|
const { data: member, error: error51 } = await ctx.client.schema("public").from("members").select("kind, job_function, capabilities").eq("workspace_id", ctx.workspaceId).eq("user_id", ctx.userId).maybeSingle();
|
|
@@ -46531,7 +46635,7 @@ function resolveWorktreeRoot(identity) {
|
|
|
46531
46635
|
if (options.worktreeRoot) return options.worktreeRoot;
|
|
46532
46636
|
const slug = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
46533
46637
|
const name = slug(identity.name) || slug(identity.email.split("@")[0] ?? "") || "agent";
|
|
46534
|
-
return
|
|
46638
|
+
return join3(homedir2(), ".cruo-work", name);
|
|
46535
46639
|
}
|
|
46536
46640
|
async function pollAssigned(ctx) {
|
|
46537
46641
|
const { data: issues, error: error51 } = await ctx.client.from("issues").select("*").eq("assignee_id", ctx.userId).order("created_at", { ascending: true });
|
|
@@ -46679,13 +46783,13 @@ async function markRead(ctx, notificationIds) {
|
|
|
46679
46783
|
}
|
|
46680
46784
|
async function writeMcpConfig() {
|
|
46681
46785
|
const session = await childSession();
|
|
46682
|
-
const dir = await mkdtemp(
|
|
46683
|
-
const path =
|
|
46786
|
+
const dir = await mkdtemp(join3(tmpdir(), CONFIG_DIR_PREFIX));
|
|
46787
|
+
const path = join3(dir, "mcp.json");
|
|
46684
46788
|
const here = fileURLToPath(new URL(".", import.meta.url));
|
|
46685
46789
|
const bundled = /[/\\]dist[/\\]?$/.test(here);
|
|
46686
|
-
const server =
|
|
46790
|
+
const server = join3(here, bundled ? "index.js" : "index.ts");
|
|
46687
46791
|
const loader = bundled ? null : createRequire(import.meta.url).resolve("tsx");
|
|
46688
|
-
await
|
|
46792
|
+
await writeFile2(
|
|
46689
46793
|
path,
|
|
46690
46794
|
JSON.stringify(
|
|
46691
46795
|
{
|
|
@@ -46709,7 +46813,7 @@ async function writeMcpConfig() {
|
|
|
46709
46813
|
),
|
|
46710
46814
|
{ mode: 384 }
|
|
46711
46815
|
);
|
|
46712
|
-
return { path, cleanup: () =>
|
|
46816
|
+
return { path, cleanup: () => rm3(dir, { recursive: true, force: true }) };
|
|
46713
46817
|
}
|
|
46714
46818
|
async function sweepStaleConfigs() {
|
|
46715
46819
|
const dir = tmpdir();
|
|
@@ -46717,18 +46821,18 @@ async function sweepStaleConfigs() {
|
|
|
46717
46821
|
const cutoff = Date.now() - Math.max(maxRunMs + 5 * 6e4, 60 * 6e4);
|
|
46718
46822
|
let entries;
|
|
46719
46823
|
try {
|
|
46720
|
-
entries = await
|
|
46824
|
+
entries = await readdir3(dir);
|
|
46721
46825
|
} catch {
|
|
46722
46826
|
return 0;
|
|
46723
46827
|
}
|
|
46724
46828
|
let removed = 0;
|
|
46725
46829
|
for (const name of entries) {
|
|
46726
46830
|
if (!name.startsWith(CONFIG_DIR_PREFIX)) continue;
|
|
46727
|
-
const path =
|
|
46831
|
+
const path = join3(dir, name);
|
|
46728
46832
|
try {
|
|
46729
46833
|
const info = await stat2(path);
|
|
46730
46834
|
if (info.mtimeMs >= cutoff) continue;
|
|
46731
|
-
await
|
|
46835
|
+
await rm3(path, { recursive: true, force: true });
|
|
46732
46836
|
removed += 1;
|
|
46733
46837
|
} catch {
|
|
46734
46838
|
}
|
|
@@ -46738,7 +46842,7 @@ async function sweepStaleConfigs() {
|
|
|
46738
46842
|
async function preflight() {
|
|
46739
46843
|
const { path: mcpConfig, cleanup } = await writeMcpConfig();
|
|
46740
46844
|
try {
|
|
46741
|
-
const parsed = JSON.parse(await
|
|
46845
|
+
const parsed = JSON.parse(await readFile2(mcpConfig, "utf8"));
|
|
46742
46846
|
const { command, args, env } = parsed.mcpServers.cruo;
|
|
46743
46847
|
await new Promise((resolve2, reject) => {
|
|
46744
46848
|
const child = spawn(command, args, {
|
|
@@ -47010,8 +47114,8 @@ async function clearAttempts(ctx, hit) {
|
|
|
47010
47114
|
const { error: error51 } = await ctx.client.from("issue_attempts").delete().eq("issue_id", hit.issue.id).eq("agent_id", ctx.userId).eq("reason", hit.reason);
|
|
47011
47115
|
if (error51) log(` could not clear attempts on ${hit.ref}: ${error51.message}`);
|
|
47012
47116
|
}
|
|
47013
|
-
async function repliedSince(ctx, hit,
|
|
47014
|
-
const { count, error: error51 } = await ctx.client.from("comments").select("id", { count: "exact", head: true }).eq("issue_id", hit.issue.id).eq("author_id", ctx.userId).gt("created_at",
|
|
47117
|
+
async function repliedSince(ctx, hit, since2) {
|
|
47118
|
+
const { count, error: error51 } = await ctx.client.from("comments").select("id", { count: "exact", head: true }).eq("issue_id", hit.issue.id).eq("author_id", ctx.userId).gt("created_at", since2);
|
|
47015
47119
|
if (error51) {
|
|
47016
47120
|
log(` could not check for a reply on ${hit.ref}: ${error51.message}`);
|
|
47017
47121
|
return false;
|
|
@@ -47046,11 +47150,29 @@ async function beat(ctx, fields) {
|
|
|
47046
47150
|
last_seen_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47047
47151
|
holding: fields.holding,
|
|
47048
47152
|
dead_ticks: fields.deadTicks,
|
|
47049
|
-
poll_interval_ms: fields.intervalMs
|
|
47153
|
+
poll_interval_ms: fields.intervalMs,
|
|
47154
|
+
// CRA-102. Without this the board cannot tell an agent someone paused
|
|
47155
|
+
// from one that is wading through a backlog, nor one stopped on purpose
|
|
47156
|
+
// from one whose laptop shut its lid — all of them simply went quiet.
|
|
47157
|
+
status: fields.status ?? (paused ? "paused" : "running")
|
|
47050
47158
|
},
|
|
47051
47159
|
{ onConflict: "user_id,workspace_id" }
|
|
47052
47160
|
);
|
|
47053
47161
|
if (error51) log(` could not record a heartbeat: ${error51.message}`);
|
|
47162
|
+
lastHolding = fields.holding;
|
|
47163
|
+
}
|
|
47164
|
+
function announce(status) {
|
|
47165
|
+
void (async () => {
|
|
47166
|
+
try {
|
|
47167
|
+
await beat(await getContext(), {
|
|
47168
|
+
holding: lastHolding,
|
|
47169
|
+
deadTicks: 0,
|
|
47170
|
+
intervalMs: options.intervalMs,
|
|
47171
|
+
status
|
|
47172
|
+
});
|
|
47173
|
+
} catch {
|
|
47174
|
+
}
|
|
47175
|
+
})();
|
|
47054
47176
|
}
|
|
47055
47177
|
async function recordRun(ctx, hit, run, outcome) {
|
|
47056
47178
|
const { error: error51 } = await ctx.client.from("harness_runs").insert({
|
|
@@ -47249,6 +47371,35 @@ async function tick(ctx, identity, deadTicks, allowance) {
|
|
|
47249
47371
|
});
|
|
47250
47372
|
return { invoked, failedToRun, suppressed: false, waiting: actionable.length };
|
|
47251
47373
|
}
|
|
47374
|
+
function installControls() {
|
|
47375
|
+
const requestStop = (why) => {
|
|
47376
|
+
if (stopping && Date.now() - stopRequestedAt < 500) return;
|
|
47377
|
+
if (stopping) {
|
|
47378
|
+
log(` ${why} again \u2014 leaving now; a harness still running will be orphaned`);
|
|
47379
|
+
process.exit(130);
|
|
47380
|
+
}
|
|
47381
|
+
stopping = true;
|
|
47382
|
+
stopRequestedAt = Date.now();
|
|
47383
|
+
log(` ${why} \u2014 finishing the current run, then stopping. Again to leave now.`);
|
|
47384
|
+
};
|
|
47385
|
+
process.on("SIGTERM", () => requestStop("stop requested"));
|
|
47386
|
+
process.on("SIGINT", () => requestStop("interrupted"));
|
|
47387
|
+
process.on("SIGUSR1", () => {
|
|
47388
|
+
if (paused) return;
|
|
47389
|
+
paused = true;
|
|
47390
|
+
log(` paused \u2014 still polling and still reporting, but starting no new runs`);
|
|
47391
|
+
announce("paused");
|
|
47392
|
+
});
|
|
47393
|
+
process.on("SIGUSR2", () => {
|
|
47394
|
+
if (!paused) return;
|
|
47395
|
+
paused = false;
|
|
47396
|
+
log(` resumed \u2014 taking work again`);
|
|
47397
|
+
announce("running");
|
|
47398
|
+
});
|
|
47399
|
+
}
|
|
47400
|
+
function identityName() {
|
|
47401
|
+
return safeName(process.env.CRUO_IDENTITY?.trim() || "default");
|
|
47402
|
+
}
|
|
47252
47403
|
async function main() {
|
|
47253
47404
|
log(
|
|
47254
47405
|
`board: ${config2.url}${config2.url === CRUO_CLOUD.apiUrl ? " (Cruo cloud)" : " (configured)"}`
|
|
@@ -47262,6 +47413,18 @@ async function main() {
|
|
|
47262
47413
|
}
|
|
47263
47414
|
const ctx = await contextOrExplain();
|
|
47264
47415
|
const identity = await identify(ctx);
|
|
47416
|
+
installControls();
|
|
47417
|
+
const runName = identityName();
|
|
47418
|
+
if (!options.once && !options.dryRun) {
|
|
47419
|
+
await writeRecord({
|
|
47420
|
+
agent: runName,
|
|
47421
|
+
pid: process.pid,
|
|
47422
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47423
|
+
procStartedAt: await processStartedAt(process.pid),
|
|
47424
|
+
log: join3(logDir(), `${runName}.log`),
|
|
47425
|
+
argv: process.argv.slice(2)
|
|
47426
|
+
});
|
|
47427
|
+
}
|
|
47265
47428
|
try {
|
|
47266
47429
|
await preflight();
|
|
47267
47430
|
} catch (error51) {
|
|
@@ -47315,7 +47478,7 @@ async function main() {
|
|
|
47315
47478
|
await getContext(),
|
|
47316
47479
|
identity,
|
|
47317
47480
|
pacing.deadTicks,
|
|
47318
|
-
allowanceAt(pacing, Date.now())
|
|
47481
|
+
paused ? "held" : allowanceAt(pacing, Date.now())
|
|
47319
47482
|
);
|
|
47320
47483
|
if (result.suppressed) {
|
|
47321
47484
|
const now = Date.now();
|
|
@@ -47342,10 +47505,12 @@ async function main() {
|
|
|
47342
47505
|
log(`poll error: ${e instanceof Error ? e.message : String(e)}`);
|
|
47343
47506
|
}
|
|
47344
47507
|
if (options.once) return;
|
|
47508
|
+
if (stopping) return;
|
|
47345
47509
|
await new Promise((r) => setTimeout(r, options.intervalMs));
|
|
47510
|
+
if (stopping) return;
|
|
47346
47511
|
}
|
|
47347
47512
|
}
|
|
47348
|
-
var argv, flag, opt, num2, ms, readOptions, options, log, worktreeConfig, lastRefusal, PRIORITY_RANK, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, HEAD_STORED, warnedAboutSpendTable;
|
|
47513
|
+
var argv, flag, opt, num2, ms, readOptions, options, log, worktreeConfig, lastRefusal, PRIORITY_RANK, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, HEAD_STORED, lastHolding, warnedAboutSpendTable, stopping, paused, stopRequestedAt;
|
|
47349
47514
|
var init_supervisor = __esm({
|
|
47350
47515
|
"src/supervisor.ts"() {
|
|
47351
47516
|
"use strict";
|
|
@@ -47355,6 +47520,7 @@ var init_supervisor = __esm({
|
|
|
47355
47520
|
init_auth();
|
|
47356
47521
|
init_options();
|
|
47357
47522
|
init_harness_signal();
|
|
47523
|
+
init_process();
|
|
47358
47524
|
init_pacing();
|
|
47359
47525
|
init_worktree();
|
|
47360
47526
|
argv = process.argv.slice(2);
|
|
@@ -47526,9 +47692,30 @@ cruo-supervisor: ${error51.message}
|
|
|
47526
47692
|
"CRUO_SESSION"
|
|
47527
47693
|
];
|
|
47528
47694
|
HEAD_STORED = 200;
|
|
47695
|
+
lastHolding = 0;
|
|
47529
47696
|
warnedAboutSpendTable = false;
|
|
47530
|
-
|
|
47697
|
+
stopping = false;
|
|
47698
|
+
paused = false;
|
|
47699
|
+
stopRequestedAt = 0;
|
|
47700
|
+
main().then(async () => {
|
|
47701
|
+
if (!options.once && !options.dryRun) {
|
|
47702
|
+
try {
|
|
47703
|
+
const ctx = await getContext();
|
|
47704
|
+
await beat(ctx, {
|
|
47705
|
+
holding: 0,
|
|
47706
|
+
deadTicks: 0,
|
|
47707
|
+
intervalMs: options.intervalMs,
|
|
47708
|
+
status: "stopped"
|
|
47709
|
+
});
|
|
47710
|
+
} catch {
|
|
47711
|
+
}
|
|
47712
|
+
await removeRecord(identityName()).catch(() => {
|
|
47713
|
+
});
|
|
47714
|
+
}
|
|
47715
|
+
}).catch(async (e) => {
|
|
47531
47716
|
console.error(e instanceof Error ? e.message : e);
|
|
47717
|
+
await removeRecord(identityName()).catch(() => {
|
|
47718
|
+
});
|
|
47532
47719
|
process.exit(1);
|
|
47533
47720
|
});
|
|
47534
47721
|
}
|
|
@@ -47538,11 +47725,12 @@ cruo-supervisor: ${error51.message}
|
|
|
47538
47725
|
init_dist();
|
|
47539
47726
|
init_dist7();
|
|
47540
47727
|
init_options();
|
|
47541
|
-
|
|
47542
|
-
import {
|
|
47543
|
-
import {
|
|
47544
|
-
|
|
47545
|
-
var
|
|
47728
|
+
init_process();
|
|
47729
|
+
import { chmod, mkdir as mkdir3, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "node:fs/promises";
|
|
47730
|
+
import { homedir as homedir3 } from "node:os";
|
|
47731
|
+
import { join as join4 } from "node:path";
|
|
47732
|
+
var CONFIG_DIR = join4(homedir3(), ".cruo");
|
|
47733
|
+
var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
|
|
47546
47734
|
function nameFromEmail(email3) {
|
|
47547
47735
|
const local = email3.split("@")[0]?.toLowerCase().replace(/[^a-z0-9._-]/g, "");
|
|
47548
47736
|
return local && local.length > 0 ? local : null;
|
|
@@ -47553,9 +47741,22 @@ var USAGE = `cruo \u2014 run a Cruo agent
|
|
|
47553
47741
|
cruo agents list what is remembered (never the tokens)
|
|
47554
47742
|
cruo usage [--days 7] what the agents have been spending
|
|
47555
47743
|
cruo logout [name] forget one, or --all
|
|
47556
|
-
cruo [options] run the agent's supervisor
|
|
47744
|
+
cruo [options] run the agent's supervisor, in this terminal
|
|
47557
47745
|
cruo --as <name> \u2026 run a particular one
|
|
47558
47746
|
|
|
47747
|
+
Running one in the background
|
|
47748
|
+
cruo start [options] same, detached \u2014 survives closing the terminal
|
|
47749
|
+
cruo ps what is running on this machine
|
|
47750
|
+
cruo stop [name] ask it to finish the run it is on, then exit
|
|
47751
|
+
cruo pause [name] keep watching the board, start nothing new
|
|
47752
|
+
cruo resume [name] take work again
|
|
47753
|
+
cruo logs [name] [-f] what a detached agent has been saying
|
|
47754
|
+
\u2026 --all stop/pause/resume every one of them
|
|
47755
|
+
|
|
47756
|
+
\`stop\` is a request, not a kill: a harness that is mid-run is allowed to
|
|
47757
|
+
finish, so the work it has done is not thrown away. Say it twice to give up
|
|
47758
|
+
waiting. macOS and Linux only \u2014 these are signals and pid files.
|
|
47759
|
+
|
|
47559
47760
|
Getting a token
|
|
47560
47761
|
Cruo \u2192 Settings \u2192 Members \u2192 Add an agent. It is shown once.
|
|
47561
47762
|
|
|
@@ -47581,7 +47782,7 @@ var EMPTY = { version: 2, default: null, agents: {} };
|
|
|
47581
47782
|
async function readConfig2() {
|
|
47582
47783
|
let parsed;
|
|
47583
47784
|
try {
|
|
47584
|
-
parsed = JSON.parse(await
|
|
47785
|
+
parsed = JSON.parse(await readFile3(CONFIG_PATH, "utf8"));
|
|
47585
47786
|
} catch {
|
|
47586
47787
|
return { ...EMPTY };
|
|
47587
47788
|
}
|
|
@@ -47600,8 +47801,8 @@ async function readConfig2() {
|
|
|
47600
47801
|
return { ...EMPTY };
|
|
47601
47802
|
}
|
|
47602
47803
|
async function writeConfig(config3) {
|
|
47603
|
-
await
|
|
47604
|
-
await
|
|
47804
|
+
await mkdir3(CONFIG_DIR, { recursive: true, mode: 448 });
|
|
47805
|
+
await writeFile3(CONFIG_PATH, JSON.stringify(config3, null, 2) + "\n", { mode: 384 });
|
|
47605
47806
|
await chmod(CONFIG_PATH, 384);
|
|
47606
47807
|
}
|
|
47607
47808
|
async function identify2(token) {
|
|
@@ -47661,7 +47862,7 @@ async function logout(which, all) {
|
|
|
47661
47862
|
const config3 = await readConfig2();
|
|
47662
47863
|
const names = Object.keys(config3.agents);
|
|
47663
47864
|
if (all) {
|
|
47664
|
-
await
|
|
47865
|
+
await rm4(CONFIG_PATH, { force: true });
|
|
47665
47866
|
console.log(
|
|
47666
47867
|
names.length === 0 ? `
|
|
47667
47868
|
Nothing was stored.
|
|
@@ -47673,7 +47874,7 @@ Forgot ${names.length} token(s): ${names.join(", ")}. ${CONFIG_PATH} is gone.
|
|
|
47673
47874
|
}
|
|
47674
47875
|
if (!which) {
|
|
47675
47876
|
if (names.length <= 1) {
|
|
47676
|
-
await
|
|
47877
|
+
await rm4(CONFIG_PATH, { force: true });
|
|
47677
47878
|
console.log(
|
|
47678
47879
|
names.length === 0 ? `
|
|
47679
47880
|
Nothing was stored.
|
|
@@ -47729,6 +47930,156 @@ No tokens stored. \`cruo login <token>\` to add one.
|
|
|
47729
47930
|
* is what a bare \`cruo\` runs. Use \`--as <name>\` for another.
|
|
47730
47931
|
`);
|
|
47731
47932
|
}
|
|
47933
|
+
async function targetName(explicit, asName) {
|
|
47934
|
+
if (explicit) return safeName(explicit);
|
|
47935
|
+
if (asName) return safeName(asName);
|
|
47936
|
+
const config3 = await readConfig2();
|
|
47937
|
+
return safeName(config3.default ?? "default");
|
|
47938
|
+
}
|
|
47939
|
+
async function targets(explicit, asName, all) {
|
|
47940
|
+
if (all) {
|
|
47941
|
+
const rows = await listRecords();
|
|
47942
|
+
return rows.filter((r) => r.live.state === "running").map((r) => r.live.record);
|
|
47943
|
+
}
|
|
47944
|
+
const name = await targetName(explicit, asName);
|
|
47945
|
+
const live = await liveness(name, true);
|
|
47946
|
+
if (live.state === "running") return [live.record];
|
|
47947
|
+
if (live.state === "gone") {
|
|
47948
|
+
console.error(`
|
|
47949
|
+
${name} is not running (it left a record behind; cleared).
|
|
47950
|
+
`);
|
|
47951
|
+
} else if (live.state === "recycled") {
|
|
47952
|
+
console.error(`
|
|
47953
|
+
${name} is not running \u2014 its pid now belongs to something else. Cleared.
|
|
47954
|
+
`);
|
|
47955
|
+
} else {
|
|
47956
|
+
console.error(`
|
|
47957
|
+
${name} is not running. Start it with \`cruo start --as ${name}\`.
|
|
47958
|
+
`);
|
|
47959
|
+
}
|
|
47960
|
+
return [];
|
|
47961
|
+
}
|
|
47962
|
+
async function startDetached(argv2, asName) {
|
|
47963
|
+
const { spawn: spawn2 } = await import("node:child_process");
|
|
47964
|
+
const { open, mkdir: mkdir4 } = await import("node:fs/promises");
|
|
47965
|
+
const name = await targetName(null, asName);
|
|
47966
|
+
const already = await liveness(name, true);
|
|
47967
|
+
if (already.state === "running") {
|
|
47968
|
+
console.error(
|
|
47969
|
+
`
|
|
47970
|
+
${name} is already running (pid ${already.record.pid}, up ${since(already.record.startedAt)}).
|
|
47971
|
+
Stop it first, or use --as <name> to run a different agent.
|
|
47972
|
+
`
|
|
47973
|
+
);
|
|
47974
|
+
process.exit(1);
|
|
47975
|
+
}
|
|
47976
|
+
await mkdir4(logDir(), { recursive: true });
|
|
47977
|
+
const logPath = join4(logDir(), `${name}.log`);
|
|
47978
|
+
const handle = await open(logPath, "a");
|
|
47979
|
+
const forwarded = argv2.slice(1).filter((a) => a !== "--all");
|
|
47980
|
+
const child = spawn2(process.execPath, [process.argv[1], ...forwarded], {
|
|
47981
|
+
detached: true,
|
|
47982
|
+
stdio: ["ignore", handle.fd, handle.fd],
|
|
47983
|
+
env: process.env
|
|
47984
|
+
});
|
|
47985
|
+
child.unref();
|
|
47986
|
+
await handle.close();
|
|
47987
|
+
console.log(
|
|
47988
|
+
`
|
|
47989
|
+
${name} started (pid ${child.pid}).
|
|
47990
|
+
|
|
47991
|
+
logs cruo logs ${name} -f
|
|
47992
|
+
check cruo ps
|
|
47993
|
+
stop cruo stop ${name}
|
|
47994
|
+
|
|
47995
|
+
It survives this terminal closing. It does not survive the machine
|
|
47996
|
+
sleeping or shutting down \u2014 for that, see the launchd notes in
|
|
47997
|
+
https://cruo.space/docs#agents.
|
|
47998
|
+
`
|
|
47999
|
+
);
|
|
48000
|
+
}
|
|
48001
|
+
async function listProcesses() {
|
|
48002
|
+
const rows = await listRecords();
|
|
48003
|
+
const running = rows.filter((r) => r.live.state === "running");
|
|
48004
|
+
if (rows.length === 0) {
|
|
48005
|
+
console.log(`
|
|
48006
|
+
Nothing running. Start one with \`cruo start\`.
|
|
48007
|
+
`);
|
|
48008
|
+
return;
|
|
48009
|
+
}
|
|
48010
|
+
console.log("");
|
|
48011
|
+
console.log(" agent pid up started with");
|
|
48012
|
+
for (const { agent, live } of rows) {
|
|
48013
|
+
if (live.state === "running") {
|
|
48014
|
+
const flags = live.record.argv.filter((a) => a.startsWith("--")).join(" ") || "(no flags)";
|
|
48015
|
+
console.log(
|
|
48016
|
+
` ${agent.padEnd(14)} ${String(live.record.pid).padEnd(8)} ${since(live.record.startedAt).padEnd(8)} ${flags}`
|
|
48017
|
+
);
|
|
48018
|
+
} else if (live.state === "gone") {
|
|
48019
|
+
console.log(` ${agent.padEnd(14)} \u2014 \u2014 died or was killed (record cleared)`);
|
|
48020
|
+
await removeRecord(agent);
|
|
48021
|
+
} else if (live.state === "recycled") {
|
|
48022
|
+
console.log(` ${agent.padEnd(14)} \u2014 \u2014 gone; its pid is now someone else's (cleared)`);
|
|
48023
|
+
await removeRecord(agent);
|
|
48024
|
+
}
|
|
48025
|
+
}
|
|
48026
|
+
console.log(
|
|
48027
|
+
`
|
|
48028
|
+
${running.length} running. Whether each is actually picking up work is on the board, not here \u2014
|
|
48029
|
+
Cruo \u2192 Settings \u2192 Members shows what it is holding and when it last looked.
|
|
48030
|
+
`
|
|
48031
|
+
);
|
|
48032
|
+
}
|
|
48033
|
+
async function control(which, explicit, asName, all) {
|
|
48034
|
+
const records = await targets(explicit, asName, all);
|
|
48035
|
+
if (records.length === 0) {
|
|
48036
|
+
if (all) console.log(`
|
|
48037
|
+
Nothing running.
|
|
48038
|
+
`);
|
|
48039
|
+
process.exitCode = all ? 0 : 1;
|
|
48040
|
+
return;
|
|
48041
|
+
}
|
|
48042
|
+
for (const record2 of records) {
|
|
48043
|
+
const sent = signal(record2.pid, which);
|
|
48044
|
+
if (!sent) {
|
|
48045
|
+
console.error(` ${record2.agent}: could not signal pid ${record2.pid} \u2014 it may have just exited`);
|
|
48046
|
+
await removeRecord(record2.agent);
|
|
48047
|
+
continue;
|
|
48048
|
+
}
|
|
48049
|
+
if (which === "stop") {
|
|
48050
|
+
console.log(
|
|
48051
|
+
` ${record2.agent}: asked to stop. It will finish the run it is on first \u2014
|
|
48052
|
+
a harness mid-run keeps its work, which is the point of asking rather than killing.
|
|
48053
|
+
Watch it leave with: cruo logs ${record2.agent} -f`
|
|
48054
|
+
);
|
|
48055
|
+
} else if (which === "pause") {
|
|
48056
|
+
console.log(` ${record2.agent}: paused. Still watching the board and still reporting; starts nothing new.`);
|
|
48057
|
+
} else {
|
|
48058
|
+
console.log(` ${record2.agent}: resumed.`);
|
|
48059
|
+
}
|
|
48060
|
+
}
|
|
48061
|
+
console.log("");
|
|
48062
|
+
}
|
|
48063
|
+
async function showLogs(explicit, asName, follow) {
|
|
48064
|
+
const { spawn: spawn2 } = await import("node:child_process");
|
|
48065
|
+
const name = await targetName(explicit, asName);
|
|
48066
|
+
const record2 = await readRecord(name);
|
|
48067
|
+
const path = record2?.log ?? join4(logDir(), `${name}.log`);
|
|
48068
|
+
const { access } = await import("node:fs/promises");
|
|
48069
|
+
try {
|
|
48070
|
+
await access(path);
|
|
48071
|
+
} catch {
|
|
48072
|
+
console.error(`
|
|
48073
|
+
No log for ${name} at ${path}.
|
|
48074
|
+
A detached run writes one; a foreground run prints to its terminal.
|
|
48075
|
+
`);
|
|
48076
|
+
process.exit(1);
|
|
48077
|
+
}
|
|
48078
|
+
const child = spawn2("tail", follow ? ["-f", "-n", "40", path] : ["-n", "200", path], {
|
|
48079
|
+
stdio: "inherit"
|
|
48080
|
+
});
|
|
48081
|
+
await new Promise((resolve2) => child.on("close", resolve2));
|
|
48082
|
+
}
|
|
47732
48083
|
async function main2() {
|
|
47733
48084
|
const argv2 = process.argv.slice(2);
|
|
47734
48085
|
const [first] = argv2;
|
|
@@ -47751,12 +48102,25 @@ Usage: cruo login <token> [--as <name>]
|
|
|
47751
48102
|
return logout(named, argv2.includes("--all"));
|
|
47752
48103
|
}
|
|
47753
48104
|
if (first === "agents" || first === "whoami") return listAgents();
|
|
48105
|
+
{
|
|
48106
|
+
const named = argv2[1] && !argv2[1].startsWith("--") ? argv2[1] : null;
|
|
48107
|
+
const all = argv2.includes("--all");
|
|
48108
|
+
const asName2 = flagValue("--as");
|
|
48109
|
+
if (first === "ps") return listProcesses();
|
|
48110
|
+
if (first === "stop") return control("stop", named, asName2, all);
|
|
48111
|
+
if (first === "pause") return control("pause", named, asName2, all);
|
|
48112
|
+
if (first === "resume") return control("resume", named, asName2, all);
|
|
48113
|
+
if (first === "logs") {
|
|
48114
|
+
return showLogs(named, asName2, argv2.includes("-f") || argv2.includes("--follow"));
|
|
48115
|
+
}
|
|
48116
|
+
}
|
|
47754
48117
|
if (first === "usage") {
|
|
47755
48118
|
}
|
|
47756
48119
|
if (first === "help" || argv2.includes("--help") || argv2.includes("-h")) {
|
|
47757
48120
|
console.log(USAGE);
|
|
47758
48121
|
return;
|
|
47759
48122
|
}
|
|
48123
|
+
if (first === "start") return startDetached(argv2, flagValue("--as"));
|
|
47760
48124
|
const flagIndex = argv2.indexOf("--token");
|
|
47761
48125
|
const fromFlag = flagIndex >= 0 ? argv2[flagIndex + 1] : void 0;
|
|
47762
48126
|
if (fromFlag) {
|
package/package.json
CHANGED