@north-light/cli 0.3.556 → 0.3.557
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/clients/attach/viewer.js +172 -172
- package/dist/commands/node/bash.js +2 -2
- package/dist/core/bash-job-supervisor.d.ts +1 -1
- package/dist/core/bash-job-supervisor.js +3 -3
- package/dist/core/bash-jobs.d.ts +26 -0
- package/dist/core/bash-jobs.js +10 -8
- package/dist/core/graph/kinds/job.js +2 -2
- package/dist/core/runtime/broker/bash-job-launch.js +1 -1
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/reconcilers/bash-deadline.d.ts +8 -0
- package/dist/daemon/reconcilers/bash-deadline.js +3 -3
- package/dist/shared/env.d.ts +4 -0
- package/dist/shared/env.js +1 -1
- package/dist/shared/inbox-entry-body.js +7 -7
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
1
|
+
var $=Object.defineProperty;var i=(e,a)=>$(e,"name",{value:a,configurable:!0});import{envNodeId as _}from"../../shared/env.js";import{defineBranch as T,defineLeaf as c}from"../../core/command.js";import{InputError as r}from"../../core/io.js";import{ExitCode as I}from"../../types.js";import{existsSync as u,mkdirSync as q,rmSync as D,writeFileSync as f}from"node:fs";import{activeBackgroundBashJobs as E,backgroundRunningBashJobs as R,bashJobCommandLine as N,bashJobPaths as w,clearJobDeadline as B,formatBashElapsed as y,isSafeBashJobId as j,jobStartedAtMs as S,markJobQuietOk as k,newBashJobId as M,readJobPgid as L,tailJobLog as U,writeJobDeadlineMs as A}from"../../core/bash-jobs.js";import{parseDurationMs as J}from"../../core/wake.js";import{cliClient as O,getNodeOrNull as b}from"../api-client.js";import{PREVIEW_RESULT_PATH_ENV as P}from"../../core/preview-result-path.js";import{nodeInPane as C}from"../surface/node/placement.js";function m(e,a){const n=e.pane??process.env.TMUX_PANE;let o=e.node;if((o===void 0||o==="")&&(o=C(n)),(o===void 0||o==="")&&(o=_()),o===void 0||o==="")throw new r({error:"no_node",message:a,next:"Run from inside a node, pass --node <id>, or --pane <pane>."});return{id:o,pane:n}}i(m,"resolveBashTarget");const K=c({name:"run",description:"start a shell command as a background job and return at once",whenToUse:"a command needs longer than one tool call \u2014 a test lane, a build, a soak \u2014 and you should be woken with its exit code instead of waiting, backgrounding with `&`, or polling a log",tier:"normal",help:{name:"node bash run",summary:"start one shell command as a background job owned by the calling node and return before it finishes",params:[{kind:"positional",name:"command",required:!0,constraint:"The shell command, run by bash from the current directory with this process\u2019s environment. The same refusals apply as for a bash tool call, judged on this command."},{kind:"flag",name:"quiet-ok",type:"bool",required:!1,constraint:"Pass for a job that legitimately sits idle \u2014 `gh run watch`, `sleep`, waiting on a lock, a `docker build` whose work runs outside the process tree \u2014 so it gets no silence messages. Omit it for anything that should be making progress."}],output:[{name:"job_id",type:"string",required:!0,constraint:"The job object\u2019s id; read or watch it with `crtr canvas`, stop it with `node bash kill`."},{name:"log_path",type:"string",required:!0,constraint:"File receiving the command\u2019s combined stdout and stderr, capped at 10 MiB."}],outputKind:"object",effects:["Works only inside a node\u2019s bash tool call; the job belongs to the calling node.","Starts the command detached from this call, so it keeps running after the call and the turn end. A `timeout` around this leaf bounds only the launch, not the job.","Records the job as a canvas object; when the command exits, its watchers (the calling node by default) get an urgent `exited` push with the exit code and log path.","Sets no cancel deadline: the job runs to completion unless stopped with `node bash kill`.","A job whose processes use almost no CPU and write to no open file for 15 minutes messages the calling node about it, again at 30 and 60 minutes, then hourly; activity restarts the schedule and nothing is stopped. `--quiet-ok` turns these messages off.","Fails, with the job id and log path, if the job could not be recorded; the command is then running but will not message the node when it exits."]},run:i(async e=>{const a=e.command,n=_(),o=process.env.CRTR_CONTEXT_DIR;if(n===void 0||n===""||o===void 0||o==="")throw new r({error:"no_node",message:"`node bash run` starts a job for the calling node and works only inside a node\u2019s bash tool",next:"Run it from a node\u2019s bash tool call."});const s=process.cwd(),{bashCommandRefusal:d}=await import("../../core/bash-guard.js"),l=d(a,s);if(l!==null)throw new r({error:"refused",message:l,next:"Run a different command."});const t=w(o,M());q(t.dir,{recursive:!0}),f(t.cmdSh,a),f(t.jobLog,""),f(t.jobBg,""),e.quietOk===!0&&k(t);const{launchBashJobSupervisor:h}=await import("../../core/runtime/broker/bash-job-launch.js"),{[P]:g,...x}=process.env;try{await h(n,t,o,s,x)}catch(p){throw D(t.dir,{recursive:!0,force:!0}),p}const{recordBackgroundedJob:v}=await import("../../core/runtime/broker/daemon-ops.js");try{await v(n,t.jobId,{command:a,job_dir:t.dir,log_path:t.jobLog})}catch(p){throw new r({error:"job_not_recorded",message:`job ${t.jobId} is running but could not be recorded (${p.message}); it will NOT message you when it exits. Log: ${t.jobLog}`,next:`Watch it with \`tail -f ${t.jobLog}\`, or stop it with \`crtr node bash kill ${t.jobId}\`.`},I.GENERAL)}return{job_id:t.jobId,log_path:t.jobLog}},"run"),render:i(e=>`[backgrounded as job ${e.job_id}. It will interrupt you with a message when it exits. Watch: tail -f ${e.log_path} ; stop: crtr node bash kill ${e.job_id}]`,"render")}),X=c({name:"background",description:"hand the focused node\u2019s running bash command to its background job system",whenToUse:"the tmux prefix menu invokes this when a foreground bash command should stop blocking the agent immediately but keep running as a file-backed background job",tier:"hidden",help:{name:"node bash background",summary:"hand every running foreground bash command for one node to its file-backed background job system",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose running bash command to background. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE. The prefix menu passes this for you."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Per handed-off job {job_id, elapsed_ms} \u2014 the foreground elapsed time in milliseconds at the moment of handoff."}],outputKind:"object",effects:["Signals every foreground bash command currently running for the node to return from its tool call immediately while its detached supervisor keeps the command running.","Each handed-off command retains its log under the node context jobs directory and becomes a job object; when it exits, the job\u2019s watchers (its node by default) get an urgent `exited` push carrying the exit code and log path."]},run:i(async e=>{const{id:a}=m(e,"no node found for the bash handoff"),n=await b(a);if(n===null)throw new r({error:"not_found",message:`no node: ${a}`,next:"List nodes with `crtr node inspect list`."});const o=R(n.paths.context_dir),s=Date.now(),d=o.map(l=>({job_id:l.jobId,elapsed_ms:s-(S(l.jobId)??s)}));return{node_id:a,jobs:d}},"run"),render:i(e=>{const a=e.jobs;if(a.length===0)return`No foreground bash command is running for ${e.node_id}.`;const n=Math.max(...a.map(o=>o.elapsed_ms));return`Handed ${a.length} bash command(s) to the background job system for ${e.node_id} \xB7 ran ${y(n)} in the foreground.`},"render")}),W=c({name:"list",description:"list a node\u2019s live background bash jobs",whenToUse:"you need the ids, commands, and ages of your jobs that are still running",help:{name:"node bash list",summary:"read every still-running background bash job owned by one node",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose background jobs to list. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."},{kind:"flag",name:"tail",type:"int",required:!1,default:0,constraint:"Include this many trailing log lines per job (0 = none)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Oldest-first {job_id, command, purpose, started_at, elapsed_ms, deadline_at, log_path, pgid, log_tail} for every live background job; purpose is a human-readable label or null; deadline_at is the auto-cancel time or null for a job that runs to completion."}],outputKind:"object",effects:["Reads the node context jobs directory. Changes nothing."]},run:i(async e=>{const{id:a}=m(e,"no node found for the bash job list"),n=await b(a);if(n===null)throw new r({error:"not_found",message:`no node: ${a}`,next:"List nodes with `crtr node inspect list`."});const o=Date.now(),s=Math.max(0,Math.trunc(Number(e.tail??0))),d=n.paths.context_dir,l=E(d).map(t=>({job_id:t.jobId,command:t.command,purpose:t.purpose,started_at:new Date(t.startedAtMs).toISOString(),elapsed_ms:o-t.startedAtMs,deadline_at:t.deadlineAtMs===null?null:new Date(t.deadlineAtMs).toISOString(),log_path:t.logPath,pgid:L(d,t.jobId)??null,log_tail:U(t.logPath,s)}));return{node_id:n.node_id,jobs:l}},"run"),render:i(e=>{const a=e.jobs;return a.length===0?`No background bash jobs for ${e.node_id}.`:a.map(n=>`${n.job_id} \xB7 ${y(n.elapsed_ms)} \xB7 ${N(n.command)}${n.purpose===null?"":`
|
|
2
2
|
purpose: ${n.purpose}`}${n.deadline_at===null?"":`
|
|
3
3
|
cancels at: ${n.deadline_at}`}
|
|
4
4
|
log: ${n.log_path}`).join(`
|
|
5
|
-
`)},"render")}),G=
|
|
5
|
+
`)},"render")}),G=c({name:"kill",description:"stop one of a node\u2019s background bash jobs",whenToUse:"a job you started is no longer wanted or is stuck, and it should stop now",help:{name:"node bash kill",summary:"best-effort stop a background bash job and tell the owning node it was canceled",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"signaled",type:"boolean",required:!0,constraint:"True when at least one observed supervisor-tree process received SIGTERM."}],outputKind:"object",effects:["Snapshots the supervisor\u2019s observable process tree, sends SIGTERM then SIGKILL to those processes, and cannot stop a descendant that detached and reparented before the snapshot.","Records the job\u2019s exit so it stops showing as live, and ends its job object as killed; the job\u2019s watchers (its node by default) get an urgent `killed` push."]},run:i(async e=>{const{id:a}=m(e,"no node found for the bash job"),n=await b(a);if(n===null)throw new r({error:"not_found",message:`no node: ${a}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!j(o))throw new r({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=await O().stopBashJob(n.node_id,o);return{node_id:n.node_id,signaled:s.signaled}},"run"),render:i(e=>`Canceled a background bash job for ${e.node_id}${e.signaled===!0?"":" (no observed job process was still running)"}.`,"render")}),F=c({name:"quiet",description:"mark a running background bash job as an expected wait so it sends no silence messages",whenToUse:"a silence message arrived for a job that is meant to sit idle",help:{name:"node bash quiet",summary:"stop silence messages for one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the silence message."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."}],outputKind:"object",effects:["Writes a marker in the job\u2019s directory; the daemon sends no further silence messages for it. Signals nothing."]},run:i(async e=>{const{id:a}=m(e,"no node found for the bash job"),n=await b(a);if(n===null)throw new r({error:"not_found",message:`no node: ${a}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!j(o))throw new r({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=w(n.paths.context_dir,o);if(!u(s.jobBg)||u(s.jobExit))throw new r({error:"not_running",message:`background job ${o} for ${n.node_id} is not running`,next:"List live jobs with `crtr node bash list`."});return k(s),{node_id:n.node_id}},"run"),render:i(e=>`Background job for ${e.node_id} will send no more silence messages.`,"render")}),H=c({name:"extend",description:"claim an auto-backgrounded bash job so the daemon does not cancel it at its deadline",whenToUse:"a command that was moved to a job automatically is worth keeping past its cancel deadline \u2014 give it more time with --for, or let it run to completion with --keep",help:{name:"node bash extend",summary:"move or clear the auto-cancel deadline on one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the auto-background notice."},{kind:"flag",name:"for",type:"string",required:!1,constraint:"New window from now before the job is canceled, as a duration (e.g. 90s, 10m, 1h30m). Mutually exclusive with --keep; give exactly one."},{kind:"flag",name:"keep",type:"bool",required:!1,constraint:"Remove the deadline entirely so the job runs to completion. Mutually exclusive with --for; give exactly one."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"deadline_at",type:"string",required:!1,constraint:"The job\u2019s new auto-cancel time (ISO), or absent when --keep cleared the deadline so the job runs to completion."}],outputKind:"object",effects:["Rewrites or removes the job\u2019s deadline file. The daemon\u2019s deadline lane reads it on its next scan. Signals nothing."]},run:i(async e=>{const{id:a}=m(e,"no node found for the bash job"),n=await b(a);if(n===null)throw new r({error:"not_found",message:`no node: ${a}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!j(o))throw new r({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=e.for,d=e.keep===!0;if(d===(s!==void 0&&s!==""))throw new r({error:"invalid_args",message:"give exactly one of --for <duration> or --keep",next:"Extend by a window with --for 10m, or drop the deadline with --keep."});const l=n.paths.context_dir,t=w(l,o);if(!u(t.dir))throw new r({error:"not_found",message:`no background job ${o} for ${n.node_id}`,next:"List live jobs with `crtr node bash list`."});if(!u(t.jobBg)||u(t.jobExit))throw new r({error:"not_running",message:`background job ${o} for ${n.node_id} is no longer running`,next:`It finished or was stopped, so there is no deadline to change. Log: ${t.jobLog}`});if(d)return B(t),{node_id:n.node_id};const h=J(s);if(h===null||h<=0)throw new r({error:"invalid_duration",message:`not a duration: ${s}`,next:"Use a value like 90s, 10m, or 1h30m."});const g=Date.now()+h;return A(t,g),{node_id:n.node_id,deadline_at:new Date(g).toISOString()}},"run"),render:i(e=>e.deadline_at===void 0?`Cleared the deadline \u2014 the background job will run to completion for ${e.node_id}.`:`Background job for ${e.node_id} will be canceled at ${e.deadline_at} unless extended again.`,"render")}),re=T({name:"bash",description:"start, list, stop, and extend your background bash jobs",whenToUse:"a command needs longer than one tool call \u2014 start it as a job and be woken with its exit code instead of using `&`, `nohup`, or a cron that polls a log \u2014 or you need to list, stop, or extend the deadline of a job you already have",help:{name:"node bash",summary:"start, list, stop, and extend background bash jobs owned by a node",model:"Every bash call runs under a detached supervisor with its output in a log file, so a command that outlives its call keeps running as a job: a canvas object its node owns, readable and watchable with `crtr canvas`. `run` starts a job on purpose and it is never auto-canceled. A job that shows no activity for 15 minutes messages its node, and is never stopped for it. A foreground command still running after 5 minutes is moved to a job automatically, with a cancel deadline unless `extend` claims it."},children:[K,X,W,G,F,H]});export{re as nodeBash};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const MAX_BACKGROUND_BASH_LOG_BYTES: number;
|
|
2
2
|
export declare const BACKGROUND_BASH_LOG_TRUNCATION_MARKER = "\n[background bash job output truncated at 10 MiB; remaining output discarded]\n";
|
|
3
|
-
/** File-backed command supervisor. It writes the exit code to job.exit and, for a foreground
|
|
3
|
+
/** File-backed command supervisor. Beside the job it runs the activity sampler. It writes the exit code to job.exit and, for a foreground
|
|
4
4
|
* command, claims job.done; the daemon reads job.exit of a backgrounded job and publishes `exited`. */
|
|
5
5
|
export declare const BASH_JOB_SUPERVISOR_SCRIPT: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const c=10485760,t=`
|
|
2
2
|
[background bash job output truncated at 10 MiB; remaining output discarded]
|
|
3
|
-
`,e=['const fs = require("node:fs");',"const cap = 10485760;",`const marker = Buffer.from(${JSON.stringify(
|
|
4
|
-
`);export{
|
|
3
|
+
`,e=['const fs = require("node:fs");',"const cap = 10485760;",`const marker = Buffer.from(${JSON.stringify(t)});`,"const prefixCap = cap - marker.length;",'const fd = fs.openSync(process.argv[1], "w");',"let written = 0;","let truncated = false;",'process.stdin.on("data", (chunk) => {'," if (truncated) return;"," const available = prefixCap - written;"," if (available > 0) {"," const prefix = chunk.subarray(0, available);"," fs.writeSync(fd, prefix);"," written += prefix.length;"," if (prefix.length === chunk.length) return;"," }"," fs.writeSync(fd, marker);"," truncated = true;","});",'process.stdin.on("end", () => fs.closeSync(fd));'].join(" "),s=['const fs = require("node:fs"), cp = require("node:child_process");',"const rootPid = Number(process.argv[1]), out = process.argv[3];","let logPath = process.argv[2];","try { logPath = fs.realpathSync(logPath); } catch {}","const interval = Number(process.env.CRTR_BASH_ACTIVITY_SAMPLE_MS) || 60000;",'const linux = fs.existsSync("/proc/self/stat");',"function processes() {"," const procs = new Map();"," if (linux) {",' for (const e of fs.readdirSync("/proc")) {'," if (!/^[0-9]+$/.test(e)) continue;"," try {",' const s = fs.readFileSync("/proc/" + e + "/stat", "utf8");',' const r = s.slice(s.lastIndexOf(")") + 2).split(" ");'," procs.set(Number(e), { ppid: Number(r[1]), cpu: (Number(r[11]) + Number(r[12])) / 100 });"," } catch {}"," }"," return procs;"," }",' const ps = cp.execFileSync("ps", ["-ax", "-o", "pid=,ppid=,time="], { encoding: "utf8", timeout: 5000 });',' for (const line of ps.split("\\n")) {'," const f = line.trim().split(/\\s+/);"," if (f.length < 3) continue;",' const [days, clock] = f[2].includes("-") ? f[2].split("-") : ["0", f[2]];',' const cpu = clock.split(":").reduce((t, x) => t * 60 + Number(x), 0) + Number(days) * 86400;'," if (Number.isFinite(cpu)) procs.set(Number(f[0]), { ppid: Number(f[1]), cpu });"," }"," return procs;","}","function tree(procs) {"," const cpus = new Map(), stack = [rootPid];"," while (stack.length > 0) {"," const pid = stack.pop();"," if (pid === process.pid || cpus.has(pid) || !procs.has(pid)) continue;"," cpus.set(pid, procs.get(pid).cpu);"," for (const [child, p] of procs) if (p.ppid === pid) stack.push(child);"," }"," return cpus;","}","function writableFiles(pids) {"," const files = new Set();"," if (linux) {"," for (const pid of pids) {"," let fds = [];",' try { fds = fs.readdirSync("/proc/" + pid + "/fd"); } catch {}'," for (const fd of fds) {"," try {",' const info = fs.readFileSync("/proc/" + pid + "/fdinfo/" + fd, "utf8").match(/flags:\\s*([0-7]+)/);'," if (info === null || (parseInt(info[1], 8) & 3) === 0) continue;",' files.add(fs.readlinkSync("/proc/" + pid + "/fd/" + fd));'," } catch {}"," }"," }"," return files;"," }",' const r = cp.spawnSync("lsof", ["-nP", "-w", "-F", "pfatn", "-p", pids.join(",")], { encoding: "utf8", timeout: 10000 });',' if (r.error || typeof r.stdout !== "string") return files;',' let access = "", type = "";',' for (const line of r.stdout.split("\\n")) {'," const key = line[0], value = line.slice(1);",' if (key === "f") { access = ""; type = ""; }',' else if (key === "a") access = value;',' else if (key === "t") type = value;',' else if (key === "n" && (access === "w" || access === "u") && type === "REG") files.add(value);'," }"," return files;","}","let lastActivity = Date.now(), prevAt = lastActivity, prevCpu = new Map(), watched = [logPath];","try { prevCpu = tree(processes()); } catch {}","function record() {",' fs.writeFileSync(out + ".tmp", lastActivity + " " + prevAt + "\\n" + watched.join("\\n") + "\\n");',' fs.renameSync(out + ".tmp", out);',"}","function sample() {",' try { process.kill(rootPid, 0); } catch (e) { if (e.code !== "EPERM") process.exit(0); }'," const at = Date.now();"," const cpus = tree(processes());"," let used = 0;"," for (const [pid, cpu] of cpus) used += Math.max(0, cpu - (prevCpu.get(pid) || 0));"," const paths = new Set([logPath]);"," let changed = false;"," for (const file of writableFiles([...cpus.keys()])) {"," try {"," const st = fs.statSync(file);"," if (!st.isFile()) continue;"," paths.add(fs.realpathSync(file));"," if (st.mtimeMs > prevAt) changed = true;"," } catch {}"," }"," try { if (fs.statSync(logPath).mtimeMs > prevAt) changed = true; } catch {}"," if (at - prevAt > 3 * interval) changed = true;"," if (changed || used > 0.02 * (at - prevAt) / 1000) lastActivity = at;"," watched = [...paths];"," prevAt = at;"," prevCpu = cpus;"," record();","}","record();","setInterval(() => { try { sample(); } catch {} }, interval);"].join(" "),n=['"$8" -e '+JSON.stringify(s)+' "$$" "$2" "$9" &',"sampler=$!","n=$(command -v nice)",'${n:+"$n" -n "$7"} "$6" "$1" 2>&1 | "$8" -e '+JSON.stringify(e)+' "$2"',"status=${PIPESTATUS[0]}",'kill "$sampler" 2>/dev/null','echo "$status" > "$3"','mv "$4" "$5" 2>/dev/null || true'].join(`
|
|
4
|
+
`);export{t as BACKGROUND_BASH_LOG_TRUNCATION_MARKER,n as BASH_JOB_SUPERVISOR_SCRIPT,c as MAX_BACKGROUND_BASH_LOG_BYTES};
|
package/dist/core/bash-jobs.d.ts
CHANGED
|
@@ -25,6 +25,13 @@ export interface BashJobPaths {
|
|
|
25
25
|
* subject to the daemon's claim-or-cancel deadline. Absent for a deliberate
|
|
26
26
|
* handoff (tmux menu, broker termination), which runs to completion. */
|
|
27
27
|
jobDeadline: string;
|
|
28
|
+
/** Written beside the job by its supervisor's activity sampler: when the job last
|
|
29
|
+
* showed life and which files it was watched through (see `readJobActivity`). */
|
|
30
|
+
jobActivity: string;
|
|
31
|
+
/** Present when the job's owner declared that it legitimately sits idle: it gets no silence messages. */
|
|
32
|
+
jobQuietOk: string;
|
|
33
|
+
/** The daemon's record of the last silence message sent: `<last-activity-ms> <threshold-ms>`. */
|
|
34
|
+
jobSilence: string;
|
|
28
35
|
}
|
|
29
36
|
/** A purpose is display text, never shell input. Keep the file-backed control
|
|
30
37
|
* plane to one bounded, visible line so old or hand-written job directories
|
|
@@ -78,6 +85,25 @@ export declare function writeJobDeadlineMs(paths: BashJobPaths, deadlineMs: numb
|
|
|
78
85
|
/** Drop a job's cancel deadline so it runs to completion — the node has claimed
|
|
79
86
|
* it. Best-effort: a job with no deadline file is already in this state. */
|
|
80
87
|
export declare function clearJobDeadline(paths: BashJobPaths): void;
|
|
88
|
+
/** What the job's activity sampler last recorded: when the job last showed life (its process tree
|
|
89
|
+
* used CPU, or a regular file one of its processes holds open for writing changed), when that was
|
|
90
|
+
* sampled, and the files it was watched through. undefined for a job with no readable record. */
|
|
91
|
+
export declare function readJobActivity(contextDir: string, jobId: string): {
|
|
92
|
+
lastActivityMs: number;
|
|
93
|
+
sampledAtMs: number;
|
|
94
|
+
files: string[];
|
|
95
|
+
} | undefined;
|
|
96
|
+
/** Whether the owner declared the job an expected wait, so it gets no silence messages. */
|
|
97
|
+
export declare function isJobQuietOk(contextDir: string, jobId: string): boolean;
|
|
98
|
+
/** Declare a job an expected wait (`node bash run --quiet-ok`, `node bash quiet`). */
|
|
99
|
+
export declare function markJobQuietOk(paths: BashJobPaths): void;
|
|
100
|
+
/** The silence message the daemon last sent for a job: the last-activity time it was sent for and
|
|
101
|
+
* the threshold it announced. undefined when none was sent. */
|
|
102
|
+
export declare function readJobSilence(contextDir: string, jobId: string): {
|
|
103
|
+
lastActivityMs: number;
|
|
104
|
+
thresholdMs: number;
|
|
105
|
+
} | undefined;
|
|
106
|
+
export declare function writeJobSilence(paths: BashJobPaths, lastActivityMs: number, thresholdMs: number): void;
|
|
81
107
|
/** Launcher jobs have a root-owned bwrap pid, not a process group; direct
|
|
82
108
|
* jobs keep their detached supervisor process group. */
|
|
83
109
|
export declare function isBashJobProcessGroupAlive(pgid: number, identity?: string): boolean;
|
package/dist/core/bash-jobs.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
var $=Object.defineProperty;var o=(e
|
|
2
|
-
`)}o(
|
|
3
|
-
`)}o(
|
|
4
|
-
`)
|
|
5
|
-
`)}
|
|
6
|
-
`)
|
|
7
|
-
`)
|
|
8
|
-
`)
|
|
1
|
+
var $=Object.defineProperty;var o=(t,e)=>$(t,"name",{value:e,configurable:!0});import{randomBytes as R}from"node:crypto";import{closeSync as y,openSync as B,readSync as S,statSync as v}from"node:fs";import{dirname as m,join as c}from"node:path";import{captureTeardownSnapshot as O,isAnyPidAlive as T,isPidAlive as M,killProcessTreePids as x}from"./canvas/pid.js";import{launcherRequest as F}from"../launcher/client.js";import{LaunchRefused as _}from"../launcher/spec.js";import{createFileBeneath as A,existsBeneath as I,readdirBeneath as C,readFileBeneath as Q,renameBeneath as z,unlinkBeneath as G,writeFileBeneath as g}from"./spaces/open-beneath.js";const H=280;function X(t){if(typeof t!="string"||/[\p{Cc}\p{Cf}]/u.test(t))return null;const e=t.trim();return e===""?null:Buffer.byteLength(e,"utf8")<=H?e:null}o(X,"normalizeBashJobPurpose");function j(t){return c(t,"jobs")}o(j,"bashJobsDir");function b(t,e){return Q(m(t),e).toString("utf8")}o(b,"readJobText");function J(t){return C(m(t),j(t)).map(e=>e.name)}o(J,"jobIds");function a(t,e){return I(t.root,e)}o(a,"has");function k(t,e){try{G(t,e)}catch(n){if(n.code!=="ENOENT")throw n}}o(k,"unlinkIfPresent");function ct(t){return typeof t=="string"&&t!==""&&t!=="."&&t!==".."&&/^[A-Za-z0-9._~-]+$/u.test(t)&&Buffer.byteLength(t,"utf8")<=128}o(ct,"isSafeBashJobId");function f(t,e){const n=c(j(t),e);return{jobId:e,root:m(t),dir:n,cmdSh:c(n,"cmd.sh"),jobLog:c(n,"job.log"),jobExit:c(n,"job.exit"),jobRun:c(n,"job.run"),jobBg:c(n,"job.bg"),jobDone:c(n,"job.done"),jobPgid:c(n,"job.pgid"),jobPgidIdentity:c(n,"job.pgid-identity"),jobPurpose:c(n,"purpose"),jobDeadline:c(n,"job.deadline"),jobActivity:c(n,"job.activity"),jobQuietOk:c(n,"job.quiet-ok"),jobSilence:c(n,"job.silence")}}o(f,"bashJobPaths");function q(t,e){try{const n=Number(b(t,f(t,e).jobPgid).trim());return Number.isInteger(n)&&n>1?n:void 0}catch{return}}o(q,"readJobPgid");function U(t,e){try{const n=b(t,f(t,e).jobPgidIdentity).trim();return n===""?void 0:n}catch{return}}o(U,"readJobPgidIdentity");function W(t,e){try{return X(b(t,f(t,e).jobPurpose))}catch{return null}}o(W,"readJobPurpose");const P=10,K=19;function N(t){return c(j(t),"niceness")}o(N,"bashNicenessPath");function at(t){try{const e=Number(b(t,N(t)).trim());return!Number.isInteger(e)||e<0||e>K?P:e}catch{return P}}o(at,"readBashNiceness");function ft(t,e){g(m(t),N(t),`${Math.trunc(e)}
|
|
2
|
+
`)}o(ft,"writeBashNiceness");function Y(t,e){try{const n=Number(b(t,f(t,e).jobDeadline).trim());return Number.isFinite(n)?n:void 0}catch{return}}o(Y,"readJobDeadlineMs");function Z(t,e){g(t.root,t.jobDeadline,`${Math.trunc(e)}
|
|
3
|
+
`)}o(Z,"writeJobDeadlineMs");function dt(t){try{k(t.root,t.jobDeadline)}catch{}}o(dt,"clearJobDeadline");function lt(t,e){try{const[n,...r]=b(t,f(t,e).jobActivity).split(`
|
|
4
|
+
`),[s,i]=(n??"").split(" ").map(Number);return!Number.isFinite(s)||!Number.isFinite(i)?void 0:{lastActivityMs:s,sampledAtMs:i,files:r.filter(u=>u!=="")}}catch{return}}o(lt,"readJobActivity");function bt(t,e){return a(f(t,e),f(t,e).jobQuietOk)}o(bt,"isJobQuietOk");function ht(t){g(t.root,t.jobQuietOk,"")}o(ht,"markJobQuietOk");function pt(t,e){try{const[n,r]=b(t,f(t,e).jobSilence).trim().split(" ").map(Number);return Number.isFinite(n)&&Number.isFinite(r)?{lastActivityMs:n,thresholdMs:r}:void 0}catch{return}}o(pt,"readJobSilence");function mt(t,e,n){g(t.root,t.jobSilence,`${Math.trunc(e)} ${Math.trunc(n)}
|
|
5
|
+
`)}o(mt,"writeJobSilence");function gt(t,e){if(e===`launcher:${t}`)return M(t);try{return process.kill(-t,0),!0}catch(n){return n.code==="EPERM"}}o(gt,"isBashJobProcessGroupAlive");function jt(t,e){if(t===void 0||e===void 0)return!1;if(e===`launcher:${t}`)return!M(t);try{return process.kill(-t,0),!1}catch(n){return n.code==="ESRCH"}}o(jt,"isBashJobProcessConfirmedGone");async function V(t,e){const n=O(t,e);if(n.reused||n.tree.length===0)return!1;const r=[...n.tree].reverse(),s=T(r);return x(r,"SIGTERM",n.identities),await new Promise(i=>setTimeout(i,400)),x(r,"SIGKILL",n.identities),s}o(V,"cancelBashJobProcessTree");async function yt(t,e,n=!1,r,s){const i=f(t,e);if(!a(i,i.dir))return{kind:"missing"};if(!a(i,i.jobBg)&&!(n&&a(i,i.jobRun))||a(i,i.jobExit))return{kind:"retired",logPath:i.jobLog};const u=q(t,e),d=U(t,e);if(u===void 0||d===void 0)return{kind:"not-stoppable",logPath:i.jobLog};const h=c(i.dir,"job.stop");try{A(i.root,h,s===void 0?"":`${s}
|
|
6
|
+
`)}catch(l){if(l.code==="EEXIST")return{kind:"retired",logPath:i.jobLog};throw l}try{let l;if(d===`launcher:${u}`){if(r===void 0)throw new Error("launcher job stop requires its owning node");try{if(!("ok"in await F({operation:"stop",pid:u,nodeId:r})))throw new Error("launcher did not stop job");l=!0}catch(p){if(!(p instanceof _&&p.refusal==="invalid_spec"))throw p;l=!1}}else l=await V(u,d);try{A(i.root,i.jobExit,`143
|
|
7
|
+
`)}catch(p){if(p.code!=="EEXIST")throw p}return{kind:"stopped",signaled:l,pgid:u,logPath:i.jobLog}}catch(l){throw k(i.root,h),l}}o(yt,"stopBackgroundBashJob");function Bt(t,e){if(e<=0)return[];const n=64*1024;let r;try{const s=v(t).size,i=Math.max(0,s-n),u=Buffer.alloc(Math.min(s,n));r=B(t,"r"),S(r,u,0,u.length,i);const d=u.toString("utf8"),h=(i>0?d.slice(d.indexOf(`
|
|
8
|
+
`)+1):d).split(`
|
|
9
|
+
`);return h[h.length-1]===""&&h.pop(),h.slice(-e)}catch{return[]}finally{r!==void 0&&y(r)}}o(Bt,"tailJobLog");function St(t){let e;try{e=B(t,"r");const n=Buffer.alloc(64*1024);let r=0,s=0;for(;;){const i=S(e,n,0,n.length,null);if(i===0)break;for(let u=0;u<i;u+=1)n[u]===10&&(r+=1);s=n[i-1]}return s!==0&&s!==10?r+1:r}catch{return 0}finally{e!==void 0&&y(e)}}o(St,"countJobLogLines");function Mt(){return`${Date.now()}-${R(4).toString("hex")}`}o(Mt,"newBashJobId");function E(t){const e=t.indexOf("-");if(e<=0||e===t.length-1)return;const n=Number(t.slice(0,e));return Number.isFinite(n)?n:void 0}o(E,"jobStartedAtMs");function xt(t){const e=Math.max(0,Math.floor(t/1e3));if(e<60)return`${e}s`;const n=Math.floor(e/60),r=e%60;if(n<60)return`${n}m ${String(r).padStart(2,"0")}s`;const s=Math.floor(n/60),i=n%60;return`${s}h ${String(i).padStart(2,"0")}m`}o(xt,"formatBashElapsed");function At(t){return(t.split(`
|
|
10
|
+
`).map(n=>n.trim()).find(n=>n!=="")??"").replace(/\s+/g," ")}o(At,"bashJobCommandLine");function w(t,e=!1){let n;try{n=J(t)}catch{return[]}const r=[];for(const s of n){const i=E(s);if(i===void 0)continue;const u=f(t,s);let d;try{if(!a(u,u.jobBg)&&!(e&&a(u,u.jobRun))||a(u,u.jobExit))continue;d=b(t,u.cmdSh)}catch{continue}r.push({jobId:s,command:d,purpose:W(t,s),startedAtMs:i,logPath:u.jobLog,deadlineAtMs:Y(t,s)??null})}return r.sort((s,i)=>s.startedAtMs-i.startedAtMs)}o(w,"activeBackgroundBashJobs");function Jt(t,e){return w(t).filter(n=>n.deadlineAtMs!==null&&e>=n.deadlineAtMs)}o(Jt,"expiredAutoBackgroundBashJobs");function kt(t,e){if(!Number.isFinite(e)||e<=0)return 0;let n=0;for(const r of w(t))if(r.deadlineAtMs!==null)try{Z(f(t,r.jobId),r.deadlineAtMs+e),n+=1}catch{}return n}o(kt,"shiftAutoBackgroundBashDeadlines");function D(t){try{return z(t.root,t.jobRun,t.jobBg),"backgrounded"}catch(e){if(e.code!=="ENOENT")throw e;return a(t,t.jobBg)?"already-backgrounded":a(t,t.jobDone)?"finished":"missing"}}o(D,"backgroundBashJob");function L(t){let e;try{e=J(t)}catch{return[]}return e.flatMap(n=>{const r=f(t,n);try{return a(r,r.cmdSh)&&a(r,r.jobLog)&&a(r,r.jobRun)?[r]:[]}catch{return[]}})}o(L,"runningBashJobs");function Pt(t){let e;for(const n of L(t)){const r=E(n.jobId);r!==void 0&&(e===void 0||r<e)&&(e=r)}return e}o(Pt,"oldestRunningBashJobStartedAtMs");function Nt(t){const e=[];for(const n of L(t))D(n)==="backgrounded"&&e.push(n);return e}o(Nt,"backgroundRunningBashJobs");export{P as DEFAULT_BASH_NICENESS,K as MAX_BASH_NICENESS,w as activeBackgroundBashJobs,D as backgroundBashJob,Nt as backgroundRunningBashJobs,At as bashJobCommandLine,f as bashJobPaths,j as bashJobsDir,N as bashNicenessPath,V as cancelBashJobProcessTree,dt as clearJobDeadline,St as countJobLogLines,Jt as expiredAutoBackgroundBashJobs,xt as formatBashElapsed,jt as isBashJobProcessConfirmedGone,gt as isBashJobProcessGroupAlive,bt as isJobQuietOk,ct as isSafeBashJobId,E as jobStartedAtMs,ht as markJobQuietOk,Mt as newBashJobId,X as normalizeBashJobPurpose,Pt as oldestRunningBashJobStartedAtMs,at as readBashNiceness,lt as readJobActivity,Y as readJobDeadlineMs,q as readJobPgid,U as readJobPgidIdentity,W as readJobPurpose,pt as readJobSilence,kt as shiftAutoBackgroundBashDeadlines,yt as stopBackgroundBashJob,Bt as tailJobLog,ft as writeBashNiceness,Z as writeJobDeadlineMs,mt as writeJobSilence};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{jobRow as d}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as l}from"./kind.js";import{recordLiveRead as p}from"./live-read.js";import{attr as r,header as w}from"./render.js";const u={name:"job",addressable:"id",events:l({exited:{level:7},killed:{level:7}}),async read(a,i,e,s){const o=await d(a,e);if(!o)throw new Error(`job object ${e.id} has no jobs row`);await p(a,i,e,s.reposFn);const
|
|
2
|
-
`);return{data:t,text:
|
|
1
|
+
import{jobRow as d}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as l}from"./kind.js";import{recordLiveRead as p}from"./live-read.js";import{attr as r,header as w}from"./render.js";const u={name:"job",addressable:"id",events:l({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(a,i,e,s){const o=await d(a,e);if(!o)throw new Error(`job object ${e.id} has no jobs row`);await p(a,i,e,s.reposFn);const n=await c(a,e),t={command:o.command,status:o.status,exit_code:o.exit_code,log_path:o.log_path},m=[w("object",n,r("status",t.status)+r("exit_code",t.exit_code)),`Command: ${t.command}`,`Log: ${t.log_path}`].join(`
|
|
2
|
+
`);return{data:t,text:m}}};export{u as jobKind};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var p=Object.defineProperty;var m=(e,o)=>p(e,"name",{value:o,configurable:!0});import{spawn as S}from"node:child_process";import{writeFileSync as a}from"node:fs";import{BASH_JOB_SUPERVISOR_SCRIPT as l}from"../../bash-job-supervisor.js";import{readBashNiceness as j}from"../../bash-jobs.js";import{capturePidIdentities as g}from"../../canvas/pid.js";import{freshChildNodeExecPath as h}from"../branded-host.js";import{resolveAdmittedHostCommand as B}from"../spawn-env.js";import{startBrokerBashJob as y}from"./daemon-ops.js";const I=process.platform==="linux";async function J(e,o,f,n,d){const b=await h();if(I){const{pid:t}=await y(e,{job_id:o.jobId,cwd:n,env:d});return{pgid:t,identity:`launcher:${t}`}}const s=B("bash",d,n),r=S(s,["-c",l,"--",o.cmdSh,o.jobLog,o.jobExit,o.jobRun,o.jobDone,s,String(j(f)),b,o.jobActivity],{cwd:n,detached:!0,stdio:"ignore",env:d}),i=await new Promise((t,u)=>{r.once("error",u),r.pid!==void 0&&t(r.pid)});r.unref();const c=g([i])?.get(i);if(c===void 0){try{process.kill(-i,"SIGKILL")}catch{}throw new Error(`bash job ${o.jobId} started without a process identity`)}return a(o.jobPgid,String(i)),a(o.jobPgidIdentity,c),{pgid:i,identity:c}}m(J,"launchBashJobSupervisor");export{I as BASH_JOBS_USE_LAUNCHER,J as launchBashJobSupervisor};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var B=Object.defineProperty;var l=(e,o)=>B(e,"name",{value:o,configurable:!0});import{killBashJobObject as E,recordBashJobObject as J}from"../../../core/spaces/stop.js";import{readerFor as P}from"../reader.js";import{join as
|
|
1
|
+
var B=Object.defineProperty;var l=(e,o)=>B(e,"name",{value:o,configurable:!0});import{killBashJobObject as E,recordBashJobObject as J}from"../../../core/spaces/stop.js";import{readerFor as P}from"../reader.js";import{join as A}from"node:path";import{createFileBeneath as g,existsBeneath as m,unlinkBeneath as $}from"../../../core/spaces/open-beneath.js";import{launchAppProcess as q,appProcessSandboxed as O}from"../../../core/spaces/process.js";import{BASH_JOB_SUPERVISOR_SCRIPT as N}from"../../../core/bash-job-supervisor.js";import{resolveAdmittedHostCommand as L}from"../../../core/runtime/spawn-env.js";import{requireCurrentExecution as _}from"./broker-recovery.js";import{isSafeNodeId as T}from"../../../core/canvas/paths.js";import{contextDir as v}from"../../../core/canvas/node-agent-paths.js";import{getNode as f}from"../../../core/canvas/canvas.js";import{isBrokerLive as D}from"../../../core/runtime/model-swap.js";import{assertNotFinalized as R}from"../../../core/runtime/reopen.js";import{reviveNode as C}from"../../../core/runtime/revive.js";import{checkStart as z}from"../../../core/runtime/start-check.js";import{activeBackgroundBashJobs as F,bashJobPaths as k,isBashJobProcessGroupAlive as M,readJobPgid as G,readJobPgidIdentity as H,isSafeBashJobId as I,readBashNiceness as Z,stopBackgroundBashJob as U}from"../../../core/bash-jobs.js";import{ApiError as i}from"../../../api/index.js";import{notFound as V}from"../../../core/errors.js";import{freshChildNodeExecPath as X}from"../../../core/runtime/branded-host.js";async function w(e){const o=e.params.id;if(o===void 0||!T(o))throw new i(400,"invalid_node_id",`invalid node id: ${JSON.stringify(o)}`);if(await f(o)===null)throw V(`unknown node: ${o}`,{received:o});const a=e.params.jobId;if(a!==void 0){if(!I(a))throw new i(400,"invalid_job_id",`invalid background job id: ${JSON.stringify(a)}`);return{nodeId:o,jobId:a,dir:await v(o)}}return{nodeId:o,dir:await v(o)}}l(w,"requireTarget");async function x(e){const{nodeId:o,dir:n}=await w(e);if(e.principal.class!=="broker"||e.principal.node!==o)throw new i(403,"permission_error","only this node\u2019s broker may start a job");const a=e.body;if(!a||typeof a!="object"||Array.isArray(a))throw new i(400,"invalid_request","job.start requires a JSON body");const d=a;await _(o,d.expected_execution_id);const r=d.job_id,s=d.cwd,p=d.env;if(typeof r!="string"||!I(r)||typeof s!="string"||!p||typeof p!="object"||Array.isArray(p)||Object.entries(p).some(([c,b])=>!/^[A-Za-z_][A-Za-z0-9_]*$/.test(c)||typeof b!="string"))throw new i(400,"invalid_request","invalid job id, cwd or environment");const h=await f(o);if(await z({grantee:h.grantee??"app:terminal",sub:h.sub,grant_id:h.grant_id}),!O())throw new i(400,"invalid_request","job.start is only used with the root launcher");const t=k(n,r);if(!(m(t.root,t.jobRun)||m(t.root,t.jobBg))||![t.cmdSh,t.jobLog].every(c=>m(t.root,c)))throw new i(404,"job_not_found","job was not prepared by this node");const j=A(t.dir,"job.starting");try{g(t.root,j,"")}catch(c){throw c.code==="EEXIST"?new i(409,"job_already_started","job was already started"):c}let u;try{const c=p,b=L("bash",c,s),y=await f(o),S=await X();return u=await q({nodeId:o,app:y.grantee??"app:terminal",isolated:y.isolated,cwd:s,env:c,capture:!1,command:[b,"-c",N,"--",t.cmdSh,t.jobLog,t.jobExit,t.jobRun,t.jobDone,b,String(Z(n)),S,t.jobActivity]}),g(t.root,t.jobPgid,String(u.pid),384),g(t.root,t.jobPgidIdentity,`launcher:${u.pid}`,384),{status:201,body:{pid:u.pid}}}catch(c){if(u!==void 0)try{await u.stop()}catch(b){throw new AggregateError([c,b],`job ${r} launched but could not be recorded or stopped`)}try{$(t.root,j)}catch(b){if(b.code!=="ENOENT")throw b}throw c}}l(x,"handleStart");async function K(e){const{nodeId:o,dir:n}=await w(e),a=Date.now();return{status:200,body:F(n).map(r=>{const s=G(n,r.jobId)??null;return{job_id:r.jobId,command:r.command,purpose:r.purpose,started_at:new Date(r.startedAtMs).toISOString(),elapsed_ms:Math.max(0,a-r.startedAtMs),pgid:s,pgid_alive:s!==null&&M(s,H(n,r.jobId))}})}}l(K,"handleList");async function Q(e){const{nodeId:o,jobId:n,dir:a}=await w(e);if(n===void 0)throw new i(400,"invalid_request","a job id is required");const d=e.query.get("foreground")==="1";if(d){if(e.principal.class!=="broker"||e.principal.node!==o)throw new i(403,"permission_error","only this node\u2019s broker may abort its foreground job");await _(o,e.body?.expected_execution_id)}const r=await U(a,n,d,o);if(r.kind==="missing")throw new i(404,"job_not_found",`no background job ${n} for ${o}`);if(r.kind==="retired")throw new i(404,"job_not_found",`background job ${n} for ${o} is no longer running`,{log_path:r.logPath});if(r.kind==="not-stoppable")throw new i(409,"job_not_stoppable",`job ${n} recorded no trusted process identity`,{log_path:r.logPath});if(d)return{status:200,body:{node_id:o,job_id:n,signaled:r.signaled}};await R(o),await E(n,async h=>{const t=await P(h,e);return t.class==="person"?t.person:t.class==="app"?t.app:t.node});const s=await f(o);if(s!==null&&!D(s))try{await C(o,{resume:!0,capacity:"freeze"})}catch{}return{status:200,body:{node_id:o,job_id:n,signaled:r.signaled}}}l(Q,"handleStop");async function W(e){const{nodeId:o,jobId:n,dir:a}=await w(e);if(n===void 0)throw new i(400,"invalid_request","a job id is required");if(e.principal.class!=="broker"||e.principal.node!==o)throw new i(403,"permission_error","only this node\u2019s broker may record its job");const d=e.body,r=k(a,n);if(typeof d?.command!="string"||d.job_dir!==r.dir||d.log_path!==r.jobLog)throw new i(400,"invalid_request",`job.background requires command, job_dir ${r.dir} and log_path ${r.jobLog}`);if(!m(r.root,r.jobBg))throw new i(404,"job_not_found",`job ${n} of ${o} is not backgrounded`);const s=d.command;return{status:201,body:{id:(await J(o,n,{command:s,jobDir:r.dir,logPath:r.jobLog})).id}}}l(W,"handleBackground");const _o=[{method:"POST",pattern:"/v1/nodes/:id/jobs/:jobId/background",handler:W},{method:"POST",pattern:"/v1/nodes/:id/jobs/start",handler:x},{method:"GET",pattern:"/v1/nodes/:id/jobs",handler:K},{method:"DELETE",pattern:"/v1/nodes/:id/jobs/:jobId",handler:Q}];export{_o as bashJobRoutes};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { NodeRow } from '../../core/canvas/index.js';
|
|
2
|
+
/** The silence message due for a job silent for `silentMs`, given the threshold last announced: the
|
|
3
|
+
* largest of `base`, 2×`base`, 4×`base`, then every 4×`base` (15, 30, 60 min, then hourly) that the
|
|
4
|
+
* silence has reached and that was not announced yet. Null when none is due. */
|
|
5
|
+
export declare function dueSilenceThresholdMs(silentMs: number, announcedMs: number, base: number): number | null;
|
|
2
6
|
export interface BashDeadlineContext {
|
|
3
7
|
rows: NodeRow[];
|
|
4
8
|
}
|
|
@@ -17,6 +21,10 @@ export declare class BashDeadlineReconciler {
|
|
|
17
21
|
/** An exit written before this daemon started is history, not a job to adopt. */
|
|
18
22
|
private readonly startedAt;
|
|
19
23
|
run(now: number, ctx: BashDeadlineContext): Promise<void>;
|
|
24
|
+
/** Tell the owner about a live background job that has shown no activity (see the supervisor's
|
|
25
|
+
* activity sampler) for 15 minutes, again at 30 and 60, then hourly. Any activity restarts the
|
|
26
|
+
* schedule; a job marked as an expected wait is skipped. Messages only: the job is never stopped. */
|
|
27
|
+
private warnSilent;
|
|
20
28
|
/** A running job whose supervisor wrote job.exit has exited: `finishJob` publishes `exited`
|
|
21
29
|
* once (the row leaves `running`). A job being stopped (job.stop) is ended by its killer. */
|
|
22
30
|
private publishExits;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
var
|
|
2
|
-
WHERE j.status = 'running' AND o.deleted_at IS NULL`).all();for(const{object:e,job_dir:
|
|
3
|
-
`)}catch(
|
|
1
|
+
var E=Object.defineProperty;var m=(s,t)=>E(s,"name",{value:t,configurable:!0});import{existsSync as a,readFileSync as S,readdirSync as x,statSync as B}from"node:fs";import{join as l}from"node:path";import{bashJobPaths as j,bashJobsDir as J,activeBackgroundBashJobs as v,expiredAutoBackgroundBashJobs as g,formatBashElapsed as M,isJobQuietOk as N,readJobActivity as I,readJobSilence as P,writeJobSilence as T,isBashJobProcessConfirmedGone as k,readJobPgid as C,readJobPgidIdentity as L,stopBackgroundBashJob as D}from"../../core/bash-jobs.js";import{killBashJobObject as O,recordBashJobObject as F}from"../../core/spaces/stop.js";import{createFileBeneath as R}from"../../core/spaces/open-beneath.js";import{withCanvasWrite as f}from"../../core/canvas/db.js";import{finishJob as A,jobRow as V}from"../../core/graph/jobs.js";import{publish as G}from"../../core/graph/events.js";import{envBashSilenceMs as U}from"../../shared/env.js";import{getById as h}from"../../core/graph/objects.js";import{isSafeNodeId as u}from"../../core/canvas/paths.js";import{contextDir as W}from"../../core/canvas/node-agent-paths.js";import{emitEvent as c}from"../../core/events/emit.js";import{operationIdContext as b}from"../../core/events/operation-id.js";import{isProfilePaused as X}from"../../core/profiles/manifest.js";const Y=300*1e3;function H(s,t,o){const e=s<o?0:s<2*o?o:s<4*o?2*o:4*o*Math.floor(s/(4*o));return e>t?e:null}m(H,"dueSilenceThresholdMs");const Q=5*1e3;class ft{static{m(this,"BashDeadlineReconciler")}lastScanAt=Number.NEGATIVE_INFINITY;startedAt=Date.now();async run(t,o){if(await this.publishExits(),!(t-this.lastScanAt<Q)){this.lastScanAt=t;for(const e of o.rows){if(e.status==="dead"||X(e.profile_id))continue;const r=await W(e.node_id);let i;try{await this.adoptUnrecorded(e.node_id,r),await this.settleVanished(r),i=g(r,t)}catch(n){b.fresh(()=>{c({level:"error",event:"bash_deadline.scan_failed",...u(e.node_id)?{node_id:e.node_id}:{},error:n})});continue}for(const n of i)await this.cancel(e.node_id,r,n.jobId);try{await this.warnSilent(r,t)}catch(n){b.fresh(()=>{c({level:"error",event:"bash_silence.scan_failed",...u(e.node_id)?{node_id:e.node_id}:{},error:n})})}}}}async warnSilent(t,o){const e=U();for(const{jobId:r}of v(t)){if(N(t,r))continue;const i=I(t,r);if(i===void 0||o-i.sampledAtMs>Y)continue;const n=o-i.lastActivityMs,y=P(t,r),_=H(n,y?.lastActivityMs===i.lastActivityMs?y.thresholdMs:0,e);if(_===null)continue;const w=j(t,r);await f(async p=>{const d=await h(p,r);d===null||d.type!=="job"||(await V(p,d))?.status!=="running"||await G(p,{event:"silent",object:d,actor:null,attrs:{"silent-for":M(n),files:i.files.join(", "),"log-path":w.jobLog}})}),T(w,i.lastActivityMs,_)}}async publishExits(){try{await f(async t=>{const o=await t.prepare(`SELECT j.object, j.job_dir FROM jobs j JOIN objects o ON o.id = j.object
|
|
2
|
+
WHERE j.status = 'running' AND o.deleted_at IS NULL`).all();for(const{object:e,job_dir:r}of o){const i=l(r,"job.exit");if(!a(i)||a(l(r,"job.stop")))continue;const n=Number(S(i,"utf8").trim());Number.isSafeInteger(n)&&await A(t,await h(t,e),{exitCode:n})}})}catch(t){b.fresh(()=>c({level:"error",event:"bash_completion.delivery_failed",error:t}))}}async adoptUnrecorded(t,o){let e;try{e=x(J(o))}catch{return}await f(async r=>{for(const i of e){if(await h(r,i)!==null)continue;const n=j(o,i);!a(n.jobBg)||!a(n.cmdSh)||a(n.jobExit)&&B(n.jobExit).mtimeMs<this.startedAt||await F(t,i,{command:S(n.cmdSh,"utf8"),jobDir:n.dir,logPath:n.jobLog})}})}async settleVanished(t){for(const{jobId:o}of v(t)){const e=j(t,o);a(e.jobExit)||a(l(e.dir,"job.stop"))||k(C(t,o),L(t,o))&&await f(async r=>{const i=await h(r,o);if(!(i===null||i.type!=="job")&&!(a(e.jobExit)||a(l(e.dir,"job.stop")))){await A(r,i,{killedBy:null,reason:"process disappeared without an exit record"});try{R(e.root,e.jobExit,`-1
|
|
3
|
+
`)}catch(n){if(n.code!=="EEXIST")throw n}}})}}async cancel(t,o,e){(await D(o,e,!1,t)).kind==="stopped"&&await b.fresh(async()=>{try{c({level:"info",event:"bash_deadline.canceled",...u(t)?{node_id:t}:{},fields:{job_id:e}}),await O(e,async()=>null,"deadline")}catch(i){c({level:"error",event:"bash_deadline.notice_failed",...u(t)?{node_id:t}:{},error:i,fields:{job_id:e}})}})}}export{ft as BashDeadlineReconciler,H as dueSilenceThresholdMs};
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -124,6 +124,10 @@ export declare function envTestUnattendedParkMs(): number | undefined;
|
|
|
124
124
|
* above the configured knob a deployment sets to tune how long an eligible
|
|
125
125
|
* resident idles before its parking turn. */
|
|
126
126
|
export declare function envUnattendedParkMs(): number | undefined;
|
|
127
|
+
/** How long a background bash job must show no activity before its owner is first told
|
|
128
|
+
* (`CRTR_BASH_SILENCE_MS`, milliseconds; default 15 min). Later messages come at twice, four times,
|
|
129
|
+
* then every four times that. */
|
|
130
|
+
export declare function envBashSilenceMs(): number;
|
|
127
131
|
/** Test-only park-summary grace-period override
|
|
128
132
|
* (`CRTR_TEST_PARK_SUMMARY_GRACE_MS`); no production default. */
|
|
129
133
|
export declare function envTestParkSummaryGraceMs(): number | undefined;
|
package/dist/shared/env.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
var p=Object.defineProperty;var n=(e,r)=>p(e,"name",{value:r,configurable:!0});import{lstatSync as f,readFileSync as c,readdirSync as T}from"node:fs";import{join as i}from"node:path";import{covers as _}from"@north-light/identity";function W(){return process.env.CRTR_NODE_ID}n(W,"envNodeId");function d(e){for(const r of["broker-launch.json","cron-launch.json"]){const t=i(e,r);if(f(t,{throwIfNoEntry:!1})?.isFile())return t}}n(d,"launchSnapshotFile");function E(){if(process.platform==="linux")try{const e="/run/crtr/nodes",r=new Set(c("/proc/self/mountinfo","utf8").split(`
|
|
2
|
-
`).map(o=>o.split(" ")[4])),t=T(e,{withFileTypes:!0}).filter(o=>o.isDirectory()&&/^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/.test(o.name)&&r.has(i(e,o.name))&&d(i(e,o.name))!==void 0);return t.length===1?t[0].name:void 0}catch{return}}n(E,"sandboxCliNodeId");function F(){return E()!==void 0}n(F,"isSandboxedNode");function h(){return process.env.CRTR_GRANTEE}n(h,"envGrantee");const v="CRTR_INBOX_BETWEEN_TURNS";function H(){return process.env[v]==="1"}n(H,"envInboxBetweenTurns");const C="CRTR_SCOPES_FILE";function K(){const e=process.env[C];if(e!==void 0&&e!==""){const t=JSON.parse(c(e,"utf8"));if(!Array.isArray(t)||t.some(o=>typeof o!="string"))throw new Error(`invalid scopes file ${e}`);return t}const r=process.env.CRTR_SCOPES;return r===void 0?null:r===""?[]:r.split(",")}n(K,"envScopes");function G(e,r){return e==null?!0:a(e,r)}n(G,"scopeAllowed");function a(e,r){return e.includes(r)?(_(r,r),!0):e.some(t=>_(t,r))}n(a,"coveredByAny");function b(){return process.env.CRTR_EXECUTION_ID}n(b,"envExecutionId");function k(){return process.env.CRTR_PROFILE_ID}n(k,"envProfileId");function y(){return process.env.CRTR_NODE_CWD}n(y,"envNodeCwd");function w(){return process.env.CRTR_MODEL_INTENT}n(w,"envModelIntent");function g(){return process.env.CRTR_MODEL_EXACT==="1"}n(g,"envModelExact");function V(){return process.env.CRTR_HOME}n(V,"envHomeOverride");function X(){return process.env.CRTR_RUNTIME_HOME}n(X,"envRuntimeHomeOverride");function s(e,r){if(e===void 0)return r;const t=Number(e);return Number.isFinite(t)&&t>0?t:r}n(s,"parsePositiveMs");const
|
|
2
|
+
`).map(o=>o.split(" ")[4])),t=T(e,{withFileTypes:!0}).filter(o=>o.isDirectory()&&/^[a-zA-Z0-9][a-zA-Z0-9-]{0,127}$/.test(o.name)&&r.has(i(e,o.name))&&d(i(e,o.name))!==void 0);return t.length===1?t[0].name:void 0}catch{return}}n(E,"sandboxCliNodeId");function F(){return E()!==void 0}n(F,"isSandboxedNode");function h(){return process.env.CRTR_GRANTEE}n(h,"envGrantee");const v="CRTR_INBOX_BETWEEN_TURNS";function H(){return process.env[v]==="1"}n(H,"envInboxBetweenTurns");const C="CRTR_SCOPES_FILE";function K(){const e=process.env[C];if(e!==void 0&&e!==""){const t=JSON.parse(c(e,"utf8"));if(!Array.isArray(t)||t.some(o=>typeof o!="string"))throw new Error(`invalid scopes file ${e}`);return t}const r=process.env.CRTR_SCOPES;return r===void 0?null:r===""?[]:r.split(",")}n(K,"envScopes");function G(e,r){return e==null?!0:a(e,r)}n(G,"scopeAllowed");function a(e,r){return e.includes(r)?(_(r,r),!0):e.some(t=>_(t,r))}n(a,"coveredByAny");function b(){return process.env.CRTR_EXECUTION_ID}n(b,"envExecutionId");function k(){return process.env.CRTR_PROFILE_ID}n(k,"envProfileId");function y(){return process.env.CRTR_NODE_CWD}n(y,"envNodeCwd");function w(){return process.env.CRTR_MODEL_INTENT}n(w,"envModelIntent");function g(){return process.env.CRTR_MODEL_EXACT==="1"}n(g,"envModelExact");function V(){return process.env.CRTR_HOME}n(V,"envHomeOverride");function X(){return process.env.CRTR_RUNTIME_HOME}n(X,"envRuntimeHomeOverride");function s(e,r){if(e===void 0)return r;const t=Number(e);return Number.isFinite(t)&&t>0?t:r}n(s,"parsePositiveMs");const S=5*6e4;function j(){return s(process.env.CRTR_STREAM_WATCHDOG_MS,S)}n(j,"envStreamWatchdogMs");const A=800;function Y(){return s(process.env.CRTR_WATCHER_TICK_MS,A)}n(Y,"envWatcherTickMs");const M=6e4;function $(){return s(process.env.CRTR_WATCHER_BACKSTOP_MS,M)}n($,"envWatcherBackstopMs");const N=1e3;function z(){return s(process.env.CRTR_WATCHER_DEBOUNCE_MS,N)}n(z,"envWatcherDebounceMs");const x=6e4;function Z(){return s(process.env.CRTR_RECAP_IDLE_MS,x)}n(Z,"envRecapIdleMs");function J(){const e=process.env.CRTR_RECAP_MODEL;return e!==void 0&&e.trim()!==""?e.trim():void 0}n(J,"envRecapModelOverride");const D=300*1e3;function q(){return s(process.env.CRTR_BASH_VALVE_MS,D)}n(q,"envBashValveMs");const O=60*1e3;function Q(){return s(process.env.CRTR_BASH_AUTO_BG_DEADLINE_MS,O)}n(Q,"envBashAutoBgDeadlineMs");function ee(){const e=process.env.CRTR_PIDFILE;return e!==void 0&&e!==""?e:void 0}n(ee,"envPidfileOverride");const I="@earendil-works/pi-coding-agent";function ne(){return process.env.CRTR_BROKER_ENGINE??I}n(ne,"envBrokerEngine");function re(){return process.env.CRTR_PI_BINARY}n(re,"envPiBinaryOverride");function te(){return process.env.CRTR_NO_DAEMON_AUTOSTART==="1"}n(te,"envNoDaemonAutostart");function oe(){const e=process.env.CRTR_DOCUMENT_PORT;if(e===void 0||!/^\d+$/.test(e))return;const r=Number(e);return r<=65535?r:void 0}n(oe,"envDocumentPort");function se(){return process.env.CRTRD_TCP}n(se,"envCrtrdTcp");function ue(){const e=process.env.CRTRD_TOKEN;return e!==void 0&&e!==""?e:void 0}n(ue,"envCrtrdToken");function ie(){return process.env.CRTR_NO_EXPORTS==="1"}n(ie,"envNoExports");function ce(){return process.env.CRTR_DEBUG==="1"}n(ce,"envDebug");function R(e){if(e===void 0||e==="")return;const r=Number(e);return Number.isSafeInteger(r)&&r>=1?r:void 0}n(R,"parsePositiveInteger");function _e(){return R(process.env.CRTR_MAX_LIVE_BROKERS)}n(_e,"envMaxLiveBrokers");function Re(){return R(process.env.CRTR_WARN_LIVE_BROKERS)}n(Re,"envWarnLiveBrokers");function u(e){if(e===void 0)return;const r=Number(e);return Number.isFinite(r)&&r>0?r:void 0}n(u,"parsePositiveMsNoDefault");function pe(){return u(process.env.CRTR_TEST_NODE_SOCKET_WINDOW_MS)}n(pe,"envTestNodeSocketWindowMs");function fe(){return u(process.env.CRTR_TEST_UNATTENDED_PARK_MS)}n(fe,"envTestUnattendedParkMs");function Te(){return u(process.env.CRTR_UNATTENDED_PARK_MS)}n(Te,"envUnattendedParkMs");function de(){return u(process.env.CRTR_BASH_SILENCE_MS)??15*6e4}n(de,"envBashSilenceMs");function l(){return u(process.env.CRTR_TEST_PARK_SUMMARY_GRACE_MS)}n(l,"envTestParkSummaryGraceMs");const m=5*6e4;function Ee(){return l()??m}n(Ee,"parkSummaryGraceMs");export{v as INBOX_BETWEEN_TURNS_ENV,C as SCOPES_FILE_ENV,a as coveredByAny,Q as envBashAutoBgDeadlineMs,de as envBashSilenceMs,q as envBashValveMs,ne as envBrokerEngine,se as envCrtrdTcp,ue as envCrtrdToken,ce as envDebug,oe as envDocumentPort,b as envExecutionId,h as envGrantee,V as envHomeOverride,H as envInboxBetweenTurns,_e as envMaxLiveBrokers,g as envModelExact,w as envModelIntent,te as envNoDaemonAutostart,ie as envNoExports,y as envNodeCwd,W as envNodeId,re as envPiBinaryOverride,ee as envPidfileOverride,k as envProfileId,Z as envRecapIdleMs,J as envRecapModelOverride,X as envRuntimeHomeOverride,K as envScopes,j as envStreamWatchdogMs,pe as envTestNodeSocketWindowMs,l as envTestParkSummaryGraceMs,fe as envTestUnattendedParkMs,Te as envUnattendedParkMs,Re as envWarnLiveBrokers,$ as envWatcherBackstopMs,z as envWatcherDebounceMs,Y as envWatcherTickMs,F as isSandboxedNode,d as launchSnapshotFile,Ee as parkSummaryGraceMs,E as sandboxCliNodeId,G as scopeAllowed};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var w=Object.defineProperty;var i=(e,t)=>w(e,"name",{value:t,configurable:!0});import{formatInboxRefInstruction as
|
|
2
|
-
`);return o.length
|
|
3
|
-
`),r=!0),t.length>
|
|
1
|
+
var w=Object.defineProperty;var i=(e,t)=>w(e,"name",{value:t,configurable:!0});import{formatInboxRefInstruction as l}from"./generated-context.js";const $=12,u=1e3,y=1e3;function A(e){return(e===void 0?null:/^([a-z0-9-]+)\/reports\/[^/]+$/.exec(e))?.[1]}i(A,"reportRefNodeId");function j(e){let t=e,r=!1;const o=t.split(`
|
|
2
|
+
`);return o.length>$&&(t=o.slice(0,$).join(`
|
|
3
|
+
`),r=!0),t.length>u&&(t=t.slice(0,u),r=!0),{text:t.trimEnd(),clipped:r}}i(j,"clipBody");function x(e){const t=typeof e.data?.body=="string"?String(e.data.body).trim():"";return t===""||t===e.label?"":t}i(x,"inlineBody");function v(e){const t=e.data?.push;return typeof t=="object"&&t!==null&&typeof t.event=="string"?t:null}i(v,"pushOf");function f(e){const t=e.owner_handle??e.owner;return t==null?e.name:`${t}/${e.name}`}i(f,"objectRef");function _(e){return e.type==="document"?`[[${f(e)}]]`:e.type==="job"?`job ${e.id} (\`${e.name}\`)`:e.type==="node"?`node ${e.name} (${e.id})${e.machine?` on ${e.machine}`:""}`:`${e.type} ${e.name}`}i(_,"objectText");function I(e,t){return t.actor===null?"the runtime":e.from_name===void 0||e.from_name===t.actor?t.actor:`${e.from_name} (${t.actor})`}i(I,"actorText");function m(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}i(m,"list");function R(e,t){const r=_(t.object),o=I(e,t),n=t.attrs,a=t.object.type==="document"?` Read it with \`crtr canvas read ${f(t.object)}\`.`:"";switch(t.event){case"created":return t.object.type==="node"?`${o} created ${r}${typeof n.kind=="string"?`, kind ${n.kind}`:""}.`:`${o} created ${r}.${a}`;case"read":return`${o} read ${r}.`;case"watched":return`${o} started watching ${r}.`;case"deleted":return`${o} deleted ${r}.`;case"edited":{const s=m(n.authors),c=m(n.rationales),d=typeof n.from_revision=="number"&&typeof n.revision=="number"&&n.revision-n.from_revision>1?`revisions ${n.from_revision+1}\u2013${n.revision}`:typeof n.revision=="number"?`revision ${n.revision}`:"a new revision",g=s.length===0?o:s.join(", "),h=c.length===0?"":` Rationale: ${c.join("; ")}.`,b=n.needs_merge===!0?" Two edits were made from the same revision; read it and edit it to merge them.":"",p=` You get this because you read or wrote it; if it no longer concerns your work, stop with \`crtr canvas unwatch ${f(t.object)}\`.`;return`${g} edited ${r} (${d}).${h}${b}${a}${p}`}case"messaged":return`${o} messaged ${r}.`;case"sent":return`${o} sent a message as ${r}.`;case"configured":return`${o} changed the settings of ${r}${typeof n.changed=="string"?`: ${n.changed}`:""}.`;case"exited":return`${r[0].toUpperCase()}${r.slice(1)} exited with code ${String(n["exit-code"])}. Log: ${String(n["log-path"])}`;case"silent":{const s=typeof n.files=="string"?n.files:"";return`${r[0].toUpperCase()}${r.slice(1)} has been silent for ${String(n["silent-for"])}: its processes used almost no CPU and none of the files they hold open for writing changed. It may be hung. Watched: ${s===""?"the job log only":s}. Inspect those files; stop it with \`crtr node bash kill ${t.object.id}\`; or, if it is meant to sit idle, stop these messages with \`crtr node bash quiet ${t.object.id}\`.`}case"killed":{const s=t.actor===null?"":` by ${o}`,c=typeof n.reason=="string"?` (${n.reason})`:"",d=n.reason==="deadline"?` It was canceled at its deadline; claim it with \`crtr node bash extend ${t.object.id} --for <duration>\` next time.`:"";return`${r[0].toUpperCase()}${r.slice(1)} was killed${s}${c}.${d} Log: ${String(n["log-path"])}`}default:return e.label}}i(R,"renderPush");function C(e,t){if(t!==null&&t.length<y&&e.ref!==void 0)return`${t.trimEnd()}
|
|
4
4
|
|
|
5
|
-
${
|
|
5
|
+
${l(e.ref)}`;const r=x(e);if(r===""){const a=v(e);return a!==null&&e.ref===void 0?R(e,a):e.ref===void 0?e.label:`${e.label}
|
|
6
6
|
|
|
7
|
-
${
|
|
7
|
+
${l(e.ref)}`}const{text:o,clipped:n}=j(r);return e.ref!==void 0?`${o}
|
|
8
8
|
|
|
9
|
-
${
|
|
10
|
-
\u2026 (body clipped)`:o}i(
|
|
9
|
+
${l(e.ref)}`:n?`${o}
|
|
10
|
+
\u2026 (body clipped)`:o}i(C,"renderEntryBody");export{y as INLINE_REPORT_MAX_CHARS,j as clipBody,x as inlineBody,C as renderEntryBody,A as reportRefNodeId};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.557",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
},
|
|
142
142
|
"license": "UNLICENSED",
|
|
143
143
|
"dependencies": {
|
|
144
|
-
"@north-light/identity": "0.3.
|
|
144
|
+
"@north-light/identity": "0.3.557",
|
|
145
145
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
146
146
|
"@earendil-works/pi-ai": "1.1.0",
|
|
147
147
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.557",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.557",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@milkdown/preset-gfm": "^7.22.3",
|
|
25
25
|
"@milkdown/prose": "^7.22.3",
|
|
26
26
|
"@milkdown/utils": "^7.22.3",
|
|
27
|
-
"@north-light/identity": "0.3.
|
|
27
|
+
"@north-light/identity": "0.3.557",
|
|
28
28
|
"cron-parser": "^5.6.0",
|
|
29
29
|
"esbuild": "^0.27.7",
|
|
30
30
|
"jose": "^6.2.12",
|
|
@@ -12455,17 +12455,17 @@
|
|
|
12455
12455
|
},
|
|
12456
12456
|
"packages/crouter-api": {
|
|
12457
12457
|
"name": "@north-light/api",
|
|
12458
|
-
"version": "0.3.
|
|
12458
|
+
"version": "0.3.557",
|
|
12459
12459
|
"license": "UNLICENSED"
|
|
12460
12460
|
},
|
|
12461
12461
|
"packages/crouter-env-docker": {
|
|
12462
12462
|
"name": "@north-light/env-docker",
|
|
12463
|
-
"version": "0.3.
|
|
12463
|
+
"version": "0.3.557",
|
|
12464
12464
|
"license": "UNLICENSED"
|
|
12465
12465
|
},
|
|
12466
12466
|
"packages/crouter-identity": {
|
|
12467
12467
|
"name": "@north-light/identity",
|
|
12468
|
-
"version": "0.3.
|
|
12468
|
+
"version": "0.3.557",
|
|
12469
12469
|
"license": "UNLICENSED",
|
|
12470
12470
|
"dependencies": {
|
|
12471
12471
|
"jose": "^6.2.1"
|
|
@@ -12473,21 +12473,21 @@
|
|
|
12473
12473
|
},
|
|
12474
12474
|
"packages/crouter-plugin": {
|
|
12475
12475
|
"name": "@north-light/plugin",
|
|
12476
|
-
"version": "0.3.
|
|
12476
|
+
"version": "0.3.557",
|
|
12477
12477
|
"license": "UNLICENSED",
|
|
12478
12478
|
"dependencies": {
|
|
12479
|
-
"@north-light/api": "0.3.
|
|
12480
|
-
"@north-light/identity": "0.3.
|
|
12479
|
+
"@north-light/api": "0.3.557",
|
|
12480
|
+
"@north-light/identity": "0.3.557"
|
|
12481
12481
|
}
|
|
12482
12482
|
},
|
|
12483
12483
|
"packages/crouter-sdk": {
|
|
12484
12484
|
"name": "@north-light/sdk",
|
|
12485
|
-
"version": "0.3.
|
|
12485
|
+
"version": "0.3.557",
|
|
12486
12486
|
"license": "UNLICENSED",
|
|
12487
12487
|
"dependencies": {
|
|
12488
|
-
"@north-light/api": "0.3.
|
|
12489
|
-
"@north-light/identity": "0.3.
|
|
12490
|
-
"@north-light/plugin": "0.3.
|
|
12488
|
+
"@north-light/api": "0.3.557",
|
|
12489
|
+
"@north-light/identity": "0.3.557",
|
|
12490
|
+
"@north-light/plugin": "0.3.557",
|
|
12491
12491
|
"jose": "^6.2.1"
|
|
12492
12492
|
},
|
|
12493
12493
|
"bin": {
|