@north-light/crouter 0.3.346 → 0.3.348

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.
@@ -1,2 +1,2 @@
1
1
  var E=Object.defineProperty;var s=(e,t)=>E(e,"name",{value:t,configurable:!0});import{envNodeId as b}from"../../shared/env.js";import{defineBranch as C,defineLeaf as y}from"../../core/command.js";import{InputError as l}from"../../core/io.js";import{cliClient as p,getNodeOrNull as D,rethrowAsCliError as f}from"../api-client.js";import{contextDir as P}from"../../core/canvas/paths.js";import{DEFAULT_BASH_NICENESS as q,MAX_BASH_NICENESS as w,writeBashNiceness as $}from"../../core/bash-jobs.js";import{nodeReviveLeaf as S}from"../node-lifecycle-revive.js";import{assertKind as k,kindsStateBlock as _}from"./create.js";import{MODEL_SPEC_FORMS as I}from"../../core/runtime/model-selection.js";import{closePane as R,paneExists as O,respawnPaneSync as U,viewerEnv as L,viewerPaneOf as M,nodeInPane as T}from"../surface/node/placement.js";const j=y({name:"recycle",description:"finish the agent in your pane + recycle it into a fresh root",whenToUse:"you are at an agent's pane and done with it: finish it cleanly and recycle the pane in one move \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane to keep working. The hands-on way to end an agent and immediately start over in place. Use `node lifecycle demote` instead to put it on a finishing track IN PLACE (flip terminal, keep it running) without ending it now, `node lifecycle close` to tear a node and its subtree down WITHOUT finishing (no report, revivable), and `push final` when the agent should finish ITSELF from inside its own turn",help:{name:"node lifecycle recycle",summary:"finish the agent in your current pane and recycle the pane \u2014 push its last message as a final report to everyone waiting on it, mark it done, then boot a fresh crtr root in the same pane",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to finish. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to recycle. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"recycled",type:"boolean",required:!0,constraint:"True when the pane was recycled into a fresh root."},{name:"node_id",type:"string",required:!1,constraint:"The finished node."},{name:"new_root",type:"string",required:!1,constraint:"The fresh root node booted into the pane."}],outputKind:"object",effects:["Pushes a final report from the node (fans out to all subscribers) and marks it done.","Kills the agent's pi and respawns a fresh resident root in the same tmux pane."]},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=T(t)),n===void 0||n==="")throw new l({error:"no_node",message:"no node found in this pane to finish",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await p().getNode(n).catch(c=>f(c,"List nodes with `crtr node inspect list`.")),o=(await p().recycleNode(n).catch(f)).node_id;let r=!1;return t!==void 0&&t!==""&&(r=U({pane:t,cwd:i.cwd,env:L(),command:"crtr"})),{recycled:r,node_id:n,new_root:o}},"run"),render:s(e=>{if(e.recycled!==!0)return e.new_root?`Finished ${e.node_id} and spawned a fresh broker root (${e.new_root}), but its viewer did not attach \u2014 the new broker did not come up. Re-focus the root to view it.`:`Recycle failed for ${e.node_id??"?"} \u2014 not in tmux, or no agent in this pane.`;const t=[`Recycled the pane \u2014 finished ${e.node_id}.`];return e.new_root&&t.push(`- new root: ${e.new_root}`),t.join(`
2
- `)},"render")}),F=y({name:"close",description:"close a node + cascade-cancel its exclusive subtree",whenToUse:"you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`node lifecycle revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C \u2192 x)",help:{name:"node lifecycle close",summary:"close a node and cascade-cancel its subtree \u2014 kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscribes_to spine), mark retained nodes canceled, and leave them a notice they read on resume. A descendant still subscribed to by a manager outside the subtree is left running. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`crtr node lifecycle revive`)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to close. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to close. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The node that was closed \u2014 the cascade root."},{name:"count",type:"number",required:!1,constraint:"How many nodes were closed (root + cascaded descendants)."},{name:"closed_ids",type:"string[]",required:!1,constraint:"All closed node ids, kill order (leaves first, root last)."},{name:"spared",type:"string[]",required:!1,constraint:"Descendants left alive because a manager outside the subtree still subscribes to them."}],outputKind:"object",effects:["Marks retained nodes and their exclusive descendants `canceled` and clears intent (the daemon never revives a canceled node).","Deletes a non-streaming node with no substantive assistant output unless it has an open managed worktree; a deleted node cannot be revived.","Kills each closed node's tmux window; retained nodes keep their pi sessions and canvas edges for a later revive.","Appends a cancellation notice to each retained node's inbox, surfaced on its next resume."]},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=T(t)),n===void 0||n==="")throw new l({error:"no_node",message:"no node found in this pane to close",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await p().closeNode(n,process.ppid>0?{caller_pid:process.ppid}:{}).catch(d=>f(d,"List nodes with `crtr node inspect list`."));return{node_id:i.root,count:i.closed.length,closed_ids:i.closed,spared:i.spared}},"run"),render:s(e=>{const t=e.spared?.length??0;return`Closed ${e.node_id} and its exclusive subtree \u2014 ${e.count} node(s) closed${t>0?`, ${t} spared (still managed from outside the subtree)`:""}.`},"render")}),A="Model change first: a live broker switches immediately over its socket; a dormant node persists the durable model override and launch recipe for its next revive.",ee=y({name:"config",description:"reconfigure a node's settings headlessly \u2014 model, lifecycle (terminal/resident), kind, mode, name; repair a missing cwd",whenToUse:"you want to reconfigure a node's settings headlessly after spawn \u2014 swap its model/provider, flip it terminal\u2194resident (interactable), respecialize its kind, switch base\u2194orchestrator mode, or rename it \u2014 without opening its viewer. It also repairs a dormant node whose recorded cwd is gone, starting its next revive in a fresh conversation. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).",help:{name:"node config",summary:"reconfigure a node's settings headlessly \u2014 model, lifecycle, kind, mode, name; repair a missing cwd",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Target node id. Defaults to the node in --pane, else CRTR_NODE_ID."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to resolve the node from. Defaults to $TMUX_PANE."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model change. Omit unless the task or governing guidance requires a different model.",focusedHelp:{whenToUse:"Use only when intentionally changing this node\u2019s durable model because the user, task, or governing agent guidance requires a specific provider, model, capability tier, or family. Omit it to keep the current model; do not use it merely to inspect available models or experiment with a live node.",value:`Accepted durable forms: ${I}. A live broker also accepts a free-text substring matched against its loaded model registry; a dormant node cannot resolve a substring and requires a durable form.`,effects:[A,"A concrete provider/id pins that provider. Portable tiers and aliases preserve logical intent for runtime routing; a persona quality floor may raise a weaker request."]}},{kind:"flag",name:"lifecycle",type:"enum",choices:["terminal","resident"],required:!1,constraint:"Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,constraint:"Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator seeds a roadmap scaffold if absent."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Rename the node; if the node has a live window, also rename that viewer window."},{kind:"flag",name:"nice",type:"int",required:!1,constraint:`Scheduler niceness every command this node's bash tool starts at, 0\u2013${w} (default ${q}). ${q} keeps the node's builds and test suites below the daemon, the brokers, the viewers, and the person's own shell; 0 lets them compete on equal terms with everything else on the host; ${w} makes them yield to it. Applies to the node's very next command, foreground and background alike.`},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Repair-only replacement launch directory. Must be a non-empty absolute existing directory and used alone; the daemon accepts it only for a dormant, non-forked node with no open managed worktree whose recorded cwd is gone. Clears the saved Pi session, so the next revive starts fresh while durable node work remains."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The reconfigured node, present for ordinary config changes."},{name:"model",type:"string",required:!1,constraint:"The resolved model, present only when --model changed it."},{name:"lifecycle",type:"string",required:!1,constraint:"The new lifecycle, present only when --lifecycle changed it."},{name:"kind",type:"string",required:!1,constraint:"The new kind, present only when --kind changed it."},{name:"mode",type:"string",required:!1,constraint:"The new mode, present only when --mode changed it."},{name:"name",type:"string",required:!1,constraint:"The new name, present only when --name changed it."},{name:"nice",type:"number",required:!1,constraint:"The new bash niceness, present only when --nice changed it."},{name:"cwd",type:"string",required:!1,constraint:"The lexically resolved replacement directory, present only after a cwd repair."}],outputKind:"object",effects:[A,"Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator seeds a roadmap scaffold if absent.","Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.","A cwd repair writes only the lexically resolved cwd and clears saved Pi session pointers; the old transcript and durable node work remain untouched.","A niceness change is stored with the node's own bash-job state, so it takes effect on the next command without a revive and survives one."],dynamicState:s(()=>_(),"dynamicState")},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await N(e,t),i=e.cwd;if(i!==void 0&&["model","kind","lifecycle","mode","name","nice"].some(v=>e[v]!==void 0))throw new l({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config flag",field:"cwd",next:"Pass --cwd alone."});const d=e.model?.trim();if(d==="")throw new l({error:"empty_spec",message:"a model spec is required",field:"model",next:"Run `crtr node config --model -h` and read the focused contract before retrying."});const o=e.kind?.trim(),r=e.lifecycle?.trim(),c=e.mode?.trim(),x=e.name,h=e.nice;if(h!==void 0&&(h<0||h>w))throw new l({error:"bad_nice",message:`niceness must be between 0 and ${w}: ${h}`,field:"nice",next:`A negative niceness needs root, so pass 0\u2013${w}.`});if(o!==void 0&&k(o),r!==void 0&&r.toLowerCase()!=="terminal"&&r.toLowerCase()!=="resident")throw new l({error:"bad_lifecycle",message:`invalid lifecycle: ${r}`,field:"lifecycle",next:"Pass `terminal` or `resident`."});if(c!==void 0&&c.toLowerCase()!=="base"&&c.toLowerCase()!=="orchestrator")throw new l({error:"bad_mode",message:`invalid mode: ${c}`,field:"mode",next:"Pass `base` or `orchestrator`."});const a=[],u={};if(d!==void 0&&(u.model=d,a.push("model")),o!==void 0&&(u.kind=o,a.push("kind")),r!==void 0&&(u.lifecycle=r.toLowerCase(),a.push("lifecycle")),c!==void 0&&(u.mode=c.toLowerCase(),a.push("mode")),x!==void 0&&(u.name=x,a.push("name")),i!==void 0&&(u.cwd=i,a.push("cwd")),h!==void 0&&($(P(n),h),a.push("nice"),a.length===1))return{node_id:n,nice:h};if(a.length===0)throw new l({error:"no_change",message:"no config changes requested",next:"Pass at least one of --model, --lifecycle, --kind, --mode, --name, --nice, --cwd."});const g=await p().patchConfig(n,u).catch(v=>f(v,"List nodes with `crtr node inspect list`."));if(i!==void 0)return{cwd:g.cwd};const m={node_id:n};return a.includes("model")&&(m.model=g.model_override??d),a.includes("lifecycle")&&(m.lifecycle=g.lifecycle),a.includes("kind")&&(m.kind=g.kind),a.includes("mode")&&(m.mode=g.mode),a.includes("name")&&(m.name=g.name),a.includes("nice")&&(m.nice=h),m},"run"),render:s(e=>{if(e.cwd!==void 0)return`Repaired cwd: ${e.cwd}.`;const t=[];return e.model!==void 0&&t.push(`model=${e.model}`),e.lifecycle!==void 0&&t.push(`lifecycle=${e.lifecycle}`),e.kind!==void 0&&t.push(`kind=${e.kind}`),e.mode!==void 0&&t.push(`mode=${e.mode}`),e.name!==void 0&&t.push(`name=${e.name}`),e.nice!==void 0&&t.push(`nice=${e.nice}`),`Configured ${e.node_id} \u2014 ${t.join(", ")}`},"render")}),ne=y({name:"promote",description:"become an orchestrator of a chosen kind",whenToUse:"the remaining work contains independent units that can run in parallel, and the task is large enough that coordinating and integrating them will materially improve intelligence, productivity, or elapsed throughput. Become a long-lived, roadmap-holding orchestrator whose primary job is steering those children across refreshes (`node yield`). Keep sequential or tightly coupled work in base mode even when it spans phases or context windows; spawning one helper does not itself require promotion",tier:"important",help:{name:"node promote",summary:"promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only \u2014 lifecycle stays as-is unless you pass --resident",params:[{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys context prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one \u2014 it sets the orchestrator persona you revive into."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"ALSO flip lifecycle\u2192resident: make the node interactable \u2014 it is never forced to submit a final, and unattended grace parks it after a summary turn instead, leaving it revivable fresh against its goal and roadmap. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done)."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model you run on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to promote. Defaults to the caller (CRTR_NODE_ID)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The promoted node."},{name:"kind",type:"string",required:!0,constraint:"The kind it now orchestrates as."},{name:"model",type:"string",required:!1,constraint:"The model tier it now runs on, present only when you changed it with --model."},{name:"lifecycle",type:"string",required:!0,constraint:'"resident" if you passed --resident, else unchanged (typically "terminal").'},{name:"roadmap_written",type:"boolean",required:!0,constraint:"True if a roadmap scaffold was seeded by this call."},{name:"roadmap_path",type:"string",required:!0,constraint:"Absolute path to your roadmap doc (context/roadmap.md) \u2014 edit it to author your plan."},{name:"goal_path",type:"string",required:!0,constraint:"Absolute path to your goal doc (context/initial-prompt.md) \u2014 the mandate you were spawned with."}],dynamicState:s(()=>_(),"dynamicState"),outputKind:"object",effects:["Flips mode\u2192orchestrator + kind\u2192chosen (lifecycle unchanged unless --resident, which also flips lifecycle\u2192resident); --model durably pins the model tier; rewrites the launch spec to that kind's orchestrator persona; seeds context/roadmap.md scaffold if absent.","The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role."]},run:s(async e=>{const t=e.node??b();if(t===void 0||t==="")throw new l({error:"no_node",message:"no node to promote (set CRTR_NODE_ID or pass --node)",next:"Run from inside a node, or pass --node <id>."});const n=e.kind;n!==void 0&&k(n);const i=e.model,d=e.resident===!0,o=await p().promoteNode(t,{...n!==void 0?{kind:n}:{},...d?{resident:!0}:{},...i!==void 0?{model:i}:{}}).catch(r=>f(r));return{node_id:o.node_id,kind:o.kind,lifecycle:o.lifecycle,...i!==void 0&&o.model_override!=null?{model:o.model_override}:{},roadmap_written:o.roadmap_written??!1,roadmap_path:o.roadmap_path??"",goal_path:o.goal_path??""}},"run")});async function N(e,t){let n=e.node;if((n===void 0||n==="")&&(n=T(t)),(n===void 0||n==="")&&(n=b()),n===void 0||n==="")throw new l({error:"no_node",message:"no node (set CRTR_NODE_ID, pass --node, or run from the agent's pane)",next:"Run from inside a node, pass --node <id>, or --pane <pane>."});if(await D(n)===null)throw new l({error:"not_found",message:`no node: ${n}`,next:"List nodes with `crtr node inspect list`."});return n}s(N,"resolveLifecycleNode");const H=y({name:"demote",description:"demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)",whenToUse:"you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE \u2014 it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal\u2192resident)",help:{name:"node lifecycle demote",summary:"demote a node to terminal IN PLACE \u2014 it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode\u2191); `--detach` also closes its viewer (the broker engine keeps running off-screen)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you."},{kind:"flag",name:"detach",type:"bool",required:!1,constraint:"After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and \u2014 now terminal \u2014 pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C \u2192 D move."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The demoted node."},{name:"detached",type:"boolean",required:!1,constraint:"True when --detach closed the agent's viewer pane (broker keeps running)."}],outputKind:"object",effects:["Flips the node's lifecycle\u2192terminal and rebuilds its launch spec so a future revive boots terminal \u2014 it stays focused and running, now owing a final up the spine.","The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.","With --detach: closes the agent's viewer pane WITHOUT touching the broker engine \u2014 it keeps generating off-screen, reconnectable by a later `focus`."]},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await N(e,t),i=await p().patchConfig(n,{lifecycle:"terminal"}).catch(o=>f(o));let d=!1;if(e.detach===!0){const o=M(n)??t;o!==void 0&&o!==""&&O(o)&&(d=R(o))}return{node_id:i.node_id,detached:d}},"run"),render:s(e=>`Demoted ${e.node_id} \u2014 lifecycle now terminal (in place)${e.detached===!0?", viewer closed (broker still running off-screen)":""}.`,"render")}),te=C({name:"lifecycle",description:"lifecycle actions \u2014 recycle, close, demote, or revive a node",whenToUse:"you are ending, transitioning, or bringing back one node you can name: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), flip a node onto a finishing track in place (`demote`), or bring a dormant node back (`revive`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node's own turn use `crtr push final`; to mass-reconnect EVERY disconnected node at once use `canvas revive --all`.",help:{name:"node lifecycle",summary:"end, transition, or revive a node \u2014 recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node",model:"Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree."},children:[j,F,H,S]}),oe=y({name:"yield",description:"refresh your context, same mode, against your roadmap or goal",whenToUse:"your context is filling but the mandate remains; refresh in the same mode, or combine the refresh with promotion when meaningful independent work can run in parallel and coordinating it should become your primary job",help:{name:"node yield",summary:"request a fresh context window against your roadmap or original goal; mode stays unchanged unless --promote is passed",guide:"Before invoking: (1) Bring `$CRTR_CONTEXT_DIR/roadmap.md` current \u2014 it, not the yield note, carries the goal into the fresh window. Keep its seeded sections (Goal, Exit criteria, Phases) and keep it short: strategy and present state only \u2014 what is settled, what remains, the current phase\u2019s one-line status, plus dead ends worth not repeating. Delete completed items instead of marking them done; the roadmap should shrink as work completes. (2) Move anything worth remembering but not needed next window into memory (`crtr memory write -h`): node-local for lessons specific to this node\u2019s task, the workspace store for lessons that would benefit other agents working here. (3) Send any pending human question; in-flight asks survive the refresh.",params:[{kind:"flag",name:"promote",type:"bool",required:!1,constraint:"Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model your fresh revive runs on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"stdin",name:"message",required:!0,constraint:"A note to your future self \u2014 what you just tried, where things stand, and the first move on waking: enough to continue efficiently, no more. Durable strategy belongs in the roadmap, not here. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The yielding node."},{name:"model",type:"string",required:!1,constraint:"The model tier your fresh revive will run on, present only when you changed it with --model."}],dynamicState:s(()=>_(),"dynamicState"),outputKind:"object",effects:["The stophook shuts the process down on next stop and the daemon revives it fresh.","--kind and/or --model durably reshape the node (kind respecialization, model-tier pin) before the refresh-revive; --promote additionally flips mode\u2192orchestrator and seeds a roadmap scaffold \u2014 mode is otherwise left exactly as it was."]},run:s(async e=>{const t=b();if(t===void 0||t==="")throw new l({error:"no_node",message:"no node to yield (CRTR_NODE_ID unset)",next:"Run from inside a node."});const n=e.kind;n!==void 0&&k(n);const i=e.model,d=e.promote===!0,o=typeof e.message=="string"?e.message.trim():"";if(o==="")throw new l({error:"empty_message",message:"a yield message is required (stdin or positional)",next:"Pass a note to your future self as an argument or pipe it on stdin."});const r=await p().yieldNode(t,{note:o,...n!==void 0?{kind:n}:{},...i!==void 0?{model:i}:{},...d?{promote:!0}:{}}).catch(c=>f(c));return{node_id:r.node_id,...i!==void 0&&r.model_override!=null?{model:r.model_override}:{}}},"run")});export{ee as nodeConfig,te as nodeLifecycle,ne as nodePromote,oe as nodeYield};
2
+ `)},"render")}),F=y({name:"close",description:"close a node + cascade-cancel its exclusive subtree",whenToUse:"you want to tear a node down WITHOUT finishing it, cascade-cancelling every descendant it exclusively owns: abandoning a line of work, killing a stuck or wrong-turn subtree, clearing a branch you no longer need. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`node lifecycle revive`). Use `node lifecycle recycle` instead to FINISH the agent in your pane with a final report, and `push final` when a worker should end its own work normally (Alt+C \u2192 x)",help:{name:"node lifecycle close",summary:"close a node and cascade-cancel its subtree \u2014 kill its tmux window plus those of every descendant it EXCLUSIVELY owns (down the subscribes_to spine), mark retained nodes canceled, and leave them a notice they read on resume. A descendant still subscribed to by a manager outside the subtree is left running. A node that never produced substantive assistant output is deleted and cannot be revived; streaming nodes and nodes with an open managed worktree are retained. Other closed nodes keep their pi session and can be revived later (`crtr node lifecycle revive`)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to close. Defaults to the node occupying --pane (or your current pane)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to close. Defaults to $TMUX_PANE / your current pane. The Alt+C menu passes this for you."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The node that was closed \u2014 the cascade root."},{name:"count",type:"number",required:!1,constraint:"How many nodes were closed (root + cascaded descendants)."},{name:"closed_ids",type:"string[]",required:!1,constraint:"All closed node ids, kill order (leaves first, root last)."},{name:"spared",type:"string[]",required:!1,constraint:"Descendants left alive because a manager outside the subtree still subscribes to them."}],outputKind:"object",effects:["Marks retained nodes and their exclusive descendants `canceled` and clears intent (the daemon never revives a canceled node).","Deletes a non-streaming node with no substantive assistant output unless it has an open managed worktree; a deleted node cannot be revived.","Kills each closed node's tmux window; retained nodes keep their pi sessions and canvas edges for a later revive.","Appends a cancellation notice to each retained node's inbox, surfaced on its next resume."]},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE;let n=e.node;if((n===void 0||n==="")&&(n=T(t)),n===void 0||n==="")throw new l({error:"no_node",message:"no node found in this pane to close",next:"Pass --node <id>, or run from inside the agent's pane."});const i=await p().closeNode(n,process.ppid>0?{caller_pid:process.ppid}:{}).catch(d=>f(d,"List nodes with `crtr node inspect list`."));return{node_id:i.root,count:i.closed.length,closed_ids:i.closed,spared:i.spared}},"run"),render:s(e=>{const t=e.spared?.length??0;return`Closed ${e.node_id} and its exclusive subtree \u2014 ${e.count} node(s) closed${t>0?`, ${t} spared (still managed from outside the subtree)`:""}.`},"render")}),A="Model change first: a live broker switches immediately over its socket; a dormant node persists the durable model override and launch recipe for its next revive.",ee=y({name:"config",description:"reconfigure a node's settings headlessly \u2014 model, lifecycle (terminal/resident), kind, mode, name; repair a missing cwd",whenToUse:"you want to reconfigure a node's settings headlessly after spawn \u2014 swap its model/provider, flip it terminal\u2194resident (interactable), respecialize its kind, switch base\u2194orchestrator mode, or rename it \u2014 without opening its viewer. It also repairs a dormant node whose recorded cwd is gone, starting its next revive in a fresh conversation. The durable attribute setter; reuses the same recipes a revive reads. Use `node promote` for the opinionated self-promotion path, and `node lifecycle` for teardown actions (recycle/close/demote).",help:{name:"node config",summary:"reconfigure a node's settings headlessly \u2014 model, lifecycle, kind, mode, name; repair a missing cwd",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Target node id. Defaults to the node in --pane, else CRTR_NODE_ID."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id to resolve the node from. Defaults to $TMUX_PANE."},{kind:"flag",name:"model",type:"string",required:!1,constraint:"Durable model change. Omit unless the task or governing guidance requires a different model.",focusedHelp:{whenToUse:"Use only when intentionally changing this node\u2019s durable model because the user, task, or governing agent guidance requires a specific provider, model, capability tier, or family. Omit it to keep the current model; do not use it merely to inspect available models or experiment with a live node.",value:`Accepted durable forms: ${I}. A live broker also accepts a free-text substring matched against its loaded model registry; a dormant node cannot resolve a substring and requires a durable form.`,effects:[A,"A concrete provider/id pins that provider. Portable tiers and aliases preserve logical intent for runtime routing; a persona quality floor may raise a weaker request."]}},{kind:"flag",name:"lifecycle",type:"enum",choices:["terminal","resident"],required:!1,constraint:"Set lifecycle headlessly without closing the viewer. terminal owes a final and reaps when done; resident stays interactable while attached or awaiting something live."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Persona kind. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here."},{kind:"flag",name:"mode",type:"enum",choices:["base","orchestrator"],required:!1,constraint:"Set persona mode headlessly. base is hands-on; orchestrator holds a roadmap and delegates. orchestrator seeds a roadmap scaffold if absent."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Rename the node; if the node has a live window, also rename that viewer window."},{kind:"flag",name:"nice",type:"int",required:!1,constraint:`Scheduler niceness every command this node's bash tool starts at, 0\u2013${w} (default ${q}). ${q} keeps the node's builds and test suites below the daemon, the brokers, the viewers, and the person's own shell; 0 lets them compete on equal terms with everything else on the host; ${w} makes them yield to it. Applies to the node's very next command, foreground and background alike.`},{kind:"flag",name:"cwd",type:"string",required:!1,constraint:"Repair-only replacement launch directory. Must be a non-empty absolute existing directory and used alone; the daemon accepts it only for a dormant, non-forked node with no open managed worktree whose recorded cwd is gone. Clears the saved Pi session, so the next revive starts fresh while durable node work remains."}],output:[{name:"node_id",type:"string",required:!1,constraint:"The reconfigured node, present for ordinary config changes."},{name:"model",type:"string",required:!1,constraint:"The resolved model, present only when --model changed it."},{name:"lifecycle",type:"string",required:!1,constraint:"The new lifecycle, present only when --lifecycle changed it."},{name:"kind",type:"string",required:!1,constraint:"The new kind, present only when --kind changed it."},{name:"mode",type:"string",required:!1,constraint:"The new mode, present only when --mode changed it."},{name:"name",type:"string",required:!1,constraint:"The new name, present only when --name changed it."},{name:"nice",type:"number",required:!1,constraint:"The new bash niceness, present only when --nice changed it."},{name:"cwd",type:"string",required:!1,constraint:"The lexically resolved replacement directory, present only after a cwd repair."}],outputKind:"object",effects:[A,"Kind, mode, and lifecycle changes rebuild the launch spec from the fresh node meta; setting mode to orchestrator seeds a roadmap scaffold if absent.","Name changes update the row and, when the node already has a live window, rename that viewer window to the new full name.","A cwd repair writes only the lexically resolved cwd and clears saved Pi session pointers; the old transcript and durable node work remain untouched.","A niceness change is stored with the node's own bash-job state, so it takes effect on the next command without a revive and survives one."],dynamicState:s(()=>_(),"dynamicState")},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await N(e,t),i=e.cwd;if(i!==void 0&&["model","kind","lifecycle","mode","name","nice"].some(v=>e[v]!==void 0))throw new l({error:"cwd_not_exclusive",message:"cwd repair cannot be combined with another config flag",field:"cwd",next:"Pass --cwd alone."});const d=e.model?.trim();if(d==="")throw new l({error:"empty_spec",message:"a model spec is required",field:"model",next:"Run `crtr node config --model -h` and read the focused contract before retrying."});const o=e.kind?.trim(),r=e.lifecycle?.trim(),c=e.mode?.trim(),x=e.name,h=e.nice;if(h!==void 0&&(h<0||h>w))throw new l({error:"bad_nice",message:`niceness must be between 0 and ${w}: ${h}`,field:"nice",next:`A negative niceness needs root, so pass 0\u2013${w}.`});if(o!==void 0&&k(o),r!==void 0&&r.toLowerCase()!=="terminal"&&r.toLowerCase()!=="resident")throw new l({error:"bad_lifecycle",message:`invalid lifecycle: ${r}`,field:"lifecycle",next:"Pass `terminal` or `resident`."});if(c!==void 0&&c.toLowerCase()!=="base"&&c.toLowerCase()!=="orchestrator")throw new l({error:"bad_mode",message:`invalid mode: ${c}`,field:"mode",next:"Pass `base` or `orchestrator`."});const a=[],u={};if(d!==void 0&&(u.model=d,a.push("model")),o!==void 0&&(u.kind=o,a.push("kind")),r!==void 0&&(u.lifecycle=r.toLowerCase(),a.push("lifecycle")),c!==void 0&&(u.mode=c.toLowerCase(),a.push("mode")),x!==void 0&&(u.name=x,a.push("name")),i!==void 0&&(u.cwd=i,a.push("cwd")),h!==void 0&&($(P(n),h),a.push("nice"),a.length===1))return{node_id:n,nice:h};if(a.length===0)throw new l({error:"no_change",message:"no config changes requested",next:"Pass at least one of --model, --lifecycle, --kind, --mode, --name, --nice, --cwd."});const g=await p().patchConfig(n,u).catch(v=>f(v,"List nodes with `crtr node inspect list`."));if(i!==void 0)return{cwd:g.cwd};const m={node_id:n};return a.includes("model")&&(m.model=g.model_override??d),a.includes("lifecycle")&&(m.lifecycle=g.lifecycle),a.includes("kind")&&(m.kind=g.kind),a.includes("mode")&&(m.mode=g.mode),a.includes("name")&&(m.name=g.name),a.includes("nice")&&(m.nice=h),m},"run"),render:s(e=>{if(e.cwd!==void 0)return`Repaired cwd: ${e.cwd}.`;const t=[];return e.model!==void 0&&t.push(`model=${e.model}`),e.lifecycle!==void 0&&t.push(`lifecycle=${e.lifecycle}`),e.kind!==void 0&&t.push(`kind=${e.kind}`),e.mode!==void 0&&t.push(`mode=${e.mode}`),e.name!==void 0&&t.push(`name=${e.name}`),e.nice!==void 0&&t.push(`nice=${e.nice}`),`Configured ${e.node_id} \u2014 ${t.join(", ")}`},"render")}),ne=y({name:"promote",description:"become an orchestrator of a chosen kind",whenToUse:"the remaining work contains independent units that can run in parallel, and the task is large enough that coordinating and integrating them will materially improve intelligence, productivity, or elapsed throughput. Become a long-lived, roadmap-holding orchestrator whose primary job is steering those children across refreshes (`node yield`). Keep sequential or tightly coupled work in base mode even when it spans phases or context windows; spawning one helper does not itself require promotion",tier:"important",help:{name:"node promote",summary:"promote yourself to an orchestrator when worthwhile parallel delegation should make coordination your primary job; task duration, sequential phases, or one helper do not qualify. Mode only \u2014 lifecycle stays as-is unless you pass --resident",params:[{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Specialize as this kind of orchestrator. The <kinds> list below names every top-level installable kind and when to use each; a registered sub-kind is valid too by exact path but not listed here. For an exact composed persona list, use `crtr sys context prompt-review --list --kind <kind>` with the target kind. Defaults to your current kind. Promoting from a generic kind? CHOOSE a concrete one \u2014 it sets the orchestrator persona you revive into."},{kind:"flag",name:"resident",type:"bool",required:!1,constraint:"ALSO flip lifecycle\u2192resident: make the node interactable \u2014 it is never forced to submit a final, and unattended grace parks it after a summary turn instead, leaving it revivable fresh against its goal and roadmap. Omit to stay terminal/orchestrator (delegates + holds a roadmap, but still owes a final up the spine and reaps when done)."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model you run on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. An orchestrator steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to promote. Defaults to the caller (CRTR_NODE_ID)."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The promoted node."},{name:"kind",type:"string",required:!0,constraint:"The kind it now orchestrates as."},{name:"model",type:"string",required:!1,constraint:"The model tier it now runs on, present only when you changed it with --model."},{name:"lifecycle",type:"string",required:!0,constraint:'"resident" if you passed --resident, else unchanged (typically "terminal").'},{name:"roadmap_written",type:"boolean",required:!0,constraint:"True if a roadmap scaffold was seeded by this call."},{name:"roadmap_path",type:"string",required:!0,constraint:"Absolute path to your roadmap doc (context/roadmap.md) \u2014 edit it to author your plan."},{name:"goal_path",type:"string",required:!0,constraint:"Absolute path to your goal doc (context/initial-prompt.md) \u2014 the mandate you were spawned with."}],dynamicState:s(()=>_(),"dynamicState"),outputKind:"object",effects:["Flips mode\u2192orchestrator + kind\u2192chosen (lifecycle unchanged unless --resident, which also flips lifecycle\u2192resident); --model durably pins the model tier; rewrites the launch spec to that kind's orchestrator persona; seeds context/roadmap.md scaffold if absent.","The next model request is gated on appending and persisting your new-role guidance. If a fresh context starts first, its rebuilt baseline carries the new role."]},run:s(async e=>{const t=e.node??b();if(t===void 0||t==="")throw new l({error:"no_node",message:"no node to promote (set CRTR_NODE_ID or pass --node)",next:"Run from inside a node, or pass --node <id>."});const n=e.kind;n!==void 0&&k(n);const i=e.model,d=e.resident===!0,o=await p().promoteNode(t,{...n!==void 0?{kind:n}:{},...d?{resident:!0}:{},...i!==void 0?{model:i}:{}}).catch(r=>f(r));return{node_id:o.node_id,kind:o.kind,lifecycle:o.lifecycle,...i!==void 0&&o.model_override!=null?{model:o.model_override}:{},roadmap_written:o.roadmap_written??!1,roadmap_path:o.roadmap_path??"",goal_path:o.goal_path??""}},"run")});async function N(e,t){let n=e.node;if((n===void 0||n==="")&&(n=T(t)),(n===void 0||n==="")&&(n=b()),n===void 0||n==="")throw new l({error:"no_node",message:"no node (set CRTR_NODE_ID, pass --node, or run from the agent's pane)",next:"Run from inside a node, pass --node <id>, or --pane <pane>."});if(await D(n)===null)throw new l({error:"not_found",message:`no node: ${n}`,next:"List nodes with `crtr node inspect list`."});return n}s(N,"resolveLifecycleNode");const H=y({name:"demote",description:"demote a node to terminal in place; it stays focused and running but now owes a final; --detach also closes its viewer (broker keeps running off-screen)",whenToUse:"you are watching a resident/interactive node and want to put it on a finishing track WITHOUT disturbing it: flip it terminal IN PLACE \u2014 it keeps its viewer and your focus, keeps running, but now owes a final report up the spine and reaps when done. The friendly counterpart to `node promote`. You can also let go entirely and close its viewer so the agent finishes off-screen on its broker. Use `node lifecycle recycle` instead to FINISH it now and reboot a fresh root in its pane, and `node config --lifecycle` for the orthogonal low-level flip (incl. terminal\u2192resident)",help:{name:"node lifecycle demote",summary:"demote a node to terminal IN PLACE \u2014 it stays focused and running but now owes a final up the spine and reaps when done. Pairs with `node promote` (mode\u2191); `--detach` also closes its viewer (the broker engine keeps running off-screen)",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node to demote. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane id whose node to demote, when --node is omitted. Defaults to $TMUX_PANE. The Alt+C menu passes this for you."},{kind:"flag",name:"detach",type:"bool",required:!1,constraint:"After flipping terminal, stop foregrounding the agent by closing its viewer pane. The broker engine keeps generating off-screen and \u2014 now terminal \u2014 pushes a final up the spine when done; a later `focus` reattaches a viewer. The Alt+C \u2192 D move."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The demoted node."},{name:"detached",type:"boolean",required:!1,constraint:"True when --detach closed the agent's viewer pane (broker keeps running)."}],outputKind:"object",effects:["Flips the node's lifecycle\u2192terminal and rebuilds its launch spec so a future revive boots terminal \u2014 it stays focused and running, now owing a final up the spine.","The next model request is gated on appending and persisting the role transition. If a fresh context starts first, its rebuilt baseline carries the new lifecycle.","With --detach: closes the agent's viewer pane WITHOUT touching the broker engine \u2014 it keeps generating off-screen, reconnectable by a later `focus`."]},run:s(async e=>{const t=e.pane??process.env.TMUX_PANE,n=await N(e,t),i=await p().patchConfig(n,{lifecycle:"terminal"}).catch(o=>f(o));let d=!1;if(e.detach===!0){const o=M(n)??t;o!==void 0&&o!==""&&O(o)&&(d=R(o))}return{node_id:i.node_id,detached:d}},"run"),render:s(e=>`Demoted ${e.node_id} \u2014 lifecycle now terminal (in place)${e.detached===!0?", viewer closed (broker still running off-screen)":""}.`,"render")}),te=C({name:"lifecycle",description:"lifecycle actions \u2014 recycle, close, demote, or revive a node",whenToUse:"you are ending, transitioning, or bringing back one node you can name: finish the agent in your pane and reboot a fresh root (`recycle`), tear a node + its exclusive subtree down without finishing (`close`), flip a node onto a finishing track in place (`demote`), or bring a dormant node back (`revive`). To merely SET the terminal/resident attribute headlessly use `node config --lifecycle`; to FINISH from inside a node's own turn use `crtr push final`; to mass-reconnect EVERY disconnected node at once use `canvas revive --all`.",help:{name:"node lifecycle",summary:"end, transition, or revive a node \u2014 recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node",model:"Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree."},children:[j,F,H,S]}),oe=y({name:"yield",description:"refresh your context, same mode, against your roadmap or goal",whenToUse:"your context is filling but the mandate remains; refresh in the same mode, or combine the refresh with promotion when meaningful independent work can run in parallel and coordinating it should become your primary job",help:{name:"node yield",summary:"request a fresh context window against your roadmap or original goal; mode stays unchanged unless --promote is passed",guide:"The yield aborts your turn the instant this command returns: no text or tool call you planned after it will run. So anything the user is owed \u2014 an answer, a status \u2014 must already be a visible message in the conversation before you call it; never plan to reply after yielding. Before invoking: (1) Bring `$CRTR_CONTEXT_DIR/roadmap.md` current \u2014 it, not the yield note, carries the goal into the fresh window. Keep its seeded sections (Goal, Exit criteria, Phases) and keep it short: strategy and present state only \u2014 what is settled, what remains, the current phase\u2019s one-line status, plus dead ends worth not repeating. Delete completed items instead of marking them done; the roadmap should shrink as work completes. (2) Move anything worth remembering but not needed next window into memory (`crtr memory write -h`): node-local for lessons specific to this node\u2019s task, the workspace store for lessons that would benefit other agents working here. (3) Send any pending human question; in-flight asks survive the refresh.",params:[{kind:"flag",name:"promote",type:"bool",required:!1,constraint:"Also become an orchestrator as you refresh. Pass only when (1) the remaining work contains independent units that can run in parallel and (2) the task is large enough that parallel execution will materially improve intelligence, productivity, or elapsed throughput after coordination and synthesis. Task length, context exhaustion, sequential phases, or one helper are not enough; keep hands-on work in base mode and yield again as needed. Promote when coordinating and integrating children becomes your primary job. Already an orchestrator? This is not an error: your mode stays orchestrator and a missing roadmap is seeded."},{kind:"flag",name:"kind",type:"string",required:!1,constraint:"Respecialize as you refresh. The <kinds> list below names every top-level available kind and when to choose it; a registered sub-kind is valid too by exact path but not listed here. Defaults to your current kind. With --promote it sets the orchestrator kind you become; without --promote it changes the kind while preserving your current mode."},{kind:"flag",name:"model",type:"enum",choices:["ultra","strong"],required:!1,constraint:"Change the model your fresh revive runs on, by capability tier: `ultra` (frontier \u2014 reserve for high-taste judgment or enormous work: speccing, planning something large, e2e-testing something hard to test) or `strong` (opus \u2014 regular dev work). Omit to keep your current model. A node steering work is never weaker than opus, so these are the only two choices; the change is durable across future revives."},{kind:"stdin",name:"message",required:!0,constraint:"A note to your future self \u2014 what you just tried, where things stand, and the first move on waking: enough to continue efficiently, no more. If the user asked something you have not answered in a visible message, say so and quote the question. Never describe a reply as sent unless it is visible in the conversation above this call. Durable strategy belongs in the roadmap, not here. Surfaced as <yield-message> in the next revive. Pass as a positional or pipe via heredoc."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The yielding node."},{name:"model",type:"string",required:!1,constraint:"The model tier your fresh revive will run on, present only when you changed it with --model."}],dynamicState:s(()=>_(),"dynamicState"),outputKind:"object",effects:["Aborts your current turn as soon as this command returns \u2014 nothing after it runs \u2014 then the daemon revives the node fresh.","--kind and/or --model durably reshape the node (kind respecialization, model-tier pin) before the refresh-revive; --promote additionally flips mode\u2192orchestrator and seeds a roadmap scaffold \u2014 mode is otherwise left exactly as it was."]},run:s(async e=>{const t=b();if(t===void 0||t==="")throw new l({error:"no_node",message:"no node to yield (CRTR_NODE_ID unset)",next:"Run from inside a node."});const n=e.kind;n!==void 0&&k(n);const i=e.model,d=e.promote===!0,o=typeof e.message=="string"?e.message.trim():"";if(o==="")throw new l({error:"empty_message",message:"a yield message is required (stdin or positional)",next:"Pass a note to your future self as an argument or pipe it on stdin."});const r=await p().yieldNode(t,{note:o,...n!==void 0?{kind:n}:{},...i!==void 0?{model:i}:{},...d?{promote:!0}:{}}).catch(c=>f(c));return{node_id:r.node_id,...i!==void 0&&r.model_override!=null?{model:r.model_override}:{}}},"run")});export{ee as nodeConfig,te as nodeLifecycle,ne as nodePromote,oe as nodeYield};
@@ -1,3 +1,3 @@
1
- var I=Object.defineProperty;var u=(e,t)=>I(e,"name",{value:t,configurable:!0});import{readFileSync as O,statSync as B}from"node:fs";import{resolve as J}from"node:path";import{request as A}from"node:http";import{request as U}from"node:https";import{validateDeclaredResult as z}from"../../../api/command-manifest/result.js";import{CrtrError as S}from"../../errors.js";import{envelopeClaimsStale as G,staleErrorDetails as F}from"../../manifest-stale.js";import{diag as H,recordPreviewError as V,recordPreviewJsonLine as q,writeStdout as C}from"../../io.js";import{ExitCode as b}from"../../../types.js";import{isRecord as R}from"../../../shared/predicates.js";const P=1e4,K=3e4,W=6e4,Q=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,X=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]);class M extends Error{static{u(this,"StreamIdleError")}}async function we(e,t,n){const r=Y(e,t,n);if(e.rest.streaming===!0){await re(e,r);return}const{status:d,body:i}=await te(e,r);if(d>=200&&d<300)return ne(e,i);L(e,d,i)}u(we,"invokeHttpLeaf");function Y(e,t,n){const r=new Map;for(const o of e.params)r.set(o.name,o);const d=Object.entries(e.rest.params),i=e.baseUrl??new URL(e.registration.endpoint).origin;let m=e.rest.path;for(const[o,s]of d){if(s.in!=="path")continue;const c=t[E(o)];m=m.split(`{${o}}`).join(ee(c))}const p=new URL(m,i);for(const[o,s]of d){if(s.in!=="query")continue;const c=E(o);_(r.get(o),c,t,n)&&p.searchParams.append(s.as??o,String(t[c]))}const a=Z(e,d,r,t,n),l=Object.keys(a).length>0?JSON.stringify(a):void 0;let y;const f=e.registration.authEnv;if(f!==void 0&&f!==""){const o=process.env[f];if(o===void 0||o==="")throw new S("auth_env_missing",`${T(e)}: environment variable ${f} is not set or empty`,b.USAGE,{field:f,received:f,next:`Export ${f} with the bearer token, then retry.`});y=`Bearer ${o}`}const h=[["Accept","application/json"]];y!==void 0&&h.push(["Authorization",y]);for(const[o,s]of d){if(s.in!=="header")continue;const c=E(o);if(!_(r.get(o),c,t,n))continue;const w=s.as,g=String(t[c]);if(/[\r\n]/.test(g))throw new S("invalid_type",`${T(e)}: header "${w}" value must not contain a newline`,b.USAGE,{field:o,received:"(newline in header value)",next:"Remove newline characters from the value."});h.push([w,g])}return l!==void 0&&h.push(["Content-Type","application/json"]),{url:p,method:e.rest.method,headers:h,...l!==void 0?{body:l}:{}}}u(Y,"buildRequestPlan");function Z(e,t,n,r,d){const i=new Map;for(const[a,l]of t){if(l.in!=="body")continue;const y=l.as??a,f=i.get(y);f!==void 0?f.push(a):i.set(y,[a])}const m={};for(const[a,l]of i){const y=l.map(s=>({name:s,param:n.get(s)})),f=y.find(s=>s.param.kind!=="stdin"),h=y.find(s=>s.param.kind==="stdin");let o;f!==void 0&&_(f.param,E(f.name),r,d)?o={param:f.param,value:r[E(f.name)]}:h!==void 0&&_(h.param,E(h.name),r,d)&&(o={param:h.param,value:r[E(h.name)]}),o!==void 0&&(m[a]=j(e,o.param,o.value))}const p={...e.rest.body};return e.rest.bodyRoot!==void 0?p[e.rest.bodyRoot]=m:Object.assign(p,m),p}u(Z,"assembleBody");function j(e,t,n){if(t.kind==="context-file")return n;if((t.kind==="positional"||t.kind==="flag")&&t.type==="path"){if(Array.isArray(n))return n.map(m=>j(e,t,m));const r=String(n),d=J(process.cwd(),r);let i;try{i=B(d)}catch{throw N(e,t.name,r,"no file exists at that path","an existing local file path")}if(!i.isFile())throw N(e,t.name,r,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return t.encoding==="base64"?O(d).toString("base64"):O(d,"utf8")}catch{throw N(e,t.name,r,"the file could not be read","a readable local file path")}}return n}u(j,"materializeBodyValue");function N(e,t,n,r,d){return new S("invalid_type",`${T(e)}: cannot read file for "${t}" \u2014 ${r}: ${n}`,b.USAGE,{field:t,received:n,next:`Pass ${d}, relative to the current directory or absolute.`})}u(N,"fileError");function _(e,t,n,r){return r!==void 0?r.providedParams.has(t):Object.prototype.hasOwnProperty.call(n,t)&&n[t]!==void 0}u(_,"isPresent");function ee(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}u(ee,"encodePathSegmentValue");function E(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}u(E,"paramKey");function te(e,t){const n=e.timeouts?.connectMs??P,r=e.timeouts?.requestMs??K,d=t.url.protocol==="https:"?U:A;return new Promise((i,m)=>{let p=!1,a=null,l=null;const y=u(()=>{a!==null&&clearTimeout(a),l!==null&&clearTimeout(l),a=null,l=null},"clearTimers"),f=u(c=>{p||(p=!0,y(),o.destroy(),m(c))},"fail"),h=u(c=>{p||(p=!0,y(),i(c))},"succeed"),o=d(t.url,{method:t.method},c=>{a!==null&&(clearTimeout(a),a=null);const w=[];c.on("data",g=>w.push(g)),c.on("end",()=>h({status:c.statusCode??0,body:Buffer.concat(w).toString("utf8")})),c.on("error",g=>f(v(e,t,`connection error (${x(g)})`)))});for(const[c,w]of t.headers)o.setHeader(c,w);a=setTimeout(()=>f(v(e,t,`connection timed out after ${n}ms`)),n),l=setTimeout(()=>f(v(e,t,`request exceeded the ${r}ms timeout`)),r),o.on("socket",c=>{c.connecting?c.once("connect",()=>s()):s()});const s=u(()=>{a!==null&&(clearTimeout(a),a=null)},"clearConnectTimer");o.on("error",c=>f(v(e,t,`could not reach the backend (${x(c)})`))),t.body!==void 0&&o.write(t.body),o.end()})}u(te,"requestUnary");function ne(e,t){let n;try{n=JSON.parse(t)}catch{throw k(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!R(n))throw k(e,`response body was not a JSON object (got ${ae(n)})`,"a JSON object matching the declared output");const r=z(e.output,n);if(r!==null){const i=e.output.find(m=>m.name===r.field)?.type??"unknown";throw r.receivedType!==void 0?k(e,`output field "${r.field}" has the wrong type`,`${r.field}: ${i}`):k(e,`response is missing required output field "${r.field}"`,`a "${r.field}" field of type ${i}`)}return n}u(ne,"mapUnary2xx");async function re(e,t){const n=e.timeouts?.connectMs??P,r=e.timeouts?.streamIdleMs??W,d=t.url.protocol==="https:"?U:A,i=await new Promise((h,o)=>{let s=!1,c=null;const w=u(()=>{c!==null&&(clearTimeout(c),c=null)},"clearConnect"),g=d(t.url,{method:t.method},$=>{s||(s=!0,w(),h($))});for(const[$,D]of t.headers)g.setHeader($,D);c=setTimeout(()=>{s||(s=!0,g.destroy(),o(v(e,t,`connection timed out after ${n}ms`)))},n),g.on("socket",$=>{$.connecting?$.once("connect",w):w()}),g.on("error",$=>{s||(s=!0,w(),o(v(e,t,`could not reach the backend (${x($)})`)))}),t.body!==void 0&&g.write(t.body),g.end()}),m=i.statusCode??0;if(!(m>=200&&m<300)){const h=await ie(i);L(e,m,h)}let p=!1,a="",l=null;const y=u(()=>{l!==null&&clearTimeout(l),l=setTimeout(()=>i.destroy(new M),r)},"armIdle"),f=u(()=>{l!==null&&(clearTimeout(l),l=null)},"clearIdle");try{y();for await(const h of i){y(),a+=h.toString("utf8");let o;for(;(o=a.indexOf(`
2
- `))!==-1;){const s=a.slice(0,o);if(a=a.slice(o+1),s.trim()==="")continue;if(p=!0,q(s),!await C(s+`
3
- `)){f(),i.destroy();return}}}f(),a.trim()!==""&&(p=!0,q(a),await C(a));return}catch(h){f();const o=h instanceof M?`stream idle for ${r}ms`:`connection error (${x(h)})`;if(p){const s=`${T(e)}: stream interrupted (${o})`;H(`crtr: ${s}`),V({error:"stream_interrupted",message:s,next:"Retry the command to begin a fresh stream."}),process.exitCode=b.NETWORK;return}throw v(e,t,o)}}u(re,"requestStreaming");function L(e,t,n){const r=oe(t);let d;try{d=JSON.parse(n)}catch{d=void 0}if(R(d)){const i=d.error;if(R(i)&&typeof i.code=="string"&&typeof i.message=="string"){const m=i.code,p=i.message,a=typeof i.field=="string"?i.field:void 0,l=typeof i.next=="string"?i.next:"Inspect the backend response, then retry if appropriate.",y=Q.test(m)&&!X.has(m),f=G(i);throw new S(y?m:"cli_protocol_error",p,r,{...i.received!==void 0?{received:i.received}:{},...a!==void 0?{field:a}:{},...f?F(e.registration.name):{},http_status:t,next:l})}}throw new S("cli_http_error",`${T(e)}: request failed with HTTP ${t}`,r,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}u(L,"throwNon2xx");function oe(e){return e===400||e===401||e===403||e===422?b.USAGE:e===404?b.NOT_FOUND:e===409?b.AMBIGUOUS:b.GENERAL}u(oe,"deriveExit");function k(e,t,n){return new S("cli_protocol_error",`${T(e)}: ${t}. Expected ${n}.`,b.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}u(k,"protocolError");function v(e,t,n){return new S("cli_unreachable",`${T(e)}: ${n}`,b.NETWORK,{received:t.url.origin,next:`Check connectivity to ${t.url.host} and retry; run \`crtr pkg plugin update ${e.registration.name}\` if the command surface changed.`})}u(v,"unreachable");function T(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}u(T,"label");function x(e){const t=e;return t?.code??t?.message??String(e)}u(x,"errCode");function ie(e){return new Promise(t=>{const n=[];e.on("data",r=>n.push(r)),e.on("end",()=>t(Buffer.concat(n).toString("utf8"))),e.on("error",()=>t(Buffer.concat(n).toString("utf8")))})}u(ie,"readAll");function ae(e){return e===null?"null":Array.isArray(e)?"array":typeof e}u(ae,"typeName");export{we as invokeHttpLeaf};
1
+ var L=Object.defineProperty;var d=(e,t)=>L(e,"name",{value:t,configurable:!0});import{randomUUID as J}from"node:crypto";import{readFileSync as x,statSync as z}from"node:fs";import{resolve as G}from"node:path";import{request as C}from"node:http";import{request as A}from"node:https";import{validateDeclaredResult as H}from"../../../api/command-manifest/result.js";import{CrtrError as S}from"../../errors.js";import{envelopeClaimsStale as K,staleErrorDetails as V}from"../../manifest-stale.js";import{diag as W,recordPreviewError as Y,recordPreviewJsonLine as U,writeStdout as M}from"../../io.js";import{ExitCode as E}from"../../../types.js";import{isRecord as N}from"../../../shared/predicates.js";const P=1e4,Q=3e4,X=6e4,Z=250,ee=2e3,te=new Set(["ECONNREFUSED","ECONNRESET","EPIPE"]);class q extends Error{static{d(this,"ConnectionError")}detail;constructor(t){super(t),this.detail=t}}const ne=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,re=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]);class D extends Error{static{d(this,"StreamIdleError")}}async function ve(e,t,n){const r=oe(e,t,n);if(e.rest.streaming===!0){await ue(e,r);return}const{status:a,body:o}=await se(e,r);if(a>=200&&a<300)return de(e,o);j(e,a,o)}d(ve,"invokeHttpLeaf");function oe(e,t,n){const r=new Map;for(const i of e.params)r.set(i.name,i);const a=Object.entries(e.rest.params),o=e.baseUrl??new URL(e.registration.endpoint).origin;let l=e.rest.path;for(const[i,c]of a){if(c.in!=="path")continue;const u=t[$(i)];l=l.split(`{${i}}`).join(ae(u))}const p=new URL(l,o);for(const[i,c]of a){if(c.in!=="query")continue;const u=$(i);R(r.get(i),u,t,n)&&p.searchParams.append(c.as??i,String(t[u]))}const s=ie(e,a,r,t,n),m=Object.keys(s).length>0?JSON.stringify(s):void 0;let y;const f=e.registration.authEnv;if(f!==void 0&&f!==""){const i=process.env[f];if(i===void 0||i==="")throw new S("auth_env_missing",`${T(e)}: environment variable ${f} is not set or empty`,E.USAGE,{field:f,received:f,next:`Export ${f} with the bearer token, then retry.`});y=`Bearer ${i}`}const h=[["Accept","application/json"],["Crtr-Request-Id",J()]];y!==void 0&&h.push(["Authorization",y]);for(const[i,c]of a){if(c.in!=="header")continue;const u=$(i);if(!R(r.get(i),u,t,n))continue;const w=c.as,g=String(t[u]);if(/[\r\n]/.test(g))throw new S("invalid_type",`${T(e)}: header "${w}" value must not contain a newline`,E.USAGE,{field:i,received:"(newline in header value)",next:"Remove newline characters from the value."});h.push([w,g])}return m!==void 0&&h.push(["Content-Type","application/json"]),{url:p,method:e.rest.method,headers:h,...m!==void 0?{body:m}:{}}}d(oe,"buildRequestPlan");function ie(e,t,n,r,a){const o=new Map;for(const[s,m]of t){if(m.in!=="body")continue;const y=m.as??s,f=o.get(y);f!==void 0?f.push(s):o.set(y,[s])}const l={};for(const[s,m]of o){const y=m.map(c=>({name:c,param:n.get(c)})),f=y.find(c=>c.param.kind!=="stdin"),h=y.find(c=>c.param.kind==="stdin");let i;f!==void 0&&R(f.param,$(f.name),r,a)?i={param:f.param,value:r[$(f.name)]}:h!==void 0&&R(h.param,$(h.name),r,a)&&(i={param:h.param,value:r[$(h.name)]}),i!==void 0&&(l[s]=I(e,i.param,i.value))}const p={...e.rest.body};return e.rest.bodyRoot!==void 0?p[e.rest.bodyRoot]=l:Object.assign(p,l),p}d(ie,"assembleBody");function I(e,t,n){if(t.kind==="context-file")return n;if((t.kind==="positional"||t.kind==="flag")&&t.type==="path"){if(Array.isArray(n))return n.map(l=>I(e,t,l));const r=String(n),a=G(process.cwd(),r);let o;try{o=z(a)}catch{throw O(e,t.name,r,"no file exists at that path","an existing local file path")}if(!o.isFile())throw O(e,t.name,r,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return t.encoding==="base64"?x(a).toString("base64"):x(a,"utf8")}catch{throw O(e,t.name,r,"the file could not be read","a readable local file path")}}return n}d(I,"materializeBodyValue");function O(e,t,n,r,a){return new S("invalid_type",`${T(e)}: cannot read file for "${t}" \u2014 ${r}: ${n}`,E.USAGE,{field:t,received:n,next:`Pass ${a}, relative to the current directory or absolute.`})}d(O,"fileError");function R(e,t,n,r){return r!==void 0?r.providedParams.has(t):Object.prototype.hasOwnProperty.call(n,t)&&n[t]!==void 0}d(R,"isPresent");function ae(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}d(ae,"encodePathSegmentValue");function $(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}d($,"paramKey");async function se(e,t){const n=e.timeouts?.requestMs??Q,r=Date.now()+n;let a=Z;for(;;)try{return await ce(e,t,r-Date.now())}catch(o){if(!(o instanceof q))throw o;if(r-Date.now()<=a)throw _(e,t,`${o.detail}; retried with the same request id until the ${n}ms deadline`);await new Promise(p=>setTimeout(p,a)),a=Math.min(a*2,ee)}}d(se,"requestUnary");function ce(e,t,n){const r=Math.min(e.timeouts?.connectMs??P,n),a=t.url.protocol==="https:"?A:C;return new Promise((o,l)=>{let p=!1,s=null,m=null;const y=d(()=>{s!==null&&clearTimeout(s),m!==null&&clearTimeout(m),s=null,m=null},"clearTimers"),f=d(u=>{p||(p=!0,y(),i.destroy(),l(u))},"fail"),h=d(u=>{p||(p=!0,y(),o(u))},"succeed"),i=a(t.url,{method:t.method},u=>{s!==null&&(clearTimeout(s),s=null);const w=[];u.on("data",g=>w.push(g)),u.on("end",()=>h({status:u.statusCode??0,body:Buffer.concat(w).toString("utf8")})),u.on("error",g=>f(F(e,t,g,"connection closed before the full response")))});for(const[u,w]of t.headers)i.setHeader(u,w);s=setTimeout(()=>f(_(e,t,`connection timed out after ${r}ms`)),r),m=setTimeout(()=>f(_(e,t,`request exceeded the ${n}ms timeout`)),n),i.on("socket",u=>{u.connecting?u.once("connect",()=>c()):c()});const c=d(()=>{s!==null&&(clearTimeout(s),s=null)},"clearConnectTimer");i.on("error",u=>f(F(e,t,u,"could not reach the backend"))),t.body!==void 0&&i.write(t.body),i.end()})}d(ce,"requestUnaryOnce");function F(e,t,n,r){const a=k(n),o=`${r} (${a})`;return te.has(a)?new q(o):_(e,t,o)}d(F,"transportFailure");function de(e,t){let n;try{n=JSON.parse(t)}catch{throw v(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!N(n))throw v(e,`response body was not a JSON object (got ${me(n)})`,"a JSON object matching the declared output");const r=H(e.output,n);if(r!==null){const o=e.output.find(l=>l.name===r.field)?.type??"unknown";throw r.receivedType!==void 0?v(e,`output field "${r.field}" has the wrong type`,`${r.field}: ${o}`):v(e,`response is missing required output field "${r.field}"`,`a "${r.field}" field of type ${o}`)}return n}d(de,"mapUnary2xx");async function ue(e,t){const n=e.timeouts?.connectMs??P,r=e.timeouts?.streamIdleMs??X,a=t.url.protocol==="https:"?A:C,o=await new Promise((h,i)=>{let c=!1,u=null;const w=d(()=>{u!==null&&(clearTimeout(u),u=null)},"clearConnect"),g=a(t.url,{method:t.method},b=>{c||(c=!0,w(),h(b))});for(const[b,B]of t.headers)g.setHeader(b,B);u=setTimeout(()=>{c||(c=!0,g.destroy(),i(_(e,t,`connection timed out after ${n}ms`)))},n),g.on("socket",b=>{b.connecting?b.once("connect",w):w()}),g.on("error",b=>{c||(c=!0,w(),i(_(e,t,`could not reach the backend (${k(b)})`)))}),t.body!==void 0&&g.write(t.body),g.end()}),l=o.statusCode??0;if(!(l>=200&&l<300)){const h=await le(o);j(e,l,h)}let p=!1,s="",m=null;const y=d(()=>{m!==null&&clearTimeout(m),m=setTimeout(()=>o.destroy(new D),r)},"armIdle"),f=d(()=>{m!==null&&(clearTimeout(m),m=null)},"clearIdle");try{y();for await(const h of o){y(),s+=h.toString("utf8");let i;for(;(i=s.indexOf(`
2
+ `))!==-1;){const c=s.slice(0,i);if(s=s.slice(i+1),c.trim()==="")continue;if(p=!0,U(c),!await M(c+`
3
+ `)){f(),o.destroy();return}}}f(),s.trim()!==""&&(p=!0,U(s),await M(s));return}catch(h){f();const i=h instanceof D?`stream idle for ${r}ms`:`connection error (${k(h)})`;if(p){const c=`${T(e)}: stream interrupted (${i})`;W(`crtr: ${c}`),Y({error:"stream_interrupted",message:c,next:"Retry the command to begin a fresh stream."}),process.exitCode=E.NETWORK;return}throw _(e,t,i)}}d(ue,"requestStreaming");function j(e,t,n){const r=fe(t);let a;try{a=JSON.parse(n)}catch{a=void 0}if(N(a)){const o=a.error;if(N(o)&&typeof o.code=="string"&&typeof o.message=="string"){const l=o.code,p=o.message,s=typeof o.field=="string"?o.field:void 0,m=typeof o.next=="string"?o.next:"Inspect the backend response, then retry if appropriate.",y=ne.test(l)&&!re.has(l),f=K(o);throw new S(y?l:"cli_protocol_error",p,r,{...o.received!==void 0?{received:o.received}:{},...s!==void 0?{field:s}:{},...f?V(e.registration.name):{},http_status:t,next:m})}}throw new S("cli_http_error",`${T(e)}: request failed with HTTP ${t}`,r,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}d(j,"throwNon2xx");function fe(e){return e===400||e===401||e===403||e===422?E.USAGE:e===404?E.NOT_FOUND:e===409?E.AMBIGUOUS:E.GENERAL}d(fe,"deriveExit");function v(e,t,n){return new S("cli_protocol_error",`${T(e)}: ${t}. Expected ${n}.`,E.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}d(v,"protocolError");function _(e,t,n){return new S("cli_unreachable",`${T(e)}: ${n}`,E.NETWORK,{received:t.url.origin,next:`Check connectivity to ${t.url.host} and retry; run \`crtr pkg plugin update ${e.registration.name}\` if the command surface changed.`})}d(_,"unreachable");function T(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}d(T,"label");function k(e){const t=e;return t?.code??t?.message??String(e)}d(k,"errCode");function le(e){return new Promise(t=>{const n=[];e.on("data",r=>n.push(r)),e.on("end",()=>t(Buffer.concat(n).toString("utf8"))),e.on("error",()=>t(Buffer.concat(n).toString("utf8")))})}d(le,"readAll");function me(e){return e===null?"null":Array.isArray(e)?"array":typeof e}d(me,"typeName");export{ve as invokeHttpLeaf};
@@ -1,6 +1,6 @@
1
- var M=Object.defineProperty;var s=(e,t)=>M(e,"name",{value:t,configurable:!0});import{existsSync as p,readFileSync as b,writeFileSync as $,rmSync as S,mkdirSync as w,readdirSync as m}from"node:fs";import{join as x}from"node:path";import{contextDir as f,reportsDir as k,getNode as v,subscriptionsOf as j,subscribersOf as T}from"../canvas/index.js";import{activeBackgroundBashJobs as A,bashJobCommandLine as B,formatBashElapsed as E}from"../bash-jobs.js";import{readRoadmap as O,roadmapPath as N}from"./roadmap.js";import{buildWakeBearings as C}from"./bearings.js";import{personaDrift as G}from"./persona.js";import{coalesce as D,nodeMailEntry as P}from"../feed/inbox.js";import{claimKickoffMail as Y}from"../canvas/deliveries.js";import{formatCard as R}from"../../shared/generated-context.js";const dt=R("restart-continuation",{},"continue");function g(e){return x(f(e),"initial-prompt.md")}s(g,"goalPath");function _(e){const t=g(e);return p(t)?b(t,"utf8"):null}s(_,"readGoal");function U(e,t){const n=t.trim();n!==""&&(w(f(e),{recursive:!0}),$(g(e),n+`
1
+ var M=Object.defineProperty;var s=(e,t)=>M(e,"name",{value:t,configurable:!0});import{existsSync as p,readFileSync as b,writeFileSync as $,rmSync as S,mkdirSync as w,readdirSync as m}from"node:fs";import{join as x}from"node:path";import{contextDir as f,reportsDir as k,getNode as v,subscriptionsOf as j,subscribersOf as T}from"../canvas/index.js";import{activeBackgroundBashJobs as A,bashJobCommandLine as B,formatBashElapsed as E}from"../bash-jobs.js";import{readRoadmap as O,roadmapPath as N}from"./roadmap.js";import{buildWakeBearings as C}from"./bearings.js";import{personaDrift as G}from"./persona.js";import{coalesce as Y,nodeMailEntry as D}from"../feed/inbox.js";import{claimKickoffMail as P}from"../canvas/deliveries.js";import{formatCard as R}from"../../shared/generated-context.js";const dt=R("restart-continuation",{},"continue");function g(e){return x(f(e),"initial-prompt.md")}s(g,"goalPath");function _(e){const t=g(e);return p(t)?b(t,"utf8"):null}s(_,"readGoal");function U(e,t){const n=t.trim();n!==""&&(w(f(e),{recursive:!0}),$(g(e),n+`
2
2
  `,"utf8"))}s(U,"writeGoal");function ut(e,t){const n=_(e);if(n!==null&&n.trim()!=="")return!1;const o=t.trim();return o===""?!1:(U(e,o),!0)}s(ut,"captureGoalIfAbsent");function y(e){return x(f(e),"yield-message.md")}s(y,"yieldMessagePath");function ct(e,t){const n=t.trim();n!==""&&(w(f(e),{recursive:!0}),$(y(e),n+`
3
- `,"utf8"))}s(ct,"writeYieldMessage");function L(e){const t=y(e);if(!p(t))return null;const n=b(t,"utf8").trim();return n!==""?n:null}s(L,"readYieldMessage");function lt(e){try{S(y(e))}catch{}}s(lt,"clearYieldMessage");function F(e){const t=f(e);return p(t)?m(t).sort():[]}s(F,"listContextDir");function J(e){const t=k(e);return p(t)&&m(t).some(n=>n.endsWith(".md"))}s(J,"hasRetainedReports");function ft(e,t){const n=e.node_id,o=L(n),i=Y(n,t).map(P);let r=null;if(i.length>0){const a=s(c=>{const h=c.data?.cards;return Array.isArray(h)?h.filter(I=>typeof I=="string"):[]},"cardsFor"),u=[];for(const c of i)(a(c).length>0||u.length===0)&&u.push([]),u.at(-1).push(c);r=u.map(c=>[...a(c[0]),D(c).map(h=>h.text).join(`
3
+ `,"utf8"))}s(ct,"writeYieldMessage");function L(e){const t=y(e);if(!p(t))return null;const n=b(t,"utf8").trim();return n!==""?n:null}s(L,"readYieldMessage");function lt(e){try{S(y(e))}catch{}}s(lt,"clearYieldMessage");function F(e){const t=f(e);return p(t)?m(t).sort():[]}s(F,"listContextDir");function J(e){const t=k(e);return p(t)&&m(t).some(n=>n.endsWith(".md"))}s(J,"hasRetainedReports");function ft(e,t){const n=e.node_id,o=L(n),i=P(n,t).map(D);let r=null;if(i.length>0){const a=s(c=>{const h=c.data?.cards;return Array.isArray(h)?h.filter(I=>typeof I=="string"):[]},"cardsFor"),u=[];for(const c of i)(a(c).length>0||u.length===0)&&u.push([]),u.at(-1).push(c);r=u.map(c=>[...a(c[0]),Y(c).map(h=>h.text).join(`
4
4
 
5
5
  `)].join(`
6
6
 
@@ -21,7 +21,7 @@ ${l.length>0?l.join(`
21
21
  `):"(empty)"}
22
22
  </context-dir>`);const a=Q(o);a!==null&&i.push(a),i.push(q(o,t.unreadDigest)),i.push(t.yieldMsg!==null?`<yield-message>
23
23
  ${t.yieldMsg.trim()}
24
- </yield-message>`:"<yield-message/>");const u=T(o).length>0;return i.push(u?"If there is work to do, perform it. Otherwise stop \u2014 pipe the result to `crtr push final` through a single-quoted heredoc if the goal is met, or end your turn to stay dormant awaiting your workers.":"If there is work to do, perform it. Otherwise end your turn \u2014 you are working directly with the user, so stay available and continue the conversation when they write back."),t.driftGuidance!==null&&i.push(`<persona-transition>
24
+ </yield-message>`:"<yield-message/>");const u=T(o).length>0;return i.push(u?"If there is work to do, perform it. Otherwise stop \u2014 pipe the result to `crtr push final` through a single-quoted heredoc if the goal is met, or end your turn without a message to stay dormant awaiting your workers.":"If there is work to do, perform it. Otherwise end your turn without writing a message \u2014 the user already has your last reply, and a status recap would only bury it. You are working directly with the user, so stay available and continue when they write back."),t.driftGuidance!==null&&i.push(`<persona-transition>
25
25
  Your role was changed while you were away. ${t.driftGuidance}
26
26
  </persona-transition>`),R("revive",{},`
27
27
  ${i.join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.346",
3
+ "version": "0.3.348",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.346",
3
+ "version": "0.3.348",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.346",
9
+ "version": "0.3.348",
10
10
  "hasInstallScript": true,
11
11
  "license": "GPL-3.0-only",
12
12
  "workspaces": [
@@ -10859,28 +10859,28 @@
10859
10859
  },
10860
10860
  "packages/crouter-api": {
10861
10861
  "name": "@north-light/crouter-api",
10862
- "version": "0.3.346",
10862
+ "version": "0.3.348",
10863
10863
  "license": "GPL-3.0-only"
10864
10864
  },
10865
10865
  "packages/crouter-env-docker": {
10866
10866
  "name": "@north-light/crouter-env-docker",
10867
- "version": "0.3.346",
10867
+ "version": "0.3.348",
10868
10868
  "license": "GPL-3.0-only"
10869
10869
  },
10870
10870
  "packages/crouter-plugin": {
10871
10871
  "name": "@north-light/crouter-plugin",
10872
- "version": "0.3.346",
10872
+ "version": "0.3.348",
10873
10873
  "license": "GPL-3.0-only",
10874
10874
  "dependencies": {
10875
- "@north-light/crouter-api": "^0.3.346"
10875
+ "@north-light/crouter-api": "^0.3.348"
10876
10876
  }
10877
10877
  },
10878
10878
  "packages/crouter-sdk": {
10879
10879
  "name": "@north-light/crouter-sdk",
10880
- "version": "0.3.346",
10880
+ "version": "0.3.348",
10881
10881
  "license": "GPL-3.0-only",
10882
10882
  "dependencies": {
10883
- "@north-light/crouter-api": "^0.3.346"
10883
+ "@north-light/crouter-api": "^0.3.348"
10884
10884
  }
10885
10885
  }
10886
10886
  }