tines 0.0.262 → 0.0.266
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 +183 -49
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3368,6 +3368,9 @@ function useColor() {
|
|
|
3368
3368
|
// ../../node_modules/.pnpm/commander@15.0.0/node_modules/commander/index.js
|
|
3369
3369
|
var program = new Command();
|
|
3370
3370
|
|
|
3371
|
+
// src/commands/context.ts
|
|
3372
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
3373
|
+
|
|
3371
3374
|
// src/body-value.ts
|
|
3372
3375
|
import { readFileSync } from "node:fs";
|
|
3373
3376
|
var processStdin = {
|
|
@@ -3432,6 +3435,7 @@ var LABEL_NAME_MAX = 50;
|
|
|
3432
3435
|
var PROMPT_MAX_BYTES = 32 * 1024;
|
|
3433
3436
|
var SKILL_MAX_FILES = 20;
|
|
3434
3437
|
var SKILL_MAX_TOTAL_BYTES = 100 * 1024;
|
|
3438
|
+
var ENV_VALUE_MAX_BYTES = 16 * 1024;
|
|
3435
3439
|
var AGENT_GUIDELINES_NAME = "agent-guidelines";
|
|
3436
3440
|
var JOURNAL_NAME = "journal";
|
|
3437
3441
|
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:
|
|
@@ -4005,6 +4009,7 @@ function createApiClient(options) {
|
|
|
4005
4009
|
const artifactPath = (issueId, name2, suffix = "") => `/api/v1/issues/${issueId}/artifacts/${encodeURIComponent(name2)}${suffix}`;
|
|
4006
4010
|
return {
|
|
4007
4011
|
getTime: () => get("/api/time"),
|
|
4012
|
+
getVersion: () => get("/api/version"),
|
|
4008
4013
|
// Projects
|
|
4009
4014
|
listProjects: (params = {}) => get(`/api/v1/projects${query(params)}`),
|
|
4010
4015
|
createProject: (body) => request("POST", "/api/v1/projects", body),
|
|
@@ -5635,6 +5640,8 @@ function contextItemSummary(item) {
|
|
|
5635
5640
|
return `${item.repo_url}${item.repo_branch ? `#${item.repo_branch}` : ""}`;
|
|
5636
5641
|
case "artifact":
|
|
5637
5642
|
return item.artifact_type ?? "artifact";
|
|
5643
|
+
case "env":
|
|
5644
|
+
return item.secret ? `secret \xB7 set${item.hint ? ` \xB7 ${item.hint}` : ""}` : `= ${item.value ?? ""}`;
|
|
5638
5645
|
}
|
|
5639
5646
|
}
|
|
5640
5647
|
function recurrenceLabel(schedule2) {
|
|
@@ -6233,6 +6240,12 @@ async function resolveScopeFlags(api, opts) {
|
|
|
6233
6240
|
if (opts.label !== void 0) scope.label_id = await resolveLabelFlag(api, opts.label);
|
|
6234
6241
|
return scope;
|
|
6235
6242
|
}
|
|
6243
|
+
function readEnvValue(raw) {
|
|
6244
|
+
if (raw === "-") return readFileSync4(0, "utf8").replace(/\r?\n$/, "");
|
|
6245
|
+
if (raw.startsWith("@@")) return raw.slice(1);
|
|
6246
|
+
if (raw.startsWith("@")) return readFileSync4(raw.slice(1), "utf8").replace(/\r?\n$/, "");
|
|
6247
|
+
return raw;
|
|
6248
|
+
}
|
|
6236
6249
|
function printContextItem(item) {
|
|
6237
6250
|
console.log(`${item.kind} "${item.name}" [${item.id}] v${item.version}`);
|
|
6238
6251
|
if (item.description) console.log(item.description);
|
|
@@ -6255,6 +6268,15 @@ files (seeded at skills/${item.name}/):`);
|
|
|
6255
6268
|
console.log(` ${f.path} (${Buffer.byteLength(f.content, "utf8")} bytes)`);
|
|
6256
6269
|
}
|
|
6257
6270
|
if ((item.files ?? []).length === 0) console.log(" (none)");
|
|
6271
|
+
} else if (item.kind === "env") {
|
|
6272
|
+
if (item.secret) {
|
|
6273
|
+
console.log(`
|
|
6274
|
+
value: (secret, set)${item.hint ? ` hint: ${item.hint}` : ""}`);
|
|
6275
|
+
} else {
|
|
6276
|
+
console.log(`
|
|
6277
|
+
value: ${item.value ?? ""}`);
|
|
6278
|
+
if (item.hint) console.log(`hint: ${item.hint}`);
|
|
6279
|
+
}
|
|
6258
6280
|
} else {
|
|
6259
6281
|
console.log(`
|
|
6260
6282
|
url: ${item.repo_url}`);
|
|
@@ -6276,13 +6298,13 @@ function withScopeFlags(cmd) {
|
|
|
6276
6298
|
}
|
|
6277
6299
|
function register(program3) {
|
|
6278
6300
|
const context2 = program3.command("context").description(
|
|
6279
|
-
"Manage context items (prompts, skills, repo pointers) scoped to projects, states, and issues"
|
|
6301
|
+
"Manage context items (prompts, skills, repo pointers, environment variables) scoped to projects, states, and issues"
|
|
6280
6302
|
);
|
|
6281
6303
|
withList(
|
|
6282
6304
|
withScopeFlags(
|
|
6283
6305
|
context2.command("list").description(
|
|
6284
6306
|
"List context items (scope filters match every item whose scope includes the element)"
|
|
6285
|
-
).option("-k, --kind <kind>", "filter by kind: prompt, skill, or
|
|
6307
|
+
).option("-k, --kind <kind>", "filter by kind: prompt, skill, repo, artifact, or env").option("--exact", "only items whose scope sets exactly the given dimensions").option("-q, --search <text>", "search names and descriptions")
|
|
6286
6308
|
)
|
|
6287
6309
|
).action(
|
|
6288
6310
|
async (opts) => {
|
|
@@ -6326,21 +6348,27 @@ function register(program3) {
|
|
|
6326
6348
|
});
|
|
6327
6349
|
withCommon(
|
|
6328
6350
|
withScopeFlags(
|
|
6329
|
-
context2.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, or
|
|
6351
|
+
context2.command("create").description("Create a context item scoped to a project, state, and/or issue").requiredOption("-k, --kind <kind>", "prompt, skill, repo, or env").requiredOption(
|
|
6330
6352
|
"-n, --name <name>",
|
|
6331
|
-
"item name (slug-like for skills; the dedup/override key)"
|
|
6353
|
+
"item name (slug-like for skills, the variable name for env; the dedup/override key)"
|
|
6332
6354
|
).option("-d, --description <text>", "one-liner shown in lists").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option(
|
|
6333
6355
|
"--file <path>=@<local>",
|
|
6334
6356
|
"skill file: workspace path = local file (repeatable)",
|
|
6335
6357
|
collect,
|
|
6336
6358
|
[]
|
|
6337
|
-
).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)")
|
|
6359
|
+
).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch to check out").option("--dir <dir>", "repo: checkout directory (defaults to the URL's basename)").option(
|
|
6360
|
+
"--value <v>",
|
|
6361
|
+
"env: the value \u2014 inline, @file, or - for stdin (keeps a secret out of shell history)"
|
|
6362
|
+
).option("--secret", "env: encrypt the value at rest; it is write-only afterwards").option("--hint <text>", 'env: a safe display hint for a secret (e.g. "github_pat_\u2026abcd")')
|
|
6338
6363
|
)
|
|
6339
6364
|
).action(
|
|
6340
6365
|
async (opts) => {
|
|
6341
6366
|
if (opts.kind === "repo" && opts.repoUrl === void 0) {
|
|
6342
6367
|
die("--kind repo needs --repo-url <clone-url> (--url is the API base URL)");
|
|
6343
6368
|
}
|
|
6369
|
+
if (opts.kind === "env" && opts.value === void 0) {
|
|
6370
|
+
die("--kind env needs --value <value|@file|->");
|
|
6371
|
+
}
|
|
6344
6372
|
const api = client(opts);
|
|
6345
6373
|
const scope = await resolveScopeFlags(api, opts);
|
|
6346
6374
|
const body = {
|
|
@@ -6355,6 +6383,9 @@ function register(program3) {
|
|
|
6355
6383
|
if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
|
|
6356
6384
|
if (opts.branch !== void 0) body.repo_branch = opts.branch;
|
|
6357
6385
|
if (opts.dir !== void 0) body.repo_dir = opts.dir;
|
|
6386
|
+
if (opts.value !== void 0) body.value = readEnvValue(opts.value);
|
|
6387
|
+
if (opts.secret) body.secret = true;
|
|
6388
|
+
if (opts.hint !== void 0) body.hint = opts.hint;
|
|
6358
6389
|
const item = await api.createContextItem(body);
|
|
6359
6390
|
if (opts.json) return printJson(item);
|
|
6360
6391
|
console.log(`created ${item.kind} "${item.name}" (${item.id}) \u2014 scope: ${item.scope.label}`);
|
|
@@ -6362,7 +6393,7 @@ function register(program3) {
|
|
|
6362
6393
|
);
|
|
6363
6394
|
withCommon(
|
|
6364
6395
|
withScopeFlags(
|
|
6365
|
-
context2.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option(
|
|
6396
|
+
context2.command("edit <id>").description("Edit a context item: payload, name, description, or scope").option("-n, --name <name>", "rename the item").option("-d, --description <text>", "set the description").option("--body <md>", "prompt body: inline Markdown or @file (escape a literal @ as @@)").option("--file <path>=@<local>", "add or replace a skill file (repeatable)", collect, []).option("--remove-file <path>", "remove a skill file (repeatable)", collect, []).option("--repo-url <url>", "repo: clone URL").option("--branch <branch>", "repo: branch (empty string clears it)").option("--dir <dir>", "repo: checkout directory (empty string restores the URL default)").option("--value <v>", "env: replace the value (inline, @file, or - for stdin)").option("--secret", "env: encrypt the stored value (irreversible; delete to make public)").option("--hint <text>", "env: display hint for a secret (empty string clears it)").option(
|
|
6366
6397
|
"--unset <dimension>",
|
|
6367
6398
|
"drop a scope dimension: project, state, issue, or label (repeatable)",
|
|
6368
6399
|
collect,
|
|
@@ -6412,6 +6443,9 @@ function register(program3) {
|
|
|
6412
6443
|
if (opts.repoUrl !== void 0) body.repo_url = opts.repoUrl;
|
|
6413
6444
|
if (opts.branch !== void 0) body.repo_branch = opts.branch === "" ? null : opts.branch;
|
|
6414
6445
|
if (opts.dir !== void 0) body.repo_dir = opts.dir === "" ? null : opts.dir;
|
|
6446
|
+
if (opts.value !== void 0) body.value = readEnvValue(opts.value);
|
|
6447
|
+
if (opts.secret) body.secret = true;
|
|
6448
|
+
if (opts.hint !== void 0) body.hint = opts.hint === "" ? null : opts.hint;
|
|
6415
6449
|
if (Object.keys(body).length === 0) {
|
|
6416
6450
|
die(
|
|
6417
6451
|
"nothing to update: pass payload flags, --name/--description, scope flags, and/or --unset"
|
|
@@ -6458,7 +6492,7 @@ function register(program3) {
|
|
|
6458
6492
|
}
|
|
6459
6493
|
|
|
6460
6494
|
// src/commands/issues.ts
|
|
6461
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as
|
|
6495
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync5, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
6462
6496
|
import { basename, dirname as dirname2, join as join2 } from "node:path";
|
|
6463
6497
|
import { createInterface } from "node:readline/promises";
|
|
6464
6498
|
|
|
@@ -7170,7 +7204,7 @@ function readTextSource(source) {
|
|
|
7170
7204
|
if (source.kind === "text-stdin") return readBodyValue("-");
|
|
7171
7205
|
if (source.kind === "text-path") {
|
|
7172
7206
|
try {
|
|
7173
|
-
return
|
|
7207
|
+
return readFileSync5(source.path, "utf8");
|
|
7174
7208
|
} catch (err) {
|
|
7175
7209
|
die(`cannot read ${source.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7176
7210
|
}
|
|
@@ -7188,7 +7222,7 @@ function walkFolder(dir) {
|
|
|
7188
7222
|
files2.push({
|
|
7189
7223
|
path: nextRel,
|
|
7190
7224
|
contentType: sniffContentType(entry.name),
|
|
7191
|
-
bytes:
|
|
7225
|
+
bytes: readFileSync5(nextAbs)
|
|
7192
7226
|
});
|
|
7193
7227
|
}
|
|
7194
7228
|
}
|
|
@@ -7643,6 +7677,9 @@ function register2(program3) {
|
|
|
7643
7677
|
console.log(`
|
|
7644
7678
|
skills: ${context2.skills.map((s) => s.name).join(", ")}`);
|
|
7645
7679
|
}
|
|
7680
|
+
for (const e of context2.env ?? []) {
|
|
7681
|
+
console.log(`env: ${e.name}${e.secret ? " (secret)" : ""}`);
|
|
7682
|
+
}
|
|
7646
7683
|
for (const repo of context2.repos) {
|
|
7647
7684
|
console.log(
|
|
7648
7685
|
`repo: ${repo.name} ${repo.url}${repo.branch ? `#${repo.branch}` : ""} \u2192 ${repo.dir}/`
|
|
@@ -7826,10 +7863,10 @@ files (v${artifact.current_version.version}):`);
|
|
|
7826
7863
|
let bytes2;
|
|
7827
7864
|
if (fromPath === null) {
|
|
7828
7865
|
if (process.stdin.isTTY) die('"-" reads the file from stdin, but stdin is a terminal');
|
|
7829
|
-
bytes2 =
|
|
7866
|
+
bytes2 = readFileSync5(0);
|
|
7830
7867
|
} else {
|
|
7831
7868
|
try {
|
|
7832
|
-
bytes2 =
|
|
7869
|
+
bytes2 = readFileSync5(fromPath);
|
|
7833
7870
|
} catch (err) {
|
|
7834
7871
|
die(`cannot read ${fromPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
7835
7872
|
}
|
|
@@ -8284,13 +8321,13 @@ function register4(program3) {
|
|
|
8284
8321
|
}
|
|
8285
8322
|
|
|
8286
8323
|
// src/commands/login.ts
|
|
8287
|
-
import { readFileSync as
|
|
8324
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
8288
8325
|
function maskKey(key) {
|
|
8289
8326
|
return `${key.slice(0, 14)}\u2026`;
|
|
8290
8327
|
}
|
|
8291
8328
|
function readKeyArg(value) {
|
|
8292
8329
|
if (value !== "-") return value;
|
|
8293
|
-
const key =
|
|
8330
|
+
const key = readFileSync6(0, "utf8").trim();
|
|
8294
8331
|
if (!key) die("no API key on stdin");
|
|
8295
8332
|
return key;
|
|
8296
8333
|
}
|
|
@@ -8730,7 +8767,7 @@ import {
|
|
|
8730
8767
|
createWriteStream,
|
|
8731
8768
|
existsSync as existsSync5,
|
|
8732
8769
|
mkdirSync as mkdirSync4,
|
|
8733
|
-
readFileSync as
|
|
8770
|
+
readFileSync as readFileSync10,
|
|
8734
8771
|
realpathSync,
|
|
8735
8772
|
rmSync as rmSync3,
|
|
8736
8773
|
statSync as statSync4,
|
|
@@ -8742,11 +8779,11 @@ import { dirname as dirname4, join as join6 } from "node:path";
|
|
|
8742
8779
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
8743
8780
|
|
|
8744
8781
|
// src/version.ts
|
|
8745
|
-
import { readFileSync as
|
|
8782
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
8746
8783
|
function cliVersion() {
|
|
8747
8784
|
try {
|
|
8748
8785
|
const manifest = new URL("../package.json", import.meta.url);
|
|
8749
|
-
return JSON.parse(
|
|
8786
|
+
return JSON.parse(readFileSync7(manifest, "utf8")).version ?? "0.0.0-unknown";
|
|
8750
8787
|
} catch {
|
|
8751
8788
|
return "0.0.0-unknown";
|
|
8752
8789
|
}
|
|
@@ -9760,7 +9797,7 @@ var EffortCapabilityRefresher = class {
|
|
|
9760
9797
|
|
|
9761
9798
|
// src/daemon/cli-refresh.ts
|
|
9762
9799
|
import { spawn as spawn2 } from "node:child_process";
|
|
9763
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
9800
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
9764
9801
|
import { dirname as dirname3, join as join4 } from "node:path";
|
|
9765
9802
|
|
|
9766
9803
|
// src/daemon/support.ts
|
|
@@ -9945,10 +9982,12 @@ var LogBatcher = class {
|
|
|
9945
9982
|
constructor(send, opts = {}) {
|
|
9946
9983
|
this.send = send;
|
|
9947
9984
|
this.opts = opts;
|
|
9985
|
+
this.redactor = new SecretRedactor(opts.redact ?? []);
|
|
9948
9986
|
}
|
|
9949
9987
|
send;
|
|
9950
9988
|
opts;
|
|
9951
9989
|
buffer = "";
|
|
9990
|
+
redactor;
|
|
9952
9991
|
timer = null;
|
|
9953
9992
|
sending = Promise.resolve();
|
|
9954
9993
|
seq = 0;
|
|
@@ -9956,6 +9995,7 @@ var LogBatcher = class {
|
|
|
9956
9995
|
pending = [];
|
|
9957
9996
|
append(text4) {
|
|
9958
9997
|
if (!text4) return;
|
|
9998
|
+
text4 = this.redactor.write(text4);
|
|
9959
9999
|
this.buffer += text4;
|
|
9960
10000
|
if (Buffer.byteLength(this.buffer, "utf8") >= (this.opts.maxBytes ?? 32 * 1024)) {
|
|
9961
10001
|
void this.flush();
|
|
@@ -9964,6 +10004,11 @@ var LogBatcher = class {
|
|
|
9964
10004
|
this.timer.unref?.();
|
|
9965
10005
|
}
|
|
9966
10006
|
}
|
|
10007
|
+
/** Release an incomplete final match only after the producer has ended. */
|
|
10008
|
+
finish() {
|
|
10009
|
+
this.buffer += this.redactor.end();
|
|
10010
|
+
return this.flush();
|
|
10011
|
+
}
|
|
9967
10012
|
/** Sends whatever is buffered now; resolves when every send so far settled. */
|
|
9968
10013
|
flush() {
|
|
9969
10014
|
if (this.timer) {
|
|
@@ -10076,15 +10121,68 @@ function pathWithin(path2, dir) {
|
|
|
10076
10121
|
function buildSpawnEnv(base, opts) {
|
|
10077
10122
|
const env = {
|
|
10078
10123
|
...base,
|
|
10124
|
+
...Object.fromEntries((opts.extra ?? []).map((e) => [e.name, e.value])),
|
|
10079
10125
|
TINES_API_KEY: opts.apiKey,
|
|
10080
10126
|
// Own canonicalization at the final child-process boundary too. This
|
|
10081
10127
|
// keeps the spawned CLI safe even if a future daemon call site passes
|
|
10082
10128
|
// the original --url value instead of its normalized local variable.
|
|
10083
10129
|
TINES_API_URL: opts.apiUrl.replace(/\/+$/, "")
|
|
10084
10130
|
};
|
|
10131
|
+
if (opts.extra?.some((e) => e.name === "PATH")) env.PATH = base.PATH;
|
|
10085
10132
|
if (opts.binDir) env.PATH = base.PATH ? `${opts.binDir}${delimiter}${base.PATH}` : opts.binDir;
|
|
10086
10133
|
return env;
|
|
10087
10134
|
}
|
|
10135
|
+
function secretForms(secrets) {
|
|
10136
|
+
return [
|
|
10137
|
+
...new Set(
|
|
10138
|
+
secrets.filter(Boolean).flatMap((secret) => [secret, JSON.stringify(secret).slice(1, -1)])
|
|
10139
|
+
)
|
|
10140
|
+
].sort((a, b) => b.length - a.length);
|
|
10141
|
+
}
|
|
10142
|
+
function secretPattern(forms) {
|
|
10143
|
+
return forms.length ? new RegExp(forms.map((form) => form.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), "g") : null;
|
|
10144
|
+
}
|
|
10145
|
+
function redactSecrets(text4, secrets) {
|
|
10146
|
+
const pattern = secretPattern(secretForms(secrets));
|
|
10147
|
+
return pattern ? text4.replace(pattern, "***") : text4;
|
|
10148
|
+
}
|
|
10149
|
+
var SecretRedactor = class {
|
|
10150
|
+
pending = "";
|
|
10151
|
+
forms;
|
|
10152
|
+
pattern;
|
|
10153
|
+
constructor(secrets) {
|
|
10154
|
+
this.forms = secretForms(secrets);
|
|
10155
|
+
this.pattern = secretPattern(this.forms);
|
|
10156
|
+
}
|
|
10157
|
+
write(chunk) {
|
|
10158
|
+
if (!this.pattern) return chunk;
|
|
10159
|
+
const text4 = this.pending + chunk;
|
|
10160
|
+
let cut = text4.length;
|
|
10161
|
+
for (const form of this.forms) {
|
|
10162
|
+
for (let size = Math.min(form.length - 1, text4.length); size > text4.length - cut; size--) {
|
|
10163
|
+
if (text4.endsWith(form.slice(0, size))) {
|
|
10164
|
+
cut = text4.length - size;
|
|
10165
|
+
break;
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
}
|
|
10169
|
+
this.pattern.lastIndex = 0;
|
|
10170
|
+
for (let match; match = this.pattern.exec(text4); ) {
|
|
10171
|
+
if (match.index >= cut) break;
|
|
10172
|
+
if (this.pattern.lastIndex > cut) {
|
|
10173
|
+
cut = match.index;
|
|
10174
|
+
break;
|
|
10175
|
+
}
|
|
10176
|
+
}
|
|
10177
|
+
this.pending = text4.slice(cut);
|
|
10178
|
+
return text4.slice(0, cut).replace(this.pattern, "***");
|
|
10179
|
+
}
|
|
10180
|
+
end() {
|
|
10181
|
+
const text4 = this.pending;
|
|
10182
|
+
this.pending = "";
|
|
10183
|
+
return this.pattern ? text4.replace(this.pattern, "***") : text4;
|
|
10184
|
+
}
|
|
10185
|
+
};
|
|
10088
10186
|
|
|
10089
10187
|
// src/daemon/cli-refresh.ts
|
|
10090
10188
|
var PACKAGE = "tines";
|
|
@@ -10097,7 +10195,7 @@ function binDirOf(prefix) {
|
|
|
10097
10195
|
}
|
|
10098
10196
|
function installedVersion(prefix) {
|
|
10099
10197
|
try {
|
|
10100
|
-
const pkg =
|
|
10198
|
+
const pkg = readFileSync8(join4(prefix, "node_modules", PACKAGE, "package.json"), "utf8");
|
|
10101
10199
|
const version = JSON.parse(pkg).version;
|
|
10102
10200
|
return typeof version === "string" ? version : null;
|
|
10103
10201
|
} catch {
|
|
@@ -10177,7 +10275,7 @@ function message(err) {
|
|
|
10177
10275
|
import { arch, hostname, platform } from "node:os";
|
|
10178
10276
|
|
|
10179
10277
|
// src/daemon/store.ts
|
|
10180
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
10278
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
|
|
10181
10279
|
import { join as join5 } from "node:path";
|
|
10182
10280
|
function credentialsKey(url, name2) {
|
|
10183
10281
|
return `${url.replace(/\/+$/, "")}#${name2}`;
|
|
@@ -10237,10 +10335,10 @@ function saveDaemonDeclines(path2, runIds) {
|
|
|
10237
10335
|
}
|
|
10238
10336
|
function processStartTimeMs(pid) {
|
|
10239
10337
|
try {
|
|
10240
|
-
const stat2 =
|
|
10338
|
+
const stat2 = readFileSync9(`/proc/${pid}/stat`, "utf8");
|
|
10241
10339
|
const afterComm = stat2.slice(stat2.lastIndexOf(")") + 2).split(" ");
|
|
10242
10340
|
const startTicks = Number(afterComm[19]);
|
|
10243
|
-
const btimeLine =
|
|
10341
|
+
const btimeLine = readFileSync9("/proc/stat", "utf8").split("\n").find((line) => line.startsWith("btime "));
|
|
10244
10342
|
const btime = Number(btimeLine?.slice("btime ".length));
|
|
10245
10343
|
if (!Number.isFinite(startTicks) || !Number.isFinite(btime)) return null;
|
|
10246
10344
|
return btime * 1e3 + startTicks / 100 * 1e3;
|
|
@@ -10367,7 +10465,7 @@ async function uploadRawLog(run3) {
|
|
|
10367
10465
|
await new Promise((resolve2) => run3.rawSpool?.end(resolve2) ?? resolve2());
|
|
10368
10466
|
const size = statSync4(path2).size;
|
|
10369
10467
|
if (size > 0) {
|
|
10370
|
-
let body =
|
|
10468
|
+
let body = readFileSync10(path2);
|
|
10371
10469
|
if (body.byteLength > RUN_LOG_RAW_MAX_BYTES) {
|
|
10372
10470
|
const marker = Buffer.from(
|
|
10373
10471
|
`{"type":"tines_truncated","dropped_bytes":${body.byteLength - RUN_LOG_RAW_MAX_BYTES}}
|
|
@@ -10534,7 +10632,7 @@ async function runDaemon(opts) {
|
|
|
10534
10632
|
};
|
|
10535
10633
|
run3.batcher.append(`[usage] ${JSON.stringify(accounting)}
|
|
10536
10634
|
`);
|
|
10537
|
-
await run3.batcher.
|
|
10635
|
+
await run3.batcher.finish();
|
|
10538
10636
|
const ended = await client2.finishRun(run3.runId, {
|
|
10539
10637
|
status,
|
|
10540
10638
|
...run3.effortEvidence ? { effort_application: run3.effortEvidence } : {},
|
|
@@ -10635,6 +10733,7 @@ async function runDaemon(opts) {
|
|
|
10635
10733
|
const effortRefresher = new EffortCapabilityRefresher(opts.harness, DAEMON_VERSION);
|
|
10636
10734
|
let effortCapabilities = await effortRefresher.get();
|
|
10637
10735
|
const launch = async (assignment) => {
|
|
10736
|
+
const secretEnvValues = (assignment.env ?? []).filter((e) => e.secret).map((e) => e.value);
|
|
10638
10737
|
const runId = assignment.run.id;
|
|
10639
10738
|
if (table2.has(runId)) return;
|
|
10640
10739
|
const resume = assignment.resume && existsSync5(assignment.resume.workspace_path) ? assignment.resume : null;
|
|
@@ -10658,10 +10757,13 @@ async function runDaemon(opts) {
|
|
|
10658
10757
|
batcher: new LogBatcher(
|
|
10659
10758
|
(chunk, seq) => client2.appendRunLog(runId, { chunk, seq }).then(() => {
|
|
10660
10759
|
}),
|
|
10661
|
-
{
|
|
10760
|
+
{
|
|
10761
|
+
onError: (err) => log(`log append for run ${runId} failed: ${message2(err)}`),
|
|
10762
|
+
...secretEnvValues.length > 0 ? { redact: secretEnvValues } : {}
|
|
10763
|
+
}
|
|
10662
10764
|
)
|
|
10663
10765
|
};
|
|
10664
|
-
run3.flush = () => run3.batcher.
|
|
10766
|
+
run3.flush = () => run3.batcher.finish();
|
|
10665
10767
|
table2.track(run3);
|
|
10666
10768
|
if (assignment.effort) effortCapabilities = await effortRefresher.get(true);
|
|
10667
10769
|
const effortRejection = assignmentEffortRejection(assignment, effortCapabilities, opts.harness);
|
|
@@ -10759,8 +10861,15 @@ async function runDaemon(opts) {
|
|
|
10759
10861
|
const spawnEnv = buildSpawnEnv(process.env, {
|
|
10760
10862
|
binDir: cli.binDir,
|
|
10761
10863
|
apiKey: assignment.run_key,
|
|
10762
|
-
apiUrl: baseUrl
|
|
10864
|
+
apiUrl: baseUrl,
|
|
10865
|
+
...assignment.env ? { extra: assignment.env } : {}
|
|
10763
10866
|
});
|
|
10867
|
+
if (assignment.env?.length) {
|
|
10868
|
+
run3.batcher.append(
|
|
10869
|
+
`[env] ${assignment.env.length} environment variable(s) set: ${assignment.env.map((e) => `${e.name}${e.secret ? " (secret)" : ""}`).join(", ")}
|
|
10870
|
+
`
|
|
10871
|
+
);
|
|
10872
|
+
}
|
|
10764
10873
|
if (opts.harness === "codex") run3.codexHome = resolveCodexHome(spawnEnv, workspace);
|
|
10765
10874
|
const child = spawn3(invocation.file, invocation.args, {
|
|
10766
10875
|
cwd: workspace,
|
|
@@ -10799,14 +10908,20 @@ async function runDaemon(opts) {
|
|
|
10799
10908
|
run3.rawSpool = createWriteStream(spoolPath);
|
|
10800
10909
|
run3.rawUpload = (body) => client2.putRunLogRaw(runId, body);
|
|
10801
10910
|
const decoder = new StringDecoder2("utf8");
|
|
10911
|
+
const rawRedactor = new SecretRedactor(secretEnvValues);
|
|
10802
10912
|
run3.drain = () => {
|
|
10803
10913
|
const trailing = decoder.end();
|
|
10804
|
-
if (trailing)
|
|
10914
|
+
if (trailing) {
|
|
10915
|
+
run3.rawSpool?.write(rawRedactor.write(trailing));
|
|
10916
|
+
renderer.write(trailing);
|
|
10917
|
+
}
|
|
10918
|
+
run3.rawSpool?.write(rawRedactor.end());
|
|
10805
10919
|
renderer.finish();
|
|
10806
10920
|
};
|
|
10807
10921
|
child.stdout?.on("data", (data) => {
|
|
10808
|
-
|
|
10809
|
-
|
|
10922
|
+
const text4 = decoder.write(data);
|
|
10923
|
+
run3.rawSpool?.write(rawRedactor.write(text4));
|
|
10924
|
+
renderer.write(text4);
|
|
10810
10925
|
});
|
|
10811
10926
|
} else if (opts.harness === "codex") {
|
|
10812
10927
|
const renderer = new CodexStreamRenderer((line) => run3.batcher.append(line));
|
|
@@ -10819,11 +10934,26 @@ async function runDaemon(opts) {
|
|
|
10819
10934
|
};
|
|
10820
10935
|
child.stdout?.on("data", (data) => renderer.write(decoder.write(data)));
|
|
10821
10936
|
} else {
|
|
10822
|
-
|
|
10937
|
+
const decoder = new StringDecoder2("utf8");
|
|
10938
|
+
const redactor = new SecretRedactor(secretEnvValues);
|
|
10939
|
+
child.stdout?.on(
|
|
10940
|
+
"data",
|
|
10941
|
+
(data) => run3.batcher.append(redactor.write(decoder.write(data)))
|
|
10942
|
+
);
|
|
10943
|
+
run3.drain = () => {
|
|
10944
|
+
run3.batcher.append(redactor.write(decoder.end()) + redactor.end());
|
|
10945
|
+
};
|
|
10823
10946
|
}
|
|
10947
|
+
const stderrDecoder = new StringDecoder2("utf8");
|
|
10948
|
+
const stderrRedactor = new SecretRedactor(secretEnvValues);
|
|
10949
|
+
const drainStdout = run3.drain;
|
|
10950
|
+
run3.drain = () => {
|
|
10951
|
+
drainStdout?.();
|
|
10952
|
+
run3.batcher.append(stderrRedactor.write(stderrDecoder.end()) + stderrRedactor.end());
|
|
10953
|
+
};
|
|
10824
10954
|
child.stderr?.on("data", (data) => {
|
|
10825
|
-
const text4 =
|
|
10826
|
-
run3.batcher.append(text4);
|
|
10955
|
+
const text4 = stderrDecoder.write(data);
|
|
10956
|
+
run3.batcher.append(stderrRedactor.write(text4));
|
|
10827
10957
|
run3.limiter?.noteStderr(text4);
|
|
10828
10958
|
});
|
|
10829
10959
|
run3.timeout = setTimeout(() => {
|
|
@@ -10834,7 +10964,7 @@ async function runDaemon(opts) {
|
|
|
10834
10964
|
if (child.pid) setTimeout(() => killTree(child.pid, "SIGKILL"), 5e3).unref?.();
|
|
10835
10965
|
}, assignment.timeout_minutes * 6e4);
|
|
10836
10966
|
child.on("error", (err) => {
|
|
10837
|
-
const reason = `failed to launch harness: ${message2(err)}
|
|
10967
|
+
const reason = redactSecrets(`failed to launch harness: ${message2(err)}`, secretEnvValues);
|
|
10838
10968
|
if (assignment.effort)
|
|
10839
10969
|
run3.effortEvidence = {
|
|
10840
10970
|
status: "rejected",
|
|
@@ -10869,18 +10999,16 @@ async function runDaemon(opts) {
|
|
|
10869
10999
|
const limited = signal ? null : run3.limiter?.signal() ?? null;
|
|
10870
11000
|
const providerError = signal ? null : run3.limiter?.providerError() ?? null;
|
|
10871
11001
|
if (limited) {
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
void table2.finishAndCleanup(run3, "failed", `rate limited: ${limited.detail}`, {
|
|
11002
|
+
const detail = redactSecrets(limited.detail, secretEnvValues);
|
|
11003
|
+
log(`run ${runId}: harness rate limited (${detail}); reporting without a strike`);
|
|
11004
|
+
void table2.finishAndCleanup(run3, "failed", `rate limited: ${detail}`, {
|
|
10876
11005
|
judgment: "rate_limited",
|
|
10877
11006
|
...limited.resumeAt !== null ? { resume_at: limited.resumeAt } : {}
|
|
10878
11007
|
});
|
|
10879
11008
|
} else if (providerError) {
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
|
|
10883
|
-
void table2.finishAndCleanup(run3, "failed", `provider error: ${providerError.detail}`, {
|
|
11009
|
+
const detail = redactSecrets(providerError.detail, secretEnvValues);
|
|
11010
|
+
log(`run ${runId}: transient provider error (${detail}); reporting without a strike`);
|
|
11011
|
+
void table2.finishAndCleanup(run3, "failed", `provider error: ${detail}`, {
|
|
10884
11012
|
judgment: "interrupted"
|
|
10885
11013
|
});
|
|
10886
11014
|
} else {
|
|
@@ -10893,7 +11021,11 @@ async function runDaemon(opts) {
|
|
|
10893
11021
|
}
|
|
10894
11022
|
});
|
|
10895
11023
|
} catch (err) {
|
|
10896
|
-
void table2.finishAndCleanup(
|
|
11024
|
+
void table2.finishAndCleanup(
|
|
11025
|
+
run3,
|
|
11026
|
+
"failed",
|
|
11027
|
+
redactSecrets(`workspace setup failed: ${message2(err)}`, secretEnvValues)
|
|
11028
|
+
);
|
|
10897
11029
|
}
|
|
10898
11030
|
};
|
|
10899
11031
|
const shutdown = async () => {
|
|
@@ -10933,6 +11065,8 @@ async function runDaemon(opts) {
|
|
|
10933
11065
|
},
|
|
10934
11066
|
...declinedAssignments.size > 0 ? { declined_assignments: [...declinedAssignments].slice(0, 100) } : {},
|
|
10935
11067
|
...effortCapabilities ? { effort_capabilities: effortCapabilities } : {},
|
|
11068
|
+
// Capability: this daemon merges assignment.env into the harness environment.
|
|
11069
|
+
env_delivery: 1,
|
|
10936
11070
|
// Stated on every poll while pending; absent otherwise, which
|
|
10937
11071
|
// the server reads as "not draining" — so a daemon that died
|
|
10938
11072
|
// mid-drain cannot pin its runner shut past its relaunch.
|
|
@@ -11111,7 +11245,7 @@ function harnessFlag(harness) {
|
|
|
11111
11245
|
|
|
11112
11246
|
// src/commands/runner-service.ts
|
|
11113
11247
|
import { execFile as execFile2 } from "node:child_process";
|
|
11114
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as
|
|
11248
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync11, rmSync as rmSync4, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
11115
11249
|
import { homedir as homedir3, hostname as hostname3, userInfo } from "node:os";
|
|
11116
11250
|
import { dirname as dirname6 } from "node:path";
|
|
11117
11251
|
import { promisify } from "node:util";
|
|
@@ -11358,7 +11492,7 @@ async function waitForStart(logPath, offset, name2) {
|
|
|
11358
11492
|
const deadline = Date.now() + STARTUP_TIMEOUT_MS;
|
|
11359
11493
|
while (Date.now() < deadline) {
|
|
11360
11494
|
if (existsSync7(logPath)) {
|
|
11361
|
-
const content =
|
|
11495
|
+
const content = readFileSync11(logPath, "utf8").slice(offset);
|
|
11362
11496
|
if (pattern.test(content)) return true;
|
|
11363
11497
|
}
|
|
11364
11498
|
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
@@ -12611,18 +12745,18 @@ function register10(program3) {
|
|
|
12611
12745
|
}
|
|
12612
12746
|
|
|
12613
12747
|
// src/commands/workflows.ts
|
|
12614
|
-
import { readFileSync as
|
|
12748
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
12615
12749
|
import { createInterface as createInterface5 } from "node:readline/promises";
|
|
12616
12750
|
|
|
12617
12751
|
// src/workflow-packages.ts
|
|
12618
12752
|
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
12619
12753
|
import { createHash as createHash2 } from "node:crypto";
|
|
12620
|
-
import { readFileSync as
|
|
12754
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
12621
12755
|
var PACKAGE_STDIN = "-";
|
|
12622
12756
|
var MAX_LOCAL_JSON_BYTES = 32 * 1024 * 1024;
|
|
12623
12757
|
function readPackageSource(path2) {
|
|
12624
12758
|
try {
|
|
12625
|
-
const bytes2 =
|
|
12759
|
+
const bytes2 = readFileSync12(path2 === PACKAGE_STDIN ? 0 : path2);
|
|
12626
12760
|
if (bytes2.byteLength > MAX_LOCAL_JSON_BYTES)
|
|
12627
12761
|
throw new Error(`input exceeds ${MAX_LOCAL_JSON_BYTES} bytes`);
|
|
12628
12762
|
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes2);
|
|
@@ -13261,14 +13395,14 @@ function readJsonBody(inline, file) {
|
|
|
13261
13395
|
if (file !== void 0 && file !== "-") {
|
|
13262
13396
|
let raw;
|
|
13263
13397
|
try {
|
|
13264
|
-
raw =
|
|
13398
|
+
raw = readFileSync13(file, "utf8");
|
|
13265
13399
|
} catch (err) {
|
|
13266
13400
|
die(`cannot read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
13267
13401
|
}
|
|
13268
13402
|
return parseJsonObject(raw, file);
|
|
13269
13403
|
}
|
|
13270
13404
|
if (file === "-" || !process.stdin.isTTY) {
|
|
13271
|
-
const raw =
|
|
13405
|
+
const raw = readFileSync13(0, "utf8");
|
|
13272
13406
|
if (raw.trim() === "") {
|
|
13273
13407
|
if (file === "-") die("no JSON on stdin");
|
|
13274
13408
|
return void 0;
|