tines 0.0.115 → 0.0.117
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 +98 -24
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3547,8 +3547,8 @@ var selfRef = () => ({ kind: "self-ref" });
|
|
|
3547
3547
|
function str(v) {
|
|
3548
3548
|
return v === null || v === void 0 ? "" : String(v);
|
|
3549
3549
|
}
|
|
3550
|
-
function joinChanged(v,
|
|
3551
|
-
return Array.isArray(v) ? v.join(
|
|
3550
|
+
function joinChanged(v, sep2) {
|
|
3551
|
+
return Array.isArray(v) ? v.join(sep2) : "";
|
|
3552
3552
|
}
|
|
3553
3553
|
function action(type) {
|
|
3554
3554
|
return type.split(".")[1] ?? type;
|
|
@@ -4204,11 +4204,11 @@ function assertNewStatesHavePrompts(states, prompts) {
|
|
|
4204
4204
|
}
|
|
4205
4205
|
}
|
|
4206
4206
|
function parseScheduleRef(ref) {
|
|
4207
|
-
const
|
|
4208
|
-
if (
|
|
4207
|
+
const sep2 = ref.indexOf("/");
|
|
4208
|
+
if (sep2 < 1 || sep2 === ref.length - 1) {
|
|
4209
4209
|
throw new CliError(`schedule reference must look like <project>/<name>, got "${ref}"`);
|
|
4210
4210
|
}
|
|
4211
|
-
return { project: ref.slice(0,
|
|
4211
|
+
return { project: ref.slice(0, sep2), name: ref.slice(sep2 + 1) };
|
|
4212
4212
|
}
|
|
4213
4213
|
function parseIssueRef(ref) {
|
|
4214
4214
|
const match = ref.match(/^(.+)\/(\d+)$/);
|
|
@@ -4216,12 +4216,12 @@ function parseIssueRef(ref) {
|
|
|
4216
4216
|
return { project: match[1], number: Number.parseInt(match[2], 10) };
|
|
4217
4217
|
}
|
|
4218
4218
|
function parseFileSpec(spec) {
|
|
4219
|
-
const
|
|
4220
|
-
if (
|
|
4219
|
+
const sep2 = spec.indexOf("=");
|
|
4220
|
+
if (sep2 < 1 || sep2 === spec.length - 1) {
|
|
4221
4221
|
throw new CliError(`--file must look like <path>=@<local-file>, got "${spec}"`);
|
|
4222
4222
|
}
|
|
4223
|
-
const path2 = spec.slice(0,
|
|
4224
|
-
const source = spec.slice(
|
|
4223
|
+
const path2 = spec.slice(0, sep2);
|
|
4224
|
+
const source = spec.slice(sep2 + 1);
|
|
4225
4225
|
if (!source.startsWith("@")) {
|
|
4226
4226
|
throw new CliError(
|
|
4227
4227
|
`skill file content always comes from a local file: --file ${path2}=@<local-file>`
|
|
@@ -4235,10 +4235,10 @@ function parseFileSpec(spec) {
|
|
|
4235
4235
|
}
|
|
4236
4236
|
}
|
|
4237
4237
|
function parseTargetSpec(spec) {
|
|
4238
|
-
const
|
|
4239
|
-
if (
|
|
4240
|
-
const name2 = spec.slice(0,
|
|
4241
|
-
const tier = spec.slice(
|
|
4238
|
+
const sep2 = spec.lastIndexOf(":");
|
|
4239
|
+
if (sep2 === -1) return { name: spec };
|
|
4240
|
+
const name2 = spec.slice(0, sep2);
|
|
4241
|
+
const tier = spec.slice(sep2 + 1);
|
|
4242
4242
|
if (!name2) throw new CliError(`target must look like <runner>[:tier], got "${spec}"`);
|
|
4243
4243
|
if (!MODEL_TIERS.includes(tier)) {
|
|
4244
4244
|
throw new CliError(`unknown tier "${tier}" in "${spec}" (tiers: ${MODEL_TIERS.join(", ")})`);
|
|
@@ -4421,12 +4421,12 @@ async function resolveIssue(api, ref) {
|
|
|
4421
4421
|
return api.getIssueByNumber(proj.id, number);
|
|
4422
4422
|
}
|
|
4423
4423
|
async function resolveStateFlag(api, ref) {
|
|
4424
|
-
const
|
|
4425
|
-
if (
|
|
4424
|
+
const sep2 = ref.indexOf("/");
|
|
4425
|
+
if (sep2 < 1 || sep2 === ref.length - 1) {
|
|
4426
4426
|
die(`--state must look like <workflow>/<state>, got "${ref}"`);
|
|
4427
4427
|
}
|
|
4428
|
-
const workflow = await resolveWorkflow(api, ref.slice(0,
|
|
4429
|
-
const stateRef = ref.slice(
|
|
4428
|
+
const workflow = await resolveWorkflow(api, ref.slice(0, sep2));
|
|
4429
|
+
const stateRef = ref.slice(sep2 + 1);
|
|
4430
4430
|
const state = workflow.states.find((s) => s.name === stateRef) ?? workflow.states.find((s) => s.id === stateRef);
|
|
4431
4431
|
if (!state) {
|
|
4432
4432
|
die(
|
|
@@ -5964,6 +5964,7 @@ import {
|
|
|
5964
5964
|
existsSync as existsSync4,
|
|
5965
5965
|
mkdirSync as mkdirSync4,
|
|
5966
5966
|
readFileSync as readFileSync9,
|
|
5967
|
+
realpathSync,
|
|
5967
5968
|
rmSync as rmSync2,
|
|
5968
5969
|
statSync as statSync3,
|
|
5969
5970
|
unlinkSync as unlinkSync2,
|
|
@@ -6085,7 +6086,7 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
6085
6086
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
6086
6087
|
|
|
6087
6088
|
// src/daemon/support.ts
|
|
6088
|
-
import { delimiter } from "node:path";
|
|
6089
|
+
import { delimiter, sep } from "node:path";
|
|
6089
6090
|
var HARNESS_KINDS = ["claude_code", "codex", "custom"];
|
|
6090
6091
|
var KEEP_WORKSPACES_MODES = ["never", "failed", "always"];
|
|
6091
6092
|
function keepWorkspace(mode, outcome) {
|
|
@@ -6333,6 +6334,14 @@ var CliRefresher = class {
|
|
|
6333
6334
|
cached = AMBIENT_CLI;
|
|
6334
6335
|
lastAttempt = null;
|
|
6335
6336
|
inFlight = null;
|
|
6337
|
+
/**
|
|
6338
|
+
* Resolves once no install is in flight, starting none. A daemon about to
|
|
6339
|
+
* exit for a self-update waits on this so it never restarts into a
|
|
6340
|
+
* half-extracted prefix.
|
|
6341
|
+
*/
|
|
6342
|
+
settled() {
|
|
6343
|
+
return this.inFlight ? this.inFlight.then(() => void 0) : Promise.resolve();
|
|
6344
|
+
}
|
|
6336
6345
|
/** The current CLI, refreshing first when the TTL elapsed. Never rejects. */
|
|
6337
6346
|
ensure() {
|
|
6338
6347
|
if (this.inFlight) return this.inFlight;
|
|
@@ -6349,6 +6358,26 @@ var CliRefresher = class {
|
|
|
6349
6358
|
return this.inFlight;
|
|
6350
6359
|
}
|
|
6351
6360
|
};
|
|
6361
|
+
function isNewerVersion(candidate, current) {
|
|
6362
|
+
const parse = (v) => /^\d+(\.\d+)*$/.test(v) ? v.split(".").map(Number) : null;
|
|
6363
|
+
const a = parse(candidate);
|
|
6364
|
+
const b = parse(current);
|
|
6365
|
+
if (!a || !b) return false;
|
|
6366
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
6367
|
+
const x = a[i] ?? 0;
|
|
6368
|
+
const y = b[i] ?? 0;
|
|
6369
|
+
if (x !== y) return x > y;
|
|
6370
|
+
}
|
|
6371
|
+
return false;
|
|
6372
|
+
}
|
|
6373
|
+
function pendingSelfUpdate(cli, running) {
|
|
6374
|
+
if (cli.source === "ambient" || !cli.version) return null;
|
|
6375
|
+
return isNewerVersion(cli.version, running) ? cli.version : null;
|
|
6376
|
+
}
|
|
6377
|
+
function pathWithin(path2, dir) {
|
|
6378
|
+
const root = dir.endsWith(sep) ? dir : dir + sep;
|
|
6379
|
+
return path2.startsWith(root);
|
|
6380
|
+
}
|
|
6352
6381
|
function buildSpawnEnv(base, opts) {
|
|
6353
6382
|
const env = {
|
|
6354
6383
|
...base,
|
|
@@ -6688,7 +6717,28 @@ async function runDaemon(opts) {
|
|
|
6688
6717
|
const refresher = new CliRefresher(() => installAgentCli({ configDir: opts.configDir, log }), {
|
|
6689
6718
|
ttlMs: CLI_REFRESH_TTL_MS
|
|
6690
6719
|
});
|
|
6691
|
-
const
|
|
6720
|
+
const prefix = agentCliPrefix(opts.configDir);
|
|
6721
|
+
const selfPath = realpathOrNull(process.argv[1]);
|
|
6722
|
+
const managedInstall = selfPath !== null && pathWithin(selfPath, realpathOrNull(prefix) ?? prefix);
|
|
6723
|
+
const selfUpdate = opts.selfUpdate && opts.cliRefresh && managedInstall;
|
|
6724
|
+
if (opts.selfUpdate && opts.cliRefresh) {
|
|
6725
|
+
log(
|
|
6726
|
+
managedInstall ? `self-update: on \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${prefix} and will restart once idle after a newer release is installed there` : `self-update: off \u2014 this daemon (tines ${DAEMON_VERSION}) runs from ${selfPath ?? process.argv[1] ?? "?"}, not ${prefix}; launch it from ${join5(prefix, "node_modules", ".bin", "tines")} to have refreshes apply on restart (docs/runner-daemon.md)`
|
|
6727
|
+
);
|
|
6728
|
+
}
|
|
6729
|
+
let pendingUpdate = null;
|
|
6730
|
+
const noteCli = (cli) => {
|
|
6731
|
+
if (!selfUpdate || pendingUpdate) return cli;
|
|
6732
|
+
const newer = pendingSelfUpdate(cli, DAEMON_VERSION);
|
|
6733
|
+
if (newer) {
|
|
6734
|
+
pendingUpdate = newer;
|
|
6735
|
+
log(
|
|
6736
|
+
`tines ${newer} is installed in ${prefix} (this daemon is ${DAEMON_VERSION}); draining \u2014 no new runs accepted, restarting once the in-flight ones finish`
|
|
6737
|
+
);
|
|
6738
|
+
}
|
|
6739
|
+
return cli;
|
|
6740
|
+
};
|
|
6741
|
+
const ensureCli = () => opts.cliRefresh ? refresher.ensure().then(noteCli) : Promise.resolve(AMBIENT_CLI);
|
|
6692
6742
|
const cliLabel = (cli) => cli.source === "ambient" ? `ambient PATH (${opts.cliRefresh ? "refresh failed" : "refresh disabled"})` : `tines ${cli.version ?? "unknown"} (daemon-managed${cli.source === "stale" ? ", last-good copy" : ""})`;
|
|
6693
6743
|
log(`agent CLI: ${cliLabel(await ensureCli())}`);
|
|
6694
6744
|
const table2 = new RunTable(
|
|
@@ -6946,7 +6996,11 @@ async function runDaemon(opts) {
|
|
|
6946
6996
|
try {
|
|
6947
6997
|
const res = await client2.pollRunner(creds.runner_id, {
|
|
6948
6998
|
owned_runs: table2.ids(),
|
|
6949
|
-
max_concurrent: opts.maxConcurrent
|
|
6999
|
+
max_concurrent: opts.maxConcurrent,
|
|
7000
|
+
// Stated on every poll while pending; absent otherwise, which
|
|
7001
|
+
// the server reads as "not draining" — so a daemon that died
|
|
7002
|
+
// mid-drain cannot pin its runner shut past its relaunch.
|
|
7003
|
+
...pendingUpdate ? { draining: true } : {}
|
|
6950
7004
|
});
|
|
6951
7005
|
failures = 0;
|
|
6952
7006
|
for (const runId of res.cancels) killWithoutFinish(runId);
|
|
@@ -6958,6 +7012,14 @@ async function runDaemon(opts) {
|
|
|
6958
7012
|
}
|
|
6959
7013
|
void launch(assignment);
|
|
6960
7014
|
}
|
|
7015
|
+
if (selfUpdate && !pendingUpdate) void ensureCli();
|
|
7016
|
+
if (pendingUpdate && res.assignments.length === 0 && table2.size === 0) {
|
|
7017
|
+
await refresher.settled();
|
|
7018
|
+
log(
|
|
7019
|
+
`exiting to restart as tines ${pendingUpdate} \u2014 the service manager relaunches this daemon (KeepAlive / Restart=always)`
|
|
7020
|
+
);
|
|
7021
|
+
process.exit(0);
|
|
7022
|
+
}
|
|
6961
7023
|
} catch (err) {
|
|
6962
7024
|
if (err instanceof ApiError && err.status === 401) {
|
|
6963
7025
|
for (const run of table2.values()) {
|
|
@@ -6988,6 +7050,14 @@ async function runDaemon(opts) {
|
|
|
6988
7050
|
function message2(err) {
|
|
6989
7051
|
return err instanceof Error ? err.message : String(err);
|
|
6990
7052
|
}
|
|
7053
|
+
function realpathOrNull(path2) {
|
|
7054
|
+
if (!path2) return null;
|
|
7055
|
+
try {
|
|
7056
|
+
return realpathSync(path2);
|
|
7057
|
+
} catch {
|
|
7058
|
+
return null;
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
6991
7061
|
function runGit(args, cwd, batcher) {
|
|
6992
7062
|
return new Promise((resolve) => {
|
|
6993
7063
|
const child = spawn2("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -7239,6 +7309,9 @@ function register8(program3) {
|
|
|
7239
7309
|
).option(
|
|
7240
7310
|
"--no-cli-refresh",
|
|
7241
7311
|
"do not install/refresh the agent-facing tines CLI from npm (harnesses use the ambient PATH)"
|
|
7312
|
+
).option(
|
|
7313
|
+
"--no-self-update",
|
|
7314
|
+
"do not exit for the service manager to relaunch a newer daemon (only applies when launched from the daemon-managed prefix)"
|
|
7242
7315
|
).option(
|
|
7243
7316
|
"--keep-workspaces <mode>",
|
|
7244
7317
|
"keep settled runs' workspaces for debugging: never | failed | always",
|
|
@@ -7294,6 +7367,7 @@ function register8(program3) {
|
|
|
7294
7367
|
pollIntervalMs: opts.pollInterval * 1e3,
|
|
7295
7368
|
configDir: defaultConfigDir(),
|
|
7296
7369
|
cliRefresh: opts.cliRefresh,
|
|
7370
|
+
selfUpdate: opts.selfUpdate,
|
|
7297
7371
|
keepWorkspaces,
|
|
7298
7372
|
keepWorkspacesForHours: opts.keepWorkspacesFor,
|
|
7299
7373
|
keepWorkspacesMax: opts.keepWorkspacesMax
|
|
@@ -7683,12 +7757,12 @@ function register10(program3) {
|
|
|
7683
7757
|
const api = client(opts);
|
|
7684
7758
|
const overrides = {};
|
|
7685
7759
|
for (const spec of opts.state) {
|
|
7686
|
-
const
|
|
7687
|
-
if (
|
|
7760
|
+
const sep2 = spec.lastIndexOf("=");
|
|
7761
|
+
if (sep2 < 1 || sep2 === spec.length - 1) {
|
|
7688
7762
|
die(`--state must look like <workflow>/<state>=<n>, got "${spec}"`);
|
|
7689
7763
|
}
|
|
7690
|
-
const limit = Number.parseInt(spec.slice(
|
|
7691
|
-
const { state } = await resolveStateFlag(api, spec.slice(0,
|
|
7764
|
+
const limit = Number.parseInt(spec.slice(sep2 + 1), 10);
|
|
7765
|
+
const { state } = await resolveStateFlag(api, spec.slice(0, sep2));
|
|
7692
7766
|
overrides[state.id] = limit;
|
|
7693
7767
|
}
|
|
7694
7768
|
const settings = await api.updateSupervisorSettings({
|