tines 0.0.277 → 0.0.278
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 +247 -145
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3436,6 +3436,7 @@ var PROMPT_MAX_BYTES = 32 * 1024;
|
|
|
3436
3436
|
var SKILL_MAX_FILES = 20;
|
|
3437
3437
|
var SKILL_MAX_TOTAL_BYTES = 100 * 1024;
|
|
3438
3438
|
var ENV_VALUE_MAX_BYTES = 16 * 1024;
|
|
3439
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
3439
3440
|
var AGENT_GUIDELINES_NAME = "agent-guidelines";
|
|
3440
3441
|
var JOURNAL_NAME = "journal";
|
|
3441
3442
|
var AGENT_GUIDELINES_BODY = `You are an agent working on a Tines issue over its HTTP API / CLI. Beyond doing the work, leave the workspace smarter than you found it. Four places to write, chosen by who should inherit what you learned:
|
|
@@ -3604,8 +3605,8 @@ function str(v) {
|
|
|
3604
3605
|
function isoTime(v) {
|
|
3605
3606
|
return typeof v === "number" && Number.isFinite(v) ? new Date(v).toISOString() : "an unknown time";
|
|
3606
3607
|
}
|
|
3607
|
-
function joinChanged(v,
|
|
3608
|
-
return Array.isArray(v) ? v.join(
|
|
3608
|
+
function joinChanged(v, sep5) {
|
|
3609
|
+
return Array.isArray(v) ? v.join(sep5) : "";
|
|
3609
3610
|
}
|
|
3610
3611
|
function action(type) {
|
|
3611
3612
|
return type.split(".")[1] ?? type;
|
|
@@ -5946,11 +5947,11 @@ function assertNewStatesHavePrompts(states, prompts) {
|
|
|
5946
5947
|
}
|
|
5947
5948
|
}
|
|
5948
5949
|
function parseScheduleRef(ref) {
|
|
5949
|
-
const
|
|
5950
|
-
if (
|
|
5950
|
+
const sep5 = ref.indexOf("/");
|
|
5951
|
+
if (sep5 < 1 || sep5 === ref.length - 1) {
|
|
5951
5952
|
throw new CliError(`schedule reference must look like <project>/<name>, got "${ref}"`);
|
|
5952
5953
|
}
|
|
5953
|
-
return { project: ref.slice(0,
|
|
5954
|
+
return { project: ref.slice(0, sep5), name: ref.slice(sep5 + 1) };
|
|
5954
5955
|
}
|
|
5955
5956
|
function parseIssueRef(ref) {
|
|
5956
5957
|
const match = ref.match(/^(.+)\/(\d+)$/);
|
|
@@ -5958,12 +5959,12 @@ function parseIssueRef(ref) {
|
|
|
5958
5959
|
return { project: match[1], number: Number.parseInt(match[2], 10) };
|
|
5959
5960
|
}
|
|
5960
5961
|
function parseFileSpec(spec) {
|
|
5961
|
-
const
|
|
5962
|
-
if (
|
|
5962
|
+
const sep5 = spec.indexOf("=");
|
|
5963
|
+
if (sep5 < 1 || sep5 === spec.length - 1) {
|
|
5963
5964
|
throw new CliError(`--file must look like <path>=@<local-file>, got "${spec}"`);
|
|
5964
5965
|
}
|
|
5965
|
-
const path2 = spec.slice(0,
|
|
5966
|
-
const source = spec.slice(
|
|
5966
|
+
const path2 = spec.slice(0, sep5);
|
|
5967
|
+
const source = spec.slice(sep5 + 1);
|
|
5967
5968
|
if (!source.startsWith("@")) {
|
|
5968
5969
|
throw new CliError(
|
|
5969
5970
|
`skill file content always comes from a local file: --file ${path2}=@<local-file>`
|
|
@@ -5977,10 +5978,10 @@ function parseFileSpec(spec) {
|
|
|
5977
5978
|
}
|
|
5978
5979
|
}
|
|
5979
5980
|
function parseTargetSpec(spec) {
|
|
5980
|
-
const
|
|
5981
|
-
if (
|
|
5982
|
-
const name2 = spec.slice(0,
|
|
5983
|
-
const tier = spec.slice(
|
|
5981
|
+
const sep5 = spec.lastIndexOf(":");
|
|
5982
|
+
if (sep5 === -1) return { name: spec };
|
|
5983
|
+
const name2 = spec.slice(0, sep5);
|
|
5984
|
+
const tier = spec.slice(sep5 + 1);
|
|
5984
5985
|
if (!name2) throw new CliError(`target must look like <runner>[:tier], got "${spec}"`);
|
|
5985
5986
|
if (!MODEL_TIERS.includes(tier)) {
|
|
5986
5987
|
throw new CliError(`unknown tier "${tier}" in "${spec}" (tiers: ${MODEL_TIERS.join(", ")})`);
|
|
@@ -6224,12 +6225,12 @@ async function resolveLabelFlag(api, ref) {
|
|
|
6224
6225
|
return match.id;
|
|
6225
6226
|
}
|
|
6226
6227
|
async function resolveStateFlag(api, ref) {
|
|
6227
|
-
const
|
|
6228
|
-
if (
|
|
6228
|
+
const sep5 = ref.indexOf("/");
|
|
6229
|
+
if (sep5 < 1 || sep5 === ref.length - 1) {
|
|
6229
6230
|
die(`--state must look like <workflow>/<state>, got "${ref}"`);
|
|
6230
6231
|
}
|
|
6231
|
-
const workflow2 = await resolveWorkflow(api, ref.slice(0,
|
|
6232
|
-
const stateRef2 = ref.slice(
|
|
6232
|
+
const workflow2 = await resolveWorkflow(api, ref.slice(0, sep5));
|
|
6233
|
+
const stateRef2 = ref.slice(sep5 + 1);
|
|
6233
6234
|
const state = workflow2.states.find((s) => s.name === stateRef2) ?? workflow2.states.find((s) => s.id === stateRef2);
|
|
6234
6235
|
if (!state) {
|
|
6235
6236
|
die(
|
|
@@ -6281,7 +6282,7 @@ type: ${item.artifact_type ?? "artifact"}`);
|
|
|
6281
6282
|
${item.body}`);
|
|
6282
6283
|
} else if (item.kind === "skill") {
|
|
6283
6284
|
console.log(`
|
|
6284
|
-
files (seeded at skills/${item.name}/):`);
|
|
6285
|
+
files (seeded at .agents/skills/${item.name}/):`);
|
|
6285
6286
|
for (const f of item.files ?? []) {
|
|
6286
6287
|
console.log(` ${f.path} (${Buffer.byteLength(f.content, "utf8")} bytes)`);
|
|
6287
6288
|
}
|
|
@@ -6510,8 +6511,8 @@ function register(program3) {
|
|
|
6510
6511
|
}
|
|
6511
6512
|
|
|
6512
6513
|
// src/commands/issues.ts
|
|
6513
|
-
import { existsSync as
|
|
6514
|
-
import { basename, dirname as dirname2, join as
|
|
6514
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
6515
|
+
import { basename, dirname as dirname2, join as join3 } from "node:path";
|
|
6515
6516
|
import { createInterface } from "node:readline/promises";
|
|
6516
6517
|
|
|
6517
6518
|
// src/issue-transfer.ts
|
|
@@ -7142,6 +7143,97 @@ function buildRecurrence(opts) {
|
|
|
7142
7143
|
}
|
|
7143
7144
|
}
|
|
7144
7145
|
|
|
7146
|
+
// src/skills.ts
|
|
7147
|
+
import { randomUUID } from "node:crypto";
|
|
7148
|
+
import { existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync2, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7149
|
+
import { isAbsolute, join as join2, relative, resolve, sep } from "node:path";
|
|
7150
|
+
var SKILLS_DIRECTORY = ".agents/skills";
|
|
7151
|
+
function validateRelativePath(value, label) {
|
|
7152
|
+
if (!value || isAbsolute(value) || value.startsWith("/") || value.includes("\\")) {
|
|
7153
|
+
throw new Error(`${label} must be a relative path using forward slashes (got "${value}")`);
|
|
7154
|
+
}
|
|
7155
|
+
if (value.includes("=")) throw new Error(`${label} cannot contain "=" (got "${value}")`);
|
|
7156
|
+
const segments = value.split("/");
|
|
7157
|
+
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
7158
|
+
throw new Error(`${label} cannot contain empty, ".", or ".." segments (got "${value}")`);
|
|
7159
|
+
}
|
|
7160
|
+
return segments;
|
|
7161
|
+
}
|
|
7162
|
+
function isPrefix(left, right) {
|
|
7163
|
+
return left.length <= right.length && left.every((segment, index) => segment.toLowerCase() === right[index]?.toLowerCase());
|
|
7164
|
+
}
|
|
7165
|
+
function assertNoSkillRepoOverlap(repoDirs) {
|
|
7166
|
+
const skills = SKILLS_DIRECTORY.split("/");
|
|
7167
|
+
for (const dir of repoDirs) {
|
|
7168
|
+
const repo = validateRelativePath(dir, "Repository checkout directory");
|
|
7169
|
+
if (isPrefix(repo, skills) || isPrefix(skills, repo)) {
|
|
7170
|
+
throw new Error(
|
|
7171
|
+
`Repository checkout directory "${dir}" overlaps generated skill directory "${SKILLS_DIRECTORY}"`
|
|
7172
|
+
);
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
function rejectSymlink(path2, label) {
|
|
7177
|
+
if (!existsSync3(path2)) return;
|
|
7178
|
+
const stat2 = lstatSync(path2);
|
|
7179
|
+
if (stat2.isSymbolicLink()) throw new Error(`${label} must not be a symlink: ${path2}`);
|
|
7180
|
+
if (!stat2.isDirectory()) throw new Error(`${label} must be a directory: ${path2}`);
|
|
7181
|
+
}
|
|
7182
|
+
function materializeSkills(root, skills) {
|
|
7183
|
+
const seenSkills = /* @__PURE__ */ new Set();
|
|
7184
|
+
for (const skill of skills) {
|
|
7185
|
+
if (!SKILL_NAME_PATTERN.test(skill.name)) {
|
|
7186
|
+
throw new Error(`Invalid skill name "${skill.name}"; expected [a-z0-9-]+`);
|
|
7187
|
+
}
|
|
7188
|
+
if (seenSkills.has(skill.name)) throw new Error(`Duplicate skill name "${skill.name}"`);
|
|
7189
|
+
seenSkills.add(skill.name);
|
|
7190
|
+
const seenFiles = /* @__PURE__ */ new Set();
|
|
7191
|
+
for (const file of skill.files) {
|
|
7192
|
+
validateRelativePath(file.path, `Skill "${skill.name}" file path`);
|
|
7193
|
+
if (seenFiles.has(file.path)) {
|
|
7194
|
+
throw new Error(`Skill "${skill.name}" file path "${file.path}" is duplicated`);
|
|
7195
|
+
}
|
|
7196
|
+
seenFiles.add(file.path);
|
|
7197
|
+
}
|
|
7198
|
+
}
|
|
7199
|
+
const agentsDir = join2(root, ".agents");
|
|
7200
|
+
const destination = join2(agentsDir, "skills");
|
|
7201
|
+
rejectSymlink(agentsDir, "Agent metadata directory");
|
|
7202
|
+
rejectSymlink(destination, "Generated skill directory");
|
|
7203
|
+
mkdirSync2(agentsDir, { recursive: true });
|
|
7204
|
+
const suffix = randomUUID();
|
|
7205
|
+
const staged = join2(agentsDir, `.skills-stage-${suffix}`);
|
|
7206
|
+
const backup = join2(agentsDir, `.skills-backup-${suffix}`);
|
|
7207
|
+
try {
|
|
7208
|
+
mkdirSync2(staged);
|
|
7209
|
+
for (const skill of skills) {
|
|
7210
|
+
const skillDir = join2(staged, skill.name);
|
|
7211
|
+
mkdirSync2(skillDir, { recursive: true });
|
|
7212
|
+
for (const file of skill.files) {
|
|
7213
|
+
const target = resolve(skillDir, ...file.path.split("/"));
|
|
7214
|
+
const within = relative(skillDir, target);
|
|
7215
|
+
if (within === ".." || within.startsWith(`..${sep}`) || isAbsolute(within)) {
|
|
7216
|
+
throw new Error(`Skill "${skill.name}" file escapes its directory: ${file.path}`);
|
|
7217
|
+
}
|
|
7218
|
+
mkdirSync2(resolve(target, ".."), { recursive: true });
|
|
7219
|
+
writeFileSync2(target, file.content);
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
if (existsSync3(destination)) renameSync2(destination, backup);
|
|
7223
|
+
try {
|
|
7224
|
+
renameSync2(staged, destination);
|
|
7225
|
+
} catch (error) {
|
|
7226
|
+
if (existsSync3(backup) && !existsSync3(destination)) renameSync2(backup, destination);
|
|
7227
|
+
throw error;
|
|
7228
|
+
}
|
|
7229
|
+
rmSync2(backup, { recursive: true, force: true });
|
|
7230
|
+
} finally {
|
|
7231
|
+
rmSync2(staged, { recursive: true, force: true });
|
|
7232
|
+
if (existsSync3(backup) && !existsSync3(destination)) renameSync2(backup, destination);
|
|
7233
|
+
else rmSync2(backup, { recursive: true, force: true });
|
|
7234
|
+
}
|
|
7235
|
+
}
|
|
7236
|
+
|
|
7145
7237
|
// src/commands/issues.ts
|
|
7146
7238
|
var systemTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
7147
7239
|
function printIssueLinks(links) {
|
|
@@ -7233,7 +7325,7 @@ function walkFolder(dir) {
|
|
|
7233
7325
|
const files2 = [];
|
|
7234
7326
|
const walk = (abs, rel) => {
|
|
7235
7327
|
for (const entry of readdirSync(abs, { withFileTypes: true })) {
|
|
7236
|
-
const nextAbs =
|
|
7328
|
+
const nextAbs = join3(abs, entry.name);
|
|
7237
7329
|
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
7238
7330
|
if (entry.isDirectory()) walk(nextAbs, nextRel);
|
|
7239
7331
|
else if (entry.isFile()) {
|
|
@@ -7693,7 +7785,7 @@ function register2(program3) {
|
|
|
7693
7785
|
"Print an issue's effective context (the assembled bundle for its current state)"
|
|
7694
7786
|
).option(
|
|
7695
7787
|
"--out <dir>",
|
|
7696
|
-
"write the bundle to a directory: prompt.md, skills/<name>/\u2026, repos.json"
|
|
7788
|
+
"write the bundle to a directory: prompt.md, .agents/skills/<name>/\u2026, repos.json"
|
|
7697
7789
|
).option("--force", "allow --out into a non-empty directory")
|
|
7698
7790
|
).action(async (ref, opts) => {
|
|
7699
7791
|
const api = client(opts);
|
|
@@ -7731,23 +7823,18 @@ skills: ${context2.skills.map((s) => s.name).join(", ")}`);
|
|
|
7731
7823
|
`refusing to write: checkout-directory conflict${context2.conflicts.length === 1 ? "" : "s"} among the effective repos (${context2.conflicts.map((c) => `"${c.dir}": ${c.item_ids.join(", ")}`).join("; ")}); rename or re-dir the items first`
|
|
7732
7824
|
);
|
|
7733
7825
|
}
|
|
7734
|
-
if (
|
|
7826
|
+
if (existsSync4(opts.out) && readdirSync(opts.out).length > 0 && !opts.force) {
|
|
7735
7827
|
die(`refusing to write into non-empty directory ${opts.out} (pass --force to override)`);
|
|
7736
7828
|
}
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7829
|
+
assertNoSkillRepoOverlap(context2.repos.map((repo) => repo.dir));
|
|
7830
|
+
mkdirSync3(opts.out, { recursive: true });
|
|
7831
|
+
materializeSkills(opts.out, context2.skills);
|
|
7832
|
+
writeFileSync3(
|
|
7833
|
+
join3(opts.out, "prompt.md"),
|
|
7740
7834
|
context2.prompt.text ? `${context2.prompt.text}
|
|
7741
7835
|
` : ""
|
|
7742
7836
|
);
|
|
7743
|
-
|
|
7744
|
-
for (const file of skill.files) {
|
|
7745
|
-
const target = join2(opts.out, "skills", skill.name, file.path);
|
|
7746
|
-
mkdirSync2(dirname2(target), { recursive: true });
|
|
7747
|
-
writeFileSync2(target, file.content);
|
|
7748
|
-
}
|
|
7749
|
-
}
|
|
7750
|
-
writeFileSync2(join2(opts.out, "repos.json"), `${JSON.stringify(context2.repos, null, 2)}
|
|
7837
|
+
writeFileSync3(join3(opts.out, "repos.json"), `${JSON.stringify(context2.repos, null, 2)}
|
|
7751
7838
|
`);
|
|
7752
7839
|
console.log(
|
|
7753
7840
|
`wrote ${opts.out}/prompt.md, ${context2.skills.length} skill${context2.skills.length === 1 ? "" : "s"}, repos.json (${context2.repos.length} repo${context2.repos.length === 1 ? "" : "s"})`
|
|
@@ -8018,10 +8105,10 @@ files (v${artifact.current_version.version}):`);
|
|
|
8018
8105
|
if (opts.out === void 0) {
|
|
8019
8106
|
die(`artifact "${name2}" is a folder \u2014 pass --out <dir> to write its tree`);
|
|
8020
8107
|
}
|
|
8021
|
-
if (
|
|
8108
|
+
if (existsSync4(opts.out) && !statSync2(opts.out).isDirectory()) {
|
|
8022
8109
|
die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
|
|
8023
8110
|
}
|
|
8024
|
-
if (
|
|
8111
|
+
if (existsSync4(opts.out) && readdirSync(opts.out).length > 0) {
|
|
8025
8112
|
die(
|
|
8026
8113
|
`refusing to write folder artifact into non-empty directory "${opts.out}"; choose a new or empty directory`
|
|
8027
8114
|
);
|
|
@@ -8033,9 +8120,9 @@ files (v${artifact.current_version.version}):`);
|
|
|
8033
8120
|
version: opts.version,
|
|
8034
8121
|
path: file.path
|
|
8035
8122
|
});
|
|
8036
|
-
const target2 =
|
|
8037
|
-
|
|
8038
|
-
|
|
8123
|
+
const target2 = join3(opts.out, file.path);
|
|
8124
|
+
mkdirSync3(dirname2(target2), { recursive: true });
|
|
8125
|
+
writeFileSync3(target2, Buffer.from(content2.bytes));
|
|
8039
8126
|
total += content2.bytes.byteLength;
|
|
8040
8127
|
}
|
|
8041
8128
|
return console.log(
|
|
@@ -8046,10 +8133,10 @@ files (v${artifact.current_version.version}):`);
|
|
|
8046
8133
|
const bytes2 = Buffer.from(content.bytes);
|
|
8047
8134
|
if (opts.out !== void 0) {
|
|
8048
8135
|
let target2 = opts.out;
|
|
8049
|
-
if (
|
|
8050
|
-
target2 =
|
|
8136
|
+
if (existsSync4(target2) && statSync2(target2).isDirectory()) {
|
|
8137
|
+
target2 = join3(target2, version.filename ?? name2);
|
|
8051
8138
|
}
|
|
8052
|
-
|
|
8139
|
+
writeFileSync3(target2, bytes2);
|
|
8053
8140
|
return console.log(`wrote ${target2} (${bytes2.byteLength} bytes, ${content.content_type})`);
|
|
8054
8141
|
}
|
|
8055
8142
|
if ((content.content_type ?? "").startsWith("text/")) {
|
|
@@ -8057,7 +8144,7 @@ files (v${artifact.current_version.version}):`);
|
|
|
8057
8144
|
return;
|
|
8058
8145
|
}
|
|
8059
8146
|
const target = version.filename ?? name2;
|
|
8060
|
-
|
|
8147
|
+
writeFileSync3(target, bytes2);
|
|
8061
8148
|
console.log(`wrote ${target} (${bytes2.byteLength} bytes, ${content.content_type})`);
|
|
8062
8149
|
}
|
|
8063
8150
|
);
|
|
@@ -8794,17 +8881,17 @@ function register7(program3) {
|
|
|
8794
8881
|
import { spawn as spawn3 } from "node:child_process";
|
|
8795
8882
|
import {
|
|
8796
8883
|
createWriteStream,
|
|
8797
|
-
existsSync as
|
|
8798
|
-
mkdirSync as
|
|
8884
|
+
existsSync as existsSync6,
|
|
8885
|
+
mkdirSync as mkdirSync5,
|
|
8799
8886
|
readFileSync as readFileSync10,
|
|
8800
8887
|
realpathSync,
|
|
8801
|
-
rmSync as
|
|
8888
|
+
rmSync as rmSync4,
|
|
8802
8889
|
statSync as statSync4,
|
|
8803
8890
|
unlinkSync as unlinkSync2,
|
|
8804
|
-
writeFileSync as
|
|
8891
|
+
writeFileSync as writeFileSync4
|
|
8805
8892
|
} from "node:fs";
|
|
8806
|
-
import { randomUUID } from "node:crypto";
|
|
8807
|
-
import { dirname as dirname4, join as
|
|
8893
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8894
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
8808
8895
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
8809
8896
|
|
|
8810
8897
|
// src/version.ts
|
|
@@ -9175,7 +9262,7 @@ var CodexStreamRenderer = class {
|
|
|
9175
9262
|
|
|
9176
9263
|
// src/daemon/codex-rollout.ts
|
|
9177
9264
|
import { open, opendir, realpath, stat } from "node:fs/promises";
|
|
9178
|
-
import { isAbsolute, join as
|
|
9265
|
+
import { isAbsolute as isAbsolute2, join as join4, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
9179
9266
|
import { homedir as homedir2 } from "node:os";
|
|
9180
9267
|
import { StringDecoder } from "node:string_decoder";
|
|
9181
9268
|
var CODEX_ROLLOUT_MAX_BYTES = 128 * 1024 * 1024;
|
|
@@ -9301,7 +9388,7 @@ function reconcileCodexRollout(records, input2) {
|
|
|
9301
9388
|
}
|
|
9302
9389
|
function resolveCodexHome(env, workspace) {
|
|
9303
9390
|
const configured = env.CODEX_HOME;
|
|
9304
|
-
return configured ?
|
|
9391
|
+
return configured ? resolve2(workspace, configured) : join4(homedir2(), ".codex");
|
|
9305
9392
|
}
|
|
9306
9393
|
function dateDirectories(startedAt, endedAt) {
|
|
9307
9394
|
const first = new Date(startedAt);
|
|
@@ -9313,7 +9400,7 @@ function dateDirectories(startedAt, endedAt) {
|
|
|
9313
9400
|
const result = [];
|
|
9314
9401
|
for (let date = first; date <= last; date = new Date(date.getTime() + 864e5)) {
|
|
9315
9402
|
result.push(
|
|
9316
|
-
|
|
9403
|
+
join4(
|
|
9317
9404
|
String(date.getUTCFullYear()),
|
|
9318
9405
|
String(date.getUTCMonth() + 1).padStart(2, "0"),
|
|
9319
9406
|
String(date.getUTCDate()).padStart(2, "0")
|
|
@@ -9380,7 +9467,7 @@ async function collectCodexRequestContext(input2) {
|
|
|
9380
9467
|
return unavailable("thread_id_missing");
|
|
9381
9468
|
const dates = dateDirectories(input2.startedAt, input2.endedAt);
|
|
9382
9469
|
if (!dates) return unavailable("limit_exceeded");
|
|
9383
|
-
const sessions =
|
|
9470
|
+
const sessions = join4(input2.codexHome, "sessions");
|
|
9384
9471
|
let root;
|
|
9385
9472
|
try {
|
|
9386
9473
|
root = await realpath(sessions);
|
|
@@ -9392,7 +9479,7 @@ async function collectCodexRequestContext(input2) {
|
|
|
9392
9479
|
const deadline = Date.now() + CODEX_ROLLOUT_TIMEOUT_MS;
|
|
9393
9480
|
try {
|
|
9394
9481
|
for (const date of dates) {
|
|
9395
|
-
const directory =
|
|
9482
|
+
const directory = join4(root, date);
|
|
9396
9483
|
let stream;
|
|
9397
9484
|
try {
|
|
9398
9485
|
stream = await opendir(directory);
|
|
@@ -9402,14 +9489,14 @@ async function collectCodexRequestContext(input2) {
|
|
|
9402
9489
|
for await (const entry of stream) {
|
|
9403
9490
|
if (++entries > 2e4 || Date.now() > deadline) return unavailable("limit_exceeded");
|
|
9404
9491
|
if ((entry.isFile() || entry.isSymbolicLink()) && entry.name.startsWith("rollout-") && entry.name.endsWith(`-${input2.threadId}.jsonl`))
|
|
9405
|
-
candidates.push(
|
|
9492
|
+
candidates.push(join4(directory, entry.name));
|
|
9406
9493
|
}
|
|
9407
9494
|
}
|
|
9408
9495
|
if (candidates.length === 0) return unavailable("rollout_missing");
|
|
9409
9496
|
if (candidates.length !== 1) return unavailable("rollout_ambiguous");
|
|
9410
9497
|
const candidate = await realpath(candidates[0]);
|
|
9411
|
-
const inside =
|
|
9412
|
-
if (inside.startsWith(`..${
|
|
9498
|
+
const inside = relative2(root, candidate);
|
|
9499
|
+
if (inside.startsWith(`..${sep2}`) || inside === ".." || isAbsolute2(inside))
|
|
9413
9500
|
return unavailable("unsafe_path");
|
|
9414
9501
|
const before = await stat(candidate);
|
|
9415
9502
|
if (!before.isFile() || before.size > CODEX_ROLLOUT_MAX_BYTES)
|
|
@@ -9530,11 +9617,11 @@ var DURATION = /(\d+)\s*([dhms])/g;
|
|
|
9530
9617
|
function parseResetTime(text4, now) {
|
|
9531
9618
|
const raw = text4.trim();
|
|
9532
9619
|
if (!raw) return null;
|
|
9533
|
-
const
|
|
9534
|
-
if (
|
|
9620
|
+
const relative3 = raw.match(/^in\s+(.+)$/i);
|
|
9621
|
+
if (relative3) {
|
|
9535
9622
|
let ms = 0;
|
|
9536
9623
|
let matched = false;
|
|
9537
|
-
for (const [, amount, unit] of
|
|
9624
|
+
for (const [, amount, unit] of relative3[1].matchAll(DURATION)) {
|
|
9538
9625
|
const n = Number(amount);
|
|
9539
9626
|
ms += n * { d: 864e5, h: 36e5, m: 6e4, s: 1e3 }[unit];
|
|
9540
9627
|
matched = true;
|
|
@@ -9658,12 +9745,12 @@ function failure(harness, daemonVersion, reason) {
|
|
|
9658
9745
|
};
|
|
9659
9746
|
}
|
|
9660
9747
|
function run(file, args) {
|
|
9661
|
-
return new Promise((
|
|
9748
|
+
return new Promise((resolve3, reject) => {
|
|
9662
9749
|
execFile(
|
|
9663
9750
|
file,
|
|
9664
9751
|
args,
|
|
9665
9752
|
{ timeout: DEADLINE_MS, maxBuffer: MAX_STDOUT },
|
|
9666
|
-
(error, stdout) => error ? reject(error) :
|
|
9753
|
+
(error, stdout) => error ? reject(error) : resolve3(stdout)
|
|
9667
9754
|
);
|
|
9668
9755
|
});
|
|
9669
9756
|
}
|
|
@@ -9716,7 +9803,7 @@ async function discoverCodex(daemonVersion) {
|
|
|
9716
9803
|
} catch (error) {
|
|
9717
9804
|
return failure("codex", daemonVersion, error instanceof Error ? error.message : String(error));
|
|
9718
9805
|
}
|
|
9719
|
-
return new Promise((
|
|
9806
|
+
return new Promise((resolve3) => {
|
|
9720
9807
|
const child = spawn("codex", ["app-server"], { stdio: ["pipe", "pipe", "ignore"] });
|
|
9721
9808
|
let buffer = "";
|
|
9722
9809
|
let bytes2 = 0;
|
|
@@ -9728,7 +9815,7 @@ async function discoverCodex(daemonVersion) {
|
|
|
9728
9815
|
settled = true;
|
|
9729
9816
|
clearTimeout(timer);
|
|
9730
9817
|
child.kill();
|
|
9731
|
-
|
|
9818
|
+
resolve3(report);
|
|
9732
9819
|
};
|
|
9733
9820
|
const fail = (reason) => finish(failure("codex", daemonVersion, reason));
|
|
9734
9821
|
const sendList = (cursor) => {
|
|
@@ -9826,11 +9913,11 @@ var EffortCapabilityRefresher = class {
|
|
|
9826
9913
|
|
|
9827
9914
|
// src/daemon/cli-refresh.ts
|
|
9828
9915
|
import { spawn as spawn2 } from "node:child_process";
|
|
9829
|
-
import { existsSync as
|
|
9830
|
-
import { dirname as dirname3, join as
|
|
9916
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
9917
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
9831
9918
|
|
|
9832
9919
|
// src/daemon/support.ts
|
|
9833
|
-
import { delimiter, sep as
|
|
9920
|
+
import { delimiter, sep as sep3 } from "node:path";
|
|
9834
9921
|
var HARNESS_KINDS = ["claude_code", "codex", "custom"];
|
|
9835
9922
|
var KEEP_WORKSPACES_MODES = ["never", "failed", "always"];
|
|
9836
9923
|
function keepWorkspace(mode, outcome) {
|
|
@@ -10144,7 +10231,7 @@ function pendingSelfUpdate(cli, running) {
|
|
|
10144
10231
|
return isNewerVersion(cli.version, running) ? cli.version : null;
|
|
10145
10232
|
}
|
|
10146
10233
|
function pathWithin(path2, dir) {
|
|
10147
|
-
const root = dir.endsWith(
|
|
10234
|
+
const root = dir.endsWith(sep3) ? dir : dir + sep3;
|
|
10148
10235
|
return path2.startsWith(root);
|
|
10149
10236
|
}
|
|
10150
10237
|
function buildSpawnEnv(base, opts) {
|
|
@@ -10217,14 +10304,14 @@ var SecretRedactor = class {
|
|
|
10217
10304
|
var PACKAGE = "tines";
|
|
10218
10305
|
var NPM_ARGS = ["--min-release-age=0", "--no-audit", "--no-fund", "--loglevel=error"];
|
|
10219
10306
|
function agentCliPrefix(configDir) {
|
|
10220
|
-
return
|
|
10307
|
+
return join5(configDir, "cli");
|
|
10221
10308
|
}
|
|
10222
10309
|
function binDirOf(prefix) {
|
|
10223
|
-
return
|
|
10310
|
+
return join5(prefix, "node_modules", ".bin");
|
|
10224
10311
|
}
|
|
10225
10312
|
function installedVersion(prefix) {
|
|
10226
10313
|
try {
|
|
10227
|
-
const pkg = readFileSync8(
|
|
10314
|
+
const pkg = readFileSync8(join5(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
|
|
10228
10315
|
const version = JSON.parse(pkg).version;
|
|
10229
10316
|
return typeof version === "string" ? version : null;
|
|
10230
10317
|
} catch {
|
|
@@ -10233,11 +10320,11 @@ function installedVersion(prefix) {
|
|
|
10233
10320
|
}
|
|
10234
10321
|
function lastGood(prefix) {
|
|
10235
10322
|
const binDir = binDirOf(prefix);
|
|
10236
|
-
if (!
|
|
10323
|
+
if (!existsSync5(join5(binDir, PACKAGE))) return AMBIENT_CLI;
|
|
10237
10324
|
return { binDir, version: installedVersion(prefix), source: "stale" };
|
|
10238
10325
|
}
|
|
10239
10326
|
function runNpm(file, args, cwd, timeoutMs) {
|
|
10240
|
-
return new Promise((
|
|
10327
|
+
return new Promise((resolve3) => {
|
|
10241
10328
|
let output = "";
|
|
10242
10329
|
let done = false;
|
|
10243
10330
|
let timedOut = false;
|
|
@@ -10252,7 +10339,7 @@ function runNpm(file, args, cwd, timeoutMs) {
|
|
|
10252
10339
|
if (done) return;
|
|
10253
10340
|
done = true;
|
|
10254
10341
|
clearTimeout(timer);
|
|
10255
|
-
|
|
10342
|
+
resolve3({ ...result, timedOut, output });
|
|
10256
10343
|
};
|
|
10257
10344
|
child.stdout?.on("data", (data) => output += data.toString("utf8"));
|
|
10258
10345
|
child.stderr?.on("data", (data) => output += data.toString("utf8"));
|
|
@@ -10270,7 +10357,7 @@ async function installAgentCli(opts) {
|
|
|
10270
10357
|
const prefix = agentCliPrefix(opts.configDir);
|
|
10271
10358
|
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
10272
10359
|
try {
|
|
10273
|
-
|
|
10360
|
+
mkdirSync4(prefix, { recursive: true });
|
|
10274
10361
|
} catch (err) {
|
|
10275
10362
|
opts.log(`agent CLI refresh: cannot create ${prefix} (${message(err)})`);
|
|
10276
10363
|
return AMBIENT_CLI;
|
|
@@ -10278,8 +10365,8 @@ async function installAgentCli(opts) {
|
|
|
10278
10365
|
const args = ["install", "--prefix", prefix, `${PACKAGE}@latest`, ...NPM_ARGS];
|
|
10279
10366
|
let result = await runNpm("npm", args, prefix, timeoutMs);
|
|
10280
10367
|
if (result.spawnError?.code === "ENOENT") {
|
|
10281
|
-
const sibling =
|
|
10282
|
-
if (
|
|
10368
|
+
const sibling = join5(dirname3(process.execPath), "npm");
|
|
10369
|
+
if (existsSync5(sibling)) result = await runNpm(sibling, args, prefix, timeoutMs);
|
|
10283
10370
|
}
|
|
10284
10371
|
if (result.code !== 0) {
|
|
10285
10372
|
const fallback = lastGood(prefix);
|
|
@@ -10304,13 +10391,13 @@ function message(err) {
|
|
|
10304
10391
|
import { arch, hostname, platform } from "node:os";
|
|
10305
10392
|
|
|
10306
10393
|
// src/daemon/store.ts
|
|
10307
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync9, rmSync as
|
|
10308
|
-
import { join as
|
|
10394
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
|
|
10395
|
+
import { join as join6 } from "node:path";
|
|
10309
10396
|
function credentialsKey(url, name2) {
|
|
10310
10397
|
return `${url.replace(/\/+$/, "")}#${name2}`;
|
|
10311
10398
|
}
|
|
10312
10399
|
function credentialsPath(dir) {
|
|
10313
|
-
return
|
|
10400
|
+
return join6(dir, "runners.json");
|
|
10314
10401
|
}
|
|
10315
10402
|
function loadRunnerCredentials(dir, url, name2) {
|
|
10316
10403
|
const all = readJsonFile(credentialsPath(dir));
|
|
@@ -10333,7 +10420,7 @@ function clearRunnerCredentials(dir, url, name2) {
|
|
|
10333
10420
|
writeJsonFile(path2, all, { secret: true });
|
|
10334
10421
|
}
|
|
10335
10422
|
function daemonStatePath(dir, runnerId) {
|
|
10336
|
-
return
|
|
10423
|
+
return join6(dir, `daemon-state-${runnerId}.json`);
|
|
10337
10424
|
}
|
|
10338
10425
|
function loadDaemonState(path2) {
|
|
10339
10426
|
const state = readJsonFile(path2);
|
|
@@ -10346,7 +10433,7 @@ function saveDaemonState(path2, runs) {
|
|
|
10346
10433
|
writeJsonFile(path2, { runs });
|
|
10347
10434
|
}
|
|
10348
10435
|
function daemonDeclinesPath(dir, runnerId) {
|
|
10349
|
-
return
|
|
10436
|
+
return join6(dir, `daemon-declines-${runnerId}.json`);
|
|
10350
10437
|
}
|
|
10351
10438
|
function loadDaemonDeclines(path2) {
|
|
10352
10439
|
const state = readJsonFile(path2);
|
|
@@ -10376,10 +10463,10 @@ function processStartTimeMs(pid) {
|
|
|
10376
10463
|
}
|
|
10377
10464
|
}
|
|
10378
10465
|
function workspacesDir(configDir) {
|
|
10379
|
-
return
|
|
10466
|
+
return join6(configDir, "workspaces");
|
|
10380
10467
|
}
|
|
10381
10468
|
function keptMarkerPath(workspace) {
|
|
10382
|
-
return
|
|
10469
|
+
return join6(workspace, "kept.json");
|
|
10383
10470
|
}
|
|
10384
10471
|
function writeKeptMarker(workspace, marker) {
|
|
10385
10472
|
writeJsonFile(keptMarkerPath(workspace), marker);
|
|
@@ -10401,7 +10488,7 @@ function listKeptWorkspaces(configDir) {
|
|
|
10401
10488
|
}
|
|
10402
10489
|
const kept = [];
|
|
10403
10490
|
for (const name2 of names) {
|
|
10404
|
-
const path2 =
|
|
10491
|
+
const path2 = join6(root, name2);
|
|
10405
10492
|
const marker = readKeptMarker(path2);
|
|
10406
10493
|
if (marker) kept.push({ ...marker, path: path2 });
|
|
10407
10494
|
}
|
|
@@ -10422,7 +10509,7 @@ function pruneKeptWorkspaces(configDir, opts) {
|
|
|
10422
10509
|
const removed = [];
|
|
10423
10510
|
for (const entry of doomed) {
|
|
10424
10511
|
try {
|
|
10425
|
-
|
|
10512
|
+
rmSync3(entry.path, { recursive: true, force: true });
|
|
10426
10513
|
removed.push(entry);
|
|
10427
10514
|
} catch {
|
|
10428
10515
|
}
|
|
@@ -10438,7 +10525,7 @@ function directorySizeBytes(path2) {
|
|
|
10438
10525
|
return 0;
|
|
10439
10526
|
}
|
|
10440
10527
|
for (const entry of entries) {
|
|
10441
|
-
const child =
|
|
10528
|
+
const child = join6(path2, entry.name);
|
|
10442
10529
|
if (entry.isDirectory()) total += directorySizeBytes(child);
|
|
10443
10530
|
else if (entry.isFile()) {
|
|
10444
10531
|
try {
|
|
@@ -10491,7 +10578,7 @@ async function uploadRawLog(run3) {
|
|
|
10491
10578
|
if (!path2 || !run3.rawUpload) return;
|
|
10492
10579
|
run3.rawSpoolPath = void 0;
|
|
10493
10580
|
try {
|
|
10494
|
-
await new Promise((
|
|
10581
|
+
await new Promise((resolve3) => run3.rawSpool?.end(resolve3) ?? resolve3());
|
|
10495
10582
|
const size = statSync4(path2).size;
|
|
10496
10583
|
if (size > 0) {
|
|
10497
10584
|
let body = readFileSync10(path2);
|
|
@@ -10516,7 +10603,7 @@ async function uploadRawLog(run3) {
|
|
|
10516
10603
|
}
|
|
10517
10604
|
}
|
|
10518
10605
|
var log = (message3) => console.log(`[${(/* @__PURE__ */ new Date()).toISOString().slice(11, 19)}] ${message3}`);
|
|
10519
|
-
var sleep = (ms) => new Promise((
|
|
10606
|
+
var sleep = (ms) => new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
10520
10607
|
function killTree(pid, signal) {
|
|
10521
10608
|
try {
|
|
10522
10609
|
process.kill(-pid, signal);
|
|
@@ -10536,10 +10623,10 @@ function pidAlive(pid) {
|
|
|
10536
10623
|
}
|
|
10537
10624
|
}
|
|
10538
10625
|
async function runDaemon(opts) {
|
|
10539
|
-
const instanceId =
|
|
10626
|
+
const instanceId = randomUUID2();
|
|
10540
10627
|
const baseUrl = opts.url.replace(/\/+$/, "");
|
|
10541
|
-
|
|
10542
|
-
|
|
10628
|
+
mkdirSync5(opts.configDir, { recursive: true });
|
|
10629
|
+
mkdirSync5(workspacesDir(opts.configDir), { recursive: true, mode: 448 });
|
|
10543
10630
|
const sweepKeptWorkspaces = () => {
|
|
10544
10631
|
const removed = pruneKeptWorkspaces(opts.configDir, {
|
|
10545
10632
|
maxAgeMs: opts.keepWorkspacesForHours * 36e5,
|
|
@@ -10554,10 +10641,10 @@ async function runDaemon(opts) {
|
|
|
10554
10641
|
sweepKeptWorkspaces();
|
|
10555
10642
|
const settleWorkspace = (workspace, keep, marker) => {
|
|
10556
10643
|
if (!keep) {
|
|
10557
|
-
|
|
10644
|
+
rmSync4(workspace, { recursive: true, force: true });
|
|
10558
10645
|
return;
|
|
10559
10646
|
}
|
|
10560
|
-
if (!
|
|
10647
|
+
if (!existsSync6(workspace)) return;
|
|
10561
10648
|
try {
|
|
10562
10649
|
writeKeptMarker(workspace, { ...marker, kept_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
10563
10650
|
log(`run ${marker.run_id}: workspace kept at ${workspace}`);
|
|
@@ -10602,7 +10689,7 @@ async function runDaemon(opts) {
|
|
|
10602
10689
|
const selfUpdate = opts.selfUpdate && opts.cliRefresh && managedInstall;
|
|
10603
10690
|
if (opts.selfUpdate && opts.cliRefresh) {
|
|
10604
10691
|
log(
|
|
10605
|
-
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 ${
|
|
10692
|
+
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 ${join7(prefix, "node_modules", ".bin", "tines")} to have refreshes apply on restart \u2014 \`tines runner install --name ${opts.name} --harness ${opts.harness.replaceAll("_", "-")}\` sets that up as a service (docs/runner-daemon.md)`
|
|
10606
10693
|
);
|
|
10607
10694
|
}
|
|
10608
10695
|
let pendingUpdate = null;
|
|
@@ -10765,13 +10852,13 @@ async function runDaemon(opts) {
|
|
|
10765
10852
|
const secretEnvValues = (assignment.env ?? []).filter((e) => e.secret).map((e) => e.value);
|
|
10766
10853
|
const runId = assignment.run.id;
|
|
10767
10854
|
if (table2.has(runId)) return;
|
|
10768
|
-
const resume = assignment.resume &&
|
|
10855
|
+
const resume = assignment.resume && existsSync6(assignment.resume.workspace_path) ? assignment.resume : null;
|
|
10769
10856
|
if (assignment.resume && !resume) {
|
|
10770
10857
|
log(
|
|
10771
10858
|
`run ${runId}: resume workspace ${assignment.resume.workspace_path} is gone; launching fresh`
|
|
10772
10859
|
);
|
|
10773
10860
|
}
|
|
10774
|
-
const workspace = resume ? resume.workspace_path :
|
|
10861
|
+
const workspace = resume ? resume.workspace_path : join7(workspacesDir(opts.configDir), runId);
|
|
10775
10862
|
const issueLabel = assignment.run.issue_ref ? `${assignment.run.issue_ref.project_name}/${assignment.run.issue_ref.number}` : void 0;
|
|
10776
10863
|
const run3 = {
|
|
10777
10864
|
runId,
|
|
@@ -10813,25 +10900,40 @@ async function runDaemon(opts) {
|
|
|
10813
10900
|
resume ? `run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); resuming run ${resume.previous_run_id} in ${workspace}` : `run ${runId} assigned (issue ${issueLabel ?? assignment.run.issue_id}); materializing workspace`
|
|
10814
10901
|
);
|
|
10815
10902
|
try {
|
|
10903
|
+
const currentRepoDirs = assignment.bundle.repos.map((repo) => repo.dir);
|
|
10904
|
+
let retainedRepoDirs = [];
|
|
10905
|
+
if (resume) {
|
|
10906
|
+
const retainedPath = join7(workspace, "repos.json");
|
|
10907
|
+
let retained;
|
|
10908
|
+
try {
|
|
10909
|
+
retained = JSON.parse(readFileSync10(retainedPath, "utf8"));
|
|
10910
|
+
} catch (error) {
|
|
10911
|
+
throw new Error(
|
|
10912
|
+
`cannot safely refresh skills: retained ${retainedPath} is unreadable or invalid JSON (${message2(error)})`
|
|
10913
|
+
);
|
|
10914
|
+
}
|
|
10915
|
+
if (!Array.isArray(retained) || retained.some(
|
|
10916
|
+
(repo) => !repo || typeof repo !== "object" || typeof repo.dir !== "string"
|
|
10917
|
+
)) {
|
|
10918
|
+
throw new Error(
|
|
10919
|
+
`cannot safely refresh skills: retained ${retainedPath} must be an array of repositories with string dir fields`
|
|
10920
|
+
);
|
|
10921
|
+
}
|
|
10922
|
+
retainedRepoDirs = retained.map((repo) => repo.dir);
|
|
10923
|
+
}
|
|
10924
|
+
assertNoSkillRepoOverlap([...currentRepoDirs, ...retainedRepoDirs]);
|
|
10816
10925
|
if (!resume) {
|
|
10817
|
-
|
|
10818
|
-
|
|
10926
|
+
rmSync4(workspace, { recursive: true, force: true });
|
|
10927
|
+
mkdirSync5(workspace, { recursive: true });
|
|
10819
10928
|
} else {
|
|
10820
|
-
|
|
10929
|
+
rmSync4(join7(workspace, "kept.json"), { force: true });
|
|
10821
10930
|
}
|
|
10822
|
-
|
|
10931
|
+
materializeSkills(workspace, assignment.bundle.skills);
|
|
10932
|
+
writeFileSync4(join7(workspace, "prompt.md"), `${assignment.prompt}
|
|
10823
10933
|
`);
|
|
10824
10934
|
if (!resume)
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
const target = join6(workspace, "skills", skill.name, file.path);
|
|
10828
|
-
mkdirSync4(dirname4(target), { recursive: true });
|
|
10829
|
-
writeFileSync3(target, file.content);
|
|
10830
|
-
}
|
|
10831
|
-
}
|
|
10832
|
-
if (!resume)
|
|
10833
|
-
writeFileSync3(
|
|
10834
|
-
join6(workspace, "repos.json"),
|
|
10935
|
+
writeFileSync4(
|
|
10936
|
+
join7(workspace, "repos.json"),
|
|
10835
10937
|
`${JSON.stringify(assignment.bundle.repos, null, 2)}
|
|
10836
10938
|
`
|
|
10837
10939
|
);
|
|
@@ -10865,7 +10967,7 @@ async function runDaemon(opts) {
|
|
|
10865
10967
|
);
|
|
10866
10968
|
const harnessInput = {
|
|
10867
10969
|
workspace,
|
|
10868
|
-
promptFile:
|
|
10970
|
+
promptFile: join7(workspace, "prompt.md"),
|
|
10869
10971
|
prompt: assignment.prompt,
|
|
10870
10972
|
model: assignment.run.model,
|
|
10871
10973
|
effort: assignment.effort?.value ?? null,
|
|
@@ -10931,8 +11033,8 @@ async function runDaemon(opts) {
|
|
|
10931
11033
|
(event) => limiter.noteStreamEvent(event)
|
|
10932
11034
|
);
|
|
10933
11035
|
run3.renderer = renderer;
|
|
10934
|
-
const spoolPath =
|
|
10935
|
-
|
|
11036
|
+
const spoolPath = join7(opts.configDir, "rawlogs", `${runId}.ndjson`);
|
|
11037
|
+
mkdirSync5(dirname4(spoolPath), { recursive: true });
|
|
10936
11038
|
run3.rawSpoolPath = spoolPath;
|
|
10937
11039
|
run3.rawSpool = createWriteStream(spoolPath);
|
|
10938
11040
|
run3.rawUpload = (body) => client2.putRunLogRaw(runId, body);
|
|
@@ -11185,12 +11287,12 @@ function realpathOrNull(path2) {
|
|
|
11185
11287
|
}
|
|
11186
11288
|
}
|
|
11187
11289
|
function runGit(args, cwd, batcher) {
|
|
11188
|
-
return new Promise((
|
|
11290
|
+
return new Promise((resolve3) => {
|
|
11189
11291
|
const child = spawn3("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
11190
11292
|
child.stdout?.on("data", (data) => batcher.append(data.toString("utf8")));
|
|
11191
11293
|
child.stderr?.on("data", (data) => batcher.append(data.toString("utf8")));
|
|
11192
|
-
child.on("error", () =>
|
|
11193
|
-
child.on("exit", (code) =>
|
|
11294
|
+
child.on("error", () => resolve3(127));
|
|
11295
|
+
child.on("exit", (code) => resolve3(code ?? 1));
|
|
11194
11296
|
});
|
|
11195
11297
|
}
|
|
11196
11298
|
|
|
@@ -11274,14 +11376,14 @@ function harnessFlag(harness) {
|
|
|
11274
11376
|
|
|
11275
11377
|
// src/commands/runner-service.ts
|
|
11276
11378
|
import { execFile as execFile2 } from "node:child_process";
|
|
11277
|
-
import { existsSync as
|
|
11379
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync5, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
11278
11380
|
import { homedir as homedir3, hostname as hostname3, userInfo } from "node:os";
|
|
11279
11381
|
import { dirname as dirname6 } from "node:path";
|
|
11280
11382
|
import { promisify } from "node:util";
|
|
11281
11383
|
|
|
11282
11384
|
// src/daemon/service.ts
|
|
11283
|
-
import { existsSync as
|
|
11284
|
-
import { delimiter as delimiter2, dirname as dirname5, join as
|
|
11385
|
+
import { existsSync as existsSync7, statSync as statSync5 } from "node:fs";
|
|
11386
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join8, sep as sep4 } from "node:path";
|
|
11285
11387
|
function defaultServiceManager(platform2 = process.platform) {
|
|
11286
11388
|
if (platform2 === "darwin") return "launchd";
|
|
11287
11389
|
if (platform2 === "linux") return "systemd";
|
|
@@ -11292,13 +11394,13 @@ function serviceLabel(name2, kind) {
|
|
|
11292
11394
|
}
|
|
11293
11395
|
function unitPath(name2, kind, home) {
|
|
11294
11396
|
const label = serviceLabel(name2, kind);
|
|
11295
|
-
return kind === "launchd" ?
|
|
11397
|
+
return kind === "launchd" ? join8(home, "Library", "LaunchAgents", `${label}.plist`) : join8(home, ".config", "systemd", "user", `${label}.service`);
|
|
11296
11398
|
}
|
|
11297
11399
|
function serviceLogPath(configDir, name2) {
|
|
11298
|
-
return
|
|
11400
|
+
return join8(configDir, "logs", `runner-${name2}.log`);
|
|
11299
11401
|
}
|
|
11300
11402
|
function managedBinary(prefix) {
|
|
11301
|
-
return
|
|
11403
|
+
return join8(prefix, "node_modules", ".bin", "tines");
|
|
11302
11404
|
}
|
|
11303
11405
|
function daemonArgs(input2) {
|
|
11304
11406
|
const args = [
|
|
@@ -11327,7 +11429,7 @@ function daemonArgs(input2) {
|
|
|
11327
11429
|
function findOnPath(name2, envPath) {
|
|
11328
11430
|
for (const dir of (envPath ?? "").split(delimiter2)) {
|
|
11329
11431
|
if (!dir) continue;
|
|
11330
|
-
const candidate =
|
|
11432
|
+
const candidate = join8(dir, name2);
|
|
11331
11433
|
try {
|
|
11332
11434
|
if (statSync5(candidate).isFile()) return candidate;
|
|
11333
11435
|
} catch {
|
|
@@ -11459,7 +11561,7 @@ function matchDaemonProcesses(psOutput, query2) {
|
|
|
11459
11561
|
else if (w.startsWith("--name=")) name2 = w.slice("--name=".length);
|
|
11460
11562
|
}
|
|
11461
11563
|
if ((name2 ?? query2.hostname) !== query2.name) continue;
|
|
11462
|
-
const root = query2.prefix.endsWith(
|
|
11564
|
+
const root = query2.prefix.endsWith(sep4) ? query2.prefix : query2.prefix + sep4;
|
|
11463
11565
|
matches.push({ pid, args, managed: args.includes(root) });
|
|
11464
11566
|
}
|
|
11465
11567
|
return matches;
|
|
@@ -11469,7 +11571,7 @@ function daemonStartedLine(name2) {
|
|
|
11469
11571
|
return new RegExp(`(reconnecting as runner|registered runner) "${escaped}"`);
|
|
11470
11572
|
}
|
|
11471
11573
|
function managedBinaryPresent(prefix) {
|
|
11472
|
-
return
|
|
11574
|
+
return existsSync7(managedBinary(prefix));
|
|
11473
11575
|
}
|
|
11474
11576
|
|
|
11475
11577
|
// src/commands/runner-service.ts
|
|
@@ -11520,11 +11622,11 @@ async function waitForStart(logPath, offset, name2) {
|
|
|
11520
11622
|
const pattern = daemonStartedLine(name2);
|
|
11521
11623
|
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
11522
11624
|
while (Date.now() < deadline) {
|
|
11523
|
-
if (
|
|
11625
|
+
if (existsSync8(logPath)) {
|
|
11524
11626
|
const content = readFileSync11(logPath, "utf8").slice(offset);
|
|
11525
11627
|
if (pattern.test(content)) return true;
|
|
11526
11628
|
}
|
|
11527
|
-
await new Promise((
|
|
11629
|
+
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
11528
11630
|
}
|
|
11529
11631
|
return false;
|
|
11530
11632
|
}
|
|
@@ -11543,7 +11645,7 @@ function registerServiceCommands(runnerCmd) {
|
|
|
11543
11645
|
const kind = resolveServiceManager(opts.serviceManager);
|
|
11544
11646
|
const baseUrl = resolveUrl(opts).replace(/\/+$/, "");
|
|
11545
11647
|
const configDir = defaultConfigDir();
|
|
11546
|
-
|
|
11648
|
+
mkdirSync6(configDir, { recursive: true });
|
|
11547
11649
|
const prefix = agentCliPrefix(configDir);
|
|
11548
11650
|
const ps = await listProcesses();
|
|
11549
11651
|
const running = ps ? matchDaemonProcesses(ps, {
|
|
@@ -11622,9 +11724,9 @@ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active
|
|
|
11622
11724
|
logPath,
|
|
11623
11725
|
env: process.env.TINES_CONFIG_DIR ? { TINES_CONFIG_DIR: process.env.TINES_CONFIG_DIR } : {}
|
|
11624
11726
|
};
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11727
|
+
mkdirSync6(dirname6(target.unitPath), { recursive: true });
|
|
11728
|
+
mkdirSync6(dirname6(logPath), { recursive: true });
|
|
11729
|
+
writeFileSync5(target.unitPath, renderUnit(spec), { mode: 420 });
|
|
11628
11730
|
log2(`wrote ${target.unitPath}`);
|
|
11629
11731
|
const offset = fileSize(logPath);
|
|
11630
11732
|
await runServiceCommands(serviceCommands(target).load);
|
|
@@ -11656,7 +11758,7 @@ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active
|
|
|
11656
11758
|
).option("--service-manager <kind>", "launchd | systemd (default: the one this OS ships)").option("--json", "print JSON instead of a message").action(async (name2, opts) => {
|
|
11657
11759
|
const kind = resolveServiceManager(opts.serviceManager);
|
|
11658
11760
|
const target = serviceTarget(name2 ?? hostname3(), kind);
|
|
11659
|
-
if (!
|
|
11761
|
+
if (!existsSync8(target.unitPath)) {
|
|
11660
11762
|
die(
|
|
11661
11763
|
`"${target.name}" is not installed as a service (no ${target.unitPath}); run \`tines runner install --name ${target.name} \u2026\` first`
|
|
11662
11764
|
);
|
|
@@ -11671,9 +11773,9 @@ stop it first \u2014 \`tines runners pause ${settings.name}\`, wait for 0 active
|
|
|
11671
11773
|
).option("--service-manager <kind>", "launchd | systemd (default: the one this OS ships)").option("--json", "print JSON instead of a message").action(async (name2, opts) => {
|
|
11672
11774
|
const kind = resolveServiceManager(opts.serviceManager);
|
|
11673
11775
|
const target = serviceTarget(name2 ?? hostname3(), kind);
|
|
11674
|
-
const existed =
|
|
11776
|
+
const existed = existsSync8(target.unitPath);
|
|
11675
11777
|
await runServiceCommands(serviceCommands(target).unload);
|
|
11676
|
-
|
|
11778
|
+
rmSync5(target.unitPath, { force: true });
|
|
11677
11779
|
if (opts.json)
|
|
11678
11780
|
return printJson({ name: target.name, service_manager: kind, removed: existed });
|
|
11679
11781
|
log2(
|
|
@@ -12752,12 +12854,12 @@ function register10(program3) {
|
|
|
12752
12854
|
const api = client(opts);
|
|
12753
12855
|
const overrides = {};
|
|
12754
12856
|
for (const spec of opts.state) {
|
|
12755
|
-
const
|
|
12756
|
-
if (
|
|
12857
|
+
const sep5 = spec.lastIndexOf("=");
|
|
12858
|
+
if (sep5 < 1 || sep5 === spec.length - 1) {
|
|
12757
12859
|
die(`--state must look like <workflow>/<state>=<n>, got "${spec}"`);
|
|
12758
12860
|
}
|
|
12759
|
-
const limit = Number.parseInt(spec.slice(
|
|
12760
|
-
const { state } = await resolveStateFlag(api, spec.slice(0,
|
|
12861
|
+
const limit = Number.parseInt(spec.slice(sep5 + 1), 10);
|
|
12862
|
+
const { state } = await resolveStateFlag(api, spec.slice(0, sep5));
|
|
12761
12863
|
overrides[state.id] = limit;
|
|
12762
12864
|
}
|
|
12763
12865
|
const settings = await api.updateSupervisorSettings({
|
|
@@ -13262,7 +13364,7 @@ function pinnedLookup(addresses) {
|
|
|
13262
13364
|
}
|
|
13263
13365
|
async function readHop(url, options) {
|
|
13264
13366
|
const pinned = await pinnedAddresses(url, options.lookup);
|
|
13265
|
-
return new Promise((
|
|
13367
|
+
return new Promise((resolve3, reject) => {
|
|
13266
13368
|
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(
|
|
13267
13369
|
url,
|
|
13268
13370
|
{
|
|
@@ -13278,7 +13380,7 @@ async function readHop(url, options) {
|
|
|
13278
13380
|
const location = response.headers.location;
|
|
13279
13381
|
if (!location) return reject(new Error("publication redirect has no destination"));
|
|
13280
13382
|
try {
|
|
13281
|
-
return
|
|
13383
|
+
return resolve3({ redirect: new URL(location, url) });
|
|
13282
13384
|
} catch {
|
|
13283
13385
|
return reject(new Error("publication redirect has an invalid destination"));
|
|
13284
13386
|
}
|
|
@@ -13313,7 +13415,7 @@ async function readHop(url, options) {
|
|
|
13313
13415
|
response.on("error", reject);
|
|
13314
13416
|
response.on("end", () => {
|
|
13315
13417
|
try {
|
|
13316
|
-
|
|
13418
|
+
resolve3({
|
|
13317
13419
|
body: new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
|
13318
13420
|
Buffer.concat(chunks)
|
|
13319
13421
|
)
|
|
@@ -13540,9 +13642,9 @@ async function resolveStateBases(states, library) {
|
|
|
13540
13642
|
}
|
|
13541
13643
|
}
|
|
13542
13644
|
function resolveBasePair(lib, pair, stateName) {
|
|
13543
|
-
const
|
|
13544
|
-
const workflowRef = pair.slice(0,
|
|
13545
|
-
const stateRef2 = pair.slice(
|
|
13645
|
+
const sep5 = pair.indexOf("/");
|
|
13646
|
+
const workflowRef = pair.slice(0, sep5).trim();
|
|
13647
|
+
const stateRef2 = pair.slice(sep5 + 1).trim();
|
|
13546
13648
|
const where = `state "${stateName}" inherits from "${pair}"`;
|
|
13547
13649
|
if (!workflowRef || !stateRef2) {
|
|
13548
13650
|
die(`${where}, which is not a <workflow>/<state> pair`);
|