@north-light/cli 0.3.584 → 0.3.585
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/api/client.d.ts +3 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/bash-jobs.d.ts +17 -0
- package/dist/api/dto/bash-jobs.js +1 -1
- package/dist/api/routes.d.ts +1 -0
- package/dist/api/routes.js +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
- package/dist/clients/attach/command.js +1 -1
- package/dist/clients/attach/input/capabilities.d.ts +4 -4
- package/dist/clients/attach/input/capabilities.js +1 -1
- package/dist/clients/attach/session/connection.d.ts +7 -8
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/session/context.d.ts +2 -2
- package/dist/clients/attach/session/reconnect.d.ts +2 -0
- package/dist/clients/attach/session/reconnect.js +1 -1
- package/dist/clients/attach/viewer.js +500 -500
- package/dist/commands/node/bash.js +4 -3
- package/dist/core/broker-client/index.d.ts +1 -0
- package/dist/core/broker-client/index.js +1 -1
- package/dist/core/broker-client/transport-relay.js +1 -1
- package/dist/core/broker-client/transport.d.ts +11 -1
- package/dist/core/broker-client/transport.js +1 -1
- package/dist/core/events/read.js +1 -1
- package/dist/core/graph/kinds/job.js +2 -2
- package/dist/core/log.d.ts +2 -0
- package/dist/core/log.js +5 -5
- package/dist/core/providers/bundles.js +2 -2
- package/dist/core/providers/call.js +1 -1
- package/dist/daemon/api/handlers/bash-jobs.js +3 -1
- package/dist/daemon/api/operations.js +1 -1
- package/dist/shared/inbox-entry-body.js +7 -7
- package/docs/sdk/release-notes.md +4 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
var
|
|
1
|
+
var T=Object.defineProperty;var r=(e,t)=>T(e,"name",{value:t,configurable:!0});import{envNodeId as _}from"../../shared/env.js";import{defineBranch as q,defineLeaf as h}from"../../core/command.js";import{InputError as i}from"../../core/io.js";import{ExitCode as R}from"../../types.js";import{existsSync as D,mkdirSync as N,rmSync as E,writeFileSync as w}from"node:fs";import{backgroundRunningBashJobs as I,bashJobCommandLine as B,bashJobPaths as y,formatBashElapsed as k,isSafeBashJobId as p,jobStartedAtMs as L,newBashJobId as M,tailJobLog as S}from"../../core/bash-jobs.js";import{parseDurationMs as U}from"../../core/wake.js";import{cliClient as b,getNodeOrNull as u}from"../api-client.js";import{ApiError as x}from"../../api/index.js";import{PREVIEW_RESULT_PATH_ENV as J}from"../../core/preview-result-path.js";import{nodeInPane as A}from"../surface/node/placement.js";function c(e,t){const n=e.pane??process.env.TMUX_PANE;let o=e.node;if((o===void 0||o==="")&&(o=A(n)),(o===void 0||o==="")&&(o=_()),o===void 0||o==="")throw new i({error:"no_node",message:t,next:"Run from inside a node, pass --node <id>, or --pane <pane>."});return{id:o,pane:n}}r(c,"resolveBashTarget");async function j(e,t,n){try{await b().patchBashJob(e,t,n)}catch(o){throw o instanceof x&&(o.status===404||o.status===409)?new i({error:"not_running",message:`background job ${t} for ${e} is not running`,next:"List live jobs with `crtr node bash list`."}):o}}r(j,"patchRunningJob");const C=h({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:r(async e=>{const t=e.command,n=_(),o=process.env.CRTR_CONTEXT_DIR;if(n===void 0||n===""||o===void 0||o==="")throw new i({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(t,s);if(l!==null)throw new i({error:"refused",message:l,next:"Run a different command."});const a=y(o,M());N(a.dir,{recursive:!0}),w(a.cmdSh,t),w(a.jobLog,""),w(a.jobBg,"");const{launchBashJobSupervisor:m}=await import("../../core/runtime/broker/bash-job-launch.js"),{[J]:g,...v}=process.env;try{await m(n,a,o,s,v)}catch(f){throw E(a.dir,{recursive:!0,force:!0}),f}const{recordBackgroundedJob:$}=await import("../../core/runtime/broker/daemon-ops.js");try{await $(n,a.jobId,{command:t}),e.quietOk===!0&&await b().patchBashJob(n,a.jobId,{quiet_ok:!0})}catch(f){throw new i({error:"job_not_recorded",message:`job ${a.jobId} is running but could not be recorded (${f.message}); it will NOT message you when it exits. Log: ${a.jobLog}`,next:`Watch it with \`tail -f ${a.jobLog}\`, or stop it with \`crtr node bash kill ${a.jobId}\`.`},R.GENERAL)}return{job_id:a.jobId,log_path:a.jobLog}},"run"),render:r(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")}),P=h({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:r(async e=>{const{id:t}=c(e,"no node found for the bash handoff"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=I(n.paths.context_dir),s=Date.now(),d=o.map(l=>({job_id:l.jobId,elapsed_ms:s-(L(l.jobId)??s)}));return{node_id:t,jobs:d}},"run"),render:r(e=>{const t=e.jobs;if(t.length===0)return`No foreground bash command is running for ${e.node_id}.`;const n=Math.max(...t.map(o=>o.elapsed_ms));return`Handed ${t.length} bash command(s) to the background job system for ${e.node_id} \xB7 ran ${k(n)} in the foreground.`},"render")}),O=h({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). Read from the local log file, so it is available only on the machine that runs the job; elsewhere log_tail is null \u2014 use `node bash log` there."}],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, output_key, log_path, pgid, log_tail} for every live background job; log_path and log_tail are null when the job\u2019s log is not on this machine (a job running in a sandbox, listed from outside it), and no tail is available from here (`node bash log` reads its stored output); 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\u2019s running job rows and, for log_tail, the local log files. Changes nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job list"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,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=(await b().listBashJobs(n.node_id)).map(a=>{const m=y(d,a.job_id).jobLog,g=D(m)?m:null;return{job_id:a.job_id,command:a.command,purpose:a.purpose,started_at:a.started_at,elapsed_ms:a.elapsed_ms,deadline_at:a.deadline_at,output_key:a.output_key,log_path:g,pgid:a.pgid,log_tail:g===null?null:S(g,s)}});return{node_id:n.node_id,jobs:l}},"run"),render:r(e=>{const t=e.jobs;return t.length===0?`No background bash jobs for ${e.node_id}.`:t.map(n=>`${n.job_id} \xB7 ${k(n.elapsed_ms)} \xB7 ${B(n.command)}${n.purpose===null?"":`
|
|
2
2
|
purpose: ${n.purpose}`}${n.deadline_at===null?"":`
|
|
3
3
|
cancels at: ${n.deadline_at}`}
|
|
4
|
-
${n.log_path===null?`log: not on this machine (the job runs in a sandbox);
|
|
5
|
-
`)},"render")}),K=
|
|
4
|
+
${n.log_path===null?`log: not on this machine (the job runs in a sandbox); read its output with \`crtr node bash log ${n.job_id} --node ${e.node_id}\``:`log: ${n.log_path}`}`).join(`
|
|
5
|
+
`)},"render")}),K=h({name:"log",description:"read a background bash job\u2019s output from the blob store, from any machine",whenToUse:"a job has exited or been killed, or its sandbox is gone, and you need what it printed \u2014 the wake and the job object name this command; for a job still running on this machine, `tail -f` on its log file shows more recent output",help:{name:"node bash log",summary:"read the stored output of one background bash job, last lines first bounded",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as named by the exit wake, `node bash list` or `node bash run`. Running and finished jobs both work."},{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). A job another node started needs its owner here."},{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,constraint:"Return only this many trailing lines. Without it you get the last 256 KiB of output; either way at most 4 MiB."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"job_id",type:"string",required:!0,constraint:"The job."},{name:"output",type:"string",required:!0,constraint:"The stored output, empty when the job has written nothing yet. The broker uploads a running job\u2019s output every couple of seconds, so it can trail the live log slightly."},{name:"truncated",type:"boolean",required:!0,constraint:"True when earlier output exists that the bound left out."}],outputKind:"object",effects:["Reads the output chunks the job\u2019s broker uploaded to the blob store through the daemon; never the local log file, so it works from any machine and after the job\u2019s sandbox is deleted. Needs read access to the owning node. Changes nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id named by the exit wake or `crtr node bash list`."});const s=e.tail===void 0?void 0:Math.trunc(Number(e.tail));if(s!==void 0&&!(s>=1))throw new i({error:"invalid_args",message:"--tail must be a positive number of lines",next:"Pass --tail 50, or omit it for the last 256 KiB."});try{const d=await b().readBashJobOutput(n.node_id,o,s===void 0?{}:{tail_lines:s});return{node_id:n.node_id,job_id:d.job_id,output:d.output,truncated:d.truncated}}catch(d){throw d instanceof x&&d.code==="job_not_found"?new i({error:"job_not_found",message:`no job ${o} owned by ${n.node_id}`,next:"A job another node started needs its owner: pass --node <owner>."}):d}},"run"),render:r(e=>`${e.truncated===!0?`[earlier output omitted]
|
|
6
|
+
`:""}${String(e.output)}`,"render")}),X=h({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:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=await b().stopBashJob(n.node_id,o);return{node_id:n.node_id,signaled:s.signaled}},"run"),render:r(e=>`Canceled a background bash job for ${e.node_id}${e.signaled===!0?"":" (no observed job process was still running)"}.`,"render")}),W=h({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:["Marks the job\u2019s row; the daemon sends no further silence messages for it. Signals nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});return await j(n.node_id,o,{quiet_ok:!0}),{node_id:n.node_id}},"run"),render:r(e=>`Background job for ${e.node_id} will send no more silence messages.`,"render")}),G=h({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 clears the deadline on the job\u2019s row. The daemon\u2019s deadline lane reads it on its next scan. Signals nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({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 i({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."});if(d)return await j(n.node_id,o,{keep:!0}),{node_id:n.node_id};const l=U(s);if(l===null||l<=0)throw new i({error:"invalid_duration",message:`not a duration: ${s}`,next:"Use a value like 90s, 10m, or 1h30m."});const a=Date.now()+l;return await j(n.node_id,o,{deadline_at_ms:a}),{node_id:n.node_id,deadline_at:new Date(a).toISOString()}},"run"),render:r(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")}),se=q({name:"bash",description:"start, list, read, 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, read the output of, stop, or extend the deadline of a job you already have",help:{name:"node bash",summary:"start, list, read, 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:[C,P,O,K,X,W,G]});export{se as nodeBash};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BrokerClient as
|
|
1
|
+
import{AttachRefusedError as o}from"./transport.js";import{BrokerClient as a,BrokerUnavailableError as n,reconnectShouldGiveUp as l,RelayTransport as p,SocketTransport as c}from"./client.js";export{o as AttachRefusedError,a as BrokerClient,n as BrokerUnavailableError,p as RelayTransport,c as SocketTransport,l as reconnectShouldGiveUp};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var l=Object.defineProperty;var a=(
|
|
1
|
+
var l=Object.defineProperty;var a=(s,t)=>l(s,"name",{value:t,configurable:!0});import{EventEmitter as d}from"node:events";import{WebSocket as h}from"ws";import{classify as m}from"../fault-classifier.js";import{AttachRefusedError as f,BrokerUnavailableError as u}from"./transport.js";function p(s,t){if("socketPath"in s)return`ws+unix://${s.socketPath}:/v1/nodes/${encodeURIComponent(t)}/attach?revive=0`;const e=new URL(s.previewEndpoint);return e.protocol=e.protocol==="http:"?"ws:":"wss:",e.pathname=`/v1/nodes/${encodeURIComponent(t)}/attach`,e.search="?wakeParked=1",e.hash="",e.toString()}a(p,"relayUrl");class S extends d{static{a(this,"RelayTransport")}nodeId;target;ws;closeEmitted=!1;constructor(t,e){super(),this.nodeId=t,this.target=e}get url(){return p(this.target,this.nodeId)}dial(){const t=new h(this.url,{..."relayToken"in this.target?{headers:{authorization:`Bearer ${this.target.relayToken}`}}:{},perMessageDeflate:!1});return this.ws=t,t}connect(){const t=this.dial();t.on("open",()=>{this.emit("connect")}),t.on("message",e=>this.onMessage(e)),t.on("error",e=>this.onError(e)),t.on("close",(e,n)=>this.onClose(e,n.toString()))}redial(t){return this.destroy(),this.closeEmitted=!1,new Promise((e,n)=>{const r=this.dial();let i=!1;r.on("open",()=>{i||(i=!0,this.emit("connect"),e())}),r.on("message",o=>this.onMessage(o)),r.on("error",o=>{if(i){this.onError(o);return}i=!0;try{r.terminate()}catch{}n(o)}),r.on("close",(o,c)=>{i&&this.onClose(o,c.toString())})})}send(t){const e=this.ws;if(!(e===void 0||e.readyState!==h.OPEN))try{e.send(JSON.stringify(t))}catch{}}destroy(){const t=this.ws;if(t!==void 0&&t.readyState<=h.OPEN)try{t.close(1e3)}catch{}}onMessage(t){let e;try{e=JSON.parse(t.toString())}catch{return}this.emit("frame",e)}onError(t){this.emitError(new Error(`relay transport error: ${t.message}`))}onClose(t,e){if(this.closeEmitted)return;this.closeEmitted=!0;const n=m("viewer\u2194broker",{code:t,reason:e}),r=t===1008||n.kind==="protocol"||t===1011&&(/^no running broker/.test(e)||/^no node /.test(e));t===1008?this.emitError(new f(this.nodeId,e)):r&&this.emitError(new u(this.nodeId)),this.emit("close")}emitError(t){this.listenerCount("error")>0&&this.emit("error",t)}}export{S as RelayTransport};
|
|
@@ -32,5 +32,15 @@ export interface Transport extends EventEmitter {
|
|
|
32
32
|
* focus/revive hint — same meaning regardless of which Transport raised it. */
|
|
33
33
|
export declare class BrokerUnavailableError extends Error {
|
|
34
34
|
readonly nodeId: string;
|
|
35
|
-
constructor(nodeId: string);
|
|
35
|
+
constructor(nodeId: string, message?: string);
|
|
36
|
+
}
|
|
37
|
+
/** A crtrd attach that closed 1008: the daemon refused this client (revoked or
|
|
38
|
+
* missing permission for the hello's role, or an invalid node id) before the
|
|
39
|
+
* hello reached any broker. A BrokerUnavailableError too, so existing callers
|
|
40
|
+
* keep treating it as a terminal connect failure; a viewer that must tell a
|
|
41
|
+
* refusal from a broker that is merely restarting (a 1011 "no running broker")
|
|
42
|
+
* checks for this class. */
|
|
43
|
+
export declare class AttachRefusedError extends BrokerUnavailableError {
|
|
44
|
+
readonly reason: string;
|
|
45
|
+
constructor(nodeId: string, reason: string);
|
|
36
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var t=Object.defineProperty;var o=(s,r)=>t(s,"name",{value:r,configurable:!0});class a extends Error{static{o(this,"BrokerUnavailableError")}nodeId;constructor(r,e){super(e??`node ${r} has no running broker \u2014 focus or revive it first`),this.nodeId=r,this.name="BrokerUnavailableError"}}class c extends a{static{o(this,"AttachRefusedError")}reason;constructor(r,e){super(r,`the attach to node ${r} was refused by the canvas (${e||"no reason given"})`),this.reason=e,this.name="AttachRefusedError"}}export{c as AttachRefusedError,a as BrokerUnavailableError};
|
package/dist/core/events/read.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var W=Object.defineProperty;var s=(n,e)=>W(n,"name",{value:e,configurable:!0});import{closeSync as X,constants as O,fstatSync as G,lstatSync as L,openSync as H,opendirSync as J,readSync as Q}from"node:fs";import{isAbsolute as Z,join as S,relative as N,resolve as I,sep as q}from"node:path";import{crtrHome as ee,isSafeNodeId as C,nodesRoot as ne}from"../canvas/paths.js";import{validateOperationId as A}from"./operation-id.js";import{MAX_EVENT_RECORD_BYTES as te}from"./serialize.js";import{errorClassCodes as re}from"./types.js";import{isRecord as E}from"../../shared/predicates.js";const k=200,ie=6*1024*1024,oe=256*1024,se=te-1,de=typeof O.O_NOFOLLOW=="number"?O.O_NOFOLLOW:0,fe=/^[a-z][a-z0-9]*(?:[._][a-z0-9]+)*$/,ae=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,ce=/^\d{4}-\d{2}-\d{2}T/,ue=new Set(["nodeId","component","operationId","sinceMs","untilMs","minimumLevel","pattern"]),me=new Set(["class","status","provider","retry_disposition"]),le=new Set(["type","message","stack","cause"]),pe=new Set(["v","ts","level","event","component","operation_id","node_id","stream_id","duration_ms","outcome","attempt","error_class","error","fields","record_truncated","original_bytes","dropped_fields_count"]),R=new Set(["debug","info","warn","error"]),F=new Set(["daemon","broker"]),ye=new Set(["succeeded","failed","skipped"]),_e=new Set(["auto","manual","fatal"]),be=new Set(re),$={debug:0,info:1,warn:2,error:3},Se={2:0,1:1,0:2};function m(n,e){return Object.prototype.hasOwnProperty.call(n,e)}s(m,"hasOwn");function h(n,e){return Object.keys(n).every(t=>e.has(t))}s(h,"hasOnlyKeys");function y(n,e,t=!1){return typeof n=="string"&&(!t||n!=="")&&Buffer.byteLength(n,"utf8")<=e}s(y,"boundedString");function we(n){if(typeof n!="object"||n===null||Array.isArray(n))throw new TypeError("event read query is invalid");const e=n;if(!h(e,ue))throw new TypeError("event read query is invalid");const t={};if(e.nodeId!==void 0){if(typeof e.nodeId!="string"||!C(e.nodeId))throw new TypeError("nodeId must be a safe non-empty path segment");t.nodeId=e.nodeId}if(e.component!==void 0){if(typeof e.component!="string"||!F.has(e.component))throw new TypeError("component is invalid");t.component=e.component}if(e.operationId!==void 0&&(t.operationId=A(e.operationId)),e.sinceMs!==void 0){if(typeof e.sinceMs!="number"||!Number.isFinite(e.sinceMs))throw new TypeError("sinceMs must be finite");t.sinceMs=e.sinceMs}if(e.untilMs!==void 0){if(typeof e.untilMs!="number"||!Number.isFinite(e.untilMs))throw new TypeError("untilMs must be finite");t.untilMs=e.untilMs}if(t.sinceMs!==void 0&&t.untilMs!==void 0&&t.sinceMs>t.untilMs)throw new TypeError("sinceMs cannot exceed untilMs");if(e.minimumLevel!==void 0){if(typeof e.minimumLevel!="string"||!R.has(e.minimumLevel))throw new TypeError("minimumLevel is invalid");t.minimumLevel=e.minimumLevel}if(e.pattern!==void 0){if(!y(e.pattern,4096,!0))throw new TypeError("pattern must be a non-empty string of at most 4,096 UTF-8 bytes");t.pattern=e.pattern}return t}s(we,"normalizeQuery");function v(n,e){return n.component===void 0||n.component===e}s(v,"selected");function g(n){return{dev:n.dev,ino:n.ino}}s(g,"identityOf");function x(n,e){return n.dev===e.dev&&n.ino===e.ino}s(x,"sameIdentity");function Ee(n,e){if(n.size<0n||n.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error(`event source has an unsafe size: ${e}`);return Number(n.size)}s(Ee,"safeSize");function B(n){return n?.code==="ENOENT"}s(B,"isMissing");function j(n,e){const t=N(n,e);return t!==""&&!Z(t)&&t!==".."&&!t.startsWith(`..${q}`)}s(j,"isContained");function b(n){for(const e of n){let t;try{t=L(e.absolute,{bigint:!0})}catch(r){throw new Error(`event source directory changed: ${e.absolute}`,{cause:r})}if(t.isSymbolicLink()||!t.isDirectory()||!x(e,g(t)))throw new Error(`event source directory is unsafe: ${e.absolute}`)}}s(b,"validateDirectories");function M(n,e,t,r){if(t.length>0&&!j(n,e))throw new Error(`event source directory escapes its home: ${e}`);b(t);let i;try{i=L(e,{bigint:!0})}catch(o){if(r&&B(o))return;throw new Error(`event source directory is unavailable: ${e}`,{cause:o})}if(i.isSymbolicLink()||!i.isDirectory())throw new Error(`event source directory is unsafe: ${e}`);return b(t),{absolute:e,...g(i)}}s(M,"proveDirectory");function he(n,e){b([n]);let t;try{t=J(n.absolute)}catch(r){throw new Error(`event source directory cannot be opened: ${n.absolute}`,{cause:r})}try{for(;;){const r=t.readSync();if(r===null)break;e(r.name,r.isDirectory())}}finally{t.closeSync()}b([n])}s(he,"forEachDirectoryEntry");function ge(n,e,t){let r=0;for(;r<e.length;){const i=Q(n,e,r,e.length-r,t+r);if(i===0)throw new Error("event source changed during read");r+=i}}s(ge,"readExact");function z(n,e,t,r){if(!j(n,t))throw new Error(`event source escapes its home: ${t}`);b(e);let i;try{i=L(t,{bigint:!0})}catch(d){if(B(d))return;throw new Error(`event source is unavailable: ${t}`,{cause:d})}if(i.isSymbolicLink()||!i.isFile())throw new Error(`event source is unsafe: ${t}`);b(e);let o;try{o=H(t,O.O_RDONLY|de)}catch(d){throw new Error(`event source cannot be opened safely: ${t}`,{cause:d})}try{const d=G(o,{bigint:!0});if(!d.isFile()||!x(g(i),g(d)))throw new Error(`event source changed before read: ${t}`);b(e);const f=Ee(d,t),c=Math.min(f,r),a=f-c,l=Buffer.allocUnsafe(c);ge(o,l,a);const u=Number(d.mtimeMs);return{bytes:l,start:a,mtimeMs:Number.isFinite(u)?u:0}}finally{X(o)}}s(z,"readOwnedFile");function Me(n){return!E(n)||!h(n,me)||typeof n.class!="string"||!be.has(n.class)||m(n,"status")&&(typeof n.status!="number"||!Number.isSafeInteger(n.status))||m(n,"provider")&&!y(n.provider,128,!0)?!1:!m(n,"retry_disposition")||typeof n.retry_disposition=="string"&&_e.has(n.retry_disposition)}s(Me,"validErrorClass");function Oe(n){return!E(n)||!h(n,le)||!y(n.type,128,!0)||!y(n.message,4096)||m(n,"stack")&&!y(n.stack,16384)?!1:!m(n,"cause")||y(n.cause,4096)}s(Oe,"validError");function T(n,e,t){if(t.remaining<=0)return!1;if(t.remaining-=1,n===null||typeof n=="boolean")return!0;if(typeof n=="number")return Number.isFinite(n);if(typeof n=="string")return Buffer.byteLength(n,"utf8")<=4096;if(e>=4||typeof n!="object"||n===null)return!1;if(Array.isArray(n))return n.length<=64&&n.every(i=>T(i,e+1,t));const r=Object.entries(n);return r.length<=64&&r.every(([i,o])=>Buffer.byteLength(i,"utf8")<=128&&T(o,e+1,t))}s(T,"validEventField");function D(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>=0}s(D,"nonnegativeInteger");function Le(n){if(n.length===0||n.length>se||n.includes(13))return;let e;try{e=JSON.parse(n.toString("utf8"))}catch{return}if(!E(e)||!h(e,pe)||e.v!==1||!y(e.ts,128,!0)||!ae.test(e.ts)||!y(e.event,128,!0)||!fe.test(e.event)||typeof e.level!="string"||!R.has(e.level)||typeof e.component!="string"||!F.has(e.component))return;const t=Date.parse(e.ts);if(!Number.isFinite(t)||new Date(t).toISOString()!==e.ts)return;let r;try{r=A(e.operation_id)}catch{return}const i={v:1,ts:e.ts,level:e.level,event:e.event,component:e.component,operation_id:r};if(m(e,"node_id")){if(!y(e.node_id,128,!0))return;i.node_id=e.node_id}if(m(e,"stream_id")){if(!y(e.stream_id,128,!0))return;i.stream_id=e.stream_id}if(m(e,"duration_ms")){if(typeof e.duration_ms!="number"||!Number.isFinite(e.duration_ms)||e.duration_ms<0)return;i.duration_ms=e.duration_ms}if(m(e,"outcome")){if(typeof e.outcome!="string"||!ye.has(e.outcome))return;i.outcome=e.outcome}if(m(e,"attempt")){if(typeof e.attempt!="number"||!Number.isSafeInteger(e.attempt)||e.attempt<=0)return;i.attempt=e.attempt}if(m(e,"error_class")){if(!Me(e.error_class))return;i.error_class=e.error_class}if(m(e,"error")){if(!Oe(e.error))return;i.error=e.error}if(m(e,"fields")){if(!E(e.fields)||!T(e.fields,0,{remaining:5e3}))return;i.fields=e.fields}const o=m(e,"record_truncated"),d=m(e,"original_bytes"),f=m(e,"dropped_fields_count");if(o){if(e.record_truncated!==!0||!d||!f||!D(e.original_bytes)||!D(e.dropped_fields_count))return;i.record_truncated=!0,i.original_bytes=e.original_bytes,i.dropped_fields_count=e.dropped_fields_count}else if(d||f)return;return{envelope:i,effectiveTsMs:t}}s(Le,"parseCanonicalLine");function Ne(n,e){return e.component!==n.component?!1:n.component==="daemon"?e.stream_id===void 0:n.component==="broker"?e.node_id===n.boundNodeId&&e.stream_id===void 0:e.stream_id===n.boundStreamId}s(Ne,"descriptorMatches");function ve(n,e,t){const{envelope:r,effectiveTsMs:i}=t;return!(n.nodeId!==void 0&&e.component!=="broker"&&r.node_id!==n.nodeId||n.operationId!==void 0&&r.operation_id!==n.operationId||n.sinceMs!==void 0&&i<n.sinceMs||n.untilMs!==void 0&&i>n.untilMs||n.minimumLevel!==void 0&&$[r.level]<$[n.minimumLevel]||n.pattern!==void 0&&!JSON.stringify(r).toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US")))}s(ve,"matchesCanonical");function K(n,e){return n.item.effectiveTsMs!==e.item.effectiveTsMs?n.item.effectiveTsMs-e.item.effectiveTsMs:n.orderSlot!==e.orderSlot?n.orderSlot<e.orderSlot?-1:1:n.generationRank!==e.generationRank?n.generationRank-e.generationRank:n.byteOffset-e.byteOffset}s(K,"compareCandidates");function Y(n,e){const t=n.candidates;if(t.length===k&&K(e,t[0])<=0)return;let r=0,i=t.length;for(;r<i;){const o=Math.floor((r+i)/2);K(t[o],e)<=0?r=o+1:i=o}t.splice(r,0,e),t.length>k&&t.shift()}s(Y,"retainCandidate");function Te(n){for(const e of n)if(e!==32&&e!==9&&e!==13)return!0;return!1}s(Te,"hasNonblank");function Ie(n){return n.generation===0?n.activeAbsolute:`${n.activeAbsolute}.${n.generation}`}s(Ie,"sourcePath");function Ce(n,e,t,r){const i=Ie(r),o=z(e,t,i,ie);if(o===void 0)return;const d=N(e,i);let f=0;const c=o.start>0;let a=!1;const l=[];if(c){const u=o.bytes.indexOf(10);if(u<0)return;f=u+1}for(;;){const u=o.bytes.indexOf(10,f);if(u<0)break;const p=Le(o.bytes.subarray(f,u));if(p===void 0||!Ne(r,p.envelope))n.invalidCanonicalLines+=1,a=!0;else if(ve(n.query,r,p)){const _=o.start+f,w={kind:"event",envelope:p.envelope,source:d,streamKey:r.streamKey,generation:r.generation,byteOffset:_,effectiveTsMs:p.effectiveTsMs,truncated:!1};l.push(w),Y(n,{item:w,orderSlot:r.streamKey,generationRank:Se[r.generation],byteOffset:_})}f=u+1}if(f<o.bytes.length&&Te(o.bytes.subarray(f))&&(n.invalidCanonicalLines+=1,a=!0),c||a)for(const u of l)u.truncated=!0}s(Ce,"scanCanonicalSource");function Ae(n,e){const t=/^(\S+)/u.exec(n)?.[1];if(t!==void 0&&ce.test(t)){const r=Date.parse(t);if(Number.isFinite(r))return r}return e}s(Ae,"rawTimestamp");function ke(n,e,t,r){return n.nodeId!==void 0&&e==="daemon"&&!t.includes(n.nodeId)||n.sinceMs!==void 0&&r<n.sinceMs||n.untilMs!==void 0&&r>n.untilMs?!1:n.pattern===void 0||t.toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US"))}s(ke,"matchesRaw");function U(n,e,t,r,i){const o=z(e,t,r,oe);if(o===void 0)return;const d=N(e,r),f=o.start>0;let c=0;if(f){const a=o.bytes.indexOf(10);if(a<0)return;c=a+1}for(;;){const a=o.bytes.indexOf(10,c);if(a<0&&c===o.bytes.length)return;const l=a<0?o.bytes.length:a,u=o.bytes.subarray(c,l).toString("utf8"),p=Ae(u,o.mtimeMs);if(ke(n.query,i,u,p)){const _=o.start+c;Y(n,{item:{kind:"raw",component:i,source:d,content:u,effectiveTsMs:p,truncated:f},orderSlot:d,generationRank:2,byteOffset:_})}if(a<0)return;c=a+1}}s(U,"scanRawSource");function P(n,e,t,r){for(const i of[2,1,0])Ce(n,e,t,{...r,generation:i})}s(P,"scanCanonicalGenerations");function V(n,e,t,r,i,o){const d=S(t[t.length-1].absolute,r),f=M(e,d,t,!0);if(f===void 0)return;const c=S(d,"job"),a=M(e,c,[...t,f],!0);if(a===void 0)return;const l=[...t,f,a];i&&P(n,e,l,{component:"broker",streamKey:`broker:${r}`,activeAbsolute:S(c,"log.jsonl"),boundNodeId:r}),o&&U(n,e,l,S(c,"broker.log"),"broker")}s(V,"scanBrokerNode");function Ke(n,e){const t=we(n);if(typeof e!="boolean")throw new TypeError("includeRaw must be boolean");const r={query:t,candidates:[],invalidCanonicalLines:0},i=I(ee()),o=M(i,i,[],!0);if(o===void 0)return{items:[],invalidCanonicalLines:0};const d=[o],f=e&&t.operationId===void 0&&t.minimumLevel===void 0;v(t,"daemon")&&(P(r,i,d,{component:"daemon",streamKey:"daemon",activeAbsolute:S(i,"crtrd.log")}),f&&U(r,i,d,S(i,"crtrd.err"),"daemon"));const c=v(t,"broker"),a=f&&v(t,"broker");if(c||a){const l=I(ne()),u=M(i,l,d,!0);if(u!==void 0){const p=[o,u];t.nodeId!==void 0?V(r,i,p,t.nodeId,c,a):he(u,(_,w)=>{w&&C(_)&&V(r,i,p,_,c,a)})}}return{items:r.candidates.map(l=>l.item),invalidCanonicalLines:r.invalidCanonicalLines}}s(Ke,"readLogSnapshot");function Ye(n){const e={v:n.v,ts:n.ts,level:n.level,event:n.event,component:n.component,operation_id:n.operation_id};return n.node_id!==void 0&&(e.node_id=n.node_id),n.stream_id!==void 0&&(e.stream_id=n.stream_id),n.duration_ms!==void 0&&(e.duration_ms=n.duration_ms),n.outcome!==void 0&&(e.outcome=n.outcome),n.attempt!==void 0&&(e.attempt=n.attempt),n.error_class!==void 0&&(e.error_class=n.error_class),e}s(Ye,"projectTier1Event");export{Ye as projectTier1Event,Ke as readLogSnapshot};
|
|
1
|
+
var X=Object.defineProperty;var s=(n,e)=>X(n,"name",{value:e,configurable:!0});import{closeSync as W,constants as O,fstatSync as G,lstatSync as L,openSync as H,opendirSync as J,readSync as Q}from"node:fs";import{isAbsolute as Z,join as S,relative as N,resolve as I,sep as q}from"node:path";import{crtrHome as ee,isSafeNodeId as A,nodesRoot as ne}from"../canvas/paths.js";import{validateOperationId as C}from"./operation-id.js";import{MAX_EVENT_RECORD_BYTES as te}from"./serialize.js";import{errorClassCodes as re}from"./types.js";import{isRecord as w}from"../../shared/predicates.js";import{MAX_DAEMON_ACTIVE_BYTES as ie}from"../log.js";const k=200,oe=ie+1024*1024,se=256*1024,de=te-1,fe=typeof O.O_NOFOLLOW=="number"?O.O_NOFOLLOW:0,ae=/^[a-z][a-z0-9]*(?:[._][a-z0-9]+)*$/,ce=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,ue=/^\d{4}-\d{2}-\d{2}T/,me=new Set(["nodeId","component","operationId","sinceMs","untilMs","minimumLevel","pattern"]),le=new Set(["class","status","provider","retry_disposition"]),pe=new Set(["type","message","stack","cause"]),_e=new Set(["v","ts","level","event","component","operation_id","node_id","stream_id","duration_ms","outcome","attempt","error_class","error","fields","record_truncated","original_bytes","dropped_fields_count"]),R=new Set(["debug","info","warn","error"]),F=new Set(["daemon","broker"]),ye=new Set(["succeeded","failed","skipped"]),be=new Set(["auto","manual","fatal"]),Se=new Set(re),$={debug:0,info:1,warn:2,error:3},Ee={2:0,1:1,0:2};function m(n,e){return Object.prototype.hasOwnProperty.call(n,e)}s(m,"hasOwn");function h(n,e){return Object.keys(n).every(t=>e.has(t))}s(h,"hasOnlyKeys");function _(n,e,t=!1){return typeof n=="string"&&(!t||n!=="")&&Buffer.byteLength(n,"utf8")<=e}s(_,"boundedString");function we(n){if(typeof n!="object"||n===null||Array.isArray(n))throw new TypeError("event read query is invalid");const e=n;if(!h(e,me))throw new TypeError("event read query is invalid");const t={};if(e.nodeId!==void 0){if(typeof e.nodeId!="string"||!A(e.nodeId))throw new TypeError("nodeId must be a safe non-empty path segment");t.nodeId=e.nodeId}if(e.component!==void 0){if(typeof e.component!="string"||!F.has(e.component))throw new TypeError("component is invalid");t.component=e.component}if(e.operationId!==void 0&&(t.operationId=C(e.operationId)),e.sinceMs!==void 0){if(typeof e.sinceMs!="number"||!Number.isFinite(e.sinceMs))throw new TypeError("sinceMs must be finite");t.sinceMs=e.sinceMs}if(e.untilMs!==void 0){if(typeof e.untilMs!="number"||!Number.isFinite(e.untilMs))throw new TypeError("untilMs must be finite");t.untilMs=e.untilMs}if(t.sinceMs!==void 0&&t.untilMs!==void 0&&t.sinceMs>t.untilMs)throw new TypeError("sinceMs cannot exceed untilMs");if(e.minimumLevel!==void 0){if(typeof e.minimumLevel!="string"||!R.has(e.minimumLevel))throw new TypeError("minimumLevel is invalid");t.minimumLevel=e.minimumLevel}if(e.pattern!==void 0){if(!_(e.pattern,4096,!0))throw new TypeError("pattern must be a non-empty string of at most 4,096 UTF-8 bytes");t.pattern=e.pattern}return t}s(we,"normalizeQuery");function T(n,e){return n.component===void 0||n.component===e}s(T,"selected");function g(n){return{dev:n.dev,ino:n.ino}}s(g,"identityOf");function x(n,e){return n.dev===e.dev&&n.ino===e.ino}s(x,"sameIdentity");function he(n,e){if(n.size<0n||n.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error(`event source has an unsafe size: ${e}`);return Number(n.size)}s(he,"safeSize");function B(n){return n?.code==="ENOENT"}s(B,"isMissing");function D(n,e){const t=N(n,e);return t!==""&&!Z(t)&&t!==".."&&!t.startsWith(`..${q}`)}s(D,"isContained");function b(n){for(const e of n){let t;try{t=L(e.absolute,{bigint:!0})}catch(r){throw new Error(`event source directory changed: ${e.absolute}`,{cause:r})}if(t.isSymbolicLink()||!t.isDirectory()||!x(e,g(t)))throw new Error(`event source directory is unsafe: ${e.absolute}`)}}s(b,"validateDirectories");function M(n,e,t,r){if(t.length>0&&!D(n,e))throw new Error(`event source directory escapes its home: ${e}`);b(t);let i;try{i=L(e,{bigint:!0})}catch(o){if(r&&B(o))return;throw new Error(`event source directory is unavailable: ${e}`,{cause:o})}if(i.isSymbolicLink()||!i.isDirectory())throw new Error(`event source directory is unsafe: ${e}`);return b(t),{absolute:e,...g(i)}}s(M,"proveDirectory");function ge(n,e){b([n]);let t;try{t=J(n.absolute)}catch(r){throw new Error(`event source directory cannot be opened: ${n.absolute}`,{cause:r})}try{for(;;){const r=t.readSync();if(r===null)break;e(r.name,r.isDirectory())}}finally{t.closeSync()}b([n])}s(ge,"forEachDirectoryEntry");function Me(n,e,t){let r=0;for(;r<e.length;){const i=Q(n,e,r,e.length-r,t+r);if(i===0)throw new Error("event source changed during read");r+=i}}s(Me,"readExact");function j(n,e,t,r){if(!D(n,t))throw new Error(`event source escapes its home: ${t}`);b(e);let i;try{i=L(t,{bigint:!0})}catch(d){if(B(d))return;throw new Error(`event source is unavailable: ${t}`,{cause:d})}if(i.isSymbolicLink()||!i.isFile())throw new Error(`event source is unsafe: ${t}`);b(e);let o;try{o=H(t,O.O_RDONLY|fe)}catch(d){throw new Error(`event source cannot be opened safely: ${t}`,{cause:d})}try{const d=G(o,{bigint:!0});if(!d.isFile()||!x(g(i),g(d)))throw new Error(`event source changed before read: ${t}`);b(e);const f=he(d,t),c=Math.min(f,r),a=f-c,l=Buffer.allocUnsafe(c);Me(o,l,a);const u=Number(d.mtimeMs);return{bytes:l,start:a,mtimeMs:Number.isFinite(u)?u:0}}finally{W(o)}}s(j,"readOwnedFile");function Oe(n){return!w(n)||!h(n,le)||typeof n.class!="string"||!Se.has(n.class)||m(n,"status")&&(typeof n.status!="number"||!Number.isSafeInteger(n.status))||m(n,"provider")&&!_(n.provider,128,!0)?!1:!m(n,"retry_disposition")||typeof n.retry_disposition=="string"&&be.has(n.retry_disposition)}s(Oe,"validErrorClass");function Le(n){return!w(n)||!h(n,pe)||!_(n.type,128,!0)||!_(n.message,4096)||m(n,"stack")&&!_(n.stack,16384)?!1:!m(n,"cause")||_(n.cause,4096)}s(Le,"validError");function v(n,e,t){if(t.remaining<=0)return!1;if(t.remaining-=1,n===null||typeof n=="boolean")return!0;if(typeof n=="number")return Number.isFinite(n);if(typeof n=="string")return Buffer.byteLength(n,"utf8")<=4096;if(e>=4||typeof n!="object"||n===null)return!1;if(Array.isArray(n))return n.length<=64&&n.every(i=>v(i,e+1,t));const r=Object.entries(n);return r.length<=64&&r.every(([i,o])=>Buffer.byteLength(i,"utf8")<=128&&v(o,e+1,t))}s(v,"validEventField");function z(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>=0}s(z,"nonnegativeInteger");function Ne(n){if(n.length===0||n.length>de||n.includes(13))return;let e;try{e=JSON.parse(n.toString("utf8"))}catch{return}if(!w(e)||!h(e,_e)||e.v!==1||!_(e.ts,128,!0)||!ce.test(e.ts)||!_(e.event,128,!0)||!ae.test(e.event)||typeof e.level!="string"||!R.has(e.level)||typeof e.component!="string"||!F.has(e.component))return;const t=Date.parse(e.ts);if(!Number.isFinite(t)||new Date(t).toISOString()!==e.ts)return;let r;try{r=C(e.operation_id)}catch{return}const i={v:1,ts:e.ts,level:e.level,event:e.event,component:e.component,operation_id:r};if(m(e,"node_id")){if(!_(e.node_id,128,!0))return;i.node_id=e.node_id}if(m(e,"stream_id")){if(!_(e.stream_id,128,!0))return;i.stream_id=e.stream_id}if(m(e,"duration_ms")){if(typeof e.duration_ms!="number"||!Number.isFinite(e.duration_ms)||e.duration_ms<0)return;i.duration_ms=e.duration_ms}if(m(e,"outcome")){if(typeof e.outcome!="string"||!ye.has(e.outcome))return;i.outcome=e.outcome}if(m(e,"attempt")){if(typeof e.attempt!="number"||!Number.isSafeInteger(e.attempt)||e.attempt<=0)return;i.attempt=e.attempt}if(m(e,"error_class")){if(!Oe(e.error_class))return;i.error_class=e.error_class}if(m(e,"error")){if(!Le(e.error))return;i.error=e.error}if(m(e,"fields")){if(!w(e.fields)||!v(e.fields,0,{remaining:5e3}))return;i.fields=e.fields}const o=m(e,"record_truncated"),d=m(e,"original_bytes"),f=m(e,"dropped_fields_count");if(o){if(e.record_truncated!==!0||!d||!f||!z(e.original_bytes)||!z(e.dropped_fields_count))return;i.record_truncated=!0,i.original_bytes=e.original_bytes,i.dropped_fields_count=e.dropped_fields_count}else if(d||f)return;return{envelope:i,effectiveTsMs:t}}s(Ne,"parseCanonicalLine");function Te(n,e){return e.component!==n.component?!1:n.component==="daemon"?e.stream_id===void 0:n.component==="broker"?e.node_id===n.boundNodeId&&e.stream_id===void 0:e.stream_id===n.boundStreamId}s(Te,"descriptorMatches");function ve(n,e,t){const{envelope:r,effectiveTsMs:i}=t;return!(n.nodeId!==void 0&&e.component!=="broker"&&r.node_id!==n.nodeId||n.operationId!==void 0&&r.operation_id!==n.operationId||n.sinceMs!==void 0&&i<n.sinceMs||n.untilMs!==void 0&&i>n.untilMs||n.minimumLevel!==void 0&&$[r.level]<$[n.minimumLevel]||n.pattern!==void 0&&!JSON.stringify(r).toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US")))}s(ve,"matchesCanonical");function K(n,e){return n.item.effectiveTsMs!==e.item.effectiveTsMs?n.item.effectiveTsMs-e.item.effectiveTsMs:n.orderSlot!==e.orderSlot?n.orderSlot<e.orderSlot?-1:1:n.generationRank!==e.generationRank?n.generationRank-e.generationRank:n.byteOffset-e.byteOffset}s(K,"compareCandidates");function Y(n,e){const t=n.candidates;if(t.length===k&&K(e,t[0])<=0)return;let r=0,i=t.length;for(;r<i;){const o=Math.floor((r+i)/2);K(t[o],e)<=0?r=o+1:i=o}t.splice(r,0,e),t.length>k&&t.shift()}s(Y,"retainCandidate");function Ie(n){for(const e of n)if(e!==32&&e!==9&&e!==13)return!0;return!1}s(Ie,"hasNonblank");function Ae(n){return n.generation===0?n.activeAbsolute:`${n.activeAbsolute}.${n.generation}`}s(Ae,"sourcePath");function Ce(n,e,t,r){const i=Ae(r),o=j(e,t,i,oe);if(o===void 0)return;const d=N(e,i);let f=0;const c=o.start>0;let a=!1;const l=[];if(c){const u=o.bytes.indexOf(10);if(u<0)return;f=u+1}for(;;){const u=o.bytes.indexOf(10,f);if(u<0)break;const p=Ne(o.bytes.subarray(f,u));if(p===void 0||!Te(r,p.envelope))n.invalidCanonicalLines+=1,a=!0;else if(ve(n.query,r,p)){const y=o.start+f,E={kind:"event",envelope:p.envelope,source:d,streamKey:r.streamKey,generation:r.generation,byteOffset:y,effectiveTsMs:p.effectiveTsMs,truncated:!1};l.push(E),Y(n,{item:E,orderSlot:r.streamKey,generationRank:Ee[r.generation],byteOffset:y})}f=u+1}if(f<o.bytes.length&&Ie(o.bytes.subarray(f))&&(n.invalidCanonicalLines+=1,a=!0),c||a)for(const u of l)u.truncated=!0}s(Ce,"scanCanonicalSource");function ke(n,e){const t=/^(\S+)/u.exec(n)?.[1];if(t!==void 0&&ue.test(t)){const r=Date.parse(t);if(Number.isFinite(r))return r}return e}s(ke,"rawTimestamp");function Re(n,e,t,r){return n.nodeId!==void 0&&e==="daemon"&&!t.includes(n.nodeId)||n.sinceMs!==void 0&&r<n.sinceMs||n.untilMs!==void 0&&r>n.untilMs?!1:n.pattern===void 0||t.toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US"))}s(Re,"matchesRaw");function U(n,e,t,r,i){const o=j(e,t,r,se);if(o===void 0)return;const d=N(e,r),f=o.start>0;let c=0;if(f){const a=o.bytes.indexOf(10);if(a<0)return;c=a+1}for(;;){const a=o.bytes.indexOf(10,c);if(a<0&&c===o.bytes.length)return;const l=a<0?o.bytes.length:a,u=o.bytes.subarray(c,l).toString("utf8"),p=ke(u,o.mtimeMs);if(Re(n.query,i,u,p)){const y=o.start+c;Y(n,{item:{kind:"raw",component:i,source:d,content:u,effectiveTsMs:p,truncated:f},orderSlot:d,generationRank:2,byteOffset:y})}if(a<0)return;c=a+1}}s(U,"scanRawSource");function P(n,e,t,r){for(const i of[2,1,0])Ce(n,e,t,{...r,generation:i})}s(P,"scanCanonicalGenerations");function V(n,e,t,r,i,o){const d=S(t[t.length-1].absolute,r),f=M(e,d,t,!0);if(f===void 0)return;const c=S(d,"job"),a=M(e,c,[...t,f],!0);if(a===void 0)return;const l=[...t,f,a];i&&P(n,e,l,{component:"broker",streamKey:`broker:${r}`,activeAbsolute:S(c,"log.jsonl"),boundNodeId:r}),o&&U(n,e,l,S(c,"broker.log"),"broker")}s(V,"scanBrokerNode");function Ue(n,e){const t=we(n);if(typeof e!="boolean")throw new TypeError("includeRaw must be boolean");const r={query:t,candidates:[],invalidCanonicalLines:0},i=I(ee()),o=M(i,i,[],!0);if(o===void 0)return{items:[],invalidCanonicalLines:0};const d=[o],f=e&&t.operationId===void 0&&t.minimumLevel===void 0;T(t,"daemon")&&(P(r,i,d,{component:"daemon",streamKey:"daemon",activeAbsolute:S(i,"crtrd.log")}),f&&U(r,i,d,S(i,"crtrd.err"),"daemon"));const c=T(t,"broker"),a=f&&T(t,"broker");if(c||a){const l=I(ne()),u=M(i,l,d,!0);if(u!==void 0){const p=[o,u];t.nodeId!==void 0?V(r,i,p,t.nodeId,c,a):ge(u,(y,E)=>{E&&A(y)&&V(r,i,p,y,c,a)})}}return{items:r.candidates.map(l=>l.item),invalidCanonicalLines:r.invalidCanonicalLines}}s(Ue,"readLogSnapshot");function Pe(n){const e={v:n.v,ts:n.ts,level:n.level,event:n.event,component:n.component,operation_id:n.operation_id};return n.node_id!==void 0&&(e.node_id=n.node_id),n.stream_id!==void 0&&(e.stream_id=n.stream_id),n.duration_ms!==void 0&&(e.duration_ms=n.duration_ms),n.outcome!==void 0&&(e.outcome=n.outcome),n.attempt!==void 0&&(e.attempt=n.attempt),n.error_class!==void 0&&(e.error_class=n.error_class),e}s(Pe,"projectTier1Event");export{Pe as projectTier1Event,Ue as readLogSnapshot};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{jobRow as
|
|
2
|
-
`);return{data:o,text:
|
|
1
|
+
import{jobRow as m}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as l}from"./kind.js";import{recordLiveRead as u}from"./live-read.js";import{attr as a,header as w}from"./render.js";const v={name:"job",addressable:"id",events:l({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(r,n,t,i){const e=await m(r,t);if(!e)throw new Error(`job object ${t.id} has no jobs row`);await u(r,n,t,i.reposFn);const s=await c(r,t),o={command:e.command,status:e.status,exit_code:e.exit_code,output_key:e.output_key},d=[w("object",s,a("status",o.status)+a("exit_code",o.exit_code)),`Command: ${o.command}`,`Output: \`crtr node bash log ${t.id}${t.owner===null?"":` --node ${t.owner}`}\``].join(`
|
|
2
|
+
`);return{data:o,text:d}}};export{v as jobKind};
|
package/dist/core/log.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const MAX_ROTATING_RECORD_BYTES = 65536;
|
|
2
|
+
/** The daemon log is the one place a day-old incident is diagnosed from; broker and job logs keep the smaller bound. */
|
|
3
|
+
export declare const MAX_DAEMON_ACTIVE_BYTES: number;
|
|
2
4
|
/** Synchronously appends one complete NDJSON record, retaining active + .1 + .2. */
|
|
3
5
|
export declare function appendRotatingRecord(path: string, recordWithoutLf: string): boolean;
|
|
4
6
|
/** One timed/bounded daemon batch instead of a mkdir/stat/open/write per event.
|
package/dist/core/log.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var S=Object.defineProperty;var f=(e,n)=>S(e,"name",{value:n,configurable:!0});import{appendFileSync as
|
|
2
|
-
`,c=Buffer.byteLength(
|
|
3
|
-
`)||n.includes("\r")||Buffer.byteLength(n,"utf8")+1>
|
|
4
|
-
`)||n.includes("\r"))return!1;const
|
|
5
|
-
`)}f(
|
|
1
|
+
var S=Object.defineProperty;var f=(e,n)=>S(e,"name",{value:n,configurable:!0});import{appendFileSync as d,existsSync as l,mkdirSync as B,renameSync as m,rmSync as T,statSync as E}from"node:fs";import{dirname as g}from"node:path";const y=65536,A=5*1024*1024,R=16*1024*1024,b=128*1024,x=200;function a(e,n,i=A){let t="";try{B(g(e),{recursive:!0});let s=l(e)?E(e).size:0;for(const p of n){const o=`${p}
|
|
2
|
+
`,c=Buffer.byteLength(o,"utf8");s+c>i&&s>0&&(t!==""&&(d(e,t,"utf8"),t=""),T(`${e}.2`,{force:!0}),l(`${e}.1`)&&m(`${e}.1`,`${e}.2`),m(e,`${e}.1`),s=0),t+=o,s+=c}return t!==""&&d(e,t,"utf8"),!0}catch{return!1}}f(a,"appendRotatingRecords");function M(e,n){return n.includes(`
|
|
3
|
+
`)||n.includes("\r")||Buffer.byteLength(n,"utf8")+1>y?!1:a(e,[n])}f(M,"appendRotatingRecord");let r,_=!1;function N(e,n){if(n.includes(`
|
|
4
|
+
`)||n.includes("\r"))return!1;const i=Buffer.byteLength(n,"utf8")+1;return i>y?!1:(r!==void 0&&r.path!==e&&u(),r===void 0&&(r={path:e,records:[],bytes:0},_||(process.on("exit",u),_=!0)),r.path=e,r.records.push(n),r.bytes+=i,r.bytes>=b?u():r.timer===void 0&&(r.timer=setTimeout(u,x),r.timer.unref()),!0)}f(N,"appendBufferedDaemonRecord");function u(){const e=r;if(!(e===void 0||e.records.length===0)&&(e.timer!==void 0&&clearTimeout(e.timer),r=void 0,!a(e.path,e.records,R)))for(const n of e.records)process.stderr.write(`[crtrd] event log append failed: ${n}
|
|
5
|
+
`)}f(u,"flushDaemonLog");export{R as MAX_DAEMON_ACTIVE_BYTES,y as MAX_ROTATING_RECORD_BYTES,N as appendBufferedDaemonRecord,M as appendRotatingRecord,u as flushDaemonLog};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
2
|
-
`),
|
|
1
|
+
var le=Object.defineProperty;var o=(e,n)=>le(e,"name",{value:n,configurable:!0});import{existsSync as b,readdirSync as z,readFileSync as _,rmSync as g,statSync as D}from"node:fs";import{join as d}from"node:path";import{validateCommandManifest as pe}from"../../api/command-manifest/manifest.js";import{crtrHome as L}from"../canvas/paths.js";import{isCloudRole as h}from"../canvas/person-state.js";import{withCanvasWrite as me}from"../canvas/db.js";import{bundleVersion as ve,isInstalledGeneration as ge,recordCheckedGeneration as he}from"../command-plugins/bundle.js";import{readState as we,updateConfig as $e,updateState as ye}from"../config.js";import{directoryIssuer as q}from"../directory/issuer.js";import{emitEvent as p}from"../events/emit.js";import{describeOutboundFailure as be,outboundFetch as _e,outboundRefusal as Z,OutboundError as Pe}from"../events/outbound.js";import{withExclusiveDirectoryLockAsync as V}from"../exclusive-lock.js";import{atomicWriteText as k,ensureDir as N,pathExists as J}from"../fs-utils.js";import{reconcilePackages as Se}from"../graph/package-reconcile.js";import{readPluginManifest as T}from"../manifest.js";import{bundleSwapLockPath as K}from"../plugin-swap-lock.js";import{pluginsDir as Q,userScopeRoot as X}from"../scope.js";import{installedProviderPlugins as Ee}from"./roots.js";import{allGrants as Me,localGrants as ke,providerIdsFromGrants as Y}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const Te="[A-Za-z0-9_-]{1,64}",ee=new RegExp(`^${Te}$`),Ie=3e4,ne=3e4;function $(e){return d(Q("user"),e)}o($,"pluginRoot");function P(e){return d(Q("user"),`.${e}.etag`)}o(P,"etagFile");function Ae(e){if(!h())return we("user").plugins[e]?.etag;try{return _(P(e),"utf8")}catch(n){if(n.code==="ENOENT")return;throw n}}o(Ae,"storedEtag");async function re(e,n){if(!h())return he("user",e,n,{swapped:!1});n===void 0?g(P(e),{force:!0}):k(P(e),n)}o(re,"recordEtag");async function te(e){if(!h())try{await me(async n=>await Se(n))}catch(n){p({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:n})}}o(te,"reconcileProviderDocs");function xe(e,n,r){return h()?T(n)?.version===r&&J(d(n,"commands.json")):ge(e,"user",n,r)}o(xe,"installedGeneration");function R(){return d(L(),"providers")}o(R,"providersRoot");function oe(e,n){return d(R(),e,`v${n}`)}o(oe,"majorDir");function B(e){if(!ee.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}o(B,"assertId");function I(){const e=q();if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e}o(I,"directoryBase");async function ae(e,n){const r=`${I()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await _e("bundle_fetch",r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(Ie)})}catch(i){return{status:"failed",message:`could not reach ${r}: ${i instanceof Error?i.message:String(i)}`,cause:i}}if(t.status===304){if(n.ifNoneMatch===void 0){const i=`HTTP 304 from ${r} without a conditional request`;return{status:"failed",message:i,cause:Z("bundle_fetch",r,i)}}return{status:304}}if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300){await t.body?.cancel().catch(()=>{});{const i=`HTTP ${t.status} from ${r}`;return{status:"failed",message:i,cause:Z("bundle_fetch",r,i)}}}let a;try{a=new Uint8Array(await t.arrayBuffer())}catch(i){return{status:"failed",message:`reading ${r} failed: ${i instanceof Error?i.message:String(i)}`,cause:new Pe("bundle_fetch",r,i)}}const u=t.headers.get("etag");return{status:200,raw:a,...u!==null?{etag:u}:{}}}o(ae,"fetchProviderBundle");const A=new WeakSet;class x extends Error{static{o(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function ie(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(a=>r[a]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}o(ie,"wrongBundle");function O(e,n,r){p({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof x?{issues:r.issues}:{}},error:r})}o(O,"logInvalid");function j(e,n,r,t,a){p({level:"info",event:"daemon.providers.bundle_checked",fields:{provider:e,...n!==void 0?{major:n}:{},status:r,...t!==void 0?{etag:t}:{},outcome:a}})}o(j,"logChecked");function se(e,n,r){p({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r instanceof Error?r.message:String(r),...be(r instanceof Error?r.cause:void 0)}})}o(se,"logFailure");async function ue(){return import("../../commands/pkg/plugin-manage.js")}o(ue,"pluginManage");const S=new Map,E=o(e=>`${L()}\0${e}`,"installKey");function je(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}o(je,"publicInstallCause");function Ce(e){if(m(e)===void 0)return S.get(E(e))??{status:"installing"}}o(Ce,"providerInstallState");function De(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${Ce(e)?.error??"bundle installation pending"}`)}o(De,"unavailableProviderError");function m(e){const n=T($(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&J(d($(e),"commands.json"))){try{if(D(d($(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}o(m,"installedManifest");async function H(e,n,r=!1){const t=X();m(e)===void 0&&S.set(E(e),{status:"installing"});try{return N(d(t,"tmp")),await V(K(t,e),async()=>{const a=$(e),u=T(a);if(u!==null&&u.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const i=m(e)!==void 0;if(i&&r)return"unchanged";const f=i&&n?Ae(e):void 0,s=await ae(e,f!==void 0?{ifNoneMatch:f}:{});if(s.status===304)return j(e,m(e)?.provider.major,304,f,"unchanged"),"unchanged";if(s.status===404||s.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${s.message})`,{param:"provider"});if(s.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${s.message}`,{cause:s.cause});const l=ve(s.raw);if(i&&xe(e,a,l))return await re(e,s.etag),j(e,m(e)?.provider.major,200,s.etag,"unchanged"),"unchanged";const{installPluginArchive:w}=await ue();h()&&g(P(e),{force:!0});try{await w(e,s.raw,{scope:"user",scopeRootPath:t,root:a,version:l,etag:s.etag,enable:!i,bundle:{endpoint:`${I()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:I()},accept:o(v=>ie(e,void 0,v),"accept"),record:!h()})}catch(v){O(e,void 0,v);const W=new c("not_found",`provider "${e}" served an invalid bundle: ${v instanceof Error?v.message:String(v)}`,{param:"provider"});throw A.add(W),W}return h()&&await re(e,s.etag),await te(e),p({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,major:m(e)?.provider.major,version:l,replaced:i,...s.etag!==void 0?{etag:s.etag}:{}}}),S.delete(E(e)),i?"updated":"installed"},{timeoutMs:ne,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(a){throw m(e)===void 0&&S.set(E(e),{status:"install_failed",error:je(a)}),a instanceof c?a:(p({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:a}),De(e))}}o(H,"refreshInstalled");async function ln(e){B(e),m(e)===void 0&&await H(e,!1,!0)}o(ln,"ensureProviderBundle");async function pn(e,n){return B(e),n.version!==void 0&&m(e)?.provider.major!==n.version?F(e,n.version,n.conditional):H(e,n.conditional)}o(pn,"refreshProviderBundle");async function de(e,n,r){const{validatePluginArchive:t}=await ue(),a=await t(r);if(a.bundle===void 0)throw new x(`provider "${e}" major ${n} bundle is invalid: ${a.issues.map(i=>i.message).join("; ")}`,a.issues);const u=ie(e,n,a.bundle);if(u!==void 0)throw new x(`provider "${e}" major ${n} bundle is invalid: ${u}`,[]);if(a.bundle.transport===void 0)throw new x(`provider "${e}" major ${n} bundle declares no transport`,[]);return a.bundle}o(de,"validateArchive");function Ne(e){N(e),k(d(e,"retired"),`${new Date().toISOString()}
|
|
2
|
+
`),g(d(e,"bundle.tar"),{force:!0}),g(d(e,"etag"),{force:!0})}o(Ne,"retire");function ce(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}o(ce,"retiredError");async function F(e,n,r){const t=oe(e,n),a=d(t,"bundle.tar"),u=b(a),i=u&&r&&b(d(t,"etag"))?_(d(t,"etag"),"utf8"):void 0,f=await ae(e,{version:n,...i!==void 0?{ifNoneMatch:i}:{}});if(f.status===304)return j(e,n,304,i,"unchanged"),"unchanged";if(f.status===410)throw Ne(t),p({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),ce(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`,{cause:f.cause});try{await de(e,n,f.raw)}catch(w){O(e,n,w);const v=new c(u?"provider_unavailable":"not_found",w instanceof Error?w.message:String(w),{param:"version"});throw A.add(v),v}N(t),g(d(t,"retired"),{force:!0});const s=u&&Buffer.from(f.raw).equals(_(a));s||k(a,f.raw),f.etag!==void 0?k(d(t,"etag"),f.etag):g(d(t,"etag"),{force:!0});const l=s?"unchanged":u?"updated":"installed";return j(e,n,200,f.etag,l),l}o(F,"refreshPinned");function Re(){const e=[],n=R();if(!b(n))return e;for(const r of z(n)){if(!ee.test(r))continue;let t;try{t=z(d(n,r))}catch{continue}for(const a of t){const u=/^v([1-9][0-9]*)$/.exec(a);u&&b(d(n,r,a,"bundle.tar"))&&e.push([r,Number(u[1])])}}return e}o(Re,"cachedMajors");function Be(e){const n=pe(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}o(Be,"readCommands");const M=new Map,G=new Map;function Oe(e,n){const r=D(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,a=G.get(e);if(a?.stamp===t)return a.commands;const u=Be(JSON.parse(_(n,"utf8")));return G.set(e,{stamp:t,commands:u}),u}o(Oe,"installedCommands");async function mn(e,n){B(e);const r=m(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=$(e),w=Oe(e,d(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:w,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=oe(e,n);if(b(d(t,"retired")))throw ce(e,n);const a=d(t,"bundle.tar");b(a)||await F(e,n,!1);const u=`${e}/${n}`,i=D(a),f=`${i.mtimeMs}:${i.size}`;let s=M.get(u)?.stamp===f?M.get(u).bundle:void 0;if(s===void 0){try{s=await de(e,n,new Uint8Array(_(a)))}catch(l){throw O(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}M.set(u,{stamp:f,bundle:s})}return{id:e,major:n,provider:s.provider,commands:s.commands,transport:s.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:s.transport.endpoint},source:"pinned"}}o(mn,"resolveProviderBundle");let y=null;const He=4*6e4;function C(e,n){e.stage=n,e.stageAt=Date.now()}o(C,"enter");function Fe(e,n){return e?.provider?.id===n&&e.bundle?.endpoint===`${I()}/providers/${encodeURIComponent(n)}/plugin`}o(Fe,"isDirectoryBundle");async function U(){return h()?Me():ke()}o(U,"bundleGrants");async function fe(e){return Y(await U()).includes(e)}o(fe,"grantNames");async function Ge(e){await V(K(X(),e),async()=>{if(!(await fe(e)||!Fe(T($(e)),e))){g($(e),{recursive:!0,force:!0}),h()?g(P(e),{force:!0}):(await $e("user",n=>{delete n.plugins[e]}),await ye("user",n=>{delete n.plugins[e]})),g(d(R(),e),{recursive:!0,force:!0}),G.delete(e),S.delete(E(e));for(const n of[...M.keys()])n.startsWith(`${e}/`)&&M.delete(n);await te(e),p({level:"info",event:"daemon.providers.bundle_uninstalled",fields:{provider:e}})}},{timeoutMs:ne,timeoutError:o(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}o(Ge,"uninstallProvider");async function Ue(){if(!(await U()).every(e=>e.status!=="active")){for(const e of Ee())if(!(e.manifest.provider===void 0||await fe(e.name)))try{await Ge(e.name)}catch(n){p({level:"error",event:"daemon.providers.bundle_uninstall_failed",fields:{provider:e.name},error:n})}}}o(Ue,"uninstallDisconnected");async function We(e){if(!q())return;C(e,"reading grants");const n=Y(await U());for(const r of n){C(e,`refreshing installed bundle ${r}`);try{await H(r,!0)}catch(t){t instanceof Object&&A.has(t)||se(r,void 0,t)}}C(e,"uninstalling disconnected bundles"),await Ue();for(const[r,t]of Re()){C(e,`refreshing pinned bundle ${r} v${t}`);try{await F(r,t,!0)}catch(a){if(a instanceof c&&a.code==="provider_version_retired"||a instanceof Object&&A.has(a))continue;se(r,t,a)}}}o(We,"syncOnce");function vn(){if(y!==null){y.rerun=!0;const n=Date.now()-y.stageAt;return n>=He&&p({level:"error",event:"daemon.providers.sync_stalled",fields:{stage:y.stage,stalled_ms:n}}),y.promise}const e={promise:Promise.resolve(),stage:"starting",stageAt:Date.now(),rerun:!1};return y=e,e.promise=(async()=>{try{do e.rerun=!1,await We(e);while(e.rerun)}finally{y=null}})(),e.promise}o(vn,"syncProviderBundles");export{ln as ensureProviderBundle,Ce as providerInstallState,pn as refreshProviderBundle,mn as resolveProviderBundle,vn as syncProviderBundles,De as unavailableProviderError};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var de=Object.defineProperty;var m=(e,t)=>de(e,"name",{value:t,configurable:!0});import{validateDeclaredResult as x}from"../../api/command-manifest/result.js";import{callerAllows as ue,requireGroup as le}from"../../daemon/api/handlers/providers.js";import{callerTerminalGrant as pe}from"../grants/terminal.js";import{findRefusal as ce,payerOf as fe,refusalError as me,refusedMark as ve,writeCharge as ge}from"../ledger/index.js";import{listInstalledPlugins as he}from"../resolver.js";import{ProviderError as l,scopeMissing as we}from"./errors.js";import{resolveProviderRef as ye}from"./roots.js";import{providerToolPath as be}from"../plugin-objects-manifest.js";import{ensureProviderBundle as _e,refreshProviderBundle as X,resolveProviderBundle as O,unavailableProviderError as Ae}from"./bundles.js";import{prepareFileCall as je,resignForRetry as Y,resolveFileResult as Z}from"./files.js";import{consumeApproval as ke,requireProviderApproval as $e}from"./approval.js";import{outboundToken as Pe}from"./mint.js";import{discardFile as Ee}from"./token-cache.js";import{registerProviderCall as qe}from"./inflight.js";import{buildRequestPlan as Ce}from"../command-plugins/transport/http-plan.js";import{newTraceId as Re}from"../../shared/traceparent.js";import{sendProviderRequest as Te,ProviderTransportError as Ie}from"./request.js";import{acceptProviderReceipt as Me}from"./receipts.js";import{writeProviderAudit as Se}from"./audit.js";import{selectDevice as Be,sendDeviceCall as Ne,deviceFailure as Oe,deviceUnreachable as ee,DEFAULT_DEVICE_REQUEST_MS as Fe}from"./device.js";import{isLibsSearch as re,wantsBundling as De,callLibsSearch as Ue,bundleExec as Le,mapLibOutcome as Je,reportLibCalls as He}from"./device-libs.js";import{ownMachineId as xe}from"../peer/directory.js";import{directoryIssuer as Ge}from"../directory/issuer.js";import{isCloudRole as ze}from"../canvas/person-state.js";import{formatCrtrCaller as te}from"../command-plugins/caller-header.js";import{getNode as Qe}from"../canvas/canvas.js";import{envelopeForProviderCaller as Ve,EnvelopeError as We}from"../peer/envelope.js";const Ke="app:terminal";function oe(e){const t=[],n=m((c,r,i)=>{const u=[...r,c.name],a=i||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)n(v,u,a);return}const j=be(e.roots.length,u);t.push({tool:j,leaf:c,hidden:a})},"walk");for(const c of e.roots)n(c,[],!1);return t}m(oe,"providerLeaves");function ie(e){if(e.kind!=="branch")return null;const t=[];for(const h of e.children)if(h.kind==="branch"){const b=ie(h);b&&t.push(b)}else if("group"in h&&h.group){const{kind:b,name:p,description:_,whenToUse:w,tier:y,summary:k,params:$,output:o,effects:d,group:A}=h;t.push({kind:b,name:p,description:_,whenToUse:w,...y===void 0?{}:{tier:y},summary:k,params:$,output:o,effects:d,group:A})}if(t.length===0)return null;const{kind:n,name:c,description:r,whenToUse:i,tier:u,summary:a,model:j,rootEntry:v}=e;return{kind:n,name:c,description:r,whenToUse:i,...u===void 0?{}:{tier:u},summary:a,...j===void 0?{}:{model:j},...v===void 0?{}:{rootEntry:v},children:t}}m(ie,"providerHelpBranch");async function Ir(e,t){const n=await O(t),c=oe(n.commands),r=he("user").find(i=>i.name===t)?.manifest.description??t;return{provider:t,status:"available",major:n.major,display_name:r,transport:n.transport.kind,groups:n.provider.groups.filter(i=>ue(e,`${t}:${i.group}`)).map(i=>({group:i.group,leaves:c.filter(({leaf:u,hidden:a})=>!a&&u.group===i.group).map(({tool:u,leaf:a})=>({tool:u,description:a.description,summary:a.summary,params:a.params,output:a.output}))})),commands:n.commands.roots.flatMap(i=>{const u=ie(i);return u?[u]:[]})}}m(Ir,"providerTools");async function Xe(e){return await Me(e.headers["crtr-receipt"],e)}m(Xe,"onProviderResponse");async function Ye(e){if(e.class==="person"){const t=(await pe())?.sub;if(!t)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:t,grantee:t}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}m(Ye,"identity");function Ze(e,t){return[`${e.id}:${t.group}`]}m(Ze,"callScope");function er(e,t){const n=oe(e.commands).find(c=>c.tool===t);if(!n)throw new l("not_found",`tool "${t}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new l("not_found",`tool "${t}" has no group`,{param:"tool"});return n.leaf}m(er,"leafFor");function G(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const t=e.error;return!t||typeof t!="object"||Array.isArray(t)?{}:t}m(G,"errorBody");function rr(e){const t=Array.isArray(e)?e[0]:e;if(!t)return;const n=Number(t);if(Number.isFinite(n)&&n>=0)return n;const c=Date.parse(t);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}m(rr,"retryAfter");function F(e,t,n,c=!1){const r=G(t),i=typeof r.code=="string"?r.code:void 0,u=typeof r.message=="string"?r.message:`provider answered HTTP ${e}`,a=typeof r.field=="string"?r.field:void 0,v={origin:"provider",details:{code:i??null,message:u,...a?{field:a}:{},...typeof r.next=="string"?{next:r.next}:{},...r.received!==void 0?{received:r.received}:{},status:e},...a?{param:a}:{}};return e===429?new l("provider_rate_limited",u,{...v,retry_after_s:rr(n["retry-after"])}):e===401||e>=500?new l("provider_unavailable",u,v):e===403&&i==="provider_refused"?new l("provider_refused",u,v):e===403&&i==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",u,{...v,status:e})}m(F,"mapProviderResponse");function ne(e,t){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:t.rest,params:t.params,output:t.output})}m(ne,"approvalShape");async function tr(e){const t=e.origin!==void 0?te({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):te({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await Qe(e.node_id))?.profile_id??void 0:void 0,machine:Ge()!==null?xe():void 0});return t===""?{}:{"Crtr-Caller":t}}m(tr,"crtrCallerHeader");async function Mr(e,t,n){const c=ye(t.provider);if(c===null)throw new l("not_found",`provider "${t.provider}" is not available`,{param:"provider"});const r={...t,provider:c},i=new AbortController,u=m(()=>i.abort(n.signal.reason),"abort");n.signal.aborted?u():n.signal.addEventListener("abort",u,{once:!0});let a=n.requestId;const j=n.traceId??Re(),v=qe({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:i});let h,b,p,_,w,y,k,$="allowed";try{if(ze()&&e.class==="person"&&e.resolved!==null&&!e.resolved.some(g=>g.startsWith(`${r.provider}:`)))throw we(`${r.provider}:*`);try{await _e(r.provider)}catch{throw Ae(r.provider)}let o=await O(r.provider,r.version),d=!1,A=!1,P,D,q,z=!1,Q=!1,U,V=0;for(;;){const g=er(o,r.tool);if(p=`${o.id}:${g.group}`,le(e,o.id,g.group),o.transport.kind!=="machine"&&r.machine!==void 0)throw new l("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&U===void 0&&(U=await Be(r.machine,void 0,{newest:re(o.major,r.tool)}),V=Date.now()+(o.commands.timeouts?.requestMs??Fe));let L=null;if(r.approval&&!z){const s=await ke(e,r,ne(o,g));q=s.option,L=s.prepared,k=r.approval,a=s.requestId,z=!0}const M={params:g.params.filter(s=>"type"in s&&s.type==="file").map(s=>s.name),output:g.output.find(s=>s.type==="file")?.name??null},W=JSON.stringify(M);if(D!==void 0&&D!==W)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});D=W,L&&(P=await Y(e,M,r.arguments,L,{signal:i.signal})),P??=await je(e,M,r.arguments,{signal:i.signal});const{sub:R,grantee:S}=await Ye(e);if(o.transport.kind==="machine"){const s=await or(e,o,r,g,P,{sub:R,requestId:a,deadlineAt:V,signal:i.signal,machine:U});return y=s.responder,h=s.cleanup,{status:200,body:s.result}}const K=o.transport.endpoint;let C;try{C=await Pe({audience:K,sub:R,grantee:S,initiating_app:S,scope:Ze(o,g)},{signal:i.signal})}catch(s){if(!(s instanceof l)||s.details?.directory_error!=="invalid_target"||A)throw s;A=!0,await X(o.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}});const E=await O(o.id,r.version);if(E.transport.kind==="machine"||E.transport.endpoint===K)throw s;o=E;continue}_=C.jti;const J=e.class==="person"?Ke:S,T=fe({call:"tool",tokenPayer:C.payer,sub:R}),H=await ce({scope:"call",payer:T.payer,funding:T.funding,sub:R,grantee:J,provider:`app:${o.id}`});if(H!==null)throw await ge({request:a,kind:"tool",what:`${o.id}:${r.tool}`,usd:0,payer:T.payer,funding:T.funding,sub:R,grantee:J,node:e.node_id,run:e.run_id,provider:`app:${o.id}`,causedBy:n.causedBy??null,refused:ve(H)}),me(H);const se={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:g.params,rest:g.rest,output:g.output,commandPath:[o.id,...r.tool.split(" ")]},ae=Ce(se,P.arguments,void 0,{authorization:`Bearer ${C.access_token}`,requestId:a,traceId:j,fileMode:"pass-through",extraHeaders:{...n.causedBy?{"Crtr-Caused-By":n.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await tr(e),...q?{"Crtr-Approval":q}:{},...P.headers}});b??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Te(ae,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:b,signal:i.signal}),i.signal.aborted)throw i.signal.reason??new Error("provider call aborted")}catch(s){throw i.signal.aborted?i.signal.reason??s:s instanceof Ie?new l("provider_unavailable",s.message,{origin:"provider",details:{status:null,code:s.kind}}):s}if(w??=await Xe({provider:o.id,tool:r.tool,request_id:a,run_id:e.run_id,sub:R,grantee:S,payer:C.payer,ledger_grantee:J,funding:T.funding,node_id:e.node_id,caused_by:n.causedBy??null,jti:C.jti,status:f.status,headers:f.headers}),y=f.headers["crtr-responder"],f.status===401&&Ee(C.key),f.status>=200&&f.status<300){const s=f.body;if(!s||typeof s!="object"||Array.isArray(s))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let E;try{E=await Z(e,P,s,{signal:i.signal})}catch(I){throw I instanceof l&&I.code==="provider_error"?new l("provider_bad_response",I.message,{origin:"provider",details:{status:f.status,message:I.message}}):I}h=E.cleanup;const N=x(g.output,E.result);if(N)throw new l("provider_bad_response",`provider result has an invalid output field "${N.field}"`,{origin:"provider",param:N.field,details:{status:f.status,...N}});if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");return{status:200,body:E.result}}const B=G(f.body);if(f.status===403&&B.code==="approval_required"&&!q&&!Q){Q=!0;const s=await $e(e,r,a,B.question,i.signal,ne(o,g),P);if(k=s.questionId,q=s.option,P=await Y(e,M,r.arguments,P,{signal:i.signal}),b=void 0,i.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&B.code==="approval_required"&&q)throw F(f.status,f.body,f.headers,!0);if(f.status!==401&&B.manifest_stale===!0&&!d){d=!0,await X(o.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),o=await O(o.id,r.version);continue}throw F(f.status,f.body,f.headers,!!q)}}catch(o){throw o instanceof l&&o.question_id&&(k=o.question_id),y===void 0&&o instanceof l&&typeof o.responder=="string"&&(y=o.responder),$=`refused:${o instanceof l?o.code:"server_error"}`,await h?.(),o}finally{try{await Se({request_id:a,caller:e,provider:r.provider,tool:r.tool,scope_checked:p,outcome:$,jti:_,receipt_id:w,responder:y,question_id:k})}finally{v()}n.signal.removeEventListener("abort",u)}}m(Mr,"callProvider");async function or(e,t,n,c,r,i){const{machine:u}=i;let a;try{a=await Ve(e,i.sub)}catch(d){throw d instanceof We?new l("invalid_request",d.message,{origin:"runtime"}):d}const j={machine:u,envelope:a,signal:i.signal,deadlineAt:i.deadlineAt,mapFailure:F};if(re(t.major,n.tool)){const d=await Ue(j,r.arguments,t.major),A=x(c.output,d);if(A)throw new l("provider_bad_response",`the libraries result has an invalid output field "${A.field}"`,{origin:"provider",param:A.field,details:{status:200,...A}});return{result:d,responder:void 0,cleanup:void 0}}let v=r.arguments,h=null,b="";if(De(t.major,n.tool,r.arguments)){const d=await Le(j,r.arguments,t.major);if(i.deadlineAt-Date.now()<=0)throw ee(u,"timeout","");d!==null&&(v=d.arguments,h=d.services,b=d.release)}const p=await Ne(u,{tool:n.tool,arguments:v,version:t.major,requestId:i.requestId,runId:e.run_id,deadlineAt:i.deadlineAt,upload:r.upload===null?null:{id:r.upload.upload_id,link:r.upload.upload_link}},a,i.signal);if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");if("unreachable"in p)throw ee(u,p.unreachable.reason,p.unreachable.message);const _=p.headers["crtr-responder"],w=G(p.body);if(p.status<200||p.status>=300){const d=Oe(u,p.status,typeof w.code=="string"?w.code:void 0,typeof w.message=="string"?w.message:void 0,p.body),A=d===null&&p.status>=500&&typeof w.code=="string"?new l("provider_error",typeof w.message=="string"?w.message:`the device answered HTTP ${p.status}`,{origin:"runtime",status:p.status,details:{code:w.code,status:p.status}}):null;throw Object.assign(d??A??F(p.status,p.body,p.headers),{responder:_})}const y=p.body&&typeof p.body=="object"&&!Array.isArray(p.body)?p.body.result:void 0;if(!y||typeof y!="object"||Array.isArray(y))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:p.status,message:"the device result is not a JSON object"}}),{responder:_});let k=y;if(h!==null){const d=Je(k,h);if(He(u,a,b,d.calls),"error"in d)throw Object.assign(d.error,{responder:_});k=d.result}let $;try{$=await Z(e,r,k,{signal:i.signal})}catch(d){throw d instanceof l&&d.code==="provider_error"?Object.assign(new l("provider_bad_response",d.message,{origin:"provider",details:{status:p.status,message:d.message}}),{responder:_}):d}const o=x(c.output,$.result);if(o)throw await $.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${o.field}"`,{origin:"provider",param:o.field,details:{status:p.status,...o}}),{responder:_});return{result:$.result,responder:_,cleanup:$.cleanup}}m(or,"callDevice");export{Mr as callProvider,tr as crtrCallerHeader,Ye as identity,F as mapProviderResponse,Xe as onProviderResponse,oe as providerLeaves,Ir as providerTools};
|
|
1
|
+
var ue=Object.defineProperty;var m=(e,o)=>ue(e,"name",{value:o,configurable:!0});import{validateDeclaredResult as G}from"../../api/command-manifest/result.js";import{callerAllows as le,requireGroup as pe}from"../../daemon/api/handlers/providers.js";import{callerTerminalGrant as ce}from"../grants/terminal.js";import{findRefusal as fe,payerOf as me,refusalError as ve,refusedMark as ge,writeCharge as he}from"../ledger/index.js";import{listInstalledPlugins as we}from"../resolver.js";import{emitEvent as ye}from"../events/emit.js";import{ProviderError as l,scopeMissing as be}from"./errors.js";import{resolveProviderRef as _e}from"./roots.js";import{providerToolPath as je}from"../plugin-objects-manifest.js";import{ensureProviderBundle as Ae,refreshProviderBundle as Y,resolveProviderBundle as O,unavailableProviderError as ke}from"./bundles.js";import{prepareFileCall as $e,resignForRetry as Z,resolveFileResult as ee}from"./files.js";import{consumeApproval as Pe,requireProviderApproval as Ee}from"./approval.js";import{outboundToken as qe}from"./mint.js";import{discardFile as Ce}from"./token-cache.js";import{registerProviderCall as Re}from"./inflight.js";import{buildRequestPlan as Me}from"../command-plugins/transport/http-plan.js";import{newTraceId as Te}from"../../shared/traceparent.js";import{sendProviderRequest as Ie,ProviderTransportError as Be}from"./request.js";import{acceptProviderReceipt as Se}from"./receipts.js";import{writeProviderAudit as Ne}from"./audit.js";import{selectDevice as Oe,sendDeviceCall as Fe,deviceFailure as De,deviceUnreachable as re,DEFAULT_DEVICE_REQUEST_MS as Ue}from"./device.js";import{isLibsSearch as te,wantsBundling as Le,callLibsSearch as Je,bundleExec as He,mapLibOutcome as xe,reportLibCalls as Ge}from"./device-libs.js";import{ownMachineId as ze}from"../peer/directory.js";import{directoryIssuer as Qe}from"../directory/issuer.js";import{isCloudRole as Ve}from"../canvas/person-state.js";import{formatCrtrCaller as oe}from"../command-plugins/caller-header.js";import{getNode as We}from"../canvas/canvas.js";import{envelopeForProviderCaller as Ke,EnvelopeError as Xe}from"../peer/envelope.js";const Ye="app:terminal";function ie(e){const o=[],n=m((c,r,i)=>{const u=[...r,c.name],a=i||c.tier==="hidden";if(c.kind==="branch"){for(const v of c.children)n(v,u,a);return}const A=je(e.roots.length,u);o.push({tool:A,leaf:c,hidden:a})},"walk");for(const c of e.roots)n(c,[],!1);return o}m(ie,"providerLeaves");function ne(e){if(e.kind!=="branch")return null;const o=[];for(const h of e.children)if(h.kind==="branch"){const b=ne(h);b&&o.push(b)}else if("group"in h&&h.group){const{kind:b,name:p,description:_,whenToUse:w,tier:y,summary:k,params:$,output:t,effects:d,group:j}=h;o.push({kind:b,name:p,description:_,whenToUse:w,...y===void 0?{}:{tier:y},summary:k,params:$,output:t,effects:d,group:j})}if(o.length===0)return null;const{kind:n,name:c,description:r,whenToUse:i,tier:u,summary:a,model:A,rootEntry:v}=e;return{kind:n,name:c,description:r,whenToUse:i,...u===void 0?{}:{tier:u},summary:a,...A===void 0?{}:{model:A},...v===void 0?{}:{rootEntry:v},children:o}}m(ne,"providerHelpBranch");async function Sr(e,o){const n=await O(o),c=ie(n.commands),r=we("user").find(i=>i.name===o)?.manifest.description??o;return{provider:o,status:"available",major:n.major,display_name:r,transport:n.transport.kind,groups:n.provider.groups.filter(i=>le(e,`${o}:${i.group}`)).map(i=>({group:i.group,leaves:c.filter(({leaf:u,hidden:a})=>!a&&u.group===i.group).map(({tool:u,leaf:a})=>({tool:u,description:a.description,summary:a.summary,params:a.params,output:a.output}))})),commands:n.commands.roots.flatMap(i=>{const u=ne(i);return u?[u]:[]})}}m(Sr,"providerTools");async function Ze(e){return await Se(e.headers["crtr-receipt"],e)}m(Ze,"onProviderResponse");async function er(e){if(e.class==="person"){const o=(await ce())?.sub;if(!o)throw new l("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:o,grantee:o}}if(!e.sub||!e.grantee)throw new l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}m(er,"identity");function rr(e,o){return[`${e.id}:${o.group}`]}m(rr,"callScope");function tr(e,o){const n=ie(e.commands).find(c=>c.tool===o);if(!n)throw new l("not_found",`tool "${o}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new l("not_found",`tool "${o}" has no group`,{param:"tool"});return n.leaf}m(tr,"leafFor");function z(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const o=e.error;return!o||typeof o!="object"||Array.isArray(o)?{}:o}m(z,"errorBody");function or(e){const o=Array.isArray(e)?e[0]:e;if(!o)return;const n=Number(o);if(Number.isFinite(n)&&n>=0)return n;const c=Date.parse(o);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}m(or,"retryAfter");function F(e,o,n,c=!1){const r=z(o),i=typeof r.code=="string"?r.code:void 0,u=typeof r.message=="string"?r.message:`provider answered HTTP ${e}`,a=typeof r.field=="string"?r.field:void 0,v={origin:"provider",details:{code:i??null,message:u,...a?{field:a}:{},...typeof r.next=="string"?{next:r.next}:{},...r.received!==void 0?{received:r.received}:{},status:e},...a?{param:a}:{}};return e===429?new l("provider_rate_limited",u,{...v,retry_after_s:or(n["retry-after"])}):e===401||e>=500?new l("provider_unavailable",u,v):e===403&&i==="provider_refused"?new l("provider_refused",u,v):e===403&&i==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...v,status:e}):new l("provider_error",u,{...v,status:e})}m(F,"mapProviderResponse");function se(e,o){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:o.rest,params:o.params,output:o.output})}m(se,"approvalShape");async function ir(e){const o=e.origin!==void 0?oe({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):oe({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await We(e.node_id))?.profile_id??void 0:void 0,machine:Qe()!==null?ze():void 0});return o===""?{}:{"Crtr-Caller":o}}m(ir,"crtrCallerHeader");async function Nr(e,o,n){const c=_e(o.provider);if(c===null)throw new l("not_found",`provider "${o.provider}" is not available`,{param:"provider"});const r={...o,provider:c},i=new AbortController,u=m(()=>i.abort(n.signal.reason),"abort");n.signal.aborted?u():n.signal.addEventListener("abort",u,{once:!0});let a=n.requestId;const A=n.traceId??Te(),v=Re({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:i});let h,b,p,_,w,y,k,$="allowed";try{if(Ve()&&e.class==="person"&&e.resolved!==null&&!e.resolved.some(g=>g.startsWith(`${r.provider}:`)))throw be(`${r.provider}:*`);try{await Ae(r.provider)}catch{throw ke(r.provider)}let t=await O(r.provider,r.version),d=!1,j=!1,P,D,q,Q=!1,V=!1,U,W=0;for(;;){const g=tr(t,r.tool);if(p=`${t.id}:${g.group}`,pe(e,t.id,g.group),t.transport.kind!=="machine"&&r.machine!==void 0)throw new l("invalid_argument",`provider "${t.id}" is not served by a device, so it takes no machine`,{param:"machine"});t.transport.kind==="machine"&&U===void 0&&(U=await Oe(r.machine,void 0,{newest:te(t.major,r.tool)}),W=Date.now()+(t.commands.timeouts?.requestMs??Ue));let L=null;if(r.approval&&!Q){const s=await Pe(e,r,se(t,g));q=s.option,L=s.prepared,k=r.approval,a=s.requestId,Q=!0}const B={params:g.params.filter(s=>"type"in s&&s.type==="file").map(s=>s.name),output:g.output.find(s=>s.type==="file")?.name??null},K=JSON.stringify(B);if(D!==void 0&&D!==K)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});D=K,L&&(P=await Z(e,B,r.arguments,L,{signal:i.signal})),P??=await $e(e,B,r.arguments,{signal:i.signal});const{sub:M,grantee:S}=await er(e);if(t.transport.kind==="machine"){const s=await nr(e,t,r,g,P,{sub:M,requestId:a,deadlineAt:W,signal:i.signal,machine:U});return y=s.responder,h=s.cleanup,{status:200,body:s.result}}const X=t.transport.endpoint;let C;try{C=await qe({audience:X,sub:M,grantee:S,initiating_app:S,scope:rr(t,g)},{signal:i.signal})}catch(s){if(!(s instanceof l)||s.details?.directory_error!=="invalid_target"||j)throw s;j=!0,await Y(t.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}});const E=await O(t.id,r.version);if(E.transport.kind==="machine"||E.transport.endpoint===X)throw s;t=E;continue}_=C.jti;const J=e.class==="person"?Ye:S,T=me({call:"tool",tokenPayer:C.payer,sub:M}),H=await fe({scope:"call",payer:T.payer,funding:T.funding,sub:M,grantee:J,provider:`app:${t.id}`});if(H!==null)throw await he({request:a,kind:"tool",what:`${t.id}:${r.tool}`,usd:0,payer:T.payer,funding:T.funding,sub:M,grantee:J,node:e.node_id,run:e.run_id,provider:`app:${t.id}`,causedBy:n.causedBy??null,refused:ge(H)}),ve(H);const ae={registration:{name:t.id,endpoint:t.transport.endpoint},...t.commands.baseUrl?{baseUrl:t.commands.baseUrl}:{},...t.commands.timeouts?{timeouts:t.commands.timeouts}:{},params:g.params,rest:g.rest,output:g.output,commandPath:[t.id,...r.tool.split(" ")]},de=Me(ae,P.arguments,void 0,{authorization:`Bearer ${C.access_token}`,requestId:a,traceId:A,fileMode:"pass-through",extraHeaders:{...n.causedBy?{"Crtr-Caused-By":n.causedBy}:{},...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await ir(e),...q?{"Crtr-Approval":q}:{},...P.headers}});b??=Date.now()+(t.commands.timeouts?.requestMs??3e4);let f;try{if(f=await Ie(de,{connectMs:t.commands.timeouts?.connectMs,requestMs:t.commands.timeouts?.requestMs,deadlineAt:b,signal:i.signal}),i.signal.aborted)throw i.signal.reason??new Error("provider call aborted")}catch(s){throw i.signal.aborted?i.signal.reason??s:s instanceof Be?new l("provider_unavailable",s.message,{origin:"provider",details:{status:null,code:s.kind}}):s}if(w??=await Ze({provider:t.id,tool:r.tool,request_id:a,run_id:e.run_id,sub:M,grantee:S,payer:C.payer,ledger_grantee:J,funding:T.funding,node_id:e.node_id,caused_by:n.causedBy??null,jti:C.jti,status:f.status,headers:f.headers}),y=f.headers["crtr-responder"],f.status===401&&Ce(C.key),f.status>=200&&f.status<300){const s=f.body;if(!s||typeof s!="object"||Array.isArray(s))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let E;try{E=await ee(e,P,s,{signal:i.signal})}catch(I){throw I instanceof l&&I.code==="provider_error"?new l("provider_bad_response",I.message,{origin:"provider",details:{status:f.status,message:I.message}}):I}h=E.cleanup;const N=G(g.output,E.result);if(N)throw new l("provider_bad_response",`provider result has an invalid output field "${N.field}"`,{origin:"provider",param:N.field,details:{status:f.status,...N}});if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");return{status:200,body:E.result}}const R=z(f.body);if(f.status===403&&R.code==="approval_required"&&!q&&!V){V=!0;const s=await Ee(e,r,a,R.question,i.signal,se(t,g),P);if(k=s.questionId,q=s.option,P=await Z(e,B,r.arguments,P,{signal:i.signal}),b=void 0,i.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&R.code==="approval_required"&&q)throw F(f.status,f.body,f.headers,!0);const x=f.status===410&&R.code==="unsupported_major"&&r.version===void 0;if(f.status!==401&&(R.manifest_stale===!0||x)&&!d){d=!0;const s=t.major;await Y(t.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),t=await O(t.id,r.version),ye({level:"info",event:"daemon.providers.call_retried",fields:{provider:t.id,tool:r.tool,reason:x?"unsupported_major":"manifest_stale",major_before:s,major_after:t.major,...x&&typeof R.received=="number"?{received:R.received}:{}}});continue}throw F(f.status,f.body,f.headers,!!q)}}catch(t){throw t instanceof l&&t.question_id&&(k=t.question_id),y===void 0&&t instanceof l&&typeof t.responder=="string"&&(y=t.responder),$=`refused:${t instanceof l?t.code:"server_error"}`,await h?.(),t}finally{try{await Ne({request_id:a,caller:e,provider:r.provider,tool:r.tool,scope_checked:p,outcome:$,jti:_,receipt_id:w,responder:y,question_id:k})}finally{v()}n.signal.removeEventListener("abort",u)}}m(Nr,"callProvider");async function nr(e,o,n,c,r,i){const{machine:u}=i;let a;try{a=await Ke(e,i.sub)}catch(d){throw d instanceof Xe?new l("invalid_request",d.message,{origin:"runtime"}):d}const A={machine:u,envelope:a,signal:i.signal,deadlineAt:i.deadlineAt,mapFailure:F};if(te(o.major,n.tool)){const d=await Je(A,r.arguments,o.major),j=G(c.output,d);if(j)throw new l("provider_bad_response",`the libraries result has an invalid output field "${j.field}"`,{origin:"provider",param:j.field,details:{status:200,...j}});return{result:d,responder:void 0,cleanup:void 0}}let v=r.arguments,h=null,b="";if(Le(o.major,n.tool,r.arguments)){const d=await He(A,r.arguments,o.major);if(i.deadlineAt-Date.now()<=0)throw re(u,"timeout","");d!==null&&(v=d.arguments,h=d.services,b=d.release)}const p=await Fe(u,{tool:n.tool,arguments:v,version:o.major,requestId:i.requestId,runId:e.run_id,deadlineAt:i.deadlineAt,upload:r.upload===null?null:{id:r.upload.upload_id,link:r.upload.upload_link}},a,i.signal);if(i.signal.aborted)throw i.signal.reason??new Error("provider call aborted");if("unreachable"in p)throw re(u,p.unreachable.reason,p.unreachable.message);const _=p.headers["crtr-responder"],w=z(p.body);if(p.status<200||p.status>=300){const d=De(u,p.status,typeof w.code=="string"?w.code:void 0,typeof w.message=="string"?w.message:void 0,p.body),j=d===null&&p.status>=500&&typeof w.code=="string"?new l("provider_error",typeof w.message=="string"?w.message:`the device answered HTTP ${p.status}`,{origin:"runtime",status:p.status,details:{code:w.code,status:p.status}}):null;throw Object.assign(d??j??F(p.status,p.body,p.headers),{responder:_})}const y=p.body&&typeof p.body=="object"&&!Array.isArray(p.body)?p.body.result:void 0;if(!y||typeof y!="object"||Array.isArray(y))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:p.status,message:"the device result is not a JSON object"}}),{responder:_});let k=y;if(h!==null){const d=xe(k,h);if(Ge(u,a,b,d.calls),"error"in d)throw Object.assign(d.error,{responder:_});k=d.result}let $;try{$=await ee(e,r,k,{signal:i.signal})}catch(d){throw d instanceof l&&d.code==="provider_error"?Object.assign(new l("provider_bad_response",d.message,{origin:"provider",details:{status:p.status,message:d.message}}),{responder:_}):d}const t=G(c.output,$.result);if(t)throw await $.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${t.field}"`,{origin:"provider",param:t.field,details:{status:p.status,...t}}),{responder:_});return{result:$.result,responder:_,cleanup:$.cleanup}}m(nr,"callDevice");export{Nr as callProvider,ir as crtrCallerHeader,er as identity,F as mapProviderResponse,Ze as onProviderResponse,ie as providerLeaves,Sr as providerTools};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var m=(o,t)=>M(o,"name",{value:t,configurable:!0});import{killBashJobObject as C,recordBashJobObject as D}from"../../../core/spaces/stop.js";import{readerFor as L}from"../reader.js";import{join as H}from"node:path";import{createFileBeneath as $,existsBeneath as k,unlinkBeneath as U}from"../../../core/spaces/open-beneath.js";import{launchAppProcess as x,appProcessSandboxed as z}from"../../../core/spaces/process.js";import{BASH_JOB_SUPERVISOR_SCRIPT as G}from"../../../core/bash-job-supervisor.js";import{resolveAdmittedHostCommand as X}from"../../../core/runtime/spawn-env.js";import{requireCurrentExecution as q}from"./broker-recovery.js";import{isSafeNodeId as K}from"../../../core/canvas/paths.js";import{contextDir as B}from"../../../core/canvas/node-agent-paths.js";import{getNode as S}from"../../../core/canvas/canvas.js";import{isBrokerLive as Q}from"../../../core/runtime/model-swap.js";import{assertNotFinalized as W}from"../../../core/runtime/reopen.js";import{reviveNode as Y}from"../../../core/runtime/revive.js";import{checkStart as Z}from"../../../core/runtime/start-check.js";import{bashJobPaths as J,isBashJobProcessGroupAlive as V,isSafeBashJobId as N,readBashNiceness as tt,signalBashJob as et,stopBackgroundBashJob as ot}from"../../../core/bash-jobs.js";import{applyJobReport as nt,jobRow as it,ownerRunsRemotely as O,runningJobs as I,setJobDeadline as T,markJobQuietOk as rt}from"../../../core/graph/jobs.js";import{withCanvasWrite as A,openDb as _}from"../../../core/canvas/db.js";import{getById as E}from"../../../core/graph/objects.js";import{blobStore as P}from"../../../core/blobs/index.js";import{ApiError as a}from"../../../api/index.js";import{BASH_JOB_OUTPUT_DEFAULT_BYTES as at,BASH_JOB_OUTPUT_MAX_BYTES as st}from"../../../api/index.js";import{notFound as dt}from"../../../core/errors.js";import{freshChildNodeExecPath as ut}from"../../../core/runtime/branded-host.js";async function g(o){const t=o.params.id;if(t===void 0||!K(t))throw new a(400,"invalid_node_id",`invalid node id: ${JSON.stringify(t)}`);if(await S(t)===null)throw dt(`unknown node: ${t}`,{received:t});const e=o.params.jobId;if(e!==void 0){if(!N(e))throw new a(400,"invalid_job_id",`invalid background job id: ${JSON.stringify(e)}`);return{nodeId:t,jobId:e,dir:await B(t)}}return{nodeId:t,dir:await B(t)}}m(g,"requireTarget");async function pt(o){const{nodeId:t,dir:i}=await g(o);if(o.principal.class!=="broker"||o.principal.node!==t)throw new a(403,"permission_error","only this node\u2019s broker may start a job");const e=o.body;if(!e||typeof e!="object"||Array.isArray(e))throw new a(400,"invalid_request","job.start requires a JSON body");const s=e;await q(t,s.expected_execution_id);const n=s.job_id,r=s.cwd,d=s.env;if(typeof n!="string"||!N(n)||typeof r!="string"||!d||typeof d!="object"||Array.isArray(d)||Object.entries(d).some(([l,p])=>!/^[A-Za-z_][A-Za-z0-9_]*$/.test(l)||typeof p!="string"))throw new a(400,"invalid_request","invalid job id, cwd or environment");const b=await S(t);if(await Z({grantee:b.grantee??"app:terminal",sub:b.sub,grant_id:b.grant_id}),!z())throw new a(400,"invalid_request","job.start is only used with the root launcher");const u=J(i,n);if(!(k(u.root,u.jobRun)||k(u.root,u.jobBg))||![u.cmdSh,u.jobLog].every(l=>k(u.root,l)))throw new a(404,"job_not_found","job was not prepared by this node");const f=H(u.dir,"job.starting");try{$(u.root,f,"")}catch(l){throw l.code==="EEXIST"?new a(409,"job_already_started","job was already started"):l}let h;try{const l=d,p=X("bash",l,r),j=await S(t),v=await ut();return h=await x({nodeId:t,app:j.grantee??"app:terminal",isolated:j.isolated,cwd:r,env:l,capture:!1,command:[p,"-c",G,"--",u.cmdSh,u.jobLog,u.jobExit,u.jobRun,u.jobDone,p,String(tt(i)),v,u.jobActivity]}),$(u.root,u.jobPgid,String(h.pid),384),$(u.root,u.jobPgidIdentity,`launcher:${h.pid}`,384),{status:201,body:{pid:h.pid}}}catch(l){if(h!==void 0)try{await h.stop()}catch(p){throw new AggregateError([l,p],`job ${n} launched but could not be recorded or stopped`)}try{U(u.root,f)}catch(p){if(p.code!=="ENOENT")throw p}throw l}}m(pt,"handleStart");async function lt(o){const{nodeId:t}=await g(o),i=Date.now(),e=await O(await _(),t);return{status:200,body:(await I(await _(),t)).map(n=>({job_id:n.jobId,command:n.command,purpose:n.purpose,started_at:new Date(n.startedAtMs).toISOString(),elapsed_ms:Math.max(0,i-n.startedAtMs),output_key:n.outputKey,deadline_at:n.deadlineAtMs===null?null:new Date(n.deadlineAtMs).toISOString(),pgid:n.pgid,pgid_alive:!e&&n.pgid!==null&&n.pgidIdentity!==null&&V(n.pgid,n.pgidIdentity)}))}}m(lt,"handleList");function R(o,t,i){const e=o.query.get(t);if(e===null)return;const s=Number(e);if(!/^[0-9]+$/.test(e)||!Number.isSafeInteger(s)||s<1||s>i)throw new a(400,"invalid_request",`${t} must be an integer from 1 to ${i}`);return s}m(R,"boundedQueryInt");async function ct(o){const{nodeId:t,jobId:i}=await g(o),e=R(o,"tail_lines",Number.MAX_SAFE_INTEGER),s=R(o,"max_bytes",st)??at,n=await _(),r=await E(n,i??"");if(r===null||r.type!=="job"||r.owner!==t||r.deleted_at!==null)throw new a(404,"job_not_found",`no job ${String(i)} for ${t}`);const d=await it(n,r);if(d===null)throw new Error(`job object ${r.id} has no jobs row`);const b=await P(),u=await b.list(`${d.output_key}/`),w=[];let f=0,h=0,l=u.length;for(;l>0&&f<=s&&(e===void 0||h<=e+1);){const c=u[--l],y=await b.get(c);if(y===null)throw new Error(`job ${r.id} output chunk ${c} is listed but cannot be read`);w.unshift(y),f+=y.length;for(const F of y)F===10&&h++}let p=Buffer.concat(w),j=l>0;if(p.length>s){p=p.subarray(p.length-s);let c=0;for(;c<p.length&&(p[c]&192)===128;)c++;p=p.subarray(c),j=!0}let v=p.toString("utf8");if(e!==void 0){const c=v.split(`
|
|
2
|
+
`),y=c[c.length-1]===""?e+1:e;c.length>y&&(v=c.slice(c.length-y).join(`
|
|
3
|
+
`),j=!0)}return{status:200,body:{job_id:r.id,output:v,truncated:j}}}m(ct,"handleOutput");async function bt(o){const{nodeId:t,jobId:i,dir:e}=await g(o);if(i===void 0)throw new a(400,"invalid_request","a job id is required");if(o.query.get("foreground")==="1"){if(o.principal.class!=="broker"||o.principal.node!==t)throw new a(403,"permission_error","only this node\u2019s broker may abort its foreground job");await q(t,o.body?.expected_execution_id);const w=await ot(e,i,!0,t);if(w.kind==="missing")throw new a(404,"job_not_found",`no job ${i} for ${t}`);if(w.kind==="retired")throw new a(404,"job_not_found",`job ${i} for ${t} is no longer running`);if(w.kind==="not-stoppable")throw new a(409,"job_not_stoppable",`job ${i} recorded no trusted process identity`);return{status:200,body:{node_id:t,job_id:i,signaled:w.signaled}}}const n=(await I(await _(),t)).find(w=>w.jobId===i);if(n===void 0)throw new a(404,"job_not_found",`no running background job ${i} for ${t}`);const r=await O(await _(),t);if(!r&&(n.pgid===null||n.pgidIdentity===null))throw new a(409,"job_not_stoppable",`job ${i} reported no trusted process identity`);await W(t);const d=r?!0:await et(n.pgid,n.pgidIdentity,t);await C(i,async w=>{const f=await L(w,o);return f.class==="person"?f.person:f.class==="app"?f.app:f.node});const b=await S(t);if(b!==null&&!Q(b))try{await Y(t,{resume:!0,capacity:"freeze"})}catch{}return{status:200,body:{node_id:t,job_id:i,signaled:d}}}m(bt,"handleStop");async function wt(o){const{nodeId:t,jobId:i,dir:e}=await g(o);if(i===void 0)throw new a(400,"invalid_request","a job id is required");if(o.principal.class!=="broker"||o.principal.node!==t)throw new a(403,"permission_error","only this node\u2019s broker may record its job");const s=o.body,n=J(e,i),r=s?.purpose,d=s?.deadline_at_ms;if(typeof s?.command!="string"||r!=null&&typeof r!="string"||d!=null&&!Number.isFinite(d))throw new a(400,"invalid_request","job.background requires command, and optionally purpose and deadline_at_ms");if(!k(n.root,n.jobBg))throw new a(404,"job_not_found",`job ${i} of ${t} is not backgrounded`);return{status:201,body:{id:(await D(t,i,{command:s.command,purpose:r??null,deadlineAtMs:d??null})).id}}}m(wt,"handleBackground");async function ft(o,t){if(t===void 0)throw new a(400,"invalid_request","a job id is required");const i=await E(await _(),t);if(i===null||i.type!=="job"||i.owner!==o)throw new a(404,"job_not_found",`no job ${t} for ${o}`);if(!(await I(await _(),o)).some(e=>e.jobId===t))throw new a(409,"job_not_running",`job ${t} for ${o} is no longer running`);return t}m(ft,"requireRunningJob");async function mt(o){const{nodeId:t,jobId:i}=await g(o);if(o.principal.class!=="broker"||o.principal.node!==t)throw new a(403,"permission_error","only this node\u2019s broker may report its job");const e=o.body;if(!e||typeof e!="object"||Array.isArray(e))throw new a(400,"invalid_request","job.report requires a JSON body");await q(t,e.expected_execution_id);const s=await E(await _(),i??"");if(s===null||s.type!=="job"||s.owner!==t)throw new a(404,"job_not_found",`no job ${String(i)} for ${t}`);if(!(await I(await _(),t)).some(d=>d.jobId===i))throw new a(409,"job_not_running",`job ${String(i)} for ${t} is no longer running`);const n=e.output;if(n!==void 0){if(!Number.isSafeInteger(n.seq)||n.seq<0||typeof n.data_base64!="string")throw new a(400,"invalid_request","output requires seq and data_base64");const d=await A(async b=>await b.prepare("SELECT output_key FROM jobs WHERE object = ?").get(i));await(await P()).put(`${d.output_key}/${String(n.seq).padStart(8,"0")}`,Buffer.from(n.data_base64,"base64"))}const r=e.activity;if(r!==void 0&&(!Number.isFinite(r.last_ms)||!Number.isFinite(r.sampled_ms)||!Array.isArray(r.files)))throw new a(400,"invalid_request","activity requires last_ms, sampled_ms and files");return await A(async d=>nt(d,i,{...typeof e.pgid=="number"?{pgid:e.pgid}:{},...typeof e.pgid_identity=="string"?{pgidIdentity:e.pgid_identity}:{},...typeof e.exit_code=="number"?{exitCode:e.exit_code}:{},...e.vanished===!0?{vanished:!0}:{},...r===void 0?{}:{activity:{lastActivityMs:r.last_ms,sampledAtMs:r.sampled_ms,files:r.files}}})),{status:204}}m(mt,"handleReport");async function ht(o){const{nodeId:t,jobId:i}=await g(o),e=await ft(t,i),s=o.body,n=s?.deadline_at_ms,r=s?.keep===!0;if(s?.quiet_ok!==!0&&!r&&!Number.isFinite(n))throw new a(400,"invalid_request","patch requires quiet_ok, keep or deadline_at_ms");if(r&&n!==void 0)throw new a(400,"invalid_request","keep and deadline_at_ms are exclusive");return await A(async d=>{s?.quiet_ok===!0&&await rt(d,e),r?await T(d,e,null):Number.isFinite(n)&&await T(d,e,n)}),{status:204}}m(ht,"handlePatch");const xt=[{method:"POST",pattern:"/v1/nodes/:id/jobs/:jobId/background",handler:wt},{method:"POST",pattern:"/v1/nodes/:id/jobs/:jobId/report",handler:mt},{method:"PATCH",pattern:"/v1/nodes/:id/jobs/:jobId",handler:ht},{method:"POST",pattern:"/v1/nodes/:id/jobs/start",handler:pt},{method:"GET",pattern:"/v1/nodes/:id/jobs",handler:lt},{method:"GET",pattern:"/v1/nodes/:id/jobs/:jobId/output",handler:ct},{method:"DELETE",pattern:"/v1/nodes/:id/jobs/:jobId",handler:bt}];export{xt as bashJobRoutes};
|